发布时间:2023-05-30 文章分类:WEB开发, 电脑百科 投稿人:李佳 字号: 默认 | | 超大 打印

文章目录

  • 泛微E9二次开发,对接金蝶云星空
  • 一、搭建开发环境,引入相关依赖
      • 一、创建项目
      • 二、配置javaSDK
      • 三、配置项目依赖
      • 四、编写测试代码
      • 五、配置编译
      • 六、resin远程debug配置
  • 二、开发部署
      • 1.编写自定义方法, 必须实现 必须实现接口weaver.interfaces.workflow.action方法public String execute(RequestInfo request);
      • 2.将文件上传至服务器D:\WEAVER\ecology\classbean\weaver\interfaces\workflow\action目录;
      • 3.在后台,新增自定义接口;
      • 4.在后台 流程引擎-路径管理-路径设置, 找到对应流程,选中流程;
      • 5.代码更新操作
  • 三、代码示例
      • 1.流程自定义接口动作(action)
      • 2.自定义Action Demo
      • 3.采购订单的同步
      • 3.ERP对接
  • 总结

泛微E9二次开发,对接金蝶云星空

泛微E9二次开发,对接金蝶云星空,可以同步采购订单,付款申请单,收料通知单等等:
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。超详细,需要的jar包文档,可以在我的主页找到资源下载。

一、搭建开发环境,引入相关依赖

可以按照此链接步骤操作:ecology后端开发环境搭建

一、创建项目

1、新建项目:File->New->Project
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
2、选择java项目
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
3、下一步:Next
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
4、继续下一步:Next,并输入项目名字之后点击Finish
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。

二、配置javaSDK

ecology开发推荐使用idea

1、进入项目设置File-Project Structure
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
2、点击SDKs->+,添加jdk1.8(e9依赖的版本)
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
3、点击project,分别选择Project SDK为1.8、、Project language level为SDK default 8
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。

三、配置项目依赖

1、进入项目设置:File->Project Structure
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
2、添加依赖库:Libaries->±>java
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
3、找到要调试的ecology demo环境,分别引入以下路径的包

ecology/classbean
ecology/web-inf/lib
resin4/lib

注:可以去服务器上找到相关的依赖
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
4、添加完之后点击Apply
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。

四、编写测试代码

1、点击src右键->new->Package
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
2、右键点击包名->new->Java,注意包名带有impl层级,才可支持无侵入注解解析
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
3、测试代码如下:

package com.api.cs.test20200529.service.impl;
import com.weaverboot.frame.ioc.anno.classAnno.WeaIocReplaceComponent;
import com.weaverboot.frame.ioc.anno.methodAnno.WeaReplaceAfter;
import com.weaverboot.frame.ioc.anno.methodAnno.WeaReplaceBefore;
import com.weaverboot.frame.ioc.handler.replace.weaReplaceParam.impl.WeaAfterReplaceParam;
import com.weaverboot.frame.ioc.handler.replace.weaReplaceParam.impl.WeaBeforeReplaceParam;
import com.weaverboot.tools.logTools.LogTools;
@WeaIocReplaceComponent
public class Test {
    @WeaReplaceBefore(value = "/api/workflow/reqlist/splitPageKey",order = 1,description = "测试拦截前置")
    public void beforeTest(WeaBeforeReplaceParam weaBeforeReplaceParam){
        //一顿操作
        LogTools.info("before:/api/workflow/reqlist/splitPageKey");
    }
    @WeaReplaceAfter(value = "/api/workflow/reqlist/splitPageKey",order = 1,description = "测试拦截后置")
    public String after(WeaAfterReplaceParam weaAfterReplaceParam){
        String data = weaAfterReplaceParam.getData();//这个就是接口执行完的报文
        LogTools.info("after:/api/workflow/reqlist/splitPageKey");
//        LogTools.info(data);
        return data;
    }
}

五、配置编译

1、进入项目设置:File->Project Structure
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
2、进入Artifacts->±>JAR->Empty
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
3、修改Name,这里要注意的是如果要支持无侵入注解解析,jar包名称必须包含下划线前缀,类似示例的写法
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
4、修改jar包输出目录(output directory)到以下目录

ecology/web-inf/lib

泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
5、添加src输出内容到jar包,点击Apply
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
6、选择build->Build Artifacts
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
7、点击build,即可完成编译
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
8、到系统中查看编译结果
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。

六、resin远程debug配置

ecology开发推荐使用idea,idea版权归属:JetBrains
特别提示:调试模式禁止使用到生产系统,必须是开发环境

1、进入resin配置文件:resin4/config/resin.properties 修改jvm_args

jvm_args  : -Xdebug -Xrunjdwp:transport=dt_socket,address=9081,server=y,suspend=n -Dcom.sun.management.jmxremote -Xloggc:/var/log/gc.log -Xmx1550m -Xms1550m -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+DisableExplicitGC
jvm_mode  : -server

泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
2、添加中间件连接
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
3、添加±>Resin->Remote远程服务,分别输入ec服务地址URL、远程Resin调试端口Port
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
4、选择configure,选择resin home
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
5、添加jar包
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
6、选择之前定义的Artifacts,点击Apply保存
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
7、如果是同一个系统内,选择
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
8、切换Startup/Connection,修改Debug->port为resin的远程调试端口
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
9、services中启动debug
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。
10、到指定页面刷新(本示例是待办页面),成功进入断点,debug配置成功
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。

二、开发部署

1.编写自定义方法, 必须实现 必须实现接口weaver.interfaces.workflow.action方法public String execute(RequestInfo request);

注: 写好的类需要编译为class 文件;

2.将文件上传至服务器D:\WEAVER\ecology\classbean\weaver\interfaces\workflow\action目录;

泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。

3.在后台,新增自定义接口;

3.1 接口动作类文件填写 classbean 下文件路径;
在后台 集成中心-流程流转集成, 注册自定义接口;
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。

4.在后台 流程引擎-路径管理-路径设置, 找到对应流程,选中流程;

4.1. 在右侧 流转设置-节点信息, 点击对应节点的 节点前后附加操作按钮;
4.2 弹出窗口中, 选择 外部接口tab, 接口来源 选择 自定义创建的接口;
4.3 点击确定 保存;
泛微E9二次开发,对接金蝶云星空,数据同步,表单同步。

5.代码更新操作

5.1 重新编译文件;
5.2 将编译后的class 文件上传至服务器对应路径;

三、代码示例

1.流程自定义接口动作(action)

一、接口说明
该接口主要实现在流程的流转当中,实时通过自定义的动作去操作异构系统的数据或者处理其他一些特定的业务,在流程的节点后、出口和节点前都可以定义这样的自定义动作。

二、实现方法&步骤
实现weaver.interfaces.workflow.action. Action接口即可

//接口定义如下:
import weaver.soa.workflow.request.RequestInfo;
public interface Action {
    public static final String SUCCESS="1";
    /**
     * 失败信息,返回此信息,如果是节点前附加操作,将会阻止流程提交
     */
    public static final String FAILURE_AND_CONTINUE = "0";
    public String execute(RequestInfo request);
}

2.自定义Action Demo

代码如下(示例):

package test.service.impl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import kingdee.bos.webapi.client.K3CloudApiClient;
import weaver.general.BaseBean;
import weaver.general.Util;
import weaver.interfaces.workflow.action.Action;
import weaver.soa.workflow.request.*;
import weaver.hrm.User;
public class DemoAction implements Action {
    private Log log = LogFactory.getLog(DemoAction.class.getName());
    private String p1; // 自定义参数1
    private String p2; // 自定义参数2
	public String getP1() {
		return p1;
	}
	public void setP1(String p1) {
		this.p1 = p1;
	}
	public String getP2() {
		return p2;
	}
	public void setP2(String p2) {
		this.p2 = p2;
	}
	public Log getLog() {
		return log;
	}
	public void setLog(Log log) {
		this.log = log;
	}
	public String execute(RequestInfo requestinfo) {
		System.out.println("进入Action requestid=" + requestinfo.getRequestid());
		String requestid = requestinfo.getRequestid();// 请求ID
		String requestlevel = requestinfo.getRequestlevel();// 请求紧急程度
		String src = requestinfo.getRequestManager().getSrc(); // 当前操作类型	submit:提交/reject:退回
		String workflowid = requestinfo.getWorkflowid();// 流程路径ID
		String tablename = requestinfo.getRequestManager().getBillTableName();// 表单主表名称
		int billid = requestinfo.getRequestManager().getBillid();// 表单数据ID
		User usr = requestinfo.getRequestManager().getUser();// 获取当前操作用户对象
		String requestname = requestinfo.getRequestManager().getRequestname();// 请求标题
		String remark = requestinfo.getRequestManager().getRemark();// 当前用户提交时的签字意见
		int formid = requestinfo.getRequestManager().getFormid();// 表单ID
		int isbill = requestinfo.getRequestManager().getIsbill();// 是否是自定义表单
		//取主表数据
		Property[] properties = requestinfo.getMainTableInfo().getProperty();// 获取表单主字段信息
		for (int i = 0; i < properties.length; i++) {
			String name = properties[i].getName();// 主字段名称
			String value = Util.null2String(properties[i].getValue());// 主字段对应的值
			System.out.println(name + " " + value);
			log.info(name + " " + value);
		}
		// 取明细数据
		DetailTable[] detailtable = requestinfo.getDetailTableInfo()
				.getDetailTable();// 获取所有明细表
		if (detailtable.length > 0) {
			for (int i = 0; i < detailtable.length; i++) {
				DetailTable dt = detailtable[i];// 指定明细表
				Row[] s = dt.getRow();// 当前明细表的所有数据,按行存储
				for (int j = 0; j < s.length; j++) {
					Row r = s[j];// 指定行
					Cell c[] = r.getCell();// 每行数据再按列存储
					for (int k = 0; k < c.length; k++) {
						Cell c1 = c[k];// 指定列
						String name = c1.getName();// 明细字段名称
						String value = c1.getValue();// 明细字段的值
						System.out.println(name + " " + value);
						log.info(name + " " + value);
					}
				}
			}
		}
//    调用ERP接口 单元测试代码
//    public static void main(String[] args) {
//        String res = "";
//        Boolean loginResult = false;
//        K3CloudApiClient client = new K3CloudApiClient("http://119.23.xx.xx/k3cloud/");
//        try {
//            loginResult = client.login("612112883069f0","134xxxx0349","JG2xxxxx10",2xxx);
//            res = client.view("BD_Currency","{\"CreateOrgId\":0,\"Number\":\"PRE001\"}");
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//
//        System.out.println("test202201174_1" + client);
//        System.out.println("test202201174_2" + loginResult);
//        System.out.println(res.getClass().getSimpleName());
//        System.out.println("test202201174_3" + res);
//
//    }
		//控制流程流转,增加以下两行,流程不会向下流转,表单上显示返回的自定义错误信息,这个控制只支持节点后附加操作
		requestinfo.getRequestManager().setMessageid("错误信息编号");//126221
		requestinfo.getRequestManager().setMessagecontent("返回自定义的错误信息");
		System.out.println("Action执行完成 传入参数p1=" + this.getP1() + "  p2="	+ this.getP2());
		log.info("Action执行完成 传入参数p1=" + this.getP1() + "  p2="	+ this.getP2());
		return SUCCESS;// return返回固定返回`SUCCESS`
//		//如果E8的版本是1604,也可以使用下面的代码进行控制,支持节点后、节点前、出口,注意必须返回 FAILURE_AND_CONTINUE;
//		requestinfo.getRequestManager().setMessagecontent("返回自定义的错误信息");
//		return FAILURE_AND_CONTINUE;
	}
}

3.采购订单的同步

代码如下(示例):

package weaver.interfaces.workflow.action;
import com.google.gson.Gson;
import com.kingdee.bos.webapi.entity.RepoRet;
import kingdee.bos.webapi.client.K3CloudApiClient;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import weaver.general.BaseBean;
import weaver.general.Util;
import weaver.interfaces.workflow.action.Action;
import weaver.soa.workflow.request.*;
/**
 * 自定义采购订单的action接口
 */
public class ActionERPCGDD extends BaseBean implements Action {
    @Override
    public String execute(weaver.soa.workflow.request.RequestInfo requestInfo) {
        // 日志
        Log log = LogFactory.getLog(this.getClass());
        // 获取主表信息
        Property[] properties = requestInfo.getMainTableInfo().getProperty();
/*        for (int i = 0; i < properties.length; i++) {
            String name = properties[i].getName();// 主字段名称
            String value = Util.null2String(properties[i].getValue());// 主字段对应的值
            log.info(name + " " + value);
            writeLog(name + " " + value);
            System.out.println("获取单据头的信息:" + name + " " + value);
        }*/
        String json_main;
        String json_sublist;
        String json1 = "";
        String json2 = "";
        // 获取所有明细表
        DetailTable[] detailtable = requestInfo.getDetailTableInfo().getDetailTable();
        if (detailtable.length > 0) {
            for (int i = 0; i < detailtable.length; i++) {
                DetailTable dt = detailtable[i];// 指定明细表
                Row[] s = dt.getRow();// 当前明细表的所有数据,按行存储
                for (int j = 0; j < s.length; j++) {
                    Row r = s[j];// 指定行
                    Cell c[] = r.getCell();// 每行数据再按列存储
/*                    for (int k = 0; k < c.length; k++) {
                        Cell c1 = c[k];// 指定列
                        String name = c1.getName();// 明细字段名称
                        String value = c1.getValue();// 明细字段的值
                        log.info(name + " " + value);
                        writeLog(name + " " + value);
                        System.out.println("获取单据体的信息:" + name + " " + value);
                    }*/
                    //如果遍历的是第一个单据体
                    if(i==0) {
                        Double TaxRate =Double.valueOf(c[0].getValue());
                        json_main = "      \n{\n" +
                                    "        \"FMaterialId\": {\n" +         //物料编码
                                    "          \"FNumber\": \"" + Util.null2String(c[4].getValue()) + "\"\n" +
                                    "        },\n" +
                                    "        \"FQty\": " + Util.null2String(c[14].getValue()) + ",\n" +          //采购数量
                                    "        \"FTaxPrice\":" + Util.null2String(c[7].getValue()) + ", \n" +    //含税单价
                                    "        \"F_RCZU_YT\":\"" + Util.null2String(c[13].getValue()) + "\",\n" +    //设备型号 用途
                                    "        \"FDeliveryDate\":\"" + Util.null2String(c[11].getValue()) + "\",\n" +    //订单交货日期
                                    "        \"FEntryTaxRate\":" +TaxRate+ ", \n" +    //税率
                                    "        \"FEntrySettleOrgId\": {\n" +    //结算组织
                                    "          \"FNumber\": \"" + Util.null2String(c[1].getValue()) + "\"\n" +
                                    "        }\n" +
                                    "      },";
                        json1 += json_main;
                    }
                    //如果遍历第二个单据体  Util.null2String(c[3].getValue())==0 true
                    else if (i==1){
                        //定义一个变量来控制是否预付款
                        Boolean flag= null;
                        if (Util.null2String(c[4].getValue()).equals("0")){
                             flag=true;
                        }else if (Util.null2String(c[4].getValue()).equals("1")) {
                             flag=false;
                        }
                        //应付比例 需要的是小数
                        Double proportion=Double.valueOf(Util.null2String(c[2].getValue()))/100;
                        json_sublist=   "       \n{\n" +
                                        "           \"FISPREPAYMENT\":"+flag+",\n" +     //是否预付  布尔值
                                        "           \"FRemarks\":\""+ Util.null2String(c[0].getValue()) +"\",\n" +  //备注
                                        "           \"FYFAMOUNT\":\""+ Util.null2String(c[3].getValue()) +"\",\n" + //应付金额 Integer
                                        "           \"FYFRATIO\":"+proportion+"\n" +     //应付比例(%) Integer
                                        "       },";
                        json2 += json_sublist;
                    }
                }
            }
            //获取主单据体的长度
            int json1_l=json1.length();
            //截取遍历完成的”,“
            json1=json1.substring(0,json1_l-1);
            //获取子单据体的长度
            int json2_l=json2.length();
            //截取遍历完成的”,“
            json2=json2.substring(0,json2_l-1);
        }
        String json="{\n" +
                "  \"IsAutoSubmitAndAudit\": \"true\",\n" +  //是否自动提交审核
                "  \"NeedUpDateFields\": [],\n" +            //需要更新的字段
                "  \"NeedReturnFields\": [],\n" +            //需返回结果的字段集合
                "  \"IsDeleteEntry\": true,\n" +             //是否删除已存在的分录
                "  \"SubSystemId\": \"\",\n" +               //子系统id
                "  \"IsVerifyBaseDataField\": false,\n" +    //是否验证基本数据字段
                "  \"IsEntryBatchFill\": true,\n" +          //是否单据体批量填写
                "  \"ValidateFlag\": true,\n" +             //生效日期
                "  \"NumberSearch\": true,\n" +             //是否用编码搜索基础资料
                "  \"IsAutoAdjustField\": true,\n" +       //是否自动调整字段
                "  \"InterationFlags\": \"\",\n" +          //交互影响的标记
                "  \"IgnoreInterationFlag\": \"\",\n" +     //忽略交互标志
                "  \"IsControlPrecision\": false,\n" +      //是否控制精度
                "  \"ValidateRepeatJson\": false,\n" +      //验证日期重复Json
                "  \"Model\": {\n" +
                "    \"FPurchaseOrgId\": {\n" +  //采购组织
                "      \"FNumber\": \""+Util.null2String(properties[1].getValue())+"\"\n" +
                "    },\n" +
                "    \"FSupplierId\": {\n" +      //供应商
                "      \"FNumber\": \""+Util.null2String(properties[18].getValue())+"\"\n" +
                "    },\n" +
                "    \"F_RCZU_ReDepartment\": {\n" +      //需求部门
                "      \"FNumber\": \""+Util.null2String(properties[2].getValue())+"\"\n" +
                "    },\n" +
                "    \"FPurchaserId\": {\n" +     //采购员
                "      \"FNumber\": \""+2010003036+"\"\n" +
                "    },\n" +
                "    \"FDate\": \""+Util.null2String(properties[6].getValue())+"\",\n" +   //采购日期
                "    \"FBillTypeID\": {\n" +      //单据类型
                "      \"FNumber\": \"CGDD04_SYS\"\n" +
                "    },\n" +
                "    \"F_RCZU_mindep\": {\n" +      //归口管理部门
                "      \"FNumber\": \""+Util.null2String(properties[4].getValue())+"\"\n" +
                "    },\n" +
                "    \"F_RCZU_ECOS\": \""+Util.null2String(properties[0].getValue())+"\",\n" +   //E-COS流程编号
                "    \"FPOOrderFinance\": {\n" +          //单据头-财务信息
               /* "      \"FExchangeRate\": \"1\",\n" +      //汇率*/
                "      \"FPriceTimePoint\": \"1\",\n" +   //定价时点
                "      \"FSettleCurrId\": {\n" +          //结算币别
                "        \"FNumber\": \""+Util.null2String(properties[17].getValue())+"\"\n" +
                "      },\n" +
                "      \"F_RCZU_Assistant\": {\n" +          //在建工程名称
                "        \"FNumber\": \""+Util.null2String(properties[15].getValue())+"\"\n" +
                "      },\n" +
                "      \"FPayConditionId\": {\n" +        //付款条件
                "        \"FNumber\": \""+Util.null2String(properties[7].getValue())+"\"\n" +
                "      }\n" +
                "    },\n" +
                "    \"FPOOrderEntry\": [\n" +           //单据体-明细信息
                //经过上边遍历,此处获得主明细表的数据
                        json1+
                "    \n],\n" +
                "   \"FIinstallment\":[\n" +                //付款计划 子单据体
                        json2+
                "   \n]\n"+
                "  }\n" +
                "}";
        // 调用ERP接口
        String resultJson = "";
        Boolean loginResult = false;
        //设置初始值默认登录成功
        String action=SUCCESS;
        K3CloudApiClient client = new K3CloudApiClient("http://10.XX.XX.22/k3cloud/");
        try {
            loginResult = client.login("账套ID", "用户名", "密码", 2052);
            // 判断登录成功就调用ERP相应方法
            if (loginResult) {
                // 具体参数和调用方法参见  https://openapi.open.kingdee.com/ApiCenterDoc
                resultJson = client.save("PUR_PurchaseOrder", json);
            }else {
                System.out.println("loginResult:"+"登录失败");
                action=FAILURE_AND_CONTINUE;
            }
/*            //用于记录结果
            Gson gson = new Gson();
            //对返回结果进行解析和校验
            RepoRet repoRet = gson.fromJson(resultJson, (Type) RepoRet.class);
            if (repoRet.getResult().getResponseStatus().isIsSuccess()) {
                System.out.printf("接口返回结果: %s%n", gson.toJson(repoRet.getResult()));
                action=SUCCESS;
            } else {
                fail("接口返回结果: " + gson.toJson(repoRet.getResult().getResponseStatus()));
                action=FAILURE_AND_CONTINUE;
            }*/
        } catch (Exception e) {
            log.info("错误信息:" + e);
            e.printStackTrace();
        }
        return action;
    }
}

3.ERP对接

云星空文档: 金蝶API文档
或者登录管理员账号,搜索WebAPI;

3.1引入 K3CloudApiClient 包;
3.2实例化 client;
3.3执行登录动作;
3.4判断登录经过为 true , 执行余下动作;

Demo
// 查看
client.View("BD_Account","{"CreateOrgId":0,"Number":"","Id":""}");
// 保存
client.Save("BD_Account","{"NeedUpDateFields":[],"NeedReturnFields")
// 审核 
client.ExcuteOperation("BD_Account","Forbid","{"CreateOrgId":0,"Numbers":[],"Ids":"","PkEntryIds":[],"NetworkCtrl":""}");
// 反审
client.UnAudit("BD_Account","{"CreateOrgId":0,"Numbers":[],"Ids":"","InterationFlags":"","NetworkCtrl":"","IsVerifyProcInst":""}");
... 
更多执行参见 金蝶云星空官方文档  https://openapi.open.kingdee.com/ApiCenterDoc
或者登录管理员账号,搜索WebAPI

总结

以上就是泛微E9对接金蝶云星空的方法,同步表单,注意如果同步多张表单到金蝶ERP中,注意ERP中的表单关联关系。