Commit 96dcd92f6cf464c5108d64356a5dfab109e9c948
1 parent
576ed951
feat: ERP 侧 rearch3 对接验收后的三处接轨
- create 载荷 sFormId 改写模块 id(与 ERP 原生同构;表单 id 无 SysBillNoSettings 编号规则,执行期重生成单号必撞已用号) - ai_op_queue 停写 sUserId(ERP 执行器已切读 sMakePerson;列暂 不可删——ERP myTodos 仍 COALESCE 引用) - saaslocal 开启 erp.dry-run.enabled(ERP /business/checkBusinessData 已上线,保存前真实校验链 validate-only) 联合 e2e 实测:建报价 BJD202607091(sFormId=模块id、主从落库)→ 审核 success+200 → 重复审核 fail+409 → 改字段 success+200 → 取消 fail+450 → 幂等 success+201;dry-run 保存时真实调用(ERP 日志 时刻对齐)且零落库。
Showing
4 changed files
with
20 additions
and
16 deletions
docs/agent-architecture.md
| ... | ... | @@ -495,8 +495,9 @@ ai_op_queue( |
| 495 | 495 | ErpClient 只剩 whoami / readForm / getUuid / checkBusinessData(dry-run 接口位)。 |
| 496 | 496 | - **状态合法性硬检查**(预览+保存两次,防 TOCTOU;依据已核实的存储过程行为):审核要求未审核、 |
| 497 | 497 | 销审要求已审核、作废要求未作废、复原要求已作废;表无对应状态列则跳过。已审核单物理删除仅警告。 |
| 498 | -- **ai_op_queue 列名兼容**:新增 `sMakePerson`(ERP 惯例)与 `sUserId` 双写(ERP 执行器仍读 | |
| 499 | - sUserId,切换后可删);迁移见 `sql/migrate_rearch3.sql`。 | |
| 498 | +- **ai_op_queue 归属列**:只写 `sMakePerson`(ERP 惯例);旧列 `sUserId` 已停写(2026-07-29, | |
| 499 | + ERP 执行器切读完成后),但暂不可删——ERP 侧 myTodos/归属校验仍以 `COALESCE(sMakePerson, sUserId)` | |
| 500 | + 兼容历史行;迁移见 `sql/migrate_rearch3.sql`。 | |
| 500 | 501 | - 护栏:反编造重试与 `maxSequentialToolsInvocations(8)` 原样保留;WRITE_CLAIM 正则补 |
| 501 | 502 | 保存/录入并用负向前瞻排除「已审核状态」类状态陈述;"出过卡"标志改为 change_preview/form_collect。 |
| 502 | 503 | |
| ... | ... | @@ -542,8 +543,10 @@ ai_op_queue( |
| 542 | 543 | |
| 543 | 544 | ### 21.5 待 ERP 侧(D 阶段对接) |
| 544 | 545 | |
| 545 | -- `/business/checkBusinessData` dry-run 端点在 ERP 仓库**未提交**(他人负责);xlyAi 已留 | |
| 546 | - `erp.dry-run.enabled` 开关 + `ErpClient.checkBusinessData`,完工后开启即接入(update 载荷 | |
| 547 | - 与入队同口径;__tables__ 多行分组形状需与 ERP 对一次)。 | |
| 548 | -- doExamine/updatebInvalid 的 ERP 侧校验补做中;xlyAi 预览暂以直读 bCheck/bInvalid 实现硬检查。 | |
| 549 | -- ai_op_queue `sUserId` 列:ERP 执行器切到 sMakePerson 后删除。 | |
| 546 | +- ~~`/business/checkBusinessData` 未提交~~ **已上线**(ERP 仓库 feature/ai @1555f0c,2026-07-29 验收): | |
| 547 | + validate-only(REQUIRES_NEW 强制回滚)、真写端点剥 bDryRun、__tables__ 两种分组形状均支持; | |
| 548 | + `erp.dry-run.enabled` 已在 saaslocal 开启(生产随 ERP 部署后开)。 | |
| 549 | +- ~~doExamine/updatebInvalid 校验补做中~~ **已上线**:执行时刻按 bCheck/bInvalid 校验,冲突回 fail+409 | |
| 550 | + (xlyAi 预览侧的硬检查保留,双层防 TOCTOU)。 | |
| 551 | +- ai_op_queue `sUserId` 列:xlyAi 已停写;**删列需等 ERP 侧去掉 COALESCE 兼容读**(其 myTodos SQL | |
| 552 | + 直接引用该列,先删会把查询打挂)。 | ... | ... |
src/main/java/com/xly/service/FormRenderService.java
| ... | ... | @@ -423,7 +423,8 @@ public class FormRenderService { |
| 423 | 423 | col.put(rc, resolver.typeDefault(rc, types.get(rc))); |
| 424 | 424 | } |
| 425 | 425 | col.put("sId", erp.newUuid(identity.token())); |
| 426 | - col.put("sFormId", out.formId); | |
| 426 | + // 记录的 sFormId 按 ERP 原生惯例存模块 id(SysBillNoSettings 编号规则按它配置;存表单 id 会撞不上规则) | |
| 427 | + col.put("sFormId", out.moduleId); | |
| 427 | 428 | String billNo = resolver.nextBillNo(out.table, identity.brandsId()); |
| 428 | 429 | if (billNo != null) { |
| 429 | 430 | col.put("sBillNo", billNo); |
| ... | ... | @@ -542,7 +543,7 @@ public class FormRenderService { |
| 542 | 543 | } |
| 543 | 544 | String masterId = erp.newUuid(identity.token()); |
| 544 | 545 | masterCol.put("sId", masterId); |
| 545 | - masterCol.put("sFormId", out.formId); | |
| 546 | + masterCol.put("sFormId", out.moduleId); | |
| 546 | 547 | String billNo = resolver.nextBillNo(out.table, identity.brandsId()); |
| 547 | 548 | if (billNo != null) { |
| 548 | 549 | masterCol.put("sBillNo", billNo); | ... | ... |
src/main/java/com/xly/service/OpService.java
| ... | ... | @@ -24,8 +24,8 @@ import java.util.Map; |
| 24 | 24 | * </ul> |
| 25 | 25 | * 旧值(draft/confirmed/executing/executed/failed/cancelled)兼容读(见 statusLabel),不再写。 |
| 26 | 26 | * |
| 27 | - * <p>列名兼容:ERP 执行器仍读 {@code sUserId} → 新列 {@code sMakePerson}(ERP 惯例)双写, | |
| 28 | - * ERP 侧切换后可删 sUserId。 | |
| 27 | + * <p>归属列只写 {@code sMakePerson};旧列 {@code sUserId} 已停写但暂不可删——ERP 侧 | |
| 28 | + * {@code myTodos}/归属校验仍以 {@code COALESCE(sMakePerson, sUserId)} 兼容历史行。 | |
| 29 | 29 | */ |
| 30 | 30 | @Service |
| 31 | 31 | public class OpService { |
| ... | ... | @@ -82,11 +82,11 @@ public class OpService { |
| 82 | 82 | String newValue, String payload, String description) { |
| 83 | 83 | String sId = "op-" + System.currentTimeMillis() + "-" + Integer.toHexString((int) (Math.random() * 0xFFFFF)); |
| 84 | 84 | jdbc.update( |
| 85 | - "INSERT INTO ai_op_queue(sId,sUserId,sMakePerson,sBrandsId,sSubsidiaryId,sConversationId,sOpType," + | |
| 85 | + "INSERT INTO ai_op_queue(sId,sMakePerson,sBrandsId,sSubsidiaryId,sConversationId,sOpType," + | |
| 86 | 86 | "sTargetFormId,sTargetModuleId,sTargetTable,sTargetBillId,sField,sFieldLabel," + |
| 87 | 87 | "sOldValue,sNewValue,sPayload,sDescription,sStatus,iStatusCode,tCreateDate,tConfirmDate) " + |
| 88 | - "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,'pending',100,NOW(),NOW())", | |
| 89 | - sId, who.userId(), who.userId(), who.brandsId(), who.subsidiaryId(), convId, opType, | |
| 88 | + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,'pending',100,NOW(),NOW())", | |
| 89 | + sId, who.userId(), who.brandsId(), who.subsidiaryId(), convId, opType, | |
| 90 | 90 | formId, moduleId, table, billId, field, trunc(fieldLabel, 128), |
| 91 | 91 | trunc(oldValue, 500), trunc(newValue, 500), payload, trunc(description, 500)); |
| 92 | 92 | return sId; | ... | ... |
src/main/resources/application-saaslocal.yml
| ... | ... | @@ -33,9 +33,9 @@ erp: |
| 33 | 33 | subsidiary: "1111111111" |
| 34 | 34 | username: admin |
| 35 | 35 | password: "666666" |
| 36 | - # Phase D 接口位:ERP dry-run 校验(/business/checkBusinessData)。ERP 侧完工前保持 false。 | |
| 36 | + # ERP dry-run 校验(/business/checkBusinessData)。ERP 侧 2026-07-29 上线(feature/ai @1555f0c)后开启。 | |
| 37 | 37 | dry-run: |
| 38 | - enabled: false | |
| 38 | + enabled: true | |
| 39 | 39 | |
| 40 | 40 | # LLM 可观测(Langfuse,架构 §1)。默认关闭;自托管起来后填 key 开启: |
| 41 | 41 | # docker compose -f docker-compose.langfuse.yml up -d → http://localhost:3000 拿 public/secret key。 | ... | ... |