# mjava-minglei 明磊业务集成服务。项目基于 Spring Boot 2.7 构建,用于对接钉钉、宜搭、员工花名册以及明磊外部 T100 接口。 本文档说明当前工程的模块边界、配置项、REST 接口、ERP 推送规则和本地构建方式。文档中的凭证均使用占位符,真实密钥必须通过环境变量或部署配置注入。 ## 1. 技术栈 | 技术 | 说明 | | ------------------ | -------------------------- | | Java | JDK 8 | | Spring Boot | 2.7.18 | | Spring MVC | REST Controller、参数绑定、异常处理 | | Spring Validation | 请求参数校验 | | Maven | 构建和依赖管理 | | `com.malk:mjava` | 钉钉、宜搭客户端和基础模型,当前版本 `0.0.3` | | Jackson / Fastjson | JSON 绑定、序列化和表单字段组装 | | RestTemplate | 调用外部 T100 HTTP 接口 | | Logback + SLF4J | 应用日志、错误日志和请求响应日志 | | JUnit 5 + Mockito | 单元测试 | ## 2. 工程结构 ```text src/main/java/com/malk/minglei |-- MjavaMingleiApplication.java |-- config | |-- DingTalkRosterConfiguration.java | |-- EmployeeRosterConfiguration.java | |-- EmployeeRosterProperties.java | |-- HttpRequestResponseLoggingFilter.java | |-- MingleiPortConfiguration.java | |-- MingleiPortProperties.java | |-- MjavaYidaConfiguration.java | `-- YidaApplicationsProperties.java |-- controller | |-- ApprovalTransferDepartmentController.java | |-- EmployeeRosterController.java | |-- ErpYidaPushController.java | |-- GlobalExceptionHandler.java | |-- MingleiPortController.java | `-- YidaProcessController.java |-- dto `-- service |-- approval |-- employee |-- port |-- yida `-- impl |-- approval |-- employee |-- port `-- yida ``` ### 2.1 分层职责 | 层级 | 职责 | | ------------------ | ----------------------------------------------------- | | `controller` | 暴露 HTTP 接口,处理请求校验和响应包装 | | `dto` | 请求体、响应体和业务数据模型 | | `service.approval` | 钉钉审批实例查询、表单解析,以及通过宜搭审批矩阵(按"部门"字段匹配)解析转入部门各级别负责人与部门负责人 | | `service.employee` | 员工花名册同步、查询和钉钉人员数据处理 | | `service.port` | 明磊外部 T100 接口转发 | | `service.yida` | 宜搭流程、宜搭表单查询、ERP 推送分发 | | `service.impl.*` | 各业务实现、处理器和内部辅助类 | 新增能力时优先放入现有业务域。只有出现清晰的新业务边界、独立生命周期或独立外部依赖时,才新增新的 `Service` 分类。 ## 3. 配置说明 配置文件位于 `src/main/resources/application.yml`。生产环境不要使用文件中的默认示例值,应通过环境变量覆盖。 | 配置项 | 环境变量 | 用途 | | ------------------------------------------------------ | ------------------------------------------------------ | --------------------------------------------- | | `server.port` | `SERVER_PORT` | HTTP 服务端口 | | `spring.profiles.active` | `SPRING_PROFILES_ACTIVE` | Spring Profile | | `dingtalk.agent-id` | `DINGTALK_AGENT_ID` | 钉钉应用 AgentId | | `dingtalk.app-key` | `DINGTALK_APP_KEY` | 钉钉应用 Key | | `dingtalk.app-secret` | `DINGTALK_APP_SECRET` | 钉钉应用 Secret | | `minglei.yida.coordination-office.app-type` | `YIDA_COORDINATION_OFFICE_APP_TYPE` | 协同办公宜搭应用编码 | | `minglei.yida.coordination-office.system-token` | `YIDA_COORDINATION_OFFICE_SYSTEM_TOKEN` | 协同办公宜搭应用 System Token | | `minglei.yida.coordination-office.process-instance-query-user-id` | `YIDA_COORDINATION_OFFICE_PROCESS_INSTANCE_QUERY_USER_ID` | 查询宜搭流程实例时用于数据权限校验的钉钉用户 ID,默认 `41603502201288023` | | `minglei.yida.master-data.app-type` | `YIDA_MASTER_DATA_APP_TYPE` | 主数据宜搭应用编码 | | `minglei.yida.master-data.system-token` | `YIDA_MASTER_DATA_SYSTEM_TOKEN` | 主数据宜搭应用 System Token | | `minglei.port.supplier-price-approval-url` | `MINGLEI_SUPPLIER_PRICE_APPROVAL_URL` | 外部 T100 接口地址 | | `minglei.port.supplier-price-approval-key` | `MINGLEI_SUPPLIER_PRICE_APPROVAL_KEY` | 外部 T100 接口接入密钥 | | `minglei.banking.base-url` | `MINGLEI_BANKING_BASE_URL` | 银企全部接口地址,默认正式环境 `https://open.nexbankapi.com` | | `minglei.banking.app-id` | `MINGLEI_BANKING_APP_ID` | NexBank 应用标识 | | `minglei.banking.app-secret` | `MINGLEI_BANKING_APP_SECRET` | NexBank API Key | | `minglei.banking.customer-code` | `MINGLEI_BANKING_CUSTOMER_CODE` | NexBank 客户编号 | | `minglei.banking.default-from-account-no` | `MINGLEI_BANKING_DEFAULT_FROM_ACCOUNT_NO` | 默认付款账号;支付请求未传 `fromAccountNo` 时使用 | | `minglei.banking.default-from-account-name` | `MINGLEI_BANKING_DEFAULT_FROM_ACCOUNT_NAME` | 默认付款账户名称;支付请求未传 `fromAccountName` 时使用 | | `minglei.banking.intra-bank-name` | `MINGLEI_BANKING_INTRA_BANK_NAME` | 本行名称;匹配收款银行时自动发送 `crossBank=0` | | `minglei.banking.payment-encryption-enabled` | `MINGLEI_BANKING_PAYMENT_ENCRYPTION_ENABLED` | 单笔支付是否启用 SM2/SM4 加密,默认 `true` | | `minglei.banking.xencio-public-key` | `MINGLEI_BANKING_XENCIO_PUBLIC_KEY` | NexBank 的 SM2 十六进制公钥,仅支付制单需要 | | `minglei.banking.enterprise-private-key` | `MINGLEI_BANKING_ENTERPRISE_PRIVATE_KEY` | 企业 SM2 十六进制私钥,仅支付制单需要 | | `minglei.banking.pending-payment.table-name` | `MINGLEI_BANKING_PENDING_PAYMENT_TABLE_NAME` | 异步支付状态补偿表名,默认 `banking_payment_tracking` | | `minglei.banking.pending-payment.schedule-fixed-delay-millis` | `MINGLEI_BANKING_PENDING_PAYMENT_SCHEDULE_FIXED_DELAY_MILLIS` | 支付状态补偿间隔,默认 300000 毫秒(5 分钟) | | `minglei.employee-roster.datasource.url` | `MINGLEI_EMPLOYEE_ROSTER_DATASOURCE_URL` | 员工花名册 MySQL JDBC 地址 | | `minglei.employee-roster.datasource.username` | `MINGLEI_EMPLOYEE_ROSTER_DATASOURCE_USERNAME` | 员工花名册数据库用户名 | | `minglei.employee-roster.datasource.password` | `MINGLEI_EMPLOYEE_ROSTER_DATASOURCE_PASSWORD` | 员工花名册数据库密码 | | `minglei.employee-roster.datasource.driver-class-name` | `MINGLEI_EMPLOYEE_ROSTER_DATASOURCE_DRIVER_CLASS_NAME` | JDBC 驱动类 | | `minglei.dictionary.datasource.url` | `MINGLEI_DICTIONARY_DATASOURCE_URL` | 数据字典 Oracle JDBC 地址 | | `minglei.dictionary.datasource.username` | `MINGLEI_DICTIONARY_DATASOURCE_USERNAME` | 数据字典 Oracle 用户名 | | `minglei.dictionary.datasource.password` | `MINGLEI_DICTIONARY_DATASOURCE_PASSWORD` | 数据字典 Oracle 密码 | | `minglei.dictionary.datasource.driver-class-name` | `MINGLEI_DICTIONARY_DATASOURCE_DRIVER_CLASS_NAME` | Oracle JDBC 驱动类,默认 `oracle.jdbc.OracleDriver` | | `logging.file.path` | `LOGGING_FILE_PATH` | 日志输出目录 | `MjavaYidaConfiguration` 注册两套宜搭客户端: ### 3.1 银企支付状态补偿 先在花名册数据源所连接的数据库执行 [banking_payment_tracking.sql](sql/banking_payment_tracking.sql) 创建表。制单成功后会根据 `businessTransNo` 写入待处理记录;定时任务每 5 分钟查询 `PENDING` 记录。状态为 `X0000` 时下载回单并记录预览链接和下载链接;回单尚未生成时记录为 `RECEIPT_PENDING`, 后续任务只重试下载回单。 支付请求可额外传入 `formID` 和 `formInstanceId`,它们只保存到跟踪表用于后续宜搭回写, 不会传给银企接口。状态回写字段为 `selectField_mvq9jruo`,说明字段为 `textField_mvq9jrup`,回单附件字段为 `attachmentField_mvq9jruq`。 付款申请表单 `FORM-5EF5C90019AE4E5AA1AFB3FC6572A7E63KRY` 使用以下回写字段: 付款状态 `selectField_mvrimme0`、详细说明 `textField_mvrimmdx`、回单附件 `attachmentField_mvrimmdz`。服务端会根据请求中的 `formID` 自动选择对应字段。 费用报销表单 `FORM-9785C335A34449B3B806DAF63B6C5AB7PRH1` 使用以下回写字段: 付款状态 `selectField_mvrj87p7`、详细说明 `textField_mvrj87p8`、回单附件 `attachmentField_mvrj87p9`。服务端会根据请求中的 `formID` 自动选择对应字段。 | 银企状态码 | 宜搭付款状态 | 处理方式 | | --- | --- | --- | | `X0000` | 付款成功 | 下载回单并回写回单附件,结束补偿 | | `X5555`、`X5556` | 待付款 | 保持定时查询 | | `X5554`、`X9997`、`X9999` | 付款失败 | 回写说明并停止定时查询 | | Bean | 用途 | | ------------------------------ | ----------------- | | `coordinationOfficeYidaClient` | 协同办公应用,主要用于发起流程 | | `masterDataYidaClient` | 主数据应用,主要用于查询主数据表单 | ## 4. REST 接口 ### 4.1 宜搭流程发起 ```http POST /api/yida/process-instances Content-Type: application/json ``` 请求示例: ```json { "type": "cpmp402", "userId": "41603502201288023", "dataTitle": "包材核价单", "fields": { "mainData": { "rq": "2026-09-08", "bm": "ML07000000", "tjr": "3075", "gs": "ML", "t100id": "ML", "ent": "87", "t100bm": "ML07000000" }, "detailData": { "detailData": [ { "hjdh": "MLCH326090800001", "gysbh": "113009", "gysmc": "供应商名称", "t100id": "ML", "ent": "87", "yy": "zh_CN" } ] } } } ``` 成功响应为 HTTP `201 Created`: ```json { "code": 200, "isSuccess": true, "result": { "processInstanceId": "process-instance-id" } } ``` ### 4.2 按流程实例 ID 查询宜搭数据 ```http GET /api/yida/process-instances/{processInstanceId} ``` 该接口直接返回钉钉宜搭流程实例详情,仅用于核对实例表单数据和字段映射,不会发起 ERP 推送。`processInstanceId` 使用宜搭流程实例 ID。 ### 4.3 宜搭主数据表单查询 分页查询: ```http GET /api/yida/minglei/forms/instances?page=1&size=20 ``` 精确查询: ```http POST /api/yida/minglei/forms/instances/query Content-Type: application/json ``` 请求示例: ```json { "dept_id": "123456", "user_id": "dingTalkUserId", "dept_name": "明磊锂能技术部" } ``` 精确查询优先级为 `dept_id`、`user_id`、`dept_name`。传入 `user_id` 时,服务端会先查询人员所属部门,再按部门 ID 查询主数据。响应固定包含 `employeeField_mszdqrl8`、`employeeField_mszdqrl9`、`employeeField_mszdqrla`、`employeeField_mszdqrlb`、`employeeField_mualkf6a`(部门负责人)和 `employeeField_mszdqrl3`;未匹配到审批矩阵记录或字段未配置时,对应字段值为 `null`。 ### 4.3 部门成员查询 ```http POST /api/yida/minglei/departments/users Content-Type: application/json ``` 请求示例: ```json { "dept_Id": "123456,234567", "type": 0, "userIds": ["user-1", "user-2"] } ``` `userIds` 为可选的用户 ID 列表。未传或传入空数组时不影响部门成员查询结果;传入非空列表时,列表中的用户 ID 会按传入顺序追加在部门成员列表之后,并与部门成员及列表内其他 ID 一并去重。 `dept_Id` 支持单个部门 ID,或多个以英文逗号分隔的部门 ID;返回结果为各部门查询范围内人员的去重合集。 `type` 取值: | 值 | 说明 | | --- | ------------ | | `0` | 查询指定部门和所有子部门 | | `1` | 仅查询指定部门 | ### 4.4 员工花名册 本地表 `employee_roster` 的 `is_resigned` 字段以 `1` 表示在职、`0` 表示离职。同步读取到钉钉通讯录用户的 `active=false` 时,会将该人员更新为离职;花名册按钉钉用户 ID 或工号的查询会自动排除离职人员。 同步还会按钉钉花名册字段名读取“入职时间”“职位”“银行卡号”“开户行”“支行联行号”,分别写入 `hire_date`、`position_name`、`bank_card_no`、`bank_name`、`bank_branch_code`。 每次同步完成后,服务会按 `username` 检查在职重名人员。存在重名时,服务从主数据宜搭表单 `FORM-66F0B2216DD64580834D75815BFEF24138F7` 的首条记录读取成员字段 `employeeField_mv94lty1`,并向其中配置的人事人员发送钉钉工作通知。 同步指定部门: ```http POST /api/yida/minglei/employee-roster/sync?departmentId=1&type=0 ``` 全量同步: ```http POST /api/yida/minglei/employee-roster/sync-full ``` 按钉钉用户 ID 查询花名册: ```http POST /api/yida/minglei/employee-roster/getUserRoster Content-Type: application/json ``` 响应的 `result` 包含 `hireDate`(入职时间)、`positionName`(职位)、`bank_card_no`(银行卡号)、`bank_name`(开户行)和 `bank_branch_code`(支行联行号)。 ### 4.5 创建钉钉自建企业账号 ```http POST /api/yida/minglei/employee-roster/dingtalk-dedicated-accounts Content-Type: application/json ``` 接口请求体仅接收钉钉 OA 审批实例 ID,也兼容字段名 `processInstanceId`: ```json { "oaProcessInstanceId": "4iO8yQmvTNiRhLl4-jgjjQ05031793083009" } ``` 服务端调用钉钉 `GET /v1.0/workflow/processInstances` 查询审批实例的 `result.formComponentValues`,从控件名称或 `bizAlias` 读取姓名、部门、工号、岗位、性别、年龄、预计到岗日期、职称、职等和身份证号。姓名和部门为审批表单必填项;工号为空时按既有 `YYMMDD + 四位日流水` 规则生成。接口忽略旧请求体中直接传入的员工字段,以审批实例数据为准。 部门优先使用部门控件 `extValue` 中的部门 ID;若 OA 仅返回部门名称或层级路径,则按现有部门名称解析规则转换为钉钉部门 ID。随后以工号为登录名、`ML` 加工号为初始密码创建钉钉账号,并把数据库已有字段写入 `employee_roster`。 OA 的“职位”控件会写入钉钉创建用户接口的标准 `title` 字段;职位为空时才回退使用职称。职位不写入本地 `employee_roster`,职称仍按既有字段写入花名册。 以下历史直接传员工字段的请求体不再适用: 接口调用钉钉 `topapi/v2/user/create` 创建自建企业账号,仅已购买并开通该能力的组织可使用。请求中的 `departmentList` 支持钉钉 OA 部门名称、部门 ID 或部门选择器原始对象。传入名称时,服务从根部门开始通过钉钉 `topapi/v2/department/listsub` 逐级查询直属子部门并解析为 `dept_id_list`,不会遍历全部部门树;支持以 `-` 或 `/` 连接的层级名称,例如 `集团运营中心-人力资源部`。非根级部门应传入完整路径。路径未匹配或当前层级存在同名部门时接口返回 `400`,不创建账号。 ```json { "name": "张三", "positionName": "研发工程师", "gender": "男", "age": 28, "expectedArrivalDate": "2026-11-01", "jobTitle": "高级工程师", "jobGrade": "P6", "idCardNo": "330101199801010011", "departmentList": ["集团运营中心-人力资源部"], "employeeNo": "1001" } ``` `employeeNo` 为可选字段:传入时直接作为员工工号;未传或为空时,服务端从 `employee_roster.employee_no` 读取当日最大工号,按 `YYMMDD` 加当日四位流水号生成,例如 `2610260001`。无论工号来自入参还是自动生成,钉钉登录名均为工号本身(例如 `2610260001`),初始登录密码为 `ML` 加工号(例如 `ML2610260001`)。岗位、年龄、职等和身份证号通过钉钉 `extension` 推送;职称、性别和预计到岗日期分别映射为钉钉 `title`、`gender` 和 `hired_date`,其中日期按上海时区转换为毫秒时间戳。性别支持 `1`/`2`、`男`/`女` 或 `male`/`female`。本地仅写入 `employee_roster` 已存在的列;性别、年龄和身份证号不写入本地表。成功时返回 HTTP `201 Created`;`result.userId` 为钉钉创建的用户 ID,`result.employeeNo` 为实际使用的员工工号,`result.departmentIds` 为实际提交的部门 ID。接口不回显登录密码。 ```json { "code": 200, "isSuccess": true, "result": { "userId": "ding-user-1", "employeeNo": "2610260001", "departmentIds": [1, 2] } } ``` ### 4.6 数据字典 完整调用说明见 [数据字典接口文档](DATA-DICTIONARY-API.md)。 按字典编码查询已注册的数据字典,筛选条件通过 URL 查询参数传入。当前支持: | 字典编码 | 查询对象 | 返回字段 | 筛选条件 | | ------------------------ | -------------------------------- | ------------------------------- | ------------------------------- | | `erp-supplier` | `DSDATA.erp_apmt440_pmaauc_v` | 视图全部字段 | 必填:`t100id`、`ent`;可选:`pmaal004` | | `erp-packaging-b9-window-view` | `DSDATA.imaf_t`、`DSDATA.imaal_t` | `imaf001`、`imaal003`、`imaal004` | 必填:`t100id`、`ent`;可选:`keyword` | | `erp-packaging-material` | `DSDATA.pmaauc_t` | `pmaauc001`、`pmaauc002`、`tp` | 必填:`ent`;可选:`keyword` | | `erp-material-original-price` | `DSDATA.pmaauc_t` | `pmaauc002`、`pmaauc015`、`pmaauc001`、`pmaaucent` | 必填:`czbh`、`ent` | | `erp-item-view` | `DSDATA.erp_apmt440_imaauc_v` | 视图全部字段 | 必填:`ent`;可选:`keyword`(匹配 `imaa001`、`imaal003`) | | `erp-sales-order` | `DSDATA.xmda_t`、`DSDATA.xmdc_t`、`DSDATA.xmdd_t`、`DSDATA.ooff_t` | 销售订单、客户、料件、未交数量和开票信息 | 必填:`t100id`;可选:`keyword`(匹配 `xmdadocno`) | | `erp-production-order` | `DSDATA.xmda_t`、`DSDATA.xmdc_t`、`DSDATA.xmdd_t`、`DSDATA.ooff_t` | 生产令、客户、料件、数量和开票信息 | 必填:`t100id`、`userId` | | `erp-supplier-disabled` | `DSDATA.ERP_APMT440_PMAAUC_N` | 禁用供应商视图全部字段 | 必填:`t100id`、`ent`;可选:`keyword`(匹配 `pmaal004`) | | `erp-active-item` | `DSDATA.imaa_t`、`DSDATA.imaf_t`、`DSDATA.imaal_t` | `imaa001`、`imaal003`、`imaal004` | 可选:`keyword`(匹配 `imaa001`、`imaal003`) | | `erp-template-item` | `DSDATA.imaa_t`、`DSDATA.imaal_t` | `imaa001`、`imaal003`、`imaal004` | 可选:`keyword`(匹配 `imaa001`、`imaal003`) | | `erp-template-orders` | `DSDATA.xmda_t`、`DSDATA.xmdc_t` | 范本料号关联订单及料件明细 | 必填:`bgfblh`;可选:`keyword`(匹配 `xmdadocno`) | | `erp-last-price` | `DSDATA.pmdi_t`、`DSDATA.pmdj_t` | `pmdj011` | 必填:`main.pmdi004`、`detail_1.ljbh` | | `erp-unfulfilled-order` | `DSDATA.xmdg_t`、`DSDATA.xmdh_t`、`DSDATA.xmda_t`、`DSDATA.xmdc_t`、`DSDATA.imaal_t`、`DSDATA.pmaal_t` | `xmdgdocno`、`xmdhseq`、`xmda048`、`xmdcud010`、`xmdh006`、`imaal003`、`imaal004`、`xmdg005`、`pmaal003` | 必填:`ent`、`site`、`keyword` | | `erp-delivery-order` | `DSDATA.xmdg_t`、`DSDATA.xmdh_t`、`DSDATA.xmdc_t` | `xmdgdocno`、`xmdcud010`、`xmdc019`、`k` | 必填:`ent`、`site`;可选:`keyword`(匹配 `出通单号-项次`) | | `erp-outsourced-purchase-order-window` | `DSDATA.pmdp_t`、`DSDATA.imaal_t`、`DSDATA.sfaa_t` | `pmdpseq`、`pmdp003`、`pmdp001`、`imaal003`、`imaal004`、`pmdp023`、`sfaaud009` | 必填:`formtable_main_158_dt1_wwcgdh` | | `erp-outsourced-bom` | `DSDATA.pmdp_t`、`DSDATA.sfba_t`、`DSDATA.imaal_t` | `sfba006`、`imaal003`、`imaal004`、`sfba013` | 必填:`formtable_main_158_dt1_wwcgdh`、`formtable_main_158_dt1_cgxc` | 供应商视图: ```http GET /api/dictionary/erp-supplier?t100id=ML&ent=明磊&pmaal004=供应商 ``` 明磊包材B9开窗视图: ```http GET /api/dictionary/erp-packaging-b9-window-view?t100id=ML&ent=明磊&keyword=B9开窗 ``` 包材材质: ```http GET /api/dictionary/erp-packaging-material?ent=明磊&keyword=彩盒 ``` 根据材料编号取原单价: ```http GET /api/dictionary/erp-material-original-price?czbh=B9-001&ent=明磊 ``` ERP 料件视图: ```http GET /api/dictionary/erp-item-view?ent=明磊&keyword=电池 ``` 销售订单开窗: ```http GET /api/dictionary/erp-sales-order?t100id=ML&keyword=SO-001 ``` 生产令开窗: ```http GET /api/dictionary/erp-production-order?t100id=ML&userId=USER-001 ``` `userId` 为钉钉用户 ID。服务端先从本地员工花名册取得对应的 `employee_no`,再使用该工号执行 Oracle 查询。 ERP 供应商禁用视图: ```http GET /api/dictionary/erp-supplier-disabled?t100id=ML&ent=明磊&keyword=供应商 ``` T100 有效料号: ```http GET /api/dictionary/erp-active-item?keyword=B9 ``` 范本料号开窗: ```http GET /api/dictionary/erp-template-item?keyword=B9 ``` 范本料号开窗对应所有订单: ```http GET /api/dictionary/erp-template-orders?bgfblh=B9&keyword=SO-001 ``` 取料号上次价格: ```http GET /api/dictionary/erp-last-price?main.pmdi004=SUP-001&detail_1.ljbh=B9-001 ``` 已审核未发齐出通单: ```http GET /api/dictionary/erp-unfulfilled-order?ent=明磊&site=ML&keyword=MLCT6261023002-1 ``` 出通单列表: ```http GET /api/dictionary/erp-delivery-order?ent=明磊&site=ML&keyword=110001-1 ``` 委外采购单开窗: ```http GET /api/dictionary/erp-outsourced-purchase-order-window?formtable_main_158_dt1_wwcgdh=WWCG-001 ``` 委外采购对应工单备料: ```http GET /api/dictionary/erp-outsourced-bom?formtable_main_158_dt1_wwcgdh=WWCG-001&formtable_main_158_dt1_cgxc=10 ``` `erp-supplier` 的 `pmaal004` 按供应商名称模糊匹配。`erp-packaging-b9-window-view` 使用单一 `keyword` 参数,后端会将其分别与 `imaf001`(料件编号)、`imaal003`(中文品名)和 `imaal004`(规格)进行模糊匹配,任一字段命中即可返回。明磊包材B9开窗视图仅返回中文料件信息,排除 `imaf016` 为 `TY` 或 `LTY` 的记录,并只包含编码以 `B9`、`VB9` 或 `J` 开头的料件。 `erp-supplier`、`erp-packaging-b9-window-view` 和 `erp-supplier-disabled` 的 `t100id`、`ent` 均为必填筛选条件;`erp-packaging-material` 和 `erp-item-view` 仅需传入 `ent`。销售订单开窗仅需传入 `t100id`,固定查询公司 `86` 中已审核、存在未交数量且不属于指定排除客户的订单。生产令开窗需传入 `t100id` 与 `userId`,服务端按该用户在 `ooag_t` 中的部门字段 `ooag003` 匹配订单部门字段 `xmdaowndp`,固定查询公司 `86` 中已审核的订单。范本料号关联订单需传入 `bgfblh`,服务端按该料号前缀匹配 ML 据点未作废订单。取料号上次价格需同时传入 `main.pmdi004`(主表料号)和 `detail_1.ljbh`(子表料件编号),服务端返回有效采购价格单中最新确认时间的 `pmdj011`。已审核未发齐出通单需传入 `ent`、`site` 和 `keyword`,其中 `keyword` 按“出通单号-项次”精确匹配,返回项次、内外销、料号、料件中文名称、规格、客户编号及客户中文名称。有效料号和范本料号均可选 `keyword`,同时模糊匹配 `imaa001`(料号)和 `imaal003`(中文品名);范本料号关联订单可选 `keyword` 模糊匹配 `xmdadocno`(订单号);ERP 供应商禁用视图可选 `keyword` 模糊匹配 `pmaal004`(供应商名称)。包材材质可用单一 `keyword` 对 `pmaauc001`(包材编号)、`pmaauc002`(包材名称)和 `tp`(中文材质类型)进行模糊匹配,任一字段命中即可返回;ERP 料件视图的 `keyword` 会同时模糊匹配 `imaa001`(料号)和 `imaal003`(中文品名)。委外采购单开窗仅需传入 `formtable_main_158_dt1_wwcgdh`;委外采购对应工单备料需同时传入采购单号 `formtable_main_158_dt1_wwcgdh` 和采购项次 `formtable_main_158_dt1_cgxc`。两者 SQL 均固定查询公司 `86`、据点 `ML`。`ent=明磊` 映射为 ERP 公司编号 `86`,`ent=越南` 映射为 ERP 公司编号 `60`,其他公司名称会返回 `400`。十五个字典接口不接收 `page` 和 `pageSize`,服务端固定返回符合条件的前 `100` 条记录。包材材质的 `tp` 字段会将 `pmaauc016` 转换为 `说明书`、`彩盒`、`纸箱` 或 `标贴`。 成功响应的 `result` 统一为: ```json { "page": 1, "pageSize": 100, "total": 238, "records": [] } ``` 数据字典实现约定:固定 SQL、字典编码和允许的筛选参数统一维护在 `DictionaryQueryServiceImpl`,Controller 只处理 HTTP 参数与响应封装;SQL 必须使用 `NamedParameterJdbcTemplate` 绑定参数,不得拼接外部输入;新增字典时在同一 ServiceImpl 中增加路由分支、参数校验、单元测试和本节文档。未注册的字典编码或未声明的筛选条件会返回 `400`。 ### 4.6.1 差旅标准查询 ```http POST /api/minglei/ports/travel-standards Content-Type: application/json ``` 请求体: ```json { "userId": "ding-user-001", "city": "海口" } ``` `userId` 和 `city` 均为必填参数;为兼容已有调用,`userID` 也可作为 `userId` 传入。接口先从本地员工花名册查询用户职等,再按职等精确查询宜搭差旅标准表单 `FORM-47ED3C35E3C34A9CA275039F93FA5ED1VR2J`,最后依据国内城市类别或国外国家类别返回住宿标准。金额不再由后端固定维护。 - 一类城市:北京、天津、上海、重庆、深圳、珠海、汕头、厦门。 - 二类城市:省会城市、计划单列市及海南省全域城市。 - 三类城市:除一类、二类城市外的其他城市。 - 一类国家:美国、加拿大、英国、法国、德国、意大利、日本、澳大利亚,读取宜搭字段 `numberField_mvevi2d3`(一类国家标准,人民币)。 - 其他国家:中国以外的其他已识别国家,读取宜搭字段 `numberField_mvevi2d4`(其他国家标准,人民币)。 国外城市会先匹配常用城市名称;城市值也可附带国家名称,例如 `巴黎(法国)`,以支持未预置的国外城市。响应的 `country` 字段返回命中的国家,`cityClass` 对国外分别返回 `一类国家` 或 `其他国家`。无法识别国家的中文城市维持原有国内三类城市逻辑,保障已接入的国内调用不受影响。 差旅标准表单与审批矩阵复用主数据宜搭应用配置 `minglei.yida.master-data`,无需新增 `system-token`。员工不存在、职等未维护、职等不是数值或宜搭未配置该职等的标准时,接口返回 `400`。 响应示例: ```json { "code": 200, "isSuccess": true, "result": { "userID": "ding-user-001", "jobGrade": "8", "city": "海口", "country": "中国", "cityClass": "二类城市", "amount": 400, "currency": "CNY" } } ``` ### 4.6.2 机票报销标准查询 ```http POST /api/expense-reimbursement/flight-standards/resolve Content-Type: application/json ``` 请求体: ```json { "userId": "728378541", "travelRegion": "东南亚", "ticketType": "往返", "tripCount": 2, "budgetAmount": 8001 } ``` `userId` 为必填项。服务先按 `userId` 查询本地员工花名册中的职等,再按职等精确查询同一份宜搭差旅标准主表。机票标准全部从主表字段读取,不查询子表;`travelRegion` 或 `ticketType` 为空或不符合预置选项时,接口正常返回,标准相关字段为 `null`。 传入 `tripCount`(次数)和 `budgetAmount`(预算金额)时,接口按 `budgetAmount > limitAmount × tripCount` 返回 `isOverStandard`:严格大于返回 `是`,小于或等于返回 `否`。任一计算参数或机票标准为空时,`isOverStandard` 返回 `null`。 | `travelRegion` | `ticketType` | 名称 | 宜搭主表字段 | | --- | --- | --- | --- | | `国内` | `单程` | 国内机票-单程 | `numberField_mvevi2dd` | | `国内` | `往返` | 国内机票-往返 | `numberField_mvevi2dc` | | `东南亚` | `单程` | 东南亚机票-单程 | `numberField_mvevi2dg` | | `东南亚` | `往返` | 东南亚机票-往返 | `numberField_mvevi2de` | | `欧美及其它` | `单程` | 欧美及其他机票-单程 | `numberField_mvevi2dl` | | `欧美及其它` | `往返` | 欧美及其他机票-往返 | `numberField_mvevi2dj` | 响应示例: ```json { "code": 200, "isSuccess": true, "result": { "expenseType": "FLIGHT", "userId": "728378541", "jobGrade": "8", "jobGradeId": "GRADE-8", "travelRegion": "东南亚", "ticketType": "往返", "flightOption": "SOUTHEAST_ASIA_ROUND_TRIP", "flightOptionName": "东南亚机票-往返", "limitAmount": 4000, "tripCount": 2, "budgetAmount": 8001, "isOverStandard": "是", "currency": "CNY", "unit": "PER_TRIP" } } ``` 员工不存在、职等未维护、职等不是数值、未找到对应职等的宜搭主表记录或对应金额未维护时,接口返回 `400`。出差区域或机票类型不支持时不返回错误,标准相关字段为 `null`。 ### 4.6.3 OA 出差申请推送 > 当前接口只接收钉钉 OA 审批实例 ID。下方本节中旧的主表和子表请求字段说明不再适用,连接器不得再传入 `userId`、`departmentId`、`title` 或三张子表数据。 服务端调用钉钉“获取单个审批实例详情”接口 `GET https://api.dingtalk.com/v1.0/workflow/processInstances`,从 `result`、`formComponentValues` 及三个明细组件中读取申请人、申请部门、标题、行程、飞机航程和费用预算,再按照原有宜搭字段映射发起出差申请流程。附件不处理。 当前请求体: ```json { "oaProcessInstanceId": "4iO8yQmvTNiRhLl4-jgjjQ05031793083009" } ``` `oaProcessInstanceId` 是唯一必填字段,也兼容使用 `processInstanceId` 传入。审批实例中缺少发起人、标题、申请部门或表单组件数据时,接口返回 HTTP `400`,不会创建宜搭流程。 ```http POST /api/minglei/ports/travel-applications Content-Type: application/json ``` 钉钉 OA 连接器在审批通过后只需传入审批实例 ID。服务端查询实例详情,使用 OA 发起人作为宜搭流程发起人,并将部门名称或部门 ID解析为钉钉部门 ID后写入申请部门;附件字段不处理,当前不做重复拦截。 请求示例: ```json { "oaProcessInstanceId": "oa-process-001" } ``` 解析兼容钉钉明细控件常见的 `rowValue`、`componentValues` 和名称值数组结构。行程中的“出发地/出发城市”“目的地/目的城市”“开始时间/开始日期”“结束时间/结束日期”等名称会按语义匹配;人员扩展值兼容 `userid`、`userId`、`emplId`、`itemId`。查询到的字段按以下宜搭字段写入: | OA 数据 | 宜搭目标 | | --- | --- | | 申请人、申请公司、申请部门、申请日期、申请人职位、手机号、实际出差人、工号、职等、出差类型、关联客户、出差事由、交通工具、单程往返 | 主表对应字段 | | 出发地、目的地、开始/结束时间、时长、出差天数、出行人(同行人)、备注 | 行程子表 `tableField_mvorr6o1` | | 是否预定机票、出差区域类型、机票类型、次数、预算金额、单价、标准金额、是否超标、备注 | 飞机航程子表 `tableField_mvorr6oe` | | 出发地点、到达地点、住宿天数、交通费用(除飞机外)、住宿费、住宿费标准、业务招待费、出差补贴、费用合计 | 费用预算子表 `tableField_mvorr6oo` | `oaProcessInstanceId` 是唯一必填字段。审批实例缺少发起人、标题、申请部门或表单组件数据时返回 HTTP `400`。 成功响应示例: ```json { "code": 200, "isSuccess": true, "result": { "oaProcessInstanceId": "oa-process-001", "yidaProcessInstanceId": "yida-process-001", "formUuid": "FORM-E1A24F72B06247ADAC36335CF7E1FEAC6DJX" } } ``` ### 4.6 审批转入部门主管查询 ```http POST /api/dingtalk/approval/transfer-department/managers Content-Type: application/json ``` 请求示例: ```json { "processInstanceId": "YuRRlAutRFi4CcsrPkqvQg05031789886060" } ``` 服务端处理链路: 1. 调用钉钉审批开放接口 `GET https://api.dingtalk.com/v1.0/workflow/processInstances`,通过 `x-acs-dingtalk-access-token` 请求头传入应用凭证。 2. 从 `result.formComponentValues` 中查找 `name` 为 `转入部门` 的表单组件;未匹配到时回退匹配 `bizAlias` 为 `transferDepartment` 的组件。 3. 解析该组件的 `extValue`,取第一个元素的 `id` 作为转入部门 ID,`deptName` 或 `name` 作为部门名称。`extValue` 中的 `id` 可能是字符串或数字,服务端统一按字符串处理。 4. 复用工程内 `MingleiDocumentService.queryDepartmentManagersByDepartmentId`(宜搭审批矩阵表单 `FORM-8F527F696C5B4AF7AB7A53979527FBA23USO`)按"部门"选择字段 `departmentSelectField_mszdqrks` 内嵌的部门 ID 精确匹配,查询各级别负责人,不再调用钉钉通讯录部门详情接口。 > 注意:第 4 步改为按"部门"选择字段匹配(而非原先的"部门编号"文本字段 `textField_msztwhs2`),且返回结果新增"部门负责人"字段(审批矩阵 `employeeField_mualkf6a`)。 响应中各级别负责人按字段分开返回,分别对应审批矩阵表单中的 `employeeField_mszdqrl8`(一级部门主管)、`employeeField_mszdqrl9`(二级部门主管)、`employeeField_mszdqrla`(三级部门主管)、`employeeField_mszdqrlb`(四级部门主管)、`employeeField_mualkf6a`(部门负责人)与 `employeeField_mszdqrl3`(部门分管领导)。 成功响应: ```json { "code": 200, "isSuccess": true, "result": { "departmentId": "972414534", "departmentName": "合作伙伴", "firstLevelManagerUserId": "manager-1", "secondLevelManagerUserId": "manager-2", "thirdLevelManagerUserId": "manager-3", "fourthLevelManagerUserId": "manager-4", "departmentHeadUserId": "manager-head", "departmentLeaderUserId": "manager-5" } } ``` 各部门负责人字段为对应级别审批人的钉钉 `userid`,审批矩阵未配置该级别负责人时该字段为 `null`。`getAllManagerUserIds()` 按一级至四级部门主管、部门负责人、部门分管领导的顺序返回所有非空的负责人 ID。 | 异常场景 | HTTP 状态码 | 说明 | | ----------------------- | -------- | -------------------------------------------------------------- | | `processInstanceId` 为空 | `400` | 请求参数不合法 | | 审批实例无表单数据或未包含转入部门字段 | `400` | 表单结构与预期不一致 | | 转入部门 `extValue` 缺少部门 ID | `400` | 无法解析部门 ID | | 审批实例接口调用失败或宜搭审批矩阵查询失败 | `502` | 返回 `DINGTALK_APPROVAL_CALL_FAILED` 或 `YIDA_CALL_FAILED` 及上游错误码 | | 宜搭应用或钉钉凭证配置缺失 | `412` | 返回配置缺失信息 | ### 4.7 外部 T100 接口转发 ```http POST /api/minglei/ports/supplier-price-approvals Content-Type: application/json ``` 宜搭连接器调用时传表单 ID 和宜搭流程实例 ID。服务端按 `formId` 选择字段映射规则,再通过钉钉开放平台 `GET /v2.0/yida/processes/instancesInfos/{id}` 读取顶层 `data`(支持对象或 JSON 字符串,同时兼容旧响应的 `formData`),后转换为 T100 核价单,并补齐 T100 协议外层的 `key`、`host`、`service`、`datakey`、`type`,以及生成 17 位毫秒时间戳。推送 T100 时使用 `Content-Type: application/json;charset=UTF-8`。该请求使用钉钉应用 access token,并传入 `appType`、`systemToken`、查询用户 ID;配置项见上表。不要在连接器请求体中传入表头、明细、密钥或 T100 外层字段。 ```json { "formId": "FORM-468B6FB2D5BF490A8BC6DBD6FAF9BCCA28R9", "processInstanceId": "宜搭流程实例 ID" } ``` 接口按 `formId` 选择对应字段映射,当前已注册表单如下: | 表单 | `formId` | 明细字段映射 | | --- | --- | --- | | 供应商价格审批单(核价) | `FORM-468B6FB2D5BF490A8BC6DBD6FAF9BCCA28R9` | `tableField_mszqctqd`:料件编号、核价、备注 | | 供应商价格审批单(暂估) | `FORM-F906C997A4A44DC8B9180198C0B2E55F6W0S` | `tableField_msh186sv`:料件编号、材料价格、备注 | | 供应商价格审批单-核价(子公司) | `FORM-C09818E73B1640A9AA9F6D7F55C47612EFV8` | `tableField_mszqctqd`:料件编号、核价、备注 | | 供应商价格审批单-暂估(子公司) | `FORM-EC46C7109182497796AA0B70F3848576UYBU` | `tableField_msh186sv`:料件编号、暂估单价、备注 | | 包材材质核价 | `FORM-69A12600F87349D3B653BF86E419224DLIX9` | `tableField_mt1av9od`:材质编号、单价(克) | | 供应商暂停/退出流程 | `FORM-0A05D30C6E0745EC9F4B74D0E038EE73O7B6` | 主表:变更类型、供应商编号、企业代码;不发送企业据点 | | 包材料号清单维护审核流程 | `FORM-F88BDA6B3A114E668AD5A6347AE4E01C91O9` | `tableField_msh186sv`:包材料号清单字段 | 四个供应商价格审批表单都会将单据编号、委外否、单号、报价单日期、核价供应商、生效日期、失效日期、使用部门、申请人映射至 T100 表头;其中申请人由宜搭成员数组的第一项姓名查询本地在职花名册后,使用对应的 `employee_no` 写入 T100 `pmdi002`。明细的料件编号、价格、备注分别映射至 `pmdj002`、`pmdj011`、`pmdj030`。宜搭委外否字段为“是”时,T100 `pmdi001` 传 `Y`;为“否”时传 `N`(兼容历史 `Y/N` 值)。暂估表单 `FORM-F906C997A4A44DC8B9180198C0B2E55F6W0S` 和 `FORM-EC46C7109182497796AA0B70F3848576UYBU` 的 `pmdiud001` 传 `Y`,其余供应商价格审批表单传 `N`;`pmdistus` 固定传 `I`。新增的三张表单按照各自 T100 请求体层级组装 `parameter`,并填充对应的 `datakey.type` 与 `service.name=DingTalk To T100`;包材材质核价以 `textField_mviyetxy` 判断 `pmaaucent`,供应商暂停/退出流程以 `textField_mvewj97z` 判断 `pmaaent`、`pmabent`,包材料号清单维护以 `selectField_msy2rh6q` 判断 `pmabucent`,字段值为“明磊”时均传 `87`,其余值传 `60`;包材料号清单子表的 `textField_mvix39fn`、`textField_mvix39fm`、`numberField_mvjb17mf` 分别映射至 `pmabuc001`、`pmabuc002`、`pmabuc024`,且 `pmabucsite` 固定传 `ML`,供应商暂停/退出流程的 `selectField_mt82p09c` 为“正常供应商”时 `pmaa083`、`pmab074` 传 `1`,为“停用”时传 `0`,并保留 `pmabsite`;包材料号清单维护固定传 `pmabucstus=Y`。未注册的 `formId` 会返回参数错误,不会访问宜搭或 T100。外部 T100 接口地址与密钥分别由 `MINGLEI_SUPPLIER_PRICE_APPROVAL_URL`、`MINGLEI_SUPPLIER_PRICE_APPROVAL_KEY` 配置。 当四张供应商价格审批单推送 T100 且返回 `success=true` 时,服务会从 `content.payload.std_data.parameter.pmdidocno` 读取 T100 单号,并按原流程实例 ID 调用宜搭流程更新接口回写单号。回写字段分别为:核价与核价(子公司)使用 `textField_mszqctqb`,暂估与暂估(子公司)使用 `textField_mt7yf5wu`。T100 未成功时不会回写;T100 已成功但宜搭回写失败时接口返回失败,避免单据状态被误判为已完整同步。 ### 4.8 ERP 推送宜搭 ```http POST /api/erp/yida/push Content-Type: application/json ``` 请求示例: ```json { "type": "axmt140", "mainData": { "rq": "2026-09-08", "bm": "123456", "tjr": "3075", "gs": "ML" }, "detailData": { "detailData1": [ { "xmfyseq": "10", "xmfy002": "DN-001", "xmfy003": "2026-09-08", "xmfy004": "CUS-001", "pmaal004": "客户名称" } ] } } ``` 成功响应使用 `McR.success()`: ```json { "success": true, "code": "200", "message": "SUCCESS" } ``` ## 5. ERP 推送规则 ### 5.1 支持的类型 `type` 会去除首尾空格并转为大写后匹配,调用方传入大小写不敏感。 | `type` 或别名 | 处理器 | 单据或流程 | 表单 UUID | 流程编码 | | ------------------------------------------- | -------------------------------------- | ----------- | ------------------------------------------- | ----------------------------------------------- | | `cpmp402` | `Cpmp402YidaProcessHandler` | 包材核价单 | `FORM-A3D4889187E54697A59590BE54F350E7AOO5` | `TPROC--98G66QB1NTF8E1R1LI0OK4D6JI9437HC92YSM1` | | `FORM-4403B45A7F184CB79088FCDAFD2FD925LZ3K` | `MlWorkOrderCraftPriceFormPushHandler` | ML-工单工艺工价审定 | `FORM-4403B45A7F184CB79088FCDAFD2FD925LZ3K` | `TPROC--WG966BA1VWF8Q1K4OGF8H9KW47RR3ZREH0YSM2` | | `axmt500` | `ApproveOrderFormPushHandler` | 客户订单审批 | `FORM-8454FA17732746058A2D90B1C559CC19G5SR` | `TPROC--9VD66QA18AJ8317KK3NXM54W1HIT30C7N28TM7` | | `axmt140` | `CustomerReleaseFormPushHandler` | 客户放行审批 | `FORM-3BA9FE18494F4FDD85FE6ABF7DE741BAE30C` | `TPROC--6TG66381N8G8Y7L9GBEEY7QVXTUY2LBBGZ7TMU` | | `cint301` | `MiscReceiptIssueFormPushHandler` | 杂收杂发审批 | `FORM-4B08ADABCDAA45788C043729A855E03ED76G` | `TPROC--MQD66371C1N84FPSJZO4W79IN4BW2AE7I08TM0` | | `asft800` | `Asft800FormPushHandler` | 包装工单变更审批流程 | `FORM-40D6FBBBF4A94864A2AF9535AAC7924CRBH2` | `TPROC--U2I66F719HF854X6PIGK5A2AP9VC2LODB7YSM1` | | `aqct300` | `Aqct300FormPushHandler` | 检验情况审批流程 | `FORM-38C8592E7F5F45B3A388DA3FEFBA67C2GCEV` | `TPROC--LX766PC193N85EWUJ9ZLACNJNPSW3HBRG78TM1` | | `axmt510` | `Axmt510FormPushHandler` | 订单变更申请流程 | `FORM-8F318292B70D4ED39080EE0C631DC74BG3MD` | `TPROC--BHC66W71Q1N87Q0YFD7FVBNYXRJ62OIA378TM4` | | `apmt100` | `Apmt100FormPushHandler` | 供应商导入流程 | `FORM-9EE14CA14D8D4AEE8102944E669E6B0CFUKS` | `TPROC--75G66PD15YG82766OFSWD8BS4ZJ33YPRFOZSM2` | ### 5.2 `mainData` 规则 | 字段 | 说明 | | -------- | ----------------------------- | | `rq` | 申请日期,通常为 `yyyy-MM-dd` 或毫秒时间戳 | | `bm` | 钉钉部门 ID | | `tjr` | 提交人工号,用于查询员工花名册并解析钉钉 `userId` | | `gs` | 公司或组织编码 | | `t100id` | T100 业务标识 | | `ent` | 地区或企业编号 | | `t100bm` | T100 部门 | 顶层 `bm`、`gs`、`tjr` 非空时,会覆盖写入 `mainData`。服务端会将 `mainData` 中的通用字段补入各明细行,便于表单处理器复用。 ### 5.3 `detailData` 规则 `detailData` 外层必须是对象,内部至少包含一个数组字段。数组字段名支持: - `detailData` - `detailData1` - `detailData2` - 其他满足 `detailData\d*` 格式的字段 不同单据的约定: | 类型 | 明细字段 | | ------------------------------------------- | --------------------------------------------- | | `cpmp402` | `detailData` | | `FORM-4403B45A7F184CB79088FCDAFD2FD925LZ3K` | `detailData` | | `axmt500` | `detailData1`、`detailData2`,兼容历史 `detailData` | | `axmt140` | 优先 `detailData1`,缺失时回退到 `detailData` | | `cint301` | `detailData1` | | `asft800` | `detailData1` | | `aqct300` | `detailData1`~`detailData4` | | `axmt510` | `detailData1`~`detailData3` | | `apmt100` | 无明细,传入空对象 `{}` | 明细对象中可以直接传入宜搭 `fieldId`。服务端会保留包含 `Field_` 的字段值,用于补充默认字段映射。 ### 5.4 发起人解析 1. `ErpYidaPushServiceImpl` 优先使用顶层 `tjr`,其次使用 `mainData.tjr`。 2. 服务端通过员工花名册查询工号对应的 `dingtalkId`。 3. `dingtalkId` 会作为 `originatorUserId`、`userId` 和 `applicant` 写入请求上下文或明细行。 4. 匹配到 `YidaFormPushHandler` 时,提交人工号允许为空,具体处理器可以使用默认发起人兜底。 5. 未匹配到表单处理器并走通用 `YidaProcessService` 时,提交人工号不能为空。 ### 5.5 `asft800` 包装工单变更审批流程 调用 `POST /api/erp/yida/push` 时传入 `type: "asft800"`。表头使用 ERP 原始字段,明细必须放在 `detailData.detailData1`。 ```json { "type": "asft800", "bm": "123456789", "gs": "87", "tjr": "EMP0001", "mainData": { "rq": "2026-09-16", "bm": "123456789", "gs": "87", "tjr": "EMP0001", "t100id": "EMP0001", "ent": "87", "t100bm": "PACK", "sfkaent": "87", "sfkasite": "ML", "sfkadocno": "WO-20260916-001", "sfka900": "2", "sfka902": "2026/09/16", "sfka057": "包装工单", "sfka010": "ITEM-001", "imaal003": "锂电池包装组件", "imaal004": "标准规格", "sfka012": 100.5, "sfka006": "SOURCE-001", "sfkaud009": "MO-001", "sfka906": "变更物料及数量", "ooagud004": "dingtalk-user-id" }, "detailData": { "detailData1": [ { "sfkgent": "87", "sfkgdocno": "WO-20260916-001", "sfkg900": "2", "sfkgseq": "10", "sfkg901": "变更", "sfkg006": "MAT-002", "imaal003": "新物料名称", "imaal004": "新物料规格", "sfkg013": 101.5, "sfkg014": "PCS", "sfba006": "MAT-001", "o_imaal003": "原物料名称", "o_imaal004": "原物料规格", "sfba013": 100.0 } ] } } ``` `tjr` 是员工工号,服务端会从员工花名册解析对应钉钉用户作为发起人;未传时,处理器使用 `mainData.ooagud004` 作为钉钉用户 ID。`bm` 为钉钉部门 ID,`sfka902` 支持 `yyyy-MM-dd` 或 `yyyy/MM/dd` 格式。 ### 5.6 `aqct300` 检验情况审批流程 调用 `POST /api/erp/yida/push` 时传入 `type: "aqct300"`。表头使用 ERP 原始字段;明细分别放入 `detailData1`(检验项目)、`detailData2`(缺点原因)、`detailData3`(计量检验)、`detailData4`(来源明细)。 当前仅发起宜搭流程,不做 ERP 回写。以下字段宜搭无对应控件,推送时丢弃:`qcba005`(交易对象编号)、`qcba010`(料件编号)、`qcba027`(不良数)、`detailData3.qcbd002`(检验位置)。`qcba000` 需直接传宜搭选项值:`IQC` / `FQC` / `Inventory QC`。`qcbadocno` 同时写入宜搭「单号」`textField_mt87k1g0` 与新增字段 `textField_muz22qad`。 ```json { "type": "aqct300", "bm": "123456789", "gs": "87", "tjr": "EMP0001", "mainData": { "rq": "2026-09-21", "bm": "123456789", "gs": "87", "tjr": "EMP0001", "qcbaent": "87", "qcbadocno": "QC-20260921-001", "qcbadocdt": "2026/09/21", "qcba000": "IQC", "qcba008": 100, "qcba009": "PCS", "imaal003": "料件品名", "imaal004": "规格", "qcba012": "产品特征", "qcba016": "EA", "qcba017": 80, "qcba022": "PASS", "qcba023": 75, "qcba024": "张三", "qcba034": "LOT-001" }, "detailData": { "detailData1": [ { "qcbdent": "87", "qcbddocno": "QC-20260921-001", "qcbdseq": "10", "qcbd001": "DIM", "oocql004": "尺寸检验", "qcbd009": 20, "qcbd010": 1, "qcbd011": "NG", "qcbd021": 1 } ], "detailData2": [ { "qcbeent": "87", "qcbedocno": "QC-20260921-001", "qcbeseq": "10", "qcbe001": "划伤", "qcbe002": 2, "qcbe003": "备注", "qcbe004": 2 } ], "detailData3": [ { "qcbdent": "87", "qcbddocno": "QC-20260921-001", "qcbdseq": "10", "qcbd013": 10.5, "qcbd015": 9.5, "qcbg002": 10.1, "qcbd017": "mm", "qcbg003": "Y", "qcbd001": "SIZE", "oocql004": "检验标准", "qcbd019": "备注" } ], "detailData4": [ { "qcbaucseq": "1", "qcbauc014": "PO", "qcbauc001": "PO-001", "qcbauc002": "10", "qcbauc003": "REF-001", "qcbauc004": "20", "qcbauc005": "SUP-001", "qcbauc008": 100, "qcbauc009": "PCS", "qcbauc010": "ITEM-001", "imaal003": "料件品名", "imaal004": "规格" } ] } } ``` `tjr` 为员工工号,服务端从花名册解析钉钉用户作为发起人与申请人;`bm` 为钉钉部门 ID;`qcbadocdt` / `rq` 支持 `yyyy-MM-dd` 或 `yyyy/MM/dd`。 ### 5.7 `axmt510` 订单变更申请流程 调用 `POST /api/erp/yida/push` 时传入 `type: "axmt510"`。表头使用 ERP 原始字段;明细分别放入 `detailData1`(订单变更单身)、`detailData2`(交期明细)、`detailData3`(配件明细)。 当前仅发起宜搭流程,不做 ERP 回写。表头「变更事件」、附件与已到物料相关字段无对应 ERP 字段,推送时不填。子表1 `xmegkk` 映射到「变更事件」选项:`数量变更` / `交期变更` / `其他变更`。 ```json { "type": "axmt510", "bm": "123456789", "gs": "87", "tjr": "EMP0001", "mainData": { "rq": "2026-09-23", "bm": "123456789", "gs": "87", "tjr": "EMP0001", "xmeeent": "87", "xmeedocno": "SO-20260923-001", "xmee900": "1", "xmeedocdt": "2026/09/20", "xmee001": 2, "xmee002": "dingtalk-sales-user", "xmee003": "dingtalk-dept-id", "xmee004": "C001", "pmaal004": "客户简称", "xmee013": "Y", "xmee042": 12800.5, "xmee901": "2", "xmee902": "2026/09/23", "xmee903": "R01", "xmee904": "交期调整" }, "detailData": { "detailData1": [ { "xmegent": "87", "xmegdocno": "SO-20260923-001", "xmegseq": "10", "xmeg900": "1", "xmeg001": "ITEM-001", "xmeg002": "feature-a", "imaal003": "品名", "imaal004": "规格", "xmdc007": 100, "xmdc012": "2026/09/30", "xmdc015": 10.5, "xmeg006": "PCS", "xmeg007": 120, "xmeg012": "2026/10/15", "xmeg015": 11, "xmeg046": 1100, "xmeg047": 1243, "xmeg048": 143, "xmeg049": "RC01", "xmeg053": "HC01", "xmeg901": "2", "xmegud010": "MO-001", "xmegkk": "交期变更" } ], "detailData2": [ { "xmehent": "87", "xmehdocno": "SO-20260923-001", "xmehseq": "10", "xmehseq1": "1", "xmehseq2": "1", "xmeh900": "1", "xmeh001": "ITEM-001", "xmeh004": "PCS", "xmeh005": 120, "xmeh009": "1", "xmeh011": "2026/10/15" } ], "detailData3": [ { "xmfwucent": "87", "xmfwucdocno": "SO-20260923-001", "xmfwucseq": "10", "xmfwucseq1": "1", "xmfwuc001": "PART-001", "imaal003": "配件品名", "imaal004": "配件规格", "xmfwucud001": "feat-p", "xmfwuc006": 2, "xmfwuc007": 1, "xmfwuc008": "PCS", "xmfwuc009": 240, "xmfwuc900": "1", "xmfwuc901": "2" } ] } } ``` `tjr` 为员工工号;`xmee002` / `xmee003` 需传钉钉用户 ID / 部门 ID。日期字段支持 `yyyy-MM-dd` 或 `yyyy/MM/dd`。流程标题为 `订单变更申请流程-{订单单号}-{变更序}`。 ### 5.8 `apmt100` 供应商导入流程 调用 `POST /api/erp/yida/push` 时传入 `type: "apmt100"`。该流程无明细,`detailData` 必须为 `{}`。 ```json { "type": "apmt100", "bm": "123456789", "gs": "ML", "tjr": "EMP0001", "mainData": { "rq": "2026-09-16", "sqlx": "NEW", "gysmc": "供应商名称", "gysbm": "SUP-001", "sh": "91330100TEST", "gczczj": "1000000.50", "clsj": "2026/09/16", "zysccp": "锂电池包装组件", "gcgm": "120", "lcbh": "PM-001", "smzq": "ACTIVE", "pmbb033all": "CNY", "pmbb034all": "VAT", "pmbb053all": "NET30", "pmbb033": "CNY", "pmbb034": "VAT", "pmbb053": "NET30", "pmbb037": "NET30", "gst100": "ML", "lxr": "张三", "gsdz": "浙江省杭州市", "lxfs": "13800000000" }, "detailData": {} } ``` `lcbh` 用于生成流程标题;若其为空,可使用 `docno`。`clsj` 支持 `yyyy-MM-dd` 或 `yyyy/MM/dd`。所有 `pmbb***all` 字段映射到集团惯用数据,未带 `all` 后缀的 `pmbb***` 映射到据点惯用数据。 ## 6. 日志和异常 ### 6.1 日志 Logback 输出: - 控制台日志。 - `${LOGGING_FILE_PATH}/application.log`。 - `${LOGGING_FILE_PATH}/error.log`。 - `archive` 目录下的滚动归档日志。 `HttpRequestResponseLoggingFilter` 会记录 HTTP 方法、URI、状态码、耗时、请求体和响应体。单个载荷最大记录 10KB,并对 `password`、`secret`、`token`、`authorization`、`accessToken`、`systemToken`、`key` 等字段脱敏。 ### 6.2 异常响应 | 场景 | HTTP 状态码 | 说明 | | -------------- | -------- | ----------------------------- | | 请求体格式错误或参数校验失败 | `400` | 请求不合法 | | 配置缺失 | `412` | 必要配置未提供 | | 宜搭或钉钉上游业务错误 | `502` | 返回上游错误码和错误信息 | | 外部 T100 调用失败 | `502` | 返回 `MINGLEI_PORT_CALL_FAILED` | ## 7. 构建和运行 环境要求: - JDK 8 - Maven 3.6+ - 可访问项目依赖仓库和内部 `malk` 依赖 本地构建: ```powershell mvn clean package ``` 本地运行: ```powershell $env:SERVER_PORT = "8080" $env:DINGTALK_APP_KEY = "" $env:DINGTALK_APP_SECRET = "" $env:YIDA_COORDINATION_OFFICE_APP_TYPE = "" $env:YIDA_COORDINATION_OFFICE_SYSTEM_TOKEN = "" $env:YIDA_MASTER_DATA_APP_TYPE = "" $env:YIDA_MASTER_DATA_SYSTEM_TOKEN = "" mvn spring-boot:run ``` 运行打包产物: ```powershell java -jar target\mjava-minglei-1.0.0-SNAPSHOT.jar ``` ## 8. 测试 执行完整测试: ```powershell mvn clean test ``` 当前测试覆盖: - Spring Boot 上下文加载。 - Controller 请求和响应结构。 - 员工花名册同步、查询和字段提取。 - 宜搭流程处理器分发。 - ERP 推送到不同宜搭表单处理器的字段映射。 - 外部 T100 请求组装和响应转发。 - 钉钉审批实例详情调用、转入部门解析和宜搭审批矩阵(按"部门"字段匹配)各级别部门负责人与部门负责人提取。 当前验证结果: ```text Tests run: 52, Failures: 0, Errors: 0, Skipped: 1 ``` ## 9. 扩展规范 ### 9.1 新增宜搭流程处理器 新增可由 `/api/yida/process-instances` 触发的通用流程时: 1. 新建 `XxxYidaProcessHandler`。 2. 实现 `YidaProcessHandler`。 3. 在 `getType()` 返回业务路由编码。 4. 在处理器中维护表单 UUID、流程编码和字段映射。 5. 补充对应单元测试。 6. 更新 README 的支持类型说明。 ### 9.2 新增 ERP 表单推送处理器 新增可由 `/api/erp/yida/push` 路由的表单流程时: 1. 新建 `XxxFormPushHandler`。 2. 实现 `YidaFormPushHandler`。 3. 在 `getTypes()` 返回主 `type` 和必要别名。 4. 在处理器中维护表单 UUID、流程编码和字段映射。 5. 在 `src/test/java` 中补充字段映射和异常测试。 6. 更新 README 的 ERP 推送类型表。 ### 9.3 字段映射原则 - 业务 `type` 是路由键,不等于表单 UUID。 - 应用凭证、表单 UUID、流程编码由服务端维护,调用方不传。 - 表单处理器优先使用 ERP 原始字段名映射。 - 调用方确需补充时,可以传入宜搭 `fieldId` 字段。 - 不要在日志、文档或测试数据中写入真实密钥、Token、Cookie 或数据库密码。 ## 10. 运维注意事项 - 部署账号必须对 `LOGGING_FILE_PATH` 有写权限。 - 宜搭和钉钉接口所需的网络连通性、权限范围、组织数据权限由部署环境保证。 - 当前 Controller 未内置业务鉴权。如需访问控制,应在网关或统一认证层配置。 - 发生宜搭配置不匹配时,确认应用编码、System Token 和处理器选择的客户端属于同一应用。 - 员工花名册依赖数据库连接和数据完整性。若 `mainData.tjr` 无法解析,优先检查花名册同步状态和员工 `dingtalkId`。