单据转换插件手册
# 单据转换插件手册
## 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单据,单据转换过程中,会触发单据转换插件事件,允...
点击下载文档
本文2024-09-23 00:26:20发表“云苍穹知识”栏目。
本文链接:https://wenku.my7c.com/article/kingdee-cangqiong-139467.html
您需要登录后才可以发表评论, 登录登录 或者 注册
最新文档
热门文章