单据转换插件手册

栏目:云苍穹知识作者:金蝶来源:金蝶云社区发布:2024-09-23浏览:1

单据转换插件手册

# 单据转换插件手册 ## 1.场景 单据转换,能够把A单据的数据,根据转换规则,转换生成B单据,单据转换过程中,会触发单据转换插件事件,允许自定义插件进行干预。 ## 2.适用版本 金蝶云苍穹 4.0.004以上 ## 3.单据转换步骤 - 读取源单到目标单之间的全部转换规则; - 匹配转换规则的业务范围,确定适用于当前所选源单的转换规则; - 提取转换规则上,字段映射页签及其他页签,使用到的源单字段; - 根据源单内码,生成源单取数条件; - 读取源单数据行: - 只读取转换规则用到的源单字段; - 如果源单是整单下推,则读取源单全部行;否则取源单所选行数据; - 把单据头、单据体字段组合在一起,生成拉平后的源单数据行; - 根据转换规则,数据范围的配置,对源单行数据,进行筛选,剔除不符合条件的行; - 根据分单、分录行合并策略,对源单数据行进行分组; - 根据分组后的源单数据行,生成目标单、目标单分录行; - 逐行填写目标分录行、单据头字段值; - 在目标单的关联子实体中,记录来源单据信息; - 后续保存目标单时,会根据关联子实体中的来源单据信息,记录单据关联关系及反写; - 如果没有在关联子实体中记录来源单据,则不记录单据关联关系,不能联查,也不能反写; - 输出生成好的目标单数据包,完成单据转换 - 特别说明:单据转换,只是生成目标单数据包,并没有保存入库; ## 4.单据转换插件各事件的触发时机及用途 ### 4.1.插件接口及基类 1. #### 单据转换插件接口 ```java package kd.bos.entity.botp.plugin; public interface IConvertPlugIn { /** * 单据转换可选参数 * @return */ default OperateOption getOption() { return OperateOption.create(); } default void setOption(OperateOption option) {} /** * 开始执行转换规则之前,创建规则上挂的插件后,触发此事件 * * @param opType * @param srcMainType * @param tgtMainType * @param rule * @remark * 由 CreateConvertPlugAction 动作触发 */ void setContext(ConvertOpType opType, BillEntityType srcMainType, BillEntityType tgtMainType, ConvertRuleElement rule); // 代码顺序:按单据转换操作,各种执行场景,各场景中执行的活动进行排序; // 阅读时,按照顺序,从上到下寻找适合的事件 // 各转换操作公共事件 * /** * 初始化变量事件,此时还没有开始编译规则 * * @param e * @remark * 获取上下文信息,构建一些必须的变量 * 由 InitializeAction 动作触发 */ default void initVariable(InitVariableEventArgs e){} /** * 编译数据筛选条件前事件:可追加定制条件,是否忽略规则原生的条件 * * @param e 事件参数:包括本次下推的源单数据、是否忽略规则原生条件 * @remark * 由 ConvertRuleCompiler 动作触发 */ default void beforeBuildRowCondition(BeforeBuildRowConditionEventArgs e){} /** * 构建分单、行合并模式之前事件:调整分单、合并策略及依赖的字段 * * @param e * @remark * 由 ConvertRuleCompiler 动作触发 */ default void beforeBuildGroupMode(BeforeBuildGroupModeEventArgs e){} /** * 构建取数参数后事件:可在正式读取源单数据之前,添加额外的字段、过滤条件 * * @param e * @remark * 由 BuildQueryParameterAction 动作触发 */ default void afterBuildQueryParemeter(AfterBuildQueryParemeterEventArgs e){} /** * 取源单数据前事件:可在正式读取源单数据之前,修改取数语句、取数条件 * * @param e * @remark * 由 LoadSourceDataAction 动作触发 */ default void beforeGetSourceData(BeforeGetSourceDataEventArgs e){} /** * 取源单数据后事件:根据源单数据,获取其他定制的引用数据;也可以替换系统自动获取到的数据 * * @param e * @remark * 由 RunDataConditionAction 动作触发 */ default void afterGetSourceData(AfterGetSourceDataEventArgs e){} /** * 初始化创建目标单据数据包前事件 (暂未触发) * * @param e * @remark * 这个事件,只在选单时触发: * 选单时,需要基于现有的目标单数据包,进行追加处理; * 插件可以在此事件,获取到现有的目标单数据包,提前进行定制处理 */ default void beforeCreateTarget(BeforeCreateTargetEventArgs e){} /** * 创建目标单据数据包后事件:把根据分单规则创建好的目标单,传递给插件 * * @param e * @remark * 由 CreateLinkEntityRowsAction 动作触发 */ default void afterCreateTarget(AfterCreateTargetEventArgs e){} /** * 目标字段赋值完毕后事件:插件可以在此基础上,继续填写目标字段值 * * @param e * @remark * 由 MappingFieldAction 动作触发 */ default void afterFieldMapping(AfterFieldMappingEventArgs e){} /** * 记录关联关系前事件:取消记录关联关系 * * @param e * @remark * 由 FillLinkInfoAction 动作触发 */ default void beforeCreateLink(BeforeCreateLinkEventArgs e){} /** * 记录关联关系后事件:根据系统自动记录的关联关系,进行相关数据的同步携带,如携带其他子单据体数据 * * @param e * @remark * 由 FillLinkInfoAction 动作触发 */ default void afterCreateLink(AfterCreateLinkEventArgs e){} /** * 单据转换后事件,最后执行:插件可以在这个事件中,对生成的目标单数据,进行最后的修改 * * @param e * @remark * 由 MergePushResultAction 动作触发 */ default void afterConvert(AfterConvertEventArgs e){} // 选单前事件 * /** * 选单条件生成后,触发此事件:供插件追加选单条件 * * @param e */ default void afterBuildDrawFilter(AfterBuildDrawFilterEventArgs e) {} } ``` 2. #### 单据转换插件基类`AbstractConvertPlugIn`,实现了转换插件接口`IConvertPlugin` ```java public class AbstractConvertPlugIn implements IConvertPlugIn { } ``` 3. #### 创建并注册插件 自定义单据转换插件,必须扩展插件基类AbstractConvertPlugIn,绑定到单据转换规则上: ![631017735f6a4f000198ae4a.webp](/download/0100e31d1f5ca69b412891896446015087df.webp) 4. #### 附:自定义单据转换插件示例 ```java package kd.bos.plugin.sample.bill.billconvert.bizcase; import kd.bos.entity.BillEntityType; import kd.bos.entity.botp.ConvertOpType; import kd.bos.entity.botp.ConvertRuleElement; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.AfterBuildQueryParemeterEventArgs; import kd.bos.entity.botp.plugin.args.AfterConvertEventArgs; import kd.bos.entity.botp.plugin.args.AfterCreateLinkEventArgs; import kd.bos.entity.botp.plugin.args.AfterCreateTargetEventArgs; import kd.bos.entity.botp.plugin.args.AfterFieldMappingEventArgs; import kd.bos.entity.botp.plugin.args.AfterGetSourceDataEventArgs; import kd.bos.entity.botp.plugin.args.BeforeBuildGroupModeEventArgs; import kd.bos.entity.botp.plugin.args.BeforeBuildRowConditionEventArgs; import kd.bos.entity.botp.plugin.args.BeforeCreateLinkEventArgs; import kd.bos.entity.botp.plugin.args.BeforeCreateTargetEventArgs; import kd.bos.entity.botp.plugin.args.BeforeGetSourceDataEventArgs; import kd.bos.entity.botp.plugin.args.InitVariableEventArgs; /** * 演示单据转换插件事件的触发时机 * * @author rd_JohnnyDing * */ public class BillConvertEventSample extends AbstractConvertPlugIn { /** * 演示如何获取上下文信息 */ private void getContext(){ // 源单主实体 BillEntityType srcMainType = this.getSrcMainType(); // 目标单主实体 BillEntityType tgtMainType = this.getTgtMainType(); // 转换规则 ConvertRuleElement rule = this.getRule(); // 转换方式:下推、选单 ConvertOpType opType = this.getOpType(); } /** * 初始化变量事件 * * @param e * @remark * 获取上下文信息,构建一些必须的变量 */ @Override public void initVariable(InitVariableEventArgs e) { this.printEventInfo("initVariable", ""); } /** * 构建取数参数后事件 * * @param e * @remark * 添加额外的字段、过滤条件 */ @Override public void afterBuildQueryParemeter(AfterBuildQueryParemeterEventArgs e) { this.printEventInfo("afterBuildQueryParemeter", ""); } /** * 编译数据筛选条件前事件 * * @param e * @remark * 设置忽略规则原生的条件,改用插件定制条件,或者在规则条件基础上,追加定制条件 * */ @Override public void beforeBuildRowCondition(BeforeBuildRowConditionEventArgs e) { this.printEventInfo("beforeBuildRowCondition", ""); } /** * 取源单数据前事件 * * @param e * @remark * 修改取数语句、取数条件 */ @Override public void beforeGetSourceData(BeforeGetSourceDataEventArgs e) { this.printEventInfo("beforeGetSourceData", ""); } /** * 取源单数据后事件 * * @param e * @remark * 根据源单数据,获取其他定制的引用数据;也可以替换系统自动获取到的数据 */ @Override public void afterGetSourceData(AfterGetSourceDataEventArgs e) { this.printEventInfo("afterGetSourceData", ""); } /** * 构建分单、行合并模式之前事件 * * @param e * @remark * 调整分单、合并策略及依赖的字段 */ @Override public void beforeBuildGroupMode(BeforeBuildGroupModeEventArgs e) { this.printEventInfo("beforeBuildGroupMode", ""); } /** * 初始化创建目标单据数据包前事件 * * @param e * @remark * 这个事件,只在选单时触发: * 选单时,需要基于现有的目标单数据包,进行追加处理; * 插件可以在此事件,获取到现有的目标单数据包,提前进行定制处理 */ @Override public void beforeCreateTarget(BeforeCreateTargetEventArgs e) { this.printEventInfo("beforeCreateTarget", ""); } /** * 创建目标单据数据包后事件 * * @param e * @remark * 这个事件,只在下推时触发,把根据分单规则创建好的目标单,传递给插件 */ @Override public void afterCreateTarget(AfterCreateTargetEventArgs e) { this.printEventInfo("afterCreateTarget", ""); } /** * 目标字段赋值完毕后事件 * * @param e * @remark * 插件可以在此基础上,继续填写目标字段值 */ @Override public void afterFieldMapping(AfterFieldMappingEventArgs e) { this.printEventInfo("afterFieldMapping", ""); } /** * 记录关联关系前事件 * * @param e * @remark * 取消记录关联关系 */ @Override public void beforeCreateLink(BeforeCreateLinkEventArgs e) { this.printEventInfo("beforeCreateLink", ""); } /** * 记录关联关系后事件 * * @param e * @remark * 根据系统自动记录的关联关系,进行相关数据的同步携带,如携带其他子单据体数据 */ @Override public void afterCreateLink(AfterCreateLinkEventArgs e) { this.printEventInfo("afterCreateLink", ""); } /** * 单据转换后事件,最后执行 * * @param e * @remark * 插件可以在这个事件中,对生成的目标单数据,进行最后的修改 */ @Override public void afterConvert(AfterConvertEventArgs e) { this.printEventInfo("afterConvert", ""); } private void printEventInfo(String eventName, String argString){ String msg = String.format("%s : %s", eventName, argString); System.out.println(msg); } } ``` ### 4.2.插件事件 单据转换插件,提供如下插件事件: | 事件 | 触发时机 | | ------------------------ | ---------------------------- | | initVariable | 初始化变量事件 | | afterBuildQueryParemeter | 构建取数参数后事件 | | beforeBuildRowCondition | 编译数据筛选条件前事件 | | beforeGetSourceData | 取源单数据前事件 | | afterGetSourceData | 取源单数据后事件 | | beforeBuildGroupMode | 构建分单、行合并模式之前事件 | | beforeCreateTarget | 暂未触发 | | afterCreateTarget | 创建目标单据数据包后事件 | | afterFieldMapping | 目标字段赋值完毕后事件 | | beforeCreateLink | 记录关联关系前事件 | | afterCreateLink | 记录关联关系后事件 | | afterConvert | 单据转换完毕事件,最后执行 | 1. #### initVariable 事件 - 事件触发时机 - 开始运行转换规则,创建好了转换规则上绑定的单据转换插件之后,即触发此事件。插件可以在此事件中,对本地变量进行初始化。 - 此事件发生时,源单主实体、目标单主实体、转换规则都已经确定,可以基于这些上下文信息,初始化本地变量。 - 一些通用的单据转换业务插件,需要自动适应各种单据,这个事件就显得比较重要: - 可以在转换规则执行前,让通用插件了解到当前的上下文,初始化一些变量,决定后续业务逻辑。 - 插件可以利用如下方法,获取到源单、目标单主实体、反写规则: ```java private void getContext(){ // 源单主实体 BillEntityType srcMainType = this.getSrcMainType(); // 目标单主实体 BillEntityType tgtMainType = this.getTgtMainType(); // 转换规则 ConvertRuleElement rule = this.getRule(); // 转换方式:下推、选单 ConvertOpType opType = this.getOpType(); } ``` - 代码模板 ```java package kd.bos.plugin.sample.bill.billconvert.template; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.InitVariableEventArgs; public class InitVariable extends AbstractConvertPlugIn { @Override public void initVariable(InitVariableEventArgs e) { // TODO 在此添加业务逻辑 } } ``` - 示例 - 案例说明 1. 需要开发一个通用单据转换插件,把目标单单据体上的金额字段值,合计到单据头上; 2. 不同的单据上,单据体、单据头上的金额字段标识不同; 3. 暂时只支持采购费用发票、销售费用发票; - 实现方案 1. 捕获 `initVariable`事件,确认源单、目标单,单据体、单据头各金额字段标识; 2. 在`afterCreateLink`事件,根据单据体金额字段,合计单据头金额字段,并换算本位币金额; - 实例代码 ```java package kd.bos.plugin.sample.bill.billconvert.bizcase; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.util.Date; import kd.bos.dataentity.entity.DynamicObject; import kd.bos.dataentity.entity.DynamicObjectCollection; import kd.bos.dataentity.utils.StringUtils; import kd.bos.entity.ExtendedDataEntity; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.AfterCreateLinkEventArgs; import kd.bos.entity.botp.plugin.args.InitVariableEventArgs; public class InitVariableSample extends AbstractConvertPlugIn { / 目标单主实体标识 */ private String targetEntityNumber; / 目标单单据体标识 */ private String key_entryentity; private boolean isOtherBill = false; / 字段标识,单据头.价税合计_原币*/ private String key_AllAmountOri; / 字段标识,单据体.价税合计_原币*/ private String key_AllAmountOri_D; / 字段标识,单据头.价税合计_本位币*/ private String key_AllAmount; / 字段标识,单据头.不含税金额_原币*/ private String key_AmountOri; / 字段标识,单据体.不含税金额_原币*/ private String key_AmountOri_D; / 字段标识,单据头.不含税金额_本位币*/ private String key_Amount; / 字段标识,单据头.税额_原币*/ private String key_TaxAmountOri; / 字段标识,单据体.税额_原币*/ private String key_TaxAmountOri_D; / 字段标识,单据头.税额_本位币*/ private String key_TaxAmount; /** * 初始化事件:根据目标单,确定价税合计金额等字段标识 */ @Override public void initVariable(InitVariableEventArgs e) { this.targetEntityNumber = this.getTgtMainType().getName(); this.setAmountKey(); } /** * 关联关系已经记录,并重算了反写控制字段值之后,触发此事件 * * @remark * 金额字段,是反写控制字段: * 反写控制字段,需要记录从每条源单行携带过来的值,然后自动合计到单据体字段上; * 用户手工修改单据体上的反写控制字段时,会根据原始携带量分配、反写到源单 * * 因此,如果要依赖于反写控制字段值进行运算,必须在afterCreateLink事件之后 * */ @Override public void afterCreateLink(AfterCreateLinkEventArgs e) { if (this.isOtherBill){ // 其他未知的单据,不合计金额 return; } ExtendedDataEntity[] billDataEntitys = e.getTargetExtDataEntitySet().FindByEntityKey(this.targetEntityNumber); // 逐单合计单据头金额字段值 for(ExtendedDataEntity billDataEntity : billDataEntitys){ this.calcAmount(billDataEntity); } } /** * 根据目标单,设置含税价格等字段标识 */ private void setAmountKey() { if (StringUtils.equals(this.targetEntityNumber, "iv_purexpinv")){ // 采购费用发票 this.key_entryentity = "entryentity"; //价税合计 this.key_AllAmountOri = "amountori"; this.key_AllAmountOri_D = "amountori"; this.key_AllAmount = "amount"; //不含税金额 this.key_AmountOri = "headauxpriceori"; this.key_AmountOri_D = "headauxpriceori"; this.key_Amount = "headauxprice"; //税额 this.key_TaxAmountOri = "deductibletaxori"; this.key_TaxAmountOri_D = "deductibletaxori"; this.key_TaxAmount = "deductibletax"; } else if (StringUtils.equals(this.targetEntityNumber, "iv_saleexpinv")){ // 销售费用发票 this.key_entryentity = "entryentity"; //价税合计 this.key_AllAmountOri = "aftertotaltaxfor"; this.key_AllAmountOri_D = "allamountori"; this.key_AllAmount = "aftertotaltax"; //不含税金额 this.key_AmountOri = "headauxpriceori"; this.key_AmountOri_D = "amountori"; this.key_Amount = "headauxprice"; //税额 this.key_TaxAmountOri = "taxamount"; this.key_TaxAmountOri_D = "detailtaxamountori"; this.key_TaxAmount = "taxamountori"; } else { // 其他单据:不在此插件中计算价税合计 isOtherBill = true; } } /** * 计算价税合计 * * @param billDataEntity */ private void calcAmount(ExtendedDataEntity billDataEntity){ long currencyID = (long) billDataEntity.getValue("currencyid_id"); // 原币 long mainCurrencyID =(long) billDataEntity.getValue("mainbookstdcurrid_id"); // 本币 long exchangeTypeID = (long) billDataEntity.getValue("exchangetype_id"); // 换算类型 Date date = (Date) billDataEntity.getValue("date"); // 业务日期 // 取当日汇率 BigDecimal rate = new BigDecimal("0"); if (currencyID > 0 && mainCurrencyID > 0) { rate = this.getExchangeBusRate(currencyID, mainCurrencyID, exchangeTypeID, date); } // 把汇率回填到单据上 billDataEntity.setValue("exchangerate", rate); // 开始合计金额:逐行循环,汇总到变量上 BigDecimal taxAmountOriH = new BigDecimal("0"); // 单据头.税额 BigDecimal noTaxAmountOriH = new BigDecimal("0"); // 单据头.不含税金额 BigDecimal totalTaxAmountOriH = new BigDecimal("0"); //单据头.价税合计 DynamicObjectCollection entryRows = (DynamicObjectCollection)billDataEntity.getValue(this.key_entryentity); for (DynamicObject entryRow : entryRows){ taxAmountOriH = taxAmountOriH.add(entryRow.getBigDecimal(key_TaxAmountOri_D)); // 原币 单据体.税额 noTaxAmountOriH = noTaxAmountOriH.add(entryRow.getBigDecimal(key_AmountOri_D)); // 原币 单据体.不含税金额 totalTaxAmountOriH = totalTaxAmountOriH.add(entryRow.getBigDecimal(key_AllAmountOri_D)); // 原币 单据体.价税合计 } // 填写单据头.原币各金额 billDataEntity.setValue(key_TaxAmountOri, taxAmountOriH); billDataEntity.setValue(key_AmountOri, noTaxAmountOriH); billDataEntity.setValue(key_AllAmountOri, totalTaxAmountOriH); // 本位币各金额 :原币金额 * 汇率 (四舍五入,10位小数) MathContext mc = new MathContext(10, RoundingMode.HALF_UP); billDataEntity.setValue(key_TaxAmount, taxAmountOriH.multiply(rate, mc)); billDataEntity.setValue(key_Amount, noTaxAmountOriH.multiply(rate, mc)); billDataEntity.setValue(key_AllAmount, totalTaxAmountOriH.multiply(rate, mc)); } /** * 取当日汇率 * @return */ private BigDecimal getExchangeBusRate(long currencyId1, long currencyId2, long exchangeType, Date date){ // 略过取汇率的逻辑,直接返回 1 return new BigDecimal("1"); } } ``` 2. #### afterBuildQueryParemeter 事件 - 事件触发时机 - 系统根据转换规则上的字段映射关系,确认好了需要加载的源单字段之后,触发此事件,并传入需转换的源单行过滤条件`( FID in [1,2,3,4] )`。 - 插件可以在此事件中,增加需要加载的源单字段,调整源单行取数条件。 - 代码模板 ```java package kd.bos.plugin.sample.bill.billconvert.template; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.AfterBuildQueryParemeterEventArgs; public class AfterBuildQueryParemeter extends AbstractConvertPlugIn { @Override public void afterBuildQueryParemeter(AfterBuildQueryParemeterEventArgs e) { // TODO 在此添加业务逻辑 } } ``` - 事件参数 ```java public class AfterBuildQueryParemeterEventArgs extends ConvertPluginEventArgs{ /** *预计会加载的源单字段及其别名; */ public Map <String, String> getSrcFldAlias(); /** *添加插件需要用到的源单字段; *传入字段标识,如textfield; *如果要取源单基础资料字段的引用属性,要传入基础资料字段标识及其引用属性,如basedatafield.name; *不需要在前面带单据体标识; */ public void addSrcField(String fullPropName); /** *系统根据传入的源单内码,生成的源单取数条件,插件可以调整此集合中的条件对象; */ public List <QFilter> getQFilters(); } ``` - 示例 - 案例说明 1. 源单有单据编号、业务日期、金额字段 2. 需要把这三个字段值,拼成一个字符串,填写到目标单内容字段上; 3. 业务日期格式化为`yyyy-MM-dd`,金额带币别 - 实现方案 1. 捕获 `afterBuildQueryParemeter` 事件,要求加载源单单据编号、业务日期、金额、币别字段 2. 捕获 `afterFieldMapping` 事件,取源单字段值,格式化后填写在目标单上 - 实例代码 ```java package kd.bos.plugin.sample.bill.billconvert.bizcase; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import kd.bos.dataentity.entity.DynamicObject; import kd.bos.entity.ExtendedDataEntity; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.AfterBuildQueryParemeterEventArgs; import kd.bos.entity.botp.plugin.args.AfterFieldMappingEventArgs; import kd.bos.entity.botp.runtime.ConvertConst; public class AfterBuildQueryParemeterSample extends AbstractConvertPlugIn { private final static String KEY_BILLNO = "billno"; private final static String KEY_DATE = "date"; private final static String KEY_AMOUNT = "amount"; private final static String KEY_CURRENCYNAME = "currency.name"; private final static String KEY_CONTENT = "content"; /** * 在开始读取源单数据前,触发此事件 * @remark * 在此事件中,要求加载插件要用到的源单字段 */ @Override public void afterBuildQueryParemeter(AfterBuildQueryParemeterEventArgs e) { e.addSrcField(KEY_BILLNO); // 单据编号 e.addSrcField(KEY_DATE); // 业务日期 e.addSrcField(KEY_AMOUNT); // 金额 e.addSrcField(KEY_CURRENCYNAME); // 币别.名称 currency.name } /** * 目标单字段值,携带完毕后,触发此事件 * @remark * 在此事件中,自行取源单字段值,格式化后填写到目标单 */ @SuppressWarnings("unchecked") @Override public void afterFieldMapping(AfterFieldMappingEventArgs e) { // 取目标单,单据头数据包 (可能会生成多张单,是个数组) String targetEntityNumber = this.getTgtMainType().getName(); ExtendedDataEntity[] billDataEntitys = e.getTargetExtDataEntitySet().FindByEntityKey(targetEntityNumber); SimpleDateFormat timesdf = new SimpleDateFormat("yyyy-MM-dd"); // 逐单处理 for(ExtendedDataEntity billDataEntity : billDataEntitys){ // 取当前目标单,对应的源单行 List<DynamicObject> srcRows = (List<DynamicObject>)billDataEntity.getValue(ConvertConst.ConvExtDataKey_SourceRows); // 取源单第一行上的字段值,忽略其他行 DynamicObject srcRow = srcRows.get(0); // 开始格式化字段值 StringBuilder sBuilder = new StringBuilder(); sBuilder.append(this.getSrcMainType().getDisplayName().toString()); // 单据编号字段值 String billno = (String)e.getFldProperties().get(KEY_BILLNO).getValue(srcRow); sBuilder.append(billno).append("; "); // 日期 Object date = e.getFldProperties().get(KEY_DATE).getValue(srcRow); if (date != null){ sBuilder.append("日期: ").append(timesdf.format((Date)date)).append("; "); } // 金额 BigDecimal amount = (BigDecimal)e.getFldProperties().get(KEY_AMOUNT).getValue(srcRow); sBuilder.append("金额: ").append(amount.toString()); // 币别 String currency = (String)e.getFldProperties().get(KEY_CURRENCYNAME).getValue(srcRow); sBuilder.append(currency); // 给目标单字段赋值 billDataEntity.setValue(KEY_CONTENT, sBuilder.toString()); } } } ``` 3. #### beforeBuildRowCondition 事件 - 事件触发时机 - 编译转换规则 - 数据范围,配置的源单数据筛选条件之前,触发此事件。 - 插件可以在此事件,忽略转换规则上配置的条件,改用插件定制条件,或者追加插件定制条件; - 代码模板 ```java package kd.bos.plugin.sample.bill.billconvert.template; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.BeforeBuildRowConditionEventArgs; public class BeforeBuildRowCondition extends AbstractConvertPlugIn { @Override public void beforeBuildRowCondition(BeforeBuildRowConditionEventArgs e) { // TODO 在此添加业务逻辑 } } ``` - 事件参数 ```java public class BeforeBuildRowConditionEventArgs extends ConvertPluginEventArgs{ /** *完全忽略转换规则上的数据范围 */ public void setIgnoreRuleFilterPolicy(boolean ignoreRuleFilterPolicy); /** *插件定制条件,用于数据库取数; */ public List<QFilter> getCustQFilters(); /** *插件定制条件表达式,用于内存运算;条件含义需要与CustQFilters一致,系统会分别用于不同的时机点; */ public void setCustFilterExpression(String custFilterExpression); /** *插件定制条件描述,当源单数据行,不符合此条件时,系统会把这段条件描述,提示给用户,告诉用户数据行不允许下推的原因; */ public void setCustFilterDesc(String custFilterDesc); } ``` - 示例 - 案例说明 1. 单据下推,需要根据系统选项,动态增加数据筛选条件: a. 如果勾选了"锁定的订单不允许下推"选项,则不能下推锁定状态为已锁定(B)的单据 b. 如果没有勾选此选项,则不做此限制 - 实现方案 1. 捕捉 `beforeBuildRowCondition` 事件,读取选项值 2. 如果勾选了选项,则追加数据行筛选条件,只允许下推未锁定的单据 - 实例代码 ```java package kd.bos.plugin.sample.bill.billconvert.bizcase; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.BeforeBuildRowConditionEventArgs; import kd.bos.orm.query.QCP; import kd.bos.orm.query.QFilter; public class BeforeBuildRowConditionSample extends AbstractConvertPlugIn { @Override public void beforeBuildRowCondition(BeforeBuildRowConditionEventArgs e) { if (!cannotPushLockBill()){ e.setCustFilterDesc("不允许下推已锁定的单据"); // 给出不允许下推的原因 // 设置条件表达式,用于脚本执行 (必选) e.setCustFilterExpression(" lockstatus = 'A' "); // 同时设置具有相同含义的QFilter条件,用于选单数据查询 (必选) QFilter qFilter = new QFilter("lockstatus", QCP.equals, "A"); e.getCustQFilters().add(qFilter); } } /** * 读取业务应用的系统参数值 * @return */ private boolean cannotPushLockBill(){ // 实际业务系统并没有这个选项,本演示代码,直接返回false,演示添加条件 return false; //return (boolean)SystemParamterServiceHelper.getParameter(0, 0, "sal", "cannotpushlockbill"); } } ``` 4. #### beforeGetSourceData 事件 - 事件触发时机 - 系统读取源单数据前,触发此事件。 - 插件可以对取数`SELECT`子句、取数条件,做最后的修改。 - 代码模板 ```java package kd.bos.plugin.sample.bill.billconvert.template; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.BeforeGetSourceDataEventArgs; public class BeforeGetSourceData extends AbstractConvertPlugIn { @Override public void beforeGetSourceData(BeforeGetSourceDataEventArgs e) { // TODO 在此添加业务逻辑 } } ``` - 事件参数 - `public String getSelectSQL()`:取数SELECT子句,包含了全部源单字段及别名; - `public List<QFilter> getQFilters()`:源单行内码筛选条件; - 示例 不建议在这个事件调整取数`SELECT`子句、取数条件; 类似需求,放在`afterBuildQueryParemeter`事件比较合适。 示例略。 5. ### afterGetSourceData 事件 - 事件触发时机 - 源单行数据读取完毕,并按照转换规则上的数据范围,对源单数据行进行筛选之后,触发此事件。 - 插件可以根据通过条件的源单行数据,获取其他定制的引用数据; - 也可以直接替换掉系统自动获取到的源单行数据,改用插件自定读取的源单行数据; - 代码模板 ```java package kd.bos.plugin.sample.bill.billconvert.template; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.AfterGetSourceDataEventArgs; public class AfterGetSourceData extends AbstractConvertPlugIn { @Override public void afterGetSourceData(AfterGetSourceDataEventArgs e) { // TODO 在此添加业务逻辑 } } ``` - 事件参数 ```java AfterGetSourceDataEventArgs ``` - `public Map<String, DynamicProperty> getFldProperties()`:源单字段与字段别名映射字典,可以通过字段标识,取到字段属性对象,然后使用属性对象到源单数据行中取字段值; - `public List<DynamicObject> getSourceRows()`:通过了数据筛选条件的全部源单行; - 示例 暂无示例。 6. ### beforeBuildGroupMode 事件 - 事件触发时机 - 系统按照单据转换规则 – 分单策略,对读取到的源单行,进行分组前,触发此事件。 - 插件可以在此事件,调整分单依赖的字段,影响后续的分单。 - 单据转换规则的分单策略,把源单行进行分组,不同的组生成不同的目标单: - 一对一:一张源单,生成一张目标单,即按照源单单据内码分组; - 多对一:本次选择的全部源单,下推到一张目标单中,即按照常量值分组,全部单,都会分到一个组; - 按规则分单:本次选择的全部源单行,按照分单字段值进行分组; - 单据转换规则支持的分录行合并策略,把源单行进行分组,不同的组合并为一条目标单分录行: - 一对一:一条源单分录行,生成一条目标单分录行,即按照源单分录行内码分组; - 多对一:本次选择的全部源单行,合并为一条目标单分录行,即按常量分组,全部源单行,都回分到一个组; - 按规则合并:本次选择的全部源单行,按照字段值进行分组;相同的字段值分为一组,合并为一条分录行; - 正常情况下,可以配置转换规则 – 分单策略,控制分单、合并。 - 插件可以根据实际业务数据,在此事件中,动态调整分单、合并依赖的字段,从而影响后续的分单。 - 代码模板 ```java package kd.bos.plugin.sample.bill.billconvert.template; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.BeforeBuildGroupModeEventArgs; public class BeforeBuildGroupMode extends AbstractConvertPlugIn { @Override public void beforeBuildGroupMode(BeforeBuildGroupModeEventArgs e) { // TODO 在此添加业务逻辑 } } ``` - 事件参数 - BeforeBuildGroupModeEventArgs - `public String getHeadGroupKey()`:现有的分单依据字段; - `public void setHeadGroupKey(String headGroupKey)` :设置新的分单依据字段 - `public String getEntryGroupKey()`:现有的单据体行合并依据字段; - `public void setEntryGroupKey(String entryGroupKey)`:设置单据体行合并依据字段; - `public String getSubEntryGroupKey()`:子单据体行合并字段; - `public void setSubEntryGroupKey(String subEntryGroupKey)`:设置子单据体行合并字段 - 示例 暂无示例。 7. beforeCreateTarget 事件 - 暂未触发。 8. afterCreateTarget 事件 - 事件触发时机 - 根据分单策略,创建好目标单据、单据体数据行之后,触发此事件; - 此事件发生时,目标单各行数据包字段仅填写了默认值。后续代码会按照字段映射关系,携带源单字段值,覆盖默认值。 - 插件可以在此事件,对目标单字段设置默认值,或者根据源单字段值,动态生成目标单字段值。 - 代码模板 ```java package kd.bos.plugin.sample.bill.billconvert.template; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.AfterCreateTargetEventArgs; public class AfterCreateTarget extends AbstractConvertPlugIn { @Override public void afterCreateTarget(AfterCreateTargetEventArgs e) { // TODO 在此添加业务逻辑 } } ``` - 事件参数 - public class AfterCreateTargetEventArgs extends ConvertPluginEventArgs - public ExtendedDataEntitySet getTargetExtDataEntitySet():生成的目标单扩展数据包; - public Map<String, DynamicProperty> getFldProperties() :源单字段与源单行数据包属性对象映射字典,需要据此到源单行中取需要的字段值; - 示例 暂无示例。 9. afterFieldMapping 事件 - 事件触发时机 - 按照字段映射配置,把源单字段值,填写到了目标单字段上之后,触发此事件。 - 插件可以在此事件中,对目标单字段值,进行修订、计算、汇总等,或者根据生成的目标单字段值,进行拆单、拆行。 - 本事件与`afterCreateTarget`事件的差别: 1. 本事件在`afterCreateTarget`事件之后触发; 2. `afterCreateTarget`事件发生时,目标单字段还没有赋值,适合赋默认值; 3. 本事件发生时,目标单字段已经根据字段映射关系,赋值完毕,适合字段值计算; - 代码模板 ```java package kd.bos.plugin.sample.bill.billconvert.template; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.AfterFieldMappingEventArgs; public class AfterFieldMapping extends AbstractConvertPlugIn { @Override public void afterFieldMapping(AfterFieldMappingEventArgs e) { // TODO 在此添加业务逻辑 } } ``` - 事件参数 - public class AfterFieldMappingEventArgs extends ConvertPluginEventArgs - `public ExtendedDataEntitySet getTargetExtDataEntitySet()`:生成的目标单扩展数据包; - `public Map<String, DynamicProperty> getFldProperties()` :源单字段与源单行数据包属性对象映射字典,需要据此到源单行中取需要的字段值; - 示例 - 参阅 `afterBuildQueryParemeter`事件示例: - 在`afterFieldMapping`事件中,组合、格式化多个源单字段值,填写到目标单字段上。 10. beforeCreateLink 事件 - 事件触发时机 - 目标单字段值填写完毕,开始在目标单关联子实体中,记录源单信息之前,触发此事件。 - 插件可以在此事件,撤销记录源单信息。 - 代码模板 ```java package kd.bos.plugin.sample.bill.billconvert.template; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.BeforeCreateLinkEventArgs; public class BeforeCreateLink extends AbstractConvertPlugIn { @Override public void beforeCreateLink(BeforeCreateLinkEventArgs e) { // TODO 在此添加业务逻辑 } } ``` - 事件参数 - public class BeforeCreateLinkEventArgs extends ConvertPluginEventArgs - `public void setCancel(boolean cancel)`:取消记录关联关系; - `public ExtendedDataEntitySet getTargetExtDataEntitySet()`:生成的目标单扩展数据包; - `public Map<String, DynamicProperty> getFldProperties()` :源单字段与源单行数据包属性对象映射字典,需要据此到源单行中取需要的字段值; - 示例 - 案例说明: 1. 根据动态条件,决定是否记录与源单的关联关系 - 实现方案: 1. 捕获`beforeCreateLink`事件 2. 判断条件,如果条件满足,则取消记录关联关系 - 实例代码: ```java package kd.bos.plugin.sample.bill.billconvert.bizcase; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.BeforeCreateLinkEventArgs; public class BeforeCreateLinkSample extends AbstractConvertPlugIn { @Override public void beforeCreateLink(BeforeCreateLinkEventArgs e) { e.setCancel(this.isCancelLink()); } private boolean isCancelLink(){ return true; } } ``` 11. afterCreateLink 事件 - 事件触发时机 - 系统在目标单关联子表,记录好源单信息、重算了反写控制字段值之后,触发此事件。 - 插件可以在此事件,根据目标单记录的源单信息,携带其他数据到目标单。 - 代码模板 ```java package kd.bos.plugin.sample.bill.billconvert.template; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.AfterCreateLinkEventArgs; public class AfterCreateLink extends AbstractConvertPlugIn { @Override public void afterCreateLink(AfterCreateLinkEventArgs e) { // TODO 在此添加业务逻辑 } } ``` - 事件参数 - public class AfterCreateLinkEventArgs extends ConvertPluginEventArgs - `public ExtendedDataEntitySet getTargetExtDataEntitySet()`:生成的目标单扩展数据包; - `public Map<String, DynamicProperty> getFldProperties()` :源单字段与源单行数据包属性对象映射字典,需要据此到源单行中取需要的字段值; - 示例 参阅 `initVariable`事件示例,在`afterCreateLink`事件,根据来源单据取值计算单据头金额。 12. afterConvert 事件 - 事件触发时机 - 目标单据生成完毕,触发此事件。 - 这个事件,是最后触发的,至此,全部业务逻辑已经执行完毕。 - 插件可以在这个事件,对生成的目标单数据,进行最后的调整。 - 代码模板 ```java package kd.bos.plugin.sample.bill.billconvert.template; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.AfterConvertEventArgs; public class AfterConvert extends AbstractConvertPlugIn { @Override public void afterConvert(AfterConvertEventArgs e) { // TODO 在此添加业务逻辑 } } ``` - 事件参数 - public class AfterConvertEventArgs extends ConvertPluginEventArgs - `public ExtendedDataEntitySet getTargetExtDataEntitySet()`:生成的目标单扩展数据包; - `public Map<String, DynamicProperty> getFldProperties()` :源单字段与源单行数据包属性对象映射字典,需要据此到源单行中取需要的字段值; - 示例 - 案例说明 1. 采购单据,转固定资产卡片时,需每个物品生成一张卡片,即按数量分单 2. 当前转换规则的分单策略,无法配置出此需求,只能插件开发 - 实现方案 1. 捕获`afterConvert`事件,复制新单:根据资产数量字段值,确认新单的复制次数 - 实例代码 ```java package kd.bos.plugin.sample.bill.billconvert.bizcase; import java.util.ArrayList; import java.util.List; import kd.bos.dataentity.entity.DynamicObject; import kd.bos.dataentity.utils.OrmUtils; import kd.bos.entity.ExtendedDataEntity; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.AfterConvertEventArgs; public class AfterConvertSample extends AbstractConvertPlugIn { private final static String FAREALCARD_ENTITYNAME = "fa_card_real"; @Override public void afterConvert(AfterConvertEventArgs e) { // 获取已生成的资产卡片 ExtendedDataEntity[] billDataEntitys = e.getTargetExtDataEntitySet().FindByEntityKey(FAREALCARD_ENTITYNAME); // 构造 ExtendedDataEntity 时需要的索引值 int dataIndex = billDataEntitys.length; List<ExtendedDataEntity> copyDataEntitys = new ArrayList<>(); for(ExtendedDataEntity billDataEntity : billDataEntitys){ // 原始的资产数量 int qty = (int) billDataEntity.getValue("assetamount"); // 将资产数量改为1 billDataEntity.setValue("assetamount", 1); // 来源分录拆分序号 从1开始 int splitSeq = 1; billDataEntity.setValue("sourceentrysplitseq", splitSeq++); // 复制 (原始的资产数量 - 1)个卡片对象 for(int i = 1; i < qty; i++){ DynamicObject copyObj = (DynamicObject) OrmUtils.clone(billDataEntity.getDataEntity(), false, true); copyObj.set("sourceentrysplitseq", splitSeq++); copyDataEntitys.add(new ExtendedDataEntity(copyObj, dataIndex++, 0)); } } // 将复制出的单据,放入 targetExtDataEntitySet ,最终就会生成那么多的卡片 e.getTargetExtDataEntitySet().AddExtendedDataEntities(FAREALCARD_ENTITYNAME, copyDataEntitys); } } ```

单据转换插件手册

# 单据转换插件手册## 1.场景单据转换,能够把A单据的数据,根据转换规则,转换生成B单据,单据转换过程中,会触发单据转换插件事件,允...
点击下载文档
确认删除?
回到顶部
客服QQ
  • 客服QQ点击这里给我发消息