本文介绍了在项目中,由于标准导入功能无法满足特殊需求,特别是在基础资料繁多且需先导入基础资料并关联ID后才能继续导入的情况下,提出了一种通过开发定制导入功能的解决方案。方案包括使用动态表单上传附件,并对其进行基本控制如上传过程中禁止再次上传;通过代码实现上传完成后的处理,包括文件名检查、文件路径获取等;最后,通过读取Excel文件内容,进行校验并导入,其中涉及对Excel内容的校验、DataTable的处理以及导入逻辑的实现。
在很多项目中,有挺多的特殊场景,标准的导入功能可能无法满足导入的需求,并且在导入单据中基础资料特别多的时候,需要先把基础资料导入后对应关联ID需要抓过来后才可以进行导入,存在一些不方便性,另外可能实际导入过程中还有一些特殊想处理和干预的场景,只能通过开发定制一个导入功能,实现方案如下:
一、上传页面尽量使用一个动态表单,需先加入一个附件上传控件
二、并需要对上传做一些基本控制,比如附件在使用中不能上传,上传过程中上传按钮不能使用等基本控制。
public override void CustomEvents(Kingdee.BOS.Core.DynamicForm.PlugIn.Args.CustomEventsArgs e)
{
if (e.Key.ToUpper() == FILEUPLOAD.ToUpper())
{
this.View.GetControl(FILEUPLOAD).SetCustomPropertyValue("NeedCallback", true);
this.View.GetControl(FILEUPLOAD).SetCustomPropertyValue("IsRequesting", false);
if (e.EventName.ToUpper() == FILECHANGED)
{
//上传结束保存上传结果
JSONObject uploadInfo = Kingdee.BOS.JSON.KDObjectConverter.DeserializeObject<JSONObject>(e.EventArgs);
if (uploadInfo != null)
{
JSONArray json = new JSONArray(uploadInfo["NewValue"].ToString());
if (json.Count > 0)
{
string fileName = (json[0] as Dictionary<string, object>)[SERVERFILENAME].ToString();
ExportFileName = fileName;
CheckFile(fileName);
_filePath = GetFilePath(fileName);
this.View.StyleManager.SetEnabled("F_PEIZ_Import", "uploadLock", true);
this.View.GetControl("F_PEIZ_ShowWait").Text = "可以点击导入数据按钮了";
}
else
{
this.View.StyleManager.SetEnabled("F_PEIZ_Import", "uploadLock", false);
this.View.GetControl("F_PEIZ_ShowWait").Text = "请等待文件上传完成后才可点击导入数据按钮";
}
}
}
else if (e.EventName.ToUpper() == STATECHANGED)
{
JSONObject state = Kingdee.BOS.JSON.KDObjectConverter.DeserializeObject<JSONObject>(e.EventArgs);
if (state["State"].ToString() != "2")
{
this.View.StyleManager.SetEnabled("F_PEIZ_Import", "uploadLock", false);
this.View.GetControl("F_PEIZ_ShowWait").Text = "请等待文件上传完成后才可点击导入数据按钮";
}
}
}
base.CustomEvents(e);
}
private void CheckFile(string fileName)
{
string[] fileType = fileName.Split('.');
if (fileType.Length == 2)
{
if (!fileType[1].EqualsIgnoreCase("xls") && !fileType[1].EqualsIgnoreCase("xlsx"))
{
throw new KDException(Kingdee.BOS.Resource.ResManager.LoadKDString("文件上传", "002013030003865", Kingdee.BOS.Resource.SubSystemType.BOS), Kingdee.BOS.Resource.ResManager.LoadKDString("请选择正确的文件进行引入", "002013030003868", Kingdee.BOS.Resource.SubSystemType.BOS));
}
}
}
/// <summary>
/// 获取服务器文件路径,写个方法 便于修改存储路径
/// </summary>
/// <param name="serverFileName"></param>
/// <returns></returns>
private string GetFilePath(string serverFileName)
{
string dir = "FileUpLoadServices\\UploadFiles";
return PathUtils.GetPhysicalPath(dir, serverFileName);
}
三、导入时从EXCEL获取根据字段名称循环获取EXCEL内容,并可以对导入前文件的内容做一些校验。
using (ExcelOperation helper = new ExcelOperation(this.View))
{
DataSet ds = helper.ReadFromFile(_filePath, 1, 0);
//不能选择在Excel转DataSet帮助方法内跳过“枚举值对应表”页签,因为是通用的帮助方法,所以改在外面移除DataTable表。
//而ExcelOperation.ReadFromFile()方法里,能跳过“引入模板说明”页签是因为最后有值得列序号colIndex == 0
bool hasData = ds.Tables.Cast<DataTable>().Any(p => p.Rows.Count > 0);
if (!hasData)
{
this.View.Session["ProcessRateValue"] = 100;
ResultErrInfo= "引入数据为空!";
this.View.ShowErrMessage(Kingdee.BOS.Resource.ResManager.LoadKDString(ResultErrInfo, "002013030003994", Kingdee.BOS.Resource.SubSystemType.BOS));
return;
}
else
{
if (!ds.Tables[0].Columns.Contains("销售单号") || !ds.Tables[0].Columns.Contains("任务单号") || !ds.Tables[0].Columns.Contains("客户编码")
|| !ds.Tables[0].Columns.Contains("产品代码") || !ds.Tables[0].Columns.Contains("产品名称") || !ds.Tables[0].Columns.Contains("批量数量")
|| !ds.Tables[0].Columns.Contains("已出库数量") || !ds.Tables[0].Columns.Contains("出库数量") || !ds.Tables[0].Columns.Contains("出库日期"))
{
this.View.Session["ProcessRateValue"] = 100;
ResultErrInfo = "非标准模板,不能执行导入,请使用标准模板进行引人";
this.View.ShowErrMessage(ResultErrInfo);
return;
}
if (ds.Tables.Contains(Kingdee.BOS.Resource.ResManager.LoadKDString("枚举值对应表", "002013000006565", Kingdee.BOS.Resource.SubSystemType.BOS)))
{
ds.Tables.Remove(Kingdee.BOS.Resource.ResManager.LoadKDString("枚举值对应表", "002013000006565", Kingdee.BOS.Resource.SubSystemType.BOS));
}
//获取EXCEL读取的值
exportTotalCount = ds.Tables[0].Rows.Count;
for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
{
string SalesNo = ds.Tables[0].Rows[i]["销售单号"].ToString(); //根据销售单号+任务单号,确定销售单下推销售出库单的下推项 string TaskNo = ds.Tables[0].Rows[i]["任务单号"].ToString();
string CustNo = ds.Tables[0].Rows[i]["客户编码"].ToString();// 不写入星空,根据订单号可以查到对应的客户编码
string ProductNo = ds.Tables[0].Rows[i]["产品代码"].ToString(); //产品代码对应星空物料编码
string TaskNo = ds.Tables[0].Rows[i]["任务单号"].ToString(); //任务代号主要用做销售订单根据任务号进行下推
string ProductName = ds.Tables[0].Rows[i]["产品名称"].ToString(); //保存星空时只要保存编码就可以
string BatchQty = ds.Tables[0].Rows[i]["批量数量"].ToString();//不写入星空
string FinishOutStockQty = ds.Tables[0].Rows[i]["已出库数量"].ToString(); //不写入星空
string OutStockQty = ds.Tables[0].Rows[i]["出库数量"].ToString();//MES发货单的数量=星空的应收数量=星空的实收数量
string OutStockCdate = DateTime.Now.ToShortDateString().ToString();//系统默认当前日期
}
四、如果单据中需要绑定基础资料时,可以根据EXCEL获取到的名称去对应的基础资料表里进行匹配获取对应的ID进行绑定,如果没有,可以在对应的基础资料表里模拟建一个对应的基础资料,然后获取创建后的ID,并进行基础资料绑定
五、构建对应的单据视图,绑定实体的值。并调保存方法保存
IBillView billView = this.CreateObjectView("SP_InStock"); //创建简单生产视图
((IBillViewService)billView).LoadData(); //加载视图数据
// 触发插件的OnLoad事件:
// 组织控制基类插件,在OnLoad事件中,对主业务组织改变是否提示选项进行初始化。
// 如果不触发OnLoad事件,会导致主业务组织赋值不成功
DynamicFormViewPlugInProxy eventProxy = billView.GetService<DynamicFormViewPlugInProxy>();
eventProxy.FireOnLoad();
FillOutStockPropertys(billView,ProductNo,OutStockQty, OutStockCdate);
OperateOption outStocksaveOption = OperateOption.Create();
string InstockErrInfo="";
bool issuccess = this.Saveinportobject(billView, outStocksaveOption, out InstockErrInfo);
}
这样整个自定义的根据EXCEL导入并模拟单据保存的实现就完成了。
推荐阅读