一、业务背景
大家都知道系统有自动核销的功能
例如正负自动核销,但是系统的自动核销只支持下面的场景
如果换了其他的场景,就无法支持了,比如客户找不到源单的情况下退料,然后再进行补料(创建补料订单),如下图
这个也是系统标准流程哦,同样有着上下游的关联关系,但是却无法进行自动核销,(下面有解释原因)
二、关键步骤
1、通过分析系统标准的核销插件,在应付单--审核上有自动核销的插件,发现系统只是对源单为采购退料单的应付做了自动核销的处理,所以导致无法进行,因为我们流程最后做的采购入库单
2、所以我们通过修改标准的插件的取数逻辑,用我们的sql替换掉标准的sql,结果发现提示“没有需要进行核销的数据”,此处是因为标准插件核销方式methodid是71,71不支持我们的逻辑,在后续接口里面会把我们的数据过滤掉。所以我们要用72,此处71表示自动核销,72是手工核销;但是将methodid改为72 以后,又报错“给定的关键字不在字典中”;这是因为需要在参数里面多两个字段两个字段FBUSINESSTYPE(如果是费用的BUSINESSTYPE就是FY,标准应收就是BZ,标准应付就是CG)跟FMatchAmountFor(价税合计);
PS:系统之前是有核销接口如下面小伙伴的帖子https://vip.kingdee.com/article/43729857239646208?productLineId=1 但是升级升级8.0以后就不能用了,而且总部研发也表示此接口后续不会再维护;
上代码如下:分别是核销与反核销
public class YFDZDHX: AbstractOperationServicePlugIn { List<long> ids = new List<long>(); public override void OnPreparePropertys(PreparePropertysEventArgs e) { base.OnPreparePropertys(e); if (!e.FieldKeys.Contains("FSetAccountType")) { e.FieldKeys.Add("FSetAccountType"); } if (!e.FieldKeys.Contains("FSOURCETYPE")) { e.FieldKeys.Add("FSOURCETYPE"); } if (!e.FieldKeys.Contains("SourceBillNo")) { e.FieldKeys.Add("SourceBillNo"); } } public virtual CalParameter GetZtCalPar(DynamicObject[] dataEntry) { CalParameter result = new CalParameter(); Dictionary<string, List<long>> dictionary = new Dictionary<string, List<long>>(); if (dataEntry != null && dataEntry.Length > 0) { List<DynamicObject> list = (from p in dataEntry where Convert.ToString(p["FSetAccountType"]) == "2" select p).ToList<DynamicObject>(); foreach (DynamicObject current in list) { List<DynamicObject> list2 = (from p in current["AP_PAYABLEENTRY"] as DynamicObjectCollection//源单类型=采购入库单 或者源单类型=采购退料单且源单编号不为空 where (Convert.ToString(p["FSOURCETYPE"]) == "STK_InStock" || Convert.ToString(p["FSOURCETYPE"]) == "PUR_MRB") && Convert.ToString(p["SourceBillNo"]).Trim().Length > 0 select p).ToList<DynamicObject>(); if (list2 != null && list2.Count > 0) { string key = Convert.ToString(list2[0]["FSOURCETYPE"]); if (dictionary.ContainsKey(key)) { if (!dictionary[key].Contains(Convert.ToInt64(current["Id"]))) { dictionary[key].Add(Convert.ToInt64(current["Id"])); } } else { dictionary[key] = new List<long> { Convert.ToInt64(current["Id"]) }; } } } } if (dictionary.Count > 0) { result = this.GetZtCalPar(dictionary); } return result; } public virtual CalParameter GetZtCalPar(Dictionary<string, List<long>> dctData) { CalParameter calParameter = new CalParameter(); IDBService service = ServiceHelper.GetService<IDBService>(); //创建临时表 string text = service.CreateTemporaryTableName(base.Context); //取出单据 this.GetOutReturnData(text, dctData); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendFormat("select distinct FID,FEntryID,FMatchQty,FBUSINESSTYPE,FMatchAmountFor from {0} ", text);//主要位置 DynamicObjectCollection dynamicObjectCollection = DBUtils.ExecuteDynamicObject(base.Context, stringBuilder.ToString(), null, null, CommandType.Text, new SqlParam[0]); if (dynamicObjectCollection != null && dynamicObjectCollection.Count > 0) { List<Dictionary<string, object>> list = new List<Dictionary<string, object>>(); List<Dictionary<string, object>> list2 = new List<Dictionary<string, object>>(); foreach (DynamicObject current in dynamicObjectCollection) { list.Add(new Dictionary<string, object> { { "FID", current["FID"] }, { "FEntryID", current["FEntryID"] }, { "FMatchQty", current["FMatchQty"] }, { "FBUSINESSTYPE", current["FBUSINESSTYPE"] }, { "FMatchAmountFor", current["FMatchAmountFor"] } }); } calParameter.SrcDataDebit = list.ToArray(); calParameter.SrcDataCredit = list2.ToArray(); } service.DeleteTemporaryTableName(base.Context, new string[] { text }); return calParameter; } public virtual void GetOutReturnData(string tableName, Dictionary<string, List<long>> dctData) { this.CreateTempTable(tableName); if (dctData.ContainsKey("STK_InStock")) { //第一步 this.GetInStockAR(tableName, dctData["STK_InStock"]); } DynamicObjectCollection DynamicObjectCollection= Gettempdata(tableName); ids = dctData["STK_InStock"];//把ids取出来,执行成功后,更新手动核销为自动核销 //第二步 this.GetInStockARBySide(tableName); DynamicObjectCollection DynamicObjectCollection1 = Gettempdata(tableName); } private DynamicObjectCollection Gettempdata(string tableName) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendFormat("select * from {0} ", tableName); DynamicObjectCollection dynamicObjectCollection = DBUtils.ExecuteDynamicObject(base.Context, stringBuilder.ToString(), null, null, CommandType.Text, new SqlParam[0]); return dynamicObjectCollection; } //第二步 获取上游退料单跟退料单的应付 public virtual void GetInStockARBySide(string tableName) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendFormat("insert into {0}(FID,FENTRYID,FMATCHQTY,FSOURCETYPE,FOUTID,FOUTENTRYID,FMatchAmountFor) ", tableName); stringBuilder.AppendLine("select t1.FID,t2.FENTRYID,t2.FBASICUNITQTY-t2.FBUYIVBASICQTY as FMatchQty,t2.FSOURCETYPE,trout.FID AS FOUTID,trout.FENTRYID AS FOUTENTRYID,A1.FMatchAmountFor*-1 "); stringBuilder.AppendLine("from T_AP_PAYABLE t1 "); stringBuilder.AppendLine("inner join T_AP_PAYABLEENTRY t2 on t1.fid=t2.fid and t1.fdocumentstatus='C' and t1.FSETACCOUNTTYPE='2' and t1.FByVerify='0' and t1.FRedBlue='0' and t2.FBASICUNITQTY-t2.FBUYIVBASICQTY<>0 "); stringBuilder.AppendLine("inner join T_AP_PAYABLE_LK lk1 on t2.FENTRYID=lk1.FENTRYID "); stringBuilder.AppendLine("inner join T_PUR_MRBENTRY tretu on lk1.FSBILLID=tretu.FID and lk1.FSID=tretu.FENTRYID and lk1.FSTABLENAME like 'T_PUR_MRBENTRY%' "); stringBuilder.AppendLine("INNER JOIN T_PUR_POORDERENTRY_LK B1 ON B1.FSBILLID=tretu.FID AND B1.FSID=tretu.FENTRYID "); stringBuilder.AppendLine("INNER JOIN t_PUR_POOrderEntry B2 ON B2.FENTRYID=B1.FENTRYID"); stringBuilder.AppendLine("INNER JOIN T_STK_INSTOCKENTRY trout on trout.FPOORDERENTRYID=B2.FENTRYID "); stringBuilder.AppendFormat("inner join {0} a1 on a1.FOUTID=trout.FID and a1.FOUTENTRYID=trout.FENTRYID and a1.fsourcetype='{1}' ", tableName, "STK_InStock"); DBUtils.Execute(base.Context, stringBuilder.ToString()); } public virtual void CreateTempTable(string tableName) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendFormat("create table {0} ( ", tableName); stringBuilder.AppendLine(" FID int not null default 0 ,"); stringBuilder.AppendLine(" FENTRYID int not null default 0 ,"); stringBuilder.AppendLine(" FMATCHQTY decimal(23,10) not null default 0 ,"); stringBuilder.AppendLine(" FSOURCETYPE varchar(80) not null default ' ' ,"); stringBuilder.AppendLine(" FOUTID int not null default 0 ,"); stringBuilder.AppendLine(" FOUTENTRYID int not null default 0 ,"); stringBuilder.AppendLine(" FBUSINESSTYPE varchar(80) not null default 'CG' ,"); stringBuilder.AppendLine(" FMatchAmountFor decimal(23,10) not null default 0 )"); DBUtils.Execute(base.Context, stringBuilder.ToString()); } //第一步,把当前的应付单跟上游的入库单插入临时表 public virtual void GetInStockAR(string tableName, List<long> lstIds) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendFormat("insert into {0}(FID,FENTRYID,FMATCHQTY,FSOURCETYPE,FOUTID,FOUTENTRYID,FMatchAmountFor) ", tableName); stringBuilder.AppendLine("select t1.FID,t2.FENTRYID,t2.FBASICUNITQTY-t2.FBUYIVBASICQTY as FMatchQty,t2.FSOURCETYPE,tout.FID,tout.FENTRYID,T2.FALLAMOUNTFOR "); stringBuilder.AppendLine("from T_AP_PAYABLE t1 "); stringBuilder.AppendFormat("inner join ({0}) tid on t1.fid=tid.fid ", StringUtils.GetSqlWithCardinality(lstIds.Count, "@FID", 1, true)); stringBuilder.AppendLine("inner join T_AP_PAYABLEENTRY t2 on t1.fid=t2.fid and t1.fdocumentstatus='C' and t1.FSETACCOUNTTYPE='2' and t1.FByVerify='0' and t1.FRedBlue='0' and t2.FBASICUNITQTY-t2.FBUYIVBASICQTY<>0 "); stringBuilder.AppendLine("inner join T_AP_PAYABLE_LK lk1 on t2.FENTRYID=lk1.FENTRYID "); stringBuilder.AppendLine("inner join T_STK_INSTOCKENTRY tout on lk1.FSBILLID=tout.FID and lk1.FSID=tout.FENTRYID and lk1.FSTABLENAME like 'T_STK_INSTOCKENTRY%' "); DBUtils.Execute(base.Context, stringBuilder.ToString(), new SqlParam("@FID", KDDbType.udt_inttable, lstIds.ToArray())); } public override void EndOperationTransaction(EndOperationTransactionArgs e) { try { base.EndOperationTransaction(e); if (base.BusinessInfo.GetForm().Id.EqualsIgnoreCase("AP_Payable")) { CalParameter ztCalPar = this.GetZtCalPar(e.DataEntitys); if (ztCalPar.SrcDataDebit != null && ztCalPar.SrcDataDebit.Count<Dictionary<string, object>>() > 0) { FinMatch finMatch = new FinMatch(); ztCalPar.Ctx = base.Context; ztCalPar.FormID = base.BusinessInfo.GetForm().Id; ztCalPar.FinMatchMethod = 72; //71自动核销 72手动核销 ztCalPar.MatchType = "3"; finMatch.para = ztCalPar; IOperationResult operationResult = finMatch.FinMatchProcess(); this.AfterFinMatch(operationResult.FuncResult); StringBuilder stringBuilder = new StringBuilder(); if (!operationResult.IsSuccess) { stringBuilder.AppendLine(ResManager.LoadKDString("核销失败", "003176000001825", SubSystemType.FIN, new object[0])); foreach (OperateResult current in operationResult.OperateResult) { stringBuilder.AppendLine(current.Message); } throw new KDException("", stringBuilder.ToString()); } if (operationResult.IsSuccess) { stringBuilder.AppendLine(ResManager.LoadKDString("核销成功", "003182000009431", SubSystemType.FIN, new object[0])); foreach (OperateResult current2 in operationResult.OperateResult) { stringBuilder.AppendLine(current2.Message); } if (operationResult.OperateResult.Count>1) { if (ids.Count>0) { string strids = string.Join<long>(",", ids); //--应付付款核销记录 StringBuilder stringBuilder1 = new StringBuilder(); stringBuilder1.AppendLine("/*dialect*/UPDATE A SET A.FISJOINMATCH=1,A.FMATCHMETHODID=71"); stringBuilder1.AppendLine("from T_AP_PAYABLE t1 "); stringBuilder1.AppendFormat("inner join (SELECT FID FROM fn_StrSplit('{0}',',') ) tid on t1.fid=tid.fid ", strids); stringBuilder1.AppendLine("INNER JOIN T_AP_PAYMATCHLOGENTRY A1 ON t1.FID=A1.FSRCBILLID "); stringBuilder1.AppendLine("INNER JOIN T_AP_PAYMATCHLOG A ON A.FID=A1.FID "); DBUtils.Execute(base.Context, stringBuilder1.ToString(), new SqlParam("@FID", KDDbType.udt_inttable, ids.ToArray())); //--应付开票核销记录 StringBuilder stringBuilder2 = new StringBuilder(); stringBuilder2.AppendLine("/*dialect*/UPDATE A SET A.FISJOINMATCH=1,A.FMATCHMETHODID=71"); stringBuilder2.AppendLine("from T_AP_PAYABLE t1 "); stringBuilder2.AppendFormat("inner join (SELECT FID FROM fn_StrSplit('{0}',',') ) tid on t1.fid=tid.fid ", strids); stringBuilder2.AppendLine("INNER JOIN T_AP_BILLINGMATCHLOGENTRY A1 ON t1.FID=A1.FSRCBILLID "); stringBuilder2.AppendLine("INNER JOIN T_AP_BILLINGMATCHLOG A ON A.FID=A1.FID "); DBUtils.Execute(base.Context, stringBuilder2.ToString(), new SqlParam("@FID", KDDbType.udt_inttable, ids.ToArray())); } } APCommonFunction.WriteLog(base.Context, ResManager.LoadKDString("审核", "003185000018287", SubSystemType.FIN, new object[0]), base.BusinessInfo.GetForm().Id, base.BusinessInfo.GetForm().SubsysId, stringBuilder.ToString()); } } } } catch (Exception ex) { Logger.Error("ARAP", ex.Message, ex); throw new KDException("", ex.Message); } } public virtual void AfterFinMatch(object objOpenMatchIDs) { } }
反核销插件要简单的多了
public override void EndOperationTransaction(EndOperationTransactionArgs e) { try { base.EndOperationTransaction(e); if (base.BusinessInfo.GetForm().Id.EqualsIgnoreCase("AP_Payable") && e.DataEntitys.Length > 0)// && base.FormOperation.OperationId == FormOperation.Operation_UnAudit) { long[] zfBillIds = this.GetZfBillIds(e.DataEntitys);//自己组装数据 if (zfBillIds != null && zfBillIds.Length > 0) { HashSet<long> billingIds = this.GetBillingIds(zfBillIds); if (billingIds != null && billingIds.Count > 0) { IOperationResult operationResult = new FinMatch { para = new CalParameter { Ctx = base.Context, FormID = base.BusinessInfo.GetForm().Id, FinMatchMethod = 72, MatchLogs = billingIds } }.UnFinMatchProcess(); bool arg_C3_0 = operationResult.IsSuccess; } } } } catch (Exception ex) { Logger.Error("ARAP", ex.Message, ex); throw new KDException("", ex.Message); } }
3、由于我们插件内调用的手工核销,所以在核销记录上会显示手工核销,如果不把手工核销伪装成自动核销,那么反核销插件就无法使用,而且反审核校验的时候也过不去;
4、插件分别放置在应付单的审核操作与反审核操作上,此处注意一定要加前置条件与标准的自动核销插件进行区分,防止后续功能冲突
三、小结
此种功能理论上还是需要总部进行完善的,毕竟采购退料流程都有两个,相应的核销功能也要跟上,不能做供应链的只管供应链,做应付的只负责应付....