yl
2022-11-08 ee767e67b091fbfd7c7e995d663d0a8a4425f692
设备点检标准、设备清单、物料清单导入接口开发
已添加2个文件
已修改14个文件
214 ■■■■ 文件已修改
.vs/VueWebApi/v16/.suo 补丁 | 查看 | 原始文档 | blame | 历史
VueWebApi/DLL/DAL/ExcelCheckDAL.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebApi/Excel/物料清单.xls 补丁 | 查看 | 原始文档 | blame | 历史
VueWebApi/Excel/设备清单.xls 补丁 | 查看 | 原始文档 | blame | 历史
VueWebApi/Excel/设备点检标准.xls 补丁 | 查看 | 原始文档 | blame | 历史
VueWebApi/InExcel/21/物料清单.xls 补丁 | 查看 | 原始文档 | blame | 历史
VueWebApi/Logs/2022-11-07.TXT 补丁 | 查看 | 原始文档 | blame | 历史
VueWebApi/Properties/PublishProfiles/FolderProfile.pubxml.user 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebApi/Tools/ImportExcelData.cs 200 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebApi/bin/VueWebApi.pdb 补丁 | 查看 | 原始文档 | blame | 历史
VueWebApi/obj/Release/Package/PackageTmp/Excel/物料清单.xls 补丁 | 查看 | 原始文档 | blame | 历史
VueWebApi/obj/Release/Package/PackageTmp/Excel/设备清单.xls 补丁 | 查看 | 原始文档 | blame | 历史
VueWebApi/obj/Release/Package/PackageTmp/Excel/设备点检标准.xls 补丁 | 查看 | 原始文档 | blame | 历史
VueWebApi/obj/Release/Package/PackageTmp/Excel/设备点检项目.xls 补丁 | 查看 | 原始文档 | blame | 历史
VueWebApi/obj/Release/Package/PackageTmp/bin/VueWebApi.pdb 补丁 | 查看 | 原始文档 | blame | 历史
VueWebApi/obj/Release/VueWebApi.pdb 补丁 | 查看 | 原始文档 | blame | 历史
.vs/VueWebApi/v16/.suo
Binary files differ
VueWebApi/DLL/DAL/ExcelCheckDAL.cs
@@ -102,7 +102,7 @@
        }
        #endregion
        #region[Excel导入数据验证]
        #region【Excel导入数据验证】
        public static List<ExcelErro> ExcelCheckData(string fileCode, string savePath, out string stuCode, out string message, out int count)
        {
            stuCode = "";
VueWebApi/Excel/ÎïÁÏÇåµ¥.xls
Binary files differ
VueWebApi/Excel/É豸Çåµ¥.xls
Binary files differ
VueWebApi/Excel/É豸µã¼ì±ê×¼.xls
Binary files differ
VueWebApi/InExcel/21/ÎïÁÏÇåµ¥.xls
Binary files differ
VueWebApi/Logs/2022-11-07.TXT
VueWebApi/Properties/PublishProfiles/FolderProfile.pubxml.user
@@ -729,16 +729,16 @@
      <publishTime>12/24/2021 15:38:29</publishTime>
    </File>
    <File Include="bin/VueWebApi.dll">
      <publishTime>11/04/2022 15:40:13</publishTime>
      <publishTime>11/08/2022 09:12:15</publishTime>
    </File>
    <File Include="bin/VueWebApi.dll.config">
      <publishTime>06/15/2022 15:31:43</publishTime>
    </File>
    <File Include="bin/VueWebApi.pdb">
      <publishTime>11/04/2022 15:40:13</publishTime>
      <publishTime>11/08/2022 09:12:15</publishTime>
    </File>
    <File Include="bin/VueWebApi.xml">
      <publishTime>11/04/2022 15:40:13</publishTime>
      <publishTime>11/08/2022 09:12:15</publishTime>
    </File>
    <File Include="bin/WebActivatorEx.dll">
      <publishTime>02/08/2013 16:42:28</publishTime>
@@ -846,10 +846,10 @@
      <publishTime>08/29/2022 12:08:02</publishTime>
    </File>
    <File Include="Excel/设备清单.xls">
      <publishTime>11/04/2022 15:40:01</publishTime>
      <publishTime>11/07/2022 16:13:05</publishTime>
    </File>
    <File Include="Excel/设备点检标准.xls">
      <publishTime>11/03/2022 18:03:35</publishTime>
      <publishTime>11/07/2022 13:22:13</publishTime>
    </File>
    <File Include="Excel/设备点检项目.xls">
      <publishTime>08/17/2022 09:56:57</publishTime>
@@ -1551,7 +1551,7 @@
      <publishTime>06/10/2022 08:20:24</publishTime>
    </File>
    <File Include="Web.config">
      <publishTime>11/04/2022 15:40:29</publishTime>
      <publishTime>11/08/2022 09:12:22</publishTime>
    </File>
  </ItemGroup>
</Project>
VueWebApi/Tools/ImportExcelData.cs
@@ -625,10 +625,10 @@
                code = "300";
                Message = "设备清单模板(设备组):表头信息不符合规范,第3列应为{设备组名称}";
            }
            else if (excelTable[1].Columns[3].ColumnName != "设备类型编号(唯一)")
            else if (excelTable[1].Columns[3].ColumnName != "设备类型编号")
            {
                code = "300";
                Message = "设备清单模板(设备组):表头信息不符合规范,第4列应为{设备类型编号(唯一)}";
                Message = "设备清单模板(设备组):表头信息不符合规范,第4列应为{设备类型编号}";
            }
            else if (excelTable[1].Columns[4].ColumnName != "设备组描述")
            {
@@ -650,15 +650,15 @@
                code = "300";
                Message = "设备清单模板(设备清单):表头信息不符合规范,第1列应为{设备名称}";
            }
            else if (excelTable[2].Columns[3].ColumnName != "设备类型编号(唯一)")
            else if (excelTable[2].Columns[3].ColumnName != "设备类型编号")
            {
                code = "300";
                Message = "设备清单模板(设备清单):表头信息不符合规范,第1列应为{设备类型编号(唯一)}";
                Message = "设备清单模板(设备清单):表头信息不符合规范,第1列应为{设备类型编号}";
            }
            else if (excelTable[2].Columns[4].ColumnName != "设备组编号(唯一)")
            else if (excelTable[2].Columns[4].ColumnName != "设备组编号")
            {
                code = "300";
                Message = "设备清单模板(设备清单):表头信息不符合规范,第1列应为{设备组编号(唯一)}";
                Message = "设备清单模板(设备清单):表头信息不符合规范,第1列应为{设备组编号}";
            }
            else if (excelTable[2].Columns[5].ColumnName != "投入日期")
            {
@@ -2273,15 +2273,15 @@
                      where !(
                          from rr in excelTable[0].AsEnumerable()
                          select rr.Field<string>("设备类型编号(唯一)")
                      ).Contains(r.Field<string>("设备类型编号(唯一)"))
                      ).Contains(r.Field<string>("设备类型编号"))
                      select r;
            List<DataRow> listRow = dt3.ToList();
            if (listRow.Count > 0)
            {
                ExcelErro erro = new ExcelErro();
                erro.RoeNumber = "/";
                erro.ErrorField = "{设备类型编号(唯一)}";
                erro.ErrorCont = "设备组信息:{设备类型编号(唯一)}字段中有值在设备类型信息:{设备类型编号(唯一)}中不存在";
                erro.ErrorField = "{设备类型编号}";
                erro.ErrorCont = "设备组信息:{设备类型编号}字段中有值在设备类型信息:{设备类型编号(唯一)}中不存在";
                list.Add(erro);
            }
            //设备清单表
@@ -2301,13 +2301,13 @@
                        list.Add(erro);
                    }
                    //判断设备组是否对应正确的设备类型
                    var yourGetData = excelTable[1].AsEnumerable().Where(x => (x.Field<string>("设备类型编号(唯一)") == excelTable[2].Rows[k][3].ToString().Trim()) && x.Field<string>("设备组编号(唯一)") == excelTable[2].Rows[k][4].ToString().Trim()).CopyToDataTable();
                    if (yourGetData.Rows.Count <= 0)
                    var yourGetData = excelTable[1].AsEnumerable().Where<DataRow>(a => a["设备类型编号"].ToString() == excelTable[2].Rows[k][3].ToString().Trim()&& a["设备组编号(唯一)"].ToString() == excelTable[2].Rows[k][4].ToString().Trim());
                    if (yourGetData.Count() <= 0)
                    {
                        ExcelErro erro = new ExcelErro();
                        erro.RoeNumber = "/";
                        erro.ErrorField = "{设备类型编号(唯一)}/{设备组编号(唯一)}";
                        erro.ErrorCont = "设备表:{设备编号(唯一)}字段" + excelTable[2].Rows[k][1].ToString().Trim() + "对应{设备类型编号(唯一)}:"+ excelTable[2].Rows[k][3].ToString().Trim() + "/{设备组编号(唯一)}:"+ excelTable[2].Rows[k][4].ToString().Trim() + "与设备组清单中设置对不上";
                        erro.ErrorField = "{设备类型编号}/{设备组编号}";
                        erro.ErrorCont = "设备表:{设备编号(唯一)}字段" + excelTable[2].Rows[k][1].ToString().Trim() + "对应{设备类型编号}:"+ excelTable[2].Rows[k][4].ToString().Trim() + "/{设备组编号}:"+ excelTable[2].Rows[k][4].ToString().Trim() + "与设备组清单中设置对不上";
                        list.Add(erro);
                    }
                }
@@ -2444,7 +2444,7 @@
                        list.Add(erro);
                    }
                    //通过设备点检标准页签模板中的标准编码查询设备点检部位页签模板中对应的数据信息
                    DataTable ds = excelTable[1].AsEnumerable().Where<DataRow>(C => C["设备点检标准编号(唯一)"].ToString() == excelTable[0].Rows[j][1].ToString().Trim()).CopyToDataTable();
                    DataTable ds = excelTable[1].AsEnumerable().Where<DataRow>(C => C["设备点检标准编号"].ToString() == excelTable[0].Rows[j][1].ToString().Trim()).CopyToDataTable();
                    if (ds.Rows.Count > 0) 
                    {
                        //判断点检标准查询点检部位数据是否有重复
@@ -2454,7 +2454,7 @@
                            ExcelErro erro = new ExcelErro();
                            erro.RoeNumber = "/";
                            erro.ErrorField = "{设备点检部位编号(唯一)}";
                            erro.ErrorCont = "设备点检部位页签模板:{设备点检标准编号(唯一)}:"+ excelTable[0].Rows[j][1].ToString().Trim() + " å¯¹åº”{设备点检部位编号(唯一)}有重复";
                            erro.ErrorCont = "设备点检部位页签模板:{设备点检标准编号}:"+ excelTable[0].Rows[j][1].ToString().Trim() + " å¯¹åº”{设备点检部位编号(唯一)}有重复";
                            list.Add(erro);
                        }
                    }
@@ -2483,15 +2483,15 @@
                      where !(
                          from rr in excelTable[0].AsEnumerable()
                          select rr.Field<string>("设备点检标准编号(唯一)")
                      ).Contains(r.Field<string>("设备点检标准编号(唯一)"))
                      ).Contains(r.Field<string>("设备点检标准编号"))
                      select r;
            List<DataRow> listRow = dt3.ToList();
            if (listRow.Count > 0)
            {
                ExcelErro erro = new ExcelErro();
                erro.RoeNumber = "/";
                erro.ErrorField = "{设备点检标准编号(唯一)}";
                erro.ErrorCont = "设备点检部位页签模板:{设备点检标准编号(唯一)}字段中有值在设备点检标准页签模板:{设备点检标准编号(唯一)}中不存在";
                erro.ErrorField = "{设备点检标准编号}";
                erro.ErrorCont = "设备点检部位页签模板:{设备点检标准编号}字段中有值在设备点检标准页签模板:{设备点检标准编号(唯一)}中不存在";
                list.Add(erro);
            }
            if (list.Count > 0)
@@ -2925,13 +2925,13 @@
            count = 0;
            string sql = "";
            var dynamicParams = new DynamicParameters();
            DataTable dt;
            DataTable dt0,dt;
            List<ExcelErro> list = new List<ExcelErro>();
            List<DataTable> excelTable = new List<DataTable>();
            list = ImportExcel.ExcelToTableListErro(FileCode);    //验证Excel数据必填字段是否为空、唯一字段是否重复
            excelTable = ImportExcel.ExcelToTableList(FileCode);  //获取Excel数据
            //Excel查询母件+版本号是否重复
            DataTable distinct = excelTable[0].DefaultView.ToTable(true, "母件编码, ç‰ˆæœ¬å·");
            DataTable distinct = excelTable[0].DefaultView.ToTable(true, new string[] {"母件编码", "版本号" });
            if (distinct.Rows.Count != excelTable[0].Rows.Count)
            {
                ExcelErro erro = new ExcelErro();
@@ -2961,6 +2961,19 @@
            {
                if (excelTable[0].Rows[j][1].ToString().Trim() != null && excelTable[0].Rows[j][1].ToString().Trim() != "")
                {
                    //判断母件在存货中是否存在
                    sql = @"select *  from TMateriel_Info where partcode=@partcode";
                    dynamicParams.Add("@partcode", excelTable[0].Rows[j][1].ToString().Trim());
                    dt0 = DapperHelper.selectdata(sql, dynamicParams);
                    if (dt0.Rows.Count<=0)
                    {
                        ExcelErro erro = new ExcelErro();
                        erro.RoeNumber = "/";
                        erro.ErrorField = "{母件编码}";
                        erro.ErrorCont = "Bom母件表:{母件编码}字段" + excelTable[0].Rows[j][1].ToString().Trim() + "在存货档案中不存在";
                        list.Add(erro);
                    }
                    sql = @"select *  from TBom_Main where materiel_code=@code and version=@version";
                    dynamicParams.Add("@code", excelTable[0].Rows[j][1].ToString().Trim());
                    dynamicParams.Add("@version", excelTable[0].Rows[j][5].ToString().Trim());
@@ -2975,16 +2988,44 @@
                    }
                    //查询母件信息对应的子件信息
                    DataTable ds = excelTable[1].AsEnumerable().Where<DataRow>(C => C["主表序列号"].ToString() == excelTable[0].Rows[j][0].ToString().Trim()).CopyToDataTable();
                    for (int i = 0; i < ds.Rows.Count; i++)
                    {
                        if (excelTable[0].Rows[j][1].ToString().Trim() == ds.Rows[i]["子件编码"].ToString().Trim())
                    //for (int i = 0; i < ds.Rows.Count; i++)
                    //{
                    //    if (excelTable[0].Rows[j][1].ToString().Trim() == ds.Rows[i]["子件编码"].ToString().Trim())
                    //    {
                    //        ExcelErro erro = new ExcelErro();
                    //        erro.RoeNumber = "/";
                    //        erro.ErrorField = "{子件编码}";
                    //        erro.ErrorCont = "子件信息:{子件编码}字段" + ds.Rows[i]["子件编码"].ToString().Trim() + "与对应的{母件编码}字段重复";
                    //        list.Add(erro);
                    //    }
                    //}
                    //判断同母件对应的子件是否重复
                    DataTable distinct1 = ds.DefaultView.ToTable(true, "子件编码");
                    if (distinct1.Rows.Count != ds.Rows.Count)
                        {
                            ExcelErro erro = new ExcelErro();
                            erro.RoeNumber = "/";
                            erro.ErrorField = "{子件编码}";
                            erro.ErrorCont = "子件信息:{子件编码}字段" + ds.Rows[i]["子件编码"].ToString().Trim() + "与对应的{母件编码}字段重复";
                        erro.ErrorCont = "物料清单对应子件信息页签模板:主表序列号:【"+ excelTable[0].Rows[j]["主表序列号"].ToString() + "】对应{子件编码}有重复";
                            list.Add(erro);
                        }
                }
            }
            //子件判断
            for (int k = 0; k < excelTable[1].Rows.Count; k++)
            {
                if (excelTable[1].Rows[k][3].ToString().Trim() != null && excelTable[1].Rows[k][3].ToString().Trim() != "")
                {
                    sql = @"select *  from TMateriel_Info where partcode=@partcode";
                    dynamicParams.Add("@partcode", excelTable[1].Rows[k][3].ToString().Trim());
                    dt = DapperHelper.selectdata(sql, dynamicParams);
                    if (dt.Rows.Count<=0)
                    {
                        ExcelErro erro = new ExcelErro();
                        erro.RoeNumber = "/";
                        erro.ErrorField = "{子件编码}";
                        erro.ErrorCont = "物料清单子件信息:{子件编码}字段" + excelTable[1].Rows[k][3].ToString().Trim() + "在存货档案中不存在";
                        list.Add(erro);
                    }
                }
            }
@@ -3109,7 +3150,7 @@
        }
        #endregion
        #region[Excel数据上传,角色权限]
        #region【Excel数据上传,角色权限】
        public static string OneSubmit(string FileCode, string User, out string StuCode)
        {
            string message = "";
@@ -3433,7 +3474,7 @@
                for (int k = 0; k < excelTable[2].Rows.Count; k++)
                {
                    //获取车间编码
                    string sql0 = @"select code,name from TOrganization where org_name=@wkshp and description='W' and is_delete<>'1'";
                    string sql0 = @"select org_code,org_name from TOrganization where org_name=@wkshp and description='W' and is_delete<>'1'";
                    dynamicParams.Add("@wkshp", excelTable[2].Rows[k][6].ToString());
                    var data = DapperHelper.selectdata(sql0, dynamicParams);
@@ -3445,13 +3486,13 @@
                        parm = new
                        {
                            devicecode = excelTable[2].Rows[k][1].ToString().Trim(),
                            devicename = excelTable[0].Rows[k][2].ToString().Trim(),
                            devicetype = excelTable[0].Rows[k][3].ToString().Trim(),
                            devicegroup = excelTable[0].Rows[k][4].ToString().Trim(),
                            workshop = excelTable[0].Rows[k][6].ToString().Trim(),
                            importdate = excelTable[0].Rows[k][5].ToString().Trim(),
                            ratio = excelTable[0].Rows[k][8].ToString().Trim(),
                            status = excelTable[0].Rows[k][7].ToString().Trim(),
                            devicename = excelTable[2].Rows[k][2].ToString().Trim(),
                            devicetype = excelTable[2].Rows[k][3].ToString().Trim(),
                            devicegroup = excelTable[2].Rows[k][4].ToString().Trim(),
                            workshop = data.Rows[0]["org_code"].ToString().Trim(),
                            importdate = excelTable[2].Rows[k][5].ToString().Trim(),
                            ratio = excelTable[2].Rows[k][8].ToString().Trim(),
                            status = excelTable[2].Rows[k][7].ToString().Trim(),
                            uom = "台",
                            lm_user = User,
                            lm_date = DateTime.Now.ToString()
@@ -3529,7 +3570,7 @@
                            break;
                    }
                    sql = @"insert into TEqpchk_Item(code,name,description,isscan,cycle,lm_user,lm_date) 
                            values(@code,@name,@descr,@Operator,@CreateDate)";
                            values(@code,@name,@descr,@isscan,@cycle,@Operator,@CreateDate)";
                    list.Add(new
                    {
                        str = sql,
@@ -3571,9 +3612,75 @@
        {
            string message = "";
            StuCode = "";
            string sql = "";
            List<object> list = new List<object>();
            var dynamicParams = new DynamicParameters();
            try
            {
                list.Clear();
                List<DataTable> excelTable = new List<DataTable>();
                excelTable = ImportExcel.ExcelToTableList(FileCode);
                //导入设备点检标准主表
                for (int k = 0; k < excelTable[0].Rows.Count; k++)
                {
                    sql = @"insert into TEqpchk_Main(code,name,description,iscontr,lm_user,lm_date)
                            values(@code,@name,@description,@iscontr,@Operator,@CreateDate)";
                    list.Add(new
                    {
                        str = sql,
                        parm = new
                        {
                            code = excelTable[0].Rows[k][1].ToString().Trim(),
                            name = excelTable[0].Rows[k][2].ToString().Trim(),
                            iscontr = excelTable[0].Rows[k][3].ToString().Trim(),
                            description = excelTable[0].Rows[k][4].ToString().Trim(),
                            CreateDate = DateTime.Now.ToString(),
                            Operator = User
                        }
                    });
                }
                //导入设备点检标准部位子表
                for (int i = 0; i < excelTable[1].Rows.Count; i++)
                {
                    string Type = "";
                    switch (excelTable[1].Rows[i][7].ToString().Trim())
                    {
                        case "日":
                            Type = "D";
                            break;
                        default:
                            break;
                    }
                    sql = @"insert into TEqpchk_Deta(seq,code,name,eqpchk_main_code,description,isscan,cycle,lm_user,lm_date)
                            values(@seq,@code,@name,@eqpchk_main_code,@description,@isscan,@cycle,@Operator,@CreateDate)";
                    list.Add(new
                    {
                        str = sql,
                        parm = new
                        {
                            seq = excelTable[1].Rows[i][1].ToString().Trim(),
                            code = excelTable[1].Rows[i][3].ToString().Trim(),
                            name = excelTable[1].Rows[i][4].ToString().Trim(),
                            eqpchk_main_code = excelTable[1].Rows[i][2].ToString().Trim(),
                            description = excelTable[1].Rows[i][5].ToString().Trim(),
                            isscan = excelTable[1].Rows[i][6].ToString().Trim(),
                            cycle = Type,
                            Operator = User,
                            CreateDate = DateTime.Now.ToString()
                        }
                    });
                }
                bool aa = DapperHelper.DoTransaction(list);
                if (aa)
                {
                    StuCode = "200";
                    message = "导入成功!";
                }
                else
                {
                    StuCode = "300";
                    message = "导入失败!";
                }
            }
            catch (Exception e)
            {
@@ -4066,8 +4173,8 @@
                for (int k = 0; k < excelTable[0].Rows.Count; k++)
                {
                    BomID = BomID + 1;
                    sql = @"insert into TBom_Main(materiel_code,quantity,status,version,lm_user,lm_date)
                            values(@materiel_code,@quantity,@status,@version,@lm_user,@lm_date)";
                    sql = @"insert into TBom_Main(materiel_code,quantity,status,version,lm_user,lm_date,startdate)
                            values(@materiel_code,@quantity,@status,@version,@lm_user,@lm_date,@startdate)";
                    list.Add(new
                    {
                        str = sql,
@@ -4077,8 +4184,9 @@
                            quantity = excelTable[0].Rows[k][3].ToString().Trim(),
                            status = excelTable[0].Rows[k][4].ToString().Trim(),
                            version = excelTable[0].Rows[k][5].ToString().Trim(),
                            CreateDate = DateTime.Now.ToString(),
                            Operator = User
                            lm_user = User,
                            lm_date = DateTime.Now.ToString(),
                            startdate = DateTime.Now.ToString()
                        }
                    });
                    //查询母件信息对应的子件信息
@@ -4086,6 +4194,18 @@
                    //导入Bom子表
                    for (int i = 0; i < ds.Rows.Count; i++)
                    {
                        string Type = "";
                        switch (ds.Rows[i][9].ToString().Trim())
                        {
                            case "主料":
                                Type = "Z";
                                break;
                            case "辅料":
                                Type = "F";
                                break;
                            default:
                                break;
                        }
                        sql = @"insert into TBom_Deta(m_id,seq,smateriel_code,base_quantity,loss_quantity,total_quantity,pn_type) 
                            values(@m_id,@seq,@smateriel_code,@base_quantity,@loss_quantity,@total_quantity,@pn_type)";
                        list.Add(new
@@ -4099,7 +4219,7 @@
                                base_quantity = ds.Rows[i][6].ToString().Trim(),
                                loss_quantity = ds.Rows[i][7].ToString().Trim(),
                                total_quantity = ds.Rows[i][8].ToString().Trim(),
                                pn_type = ds.Rows[i][9].ToString().Trim()
                                pn_type = Type
                            }
                        });
                    }
VueWebApi/bin/VueWebApi.pdb
Binary files differ
VueWebApi/obj/Release/Package/PackageTmp/Excel/ÎïÁÏÇåµ¥.xls
Binary files differ
VueWebApi/obj/Release/Package/PackageTmp/Excel/É豸Çåµ¥.xls
Binary files differ
VueWebApi/obj/Release/Package/PackageTmp/Excel/É豸µã¼ì±ê×¼.xls
Binary files differ
VueWebApi/obj/Release/Package/PackageTmp/Excel/É豸µã¼ìÏîÄ¿.xls
Binary files differ
VueWebApi/obj/Release/Package/PackageTmp/bin/VueWebApi.pdb
Binary files differ
VueWebApi/obj/Release/VueWebApi.pdb
Binary files differ