轻易云
注册体验

集成转换通用骨架:transform_documents × payload JSONB × columnSchema × editableFields

· 何金辉· AI 财务对账· 13 次浏览· 约 12 分钟读完
集成转换transform_documentspayload JSONBcolumnSchemaeditableFields第四类沙箱列声明快照金额禁改编辑白名单批次快照

集成转换通用骨架:transform_documents × payload JSONB × columnSchema × editableFields

摘要:金蝶云星空、用户系统里的对账结果要生成应收单、应付单,自研 ERP 要的是另一套字段……这套「目标系统千差万别」的中间产物,怎么在 Prisma schema 里只占 4 张表、不随集成数线性膨胀?答案就是「transform_documents × payload JSONB × columnSchema × editableFields」这一组通用骨架——骨架字段固定、目标系统差异全收敛进 JSONB、列声明与编辑白名单随批次快照落库、金额字段一律禁改。

关键词:集成转换 / transform_documents / payload JSONB / columnSchema / editableFields / 第四类沙箱 / 列声明快照 / 金额禁改 / 编辑白名单 / 批次快照

钩子:一张图看 232 张单据是怎么「一锅出」的

2026 年 8 月验收数据:把当月 5 个店铺、京东 POP / 抖店 / 亚马逊 / 支付宝 / 拼多多 5 大平台的收入对账结果推送到金蝶云星空,集成中心转换引擎一次性产出 232 张单据、合计 9,345,659.28 元——其中预览中 64 张、已确认 10 张、推送中 0 张、已推送 154 张、推送失败 4 张、含异常体行 7 张。这 232 张单据形态各异:财务应收单(暂估下推)、费用应付单(公摊反写)、销售收款单(余利宝清算)、补差价单……金蝶 formId 从 AR_receivable|YSD01_SYS|FIN_FROM_HOOK 到 AP_OtherPayable|Save,10+ 套字段语义同时存在,但它们全落在同一张数据库表上——transform_documents。

为什么会这样?因为把「目标系统差异」从代码结构里剥出来后,集成转换需要的是一张「骨架表 + JSONB」组合,而不是「金蝶一张表、用友一张表、SAP 一张表」的化石式扩张。下面这张架构图,把这套通用骨架画得很清楚:

轻易云集成转换通用骨架架构图:transform_documents 头表与 transform_entries 体行表的 4 层结构,head / payload / sourceRefs 全部 JSONB 化

图里把单据拆成了 4 层——头表(id / docType / head / status)、单据头 payload(JSONB)、体行 payload(JSONB)、追溯 sourceRefs(JSONB)。骨架字段只是状态机、金额合计、追溯、回写的「底座」;金蝶的 FOrgId、用友的部门编码、SAP 的 CardCode,全部进 JSONB。这是 4 类沙箱脚本里唯一一类「输出字段是 JSONB」的脚本——因为它的输出要适配的目标系统差异比 parse / reconcile / allocate 大几个量级。

下面我们逐层拆解这 4 个组件为什么这么设计。

一、决策 1:documents 头 + 体行骨架,4 张表搞定全平台

集成转换的产物在数据库里只占 4 张表:

prisma
// apps/api/prisma/schema/transform.prisma:104-191
model TransformBatch {
  batchNo        String   @unique        // TFB-{YYYYMMDD}-{seq4}
  shopId         String
  platform       PlatformEnum
  periodFrom     String   @db.VarChar(7) // YYYY-MM
  periodTo       String   @db.VarChar(7)
  planId         String?                  // P9-T3:单计划粒度(可空)
  targetSystem   String                   // 自由文本,如 "kingdee-cloud-galaxy"
  scriptVersion  String                   // 生成时快照
  columnSchema   Json     @default("[]")  // S2 快照:{ columns, editableFields }
  unmatchedSummary Json   @default("[]")
  // ...
}

model TransformDocument {
  id            String   @id @default(cuid())
  batchId       String
  shopId        String
  period        String   @db.VarChar(16)  // 单月 "2026-06" 或区间 "2026-06~2026-07"
  targetSystem  String                    // 自由文本,不设枚举
  docType       String                    // 单据分类键:金蝶约定 = formId 或 "formId|billTypeId"
  docTypeName   String   @default("")
  head          Json     @default("{}")   // 单据头 payload(系统不解释)
  totalAmount   Decimal  @default(0) @db.Decimal(20, 4)  // 体行合计(快照,禁改)
  status        TransformDocumentStatus @default(DRAFT)  // 5 态机
  externalBillNo String?                  // 推送回写:目标系统单号
  pushedAt      DateTime?
  // ...
}

model TransformEntry {
  id            String   @id @default(cuid())
  documentId    String
  entryKey      String                    // 体行合并/展示键
  entryName     String   @default("")
  amount        Decimal? @db.Decimal(20, 4)  // Σ sign × 来源金额(带符号,禁改)
  payload       Json     @default("{}")   // 体行其余字段(目标系统格式)
  sourceSummary Json     @default("{}")   // 追溯快照:{ rules, planItemIds, aggregationIds }
  isAbnormal    Boolean  @default(false)
  abnormalReason String?
  sortOrder     Int      @default(0)
  // ...
}

model TransformRule {
  sourceType        TransformRuleSourceType  // INCOME_MAIN / EXPENSE_ITEM / INCOME_DIFF_LABEL ...
  accountingItemId  String @default("")      // 空串 = 收入侧口径
  amountKind        String @default("")      // "" / FEE / INCOME / 差异标签机读码
  direction         String @default("")      // "" 通用兜底;POSITIVE / NEGATIVE 分向
  targetSystem      String                   // 自由文本,不设枚举
  docType           String                   // 单据分类键
  entryKey          String @default("")      // 空 = 回退 AccountingItem.erpExpenseItemCode
  sign              Int      @default(1)     // 1 = 加 / -1 = 减
  @@unique([sourceType, accountingItemId, amountKind, direction, targetSystem])
}

边界画在哪?骨架字段固定、payload JSONB 化——transform_documents 上的 id / batchId / shopId / period / targetSystem / docType / status / totalAmount / externalBillNo 这些是与目标系统无关的底座,必须固定(不固定就按 docType 排序、按金额合计筛选等能力会被目标系统差异击穿)。head 与 entry.payload 是金蝶的 FOrgId、用友的部门、SAP 的 CardCode 各进各的 JSON。

被否决的两个候选值得记一笔:

  • ❌ 每目标系统独立成表——BillRow 按平台拆表是因为列结构由外部平台账单决定、不可统一;转换输出是我们自己生成的中间产物,骨架完全可自定义,没有拆表理由。
  • ❌ 纯 JSONB 无骨架——会丢失状态机、金额索引、追溯关系、分页排序能力。前端表格没法按金额合计排序、推送 handler 无法按 docType 路由、批次无法按金额合计筛选——这些能力全部依赖骨架字段。

把这两条否决理由记下来,下次有人提「目标系统独立成表」时直接拿出来用。

二、决策 2:payload JSONB 何时写、何时读

payload JSONB 的契约来自沙箱输出的 TS 接口:

typescript
// apps/api/src/biz_reconciliation/transform/transform-sandbox-runner-host.ts:148-159
export interface TransformDocumentEntry {
  entryKey: string;
  entryName?: string;
  amount?: string;                   // Decimal 字符串
  payload?: Record<string, unknown>; // 体行 payload(目标系统字段全收敛于此)
  sourceRefs?: unknown;              // 追溯引用(planItem / aggregation / rule)
  isAbnormal?: boolean;
  abnormalReason?: string;
  sortOrder?: number;
}

export interface TransformDocumentOutput {
  docType: string;
  docTypeName?: string;
  head: Record<string, unknown>;     // 单据头 payload
  entries: TransformDocumentEntry[];
}

脚本返回的 head 与 entry.payload 在沙箱出口会被归一化落库,落库形态是 transform_documents.head(JSONB) 与 transform_entries.payload(JSONB)。归一化函数 buildTransformPersistPlan(transform-generate.ts:86-123)的核心职责是:

  1. 把 entry.amount 字符串解析成 Decimal(用 new Decimal(entry.amount)),零额保留为 null;
  2. 把 entry.sourceRefs 收敛成 sourceSummary JSONB(结构 { rules: [...], planItemIds?: string[], aggregationIds?: string[] });
  3. 用 entry.amount 求 Σ 作为 transform_documents.totalAmount,这一列是快照的 Decimal(20,4),禁改。

读出侧也很有意思——head 是浅合并语义:

typescript
// apps/api/src/biz_reconciliation/transform/transform-documents.service.ts:316-321
// head 合并语义:传什么改什么(仅白名单键)
patch.head = {
  ...(doc.head as Record<string, unknown>),
  ...data.head,
} as Prisma.InputJsonValue;

用户在编辑头表时,传的 data.head 是「我只想改的那几个键」,系统把它与现有 doc.head 浅合并后整体覆盖——这是白名单约束下的局部编辑。导出侧则按 columnSchema 的 key 列表从 payload 里取单元格:

typescript
// transform-documents.service.ts:755-769
const entryRows: string[][] = [];
for (const d of docs) {
  for (const e of d.entries) {
    const payload = (e.payload ?? {}) as Record<string, unknown>;
    entryRows.push([
      ...extraColumns.map((c) => exportCellText(payload[c.key])),   // ← payload 字段走声明列
    ]);
  }
}

「系统只认骨架、payload 不解释」是这套设计的关键定性。系统不读 head["FOrgId"]、不读 payload["FSUPPLIERID"]——这些键名是金蝶的,系统根本不关心;它只关心 head 与 payload 是合法 JSON、体行合计与 sourceRefs 引用关系闭合。这一约束让「换目标系统 = 换脚本 + 换规则取值」真正落地——目标系统接进来时,没有任何一处代码需要被改,也是轻易云把「对账 + 集成」做成产品一体化的关键抓手。

来看集成中心整体的位置——集成转换只是三道独立车道之一:

轻易云集成中心架构图:3 道横向车道 PULL 拉取 × PUSH 推送 × 集成转换,并列并行、互不耦合

这张图把集成中心的边界画得极清——PULL 负责从外部系统拉数据(暂估应收单查询),PUSH 负责向外部系统写数据(暂估下推财务应收单),集成转换负责在两者之间架一道翻译器。集成转换不是 PUSH 的一种——它是独立的一道「对账结果 → 目标系统形态」转换车道,与 PUSH/PULL 在数据流上没有先后顺序。

三、决策 3:columnSchema 跟随批次快照落库

columnSchema 是脚本「向系统声明」自己对渲染的诉求,结构很简单:

typescript
// apps/api/src/biz_reconciliation/transform/transform-sandbox-runner-host.ts:168-174
export interface TransformColumnSchemaItem {
  key: string;       // 在 head / payload 里的字段名
  label: string;     // 列显示名
  width?: number;    // 列宽(前端约定)
  align?: string;    // "right" / "center" / "left"
  format?: string;   // "amount" 等格式约定
}

来看一份真实的 manifest 节选(来自默认金蝶云星空收入脚本 DEFAULT_KINGDEE_INCOME_TRANSFORM_SCRIPT_V140):

javascript
columnSchema: [
  { key: "docType", label: "单据类型", width: 190 },
  { key: "docTypeName", label: "单据名称", width: 200 },
  { key: "orderNo", label: "业务订单号", width: 180 },
  { key: "orgCode", label: "组织编码", width: 110 },
  { key: "customerCode", label: "客户编码", width: 110 },
  { key: "billDate", label: "业务日期", width: 110 },
  { key: "summary", label: "摘要", width: 260 },
  { key: "currency", label: "币别", width: 80 },
  { key: "totalAmount", label: "金额合计", width: 140, align: "right", format: "amount" },
  { key: "skuCode", label: "SKU编码", width: 130 },
  { key: "skuName", label: "SKU名称", width: 180 },
  { key: "quantity", label: "数量", width: 90, align: "right" },
  { key: "unitPrice", label: "单价", width: 110, align: "right", format: "amount" },
  { key: "expenseItemCode", label: "费用项目编码", width: 120 },
  { key: "taxPrice", label: "含税单价", width: 110, align: "right", format: "amount" },
  { key: "taxRate", label: "税率", width: 80 },
  { key: "remark", label: "体行备注", width: 220 },
],

为什么 columnSchema 要随批次快照?

因为快照语义是批次层的核心契约——批次生成即快照,此后脚本升级、字段重命名、目标系统字段映射调整都不影响已生成批次。columnSchema 是脚本的自我声明,自然也要进快照。落库形态很有意思:transform_batches.columnSchema 是 Json,但实际写入的是对象快照 { columns, editableFields }:

typescript
// apps/api/src/biz_reconciliation/transform/transform-generate.ts:118-122
snapshot: {
  columns: output.columnSchema ?? [],
  editableFields: output.editableFields ?? [],
},

读取时还要容忍两种形态(数组 vs 对象)——历史批次是 Json[],新批次是 Json<{ columns, editableFields }>:

typescript
// transform-generate.ts:129-145
export function readBatchColumns(columnSchema: unknown): TransformColumnSchemaItem[] {
  if (Array.isArray(columnSchema)) return columnSchema as TransformColumnSchemaItem[];
  if (columnSchema && typeof columnSchema === "object") {
    const snapshot = columnSchema as Partial<TransformBatchSnapshot>;
    return Array.isArray(snapshot.columns) ? snapshot.columns : [];
  }
  return [];
}

这条兼容性逻辑看起来不优雅,但它是有意的回退兼容——业务规范迭代时不能因为结构升级让历史批次打开就崩。生产环境里 232 张转换单据来自不同批次的快照,两种形态都能正确读出。

columnSchema 的工程价值

让前端不用懂目标系统——脚本告诉前端「这一批单据要展示这些列」,前端按声明渲染,不用为每个目标系统写一套列定义。改列名、加列宽、调整对齐方式,改脚本就够了,不用改前端。如果未来接用友,加一个 entryKey: "财务费用编码" 的列出来,脚本里加一行,前端无须感知。

来看一份生产环境的实物对照:

轻易云智能对账系统的集成转换规则列表:164 条规则指向 kingdee-cloud-galaxy,按来源类型分组,覆盖清仓盘点头程海运、FBA 赔偿、补差价等业务场景

这张表是生产环境里跑的「集成转换规则」——164 条规则全部指向同一个目标系统(金蝶云星空),按来源类型分两组:费用按核算项目 + 收入按差异标签。每一行对应一条转换规则 = (sourceType, accountingItemId, amountKind, direction) 四元键命中后落到哪个 docType + entryKey + sign。

这种「规则表是脚本的输入之一」的设计带来一个反直觉的收益:场景扩展 = 加规则,零代码。新来一个核算项目需要出新的费用类型单据?只需在规则表加一行,脚本不用动;新来一种差异标签需要出单?加规则,脚本已有的「按差异标签出单」分支自然兜住。这种「配置驱动扩展」是与抽象基类 + 工厂模式截然相反的工程哲学——前者让业务人员能自己动手,后者把每次扩展都推回研发排期。

四、决策 4:editableFields + 金额禁改的三层防御

editableFields 是脚本声明「head/payload 哪些字段允许编辑」的白名单:

typescript
// apps/api/src/biz_reconciliation/transform/transform-sandbox-runner-host.ts:182-183
/** S2:head/payload 可编辑白名单(金额全禁改) */
editableFields?: string[];

来看真实的金蝶云星空默认白名单:

javascript
// apps/api/src/biz_reconciliation/transform/default-transform-scripts.ts:491-508
// head/payload 可编辑白名单(金额语义键 amount/totalAmount/quantity/taxPrice/unitPrice 禁改,Q3e;
// orderNo 是追溯键(= businessOrderNo),不入白名单——允许编辑会削弱追溯可靠性,仅 columnSchema 展示)
editableFields: [
  "orgCode",
  "departmentCode",
  "customerCode",
  "billDate",
  "summary",
  "currency",
  "docTypeName",
  "entryKey",
  "entryName",
  "skuCode",
  "skuName",
  "expenseItemCode",
  "taxRate",
  "remark",
],

注意:amount / totalAmount / quantity / taxPrice / unitPrice / orderNo 全部不在白名单内。这条硬约束看起来反直觉——「金额不能改,那生成出来的批次还有什么可改的?」——但背后是 3 层防御体系的严密设计:

第 1 层 · Patch Zod schema(无 amount 字段)

typescript
// packages/validation/src/integration/transform.ts:355-358
/** 单据编辑白名单(仅 DRAFT;金额禁改 Q3e,head 内可编辑字段由批次快照 editableFields 二次约束) */
export const transformDocumentPatchSchema = z.object({
  docTypeName: z.string().max(100).optional(),
  head: z.record(z.any()).optional(),
});

// ===== 体行(transform_entries):金额禁改(Q3e);仅所属单据 DRAFT 可改 =====
export const transformEntryPatchSchema = z.object({
  entryKey: z.string().max(100).optional(),
  entryName: z.string().max(200).optional(),
  payload: z.record(z.any()).optional(),
  isAbnormal: z.boolean().optional(),
  abnormalReason: z.string().max(500).optional().nullable(),
  sortOrder: z.coerce.number().int().optional(),
  // amount / totalAmount 字段不存在于 schema —— 业务层不可传
});

Zod schema 里根本没有 amount 字段——就算前端有 bug 误传 { amount: "999.99" },Zod 解析时会直接 strip 掉,业务层根本拿不到。

第 2 层 · 服务端白名单二次校验

typescript
// apps/api/src/biz_reconciliation/transform/transform-documents.service.ts:298-304
/** head 白名单二次约束:可编辑键必须 ⊆ 批次快照 editableFields,越权键 400 */
private assertHeadKeysAllowed(head: Record<string, unknown>, columnSchema: unknown): void {
  const unauthorized = findUnauthorizedEditKeys(head, readBatchEditableFields(columnSchema));
  if (unauthorized.length > 0) {
    throw new BadRequestException(`以下字段不在可编辑白名单内:${unauthorized.join("、")}`);
  }
}

就算有人绕过了 Zod 直传 data.head,这个 guard 仍然会拦——它读 transform_batches.columnSchema.editableFields 拿到批次生成时的白名单,与 data.head 求差集,越权键直接 400 返回。白名单跟随批次快照——意味着历史批次的可编辑边界是「冻结」的,脚本升级改了白名单,老批次仍然按原白名单生效。

第 3 层 · 核心比对函数

typescript
// apps/api/src/biz_reconciliation/transform/transform-generate.ts:151-157
export function findUnauthorizedEditKeys(
  patch: Record<string, unknown>,
  editableFields: string[],
): string[] {
  const allowed = new Set(editableFields);
  return Object.keys(patch).filter((key) => !allowed.has(key));
}

这一层是单测覆盖最密集的地方——E2E 测试脚本 scripts/e2e/tests/transform/batch-lifecycle.mjs:171 里有专门的断言:

javascript
ok(
  !snapshot.editableFields.includes("totalAmount") && !snapshot.editableFields.includes("amount"),
  "editableFields 不含金额语义键(Q3e)",
);

三层防御落地后,用户能在批次上做的事被严格圈定——能改业务映射(备注、税率、摘要、费用项目编码)、能改 entryKey/entryName 修正展示、不能动金额。这种「白名单模式」比「黑名单模式」(硬编码禁改字段)安全得多——金蝶要新加可编辑字段时,改脚本 + 重新生成批次即可,前端零改动。

为什么金额不能改?

因为金额是追溯链的起点——它聚合自 BillRow 的解析结果,经过 IncomePlanItem / ExpenseAggregation 的对账聚合,再经过 transform_entries 的体行合并。改一处金额,整条追溯链就断了——前端再点「来源追溯」找不到原始 BillRow,财务复核对账结果时看不到原始账单行。

正确的金额调整路径是「改账单 → 重新对账 → 重新生成」——这三步走完,新批次的金额自然正确;不要在转换批次里直接改金额,因为改了也对不上来源。

来看完整的转换执行 5 阶段流程:

轻易云集成转换完整流程图:5 阶段——对账结果(已 CONFIRMED)→ 生成批次(TFB-YYYYMMDD-NNNN,transform_documents 头表)→ 沙箱执行(INCOME/EXPENSE scriptKind 分离)→ 单据编辑与确认 → 集成中心推送(按 docType 自动路由推送方案)

图里 5 个关键节点拎出来讲:

节点 2「生成批次」:POST /transform-batches 校验后建 JobTask(type=TRANSFORM_GENERATE,幂等键 = hash(scope+scriptId)),同 scope 进行中拒绝,入队 transform-generate(BullMQ),返回 202 { jobTaskId }。前端轮询 GET /job-tasks/:id 拿终态,刷新批次列表。试跑端点 POST /transform-scripts/test 保持同步 30s(小样本)不变——试跑与生产是两条路径,不混用。

节点 3「沙箱执行」:第 4 类沙箱的执行入口是 executeTransformInSandbox(scriptCode, input, { timeoutMs }),与 parse / reconcile 沙箱同款 fork + IPC + SIGKILL 兜底,但契约由「逐行 batch」改为「整批单次执行」——4 类沙箱里唯一的差异。沙箱内 globals 注入 Decimal、query(只读数据查询出口)、受控 console shim,禁用 require / process / fetch / Buffer——禁网络不变。

节点 4「单据编辑与确认」:可编辑边界 = 骨架展示字段恒可编辑(docTypeName、entryKey/entryName)+ head/payload 受 editableFields 白名单约束;金额禁改;DRAFT 可编辑、锁定状态禁编辑。状态机:DRAFT → CONFIRMED → PUSHING → PUSHED / FAILED;FAILED → reset → DRAFT;CONFIRMED → DRAFT 反确认。

节点 5「集成中心推送」:按 docType 自动路由推送方案——以 integration_handlers.configJson.docTypes 声明为唯一事实源:精确优先、声明为单据分类前缀兜底;0 命中 404 / 多命中 409。这条路由约定让脚本分类与推送方案解耦——脚本只管「这张单据属于哪种分类」,不管「哪种分类推给哪个 ERP / 哪个 handler」。

五、集成转换骨架的全景:232 张单据 + 9 个 handler

把这套骨架放在集成中心的全景里看——从「转换生成」到「推送落地」的端到端链路:

轻易云智能对账系统转换单据全局:232 张 9,345,659.28 元,单据分类涵盖财务应收单 / 费用应付单 / 销售收款单,状态机分布预览中 64 / 已确认 10 / 推送中 0 / 已推送 154 / 推送失败 4 / 含异常体行 7

这张全景表里有几个值得拎出来讲的数字:

  • 232 张单据、9,345,659.28 元——单店铺 × 单账期 × 多平台合并的产出形态,骨架表 1 张就够了
  • 状态分布 64 + 10 + 0 + 154 + 4 + 7——5 个状态(DRAFT 64 / CONFIRMED 10 / PUSHING 0 / PUSHED 154 / FAILED 4)+ 7 张含异常体行
  • 外部单号已回填(如 AP202607-0083Q、SKD260911008)——推送成功后回写到 transform_documents.externalBillNo,让财务复盘时能反查到目标系统单号

把转换骨架与集成中心的 9 个 handler 拼起来看完整链路:金蝶云星空覆盖了 5 大场景——health_check / cn_bank_transfer / ar_receivable_bill / ar_receivable / ar_fin_receivable,再加上 ap-other-payable 等其他 handler 共 9 个;这些 handler 全部以 transform_documents 为输入,不直接读对账结果。骨架表 = 转换与推送的解耦边界。

「换目标系统 = 换脚本 + 换规则 + 换 handler」三件事互不耦合——接用友时只需再加 handler + 加脚本 + 加规则,数据形态层(transform_documents / transform_entries)一行不动。这就是「目标系统只是配置与脚本的取值,不是代码结构」在生产环境里真正的样子。

六、给 CTO 与集成工程师的 5 条落地建议

如果你正在评估或自研类似系统,按这 5 条做能避免多数踩坑:

  1. 先把目标系统差异收敛进 JSONB,再考虑表结构。transform_documents.head 是 JSONB 不是字段缺失——它是有意为之,让每个目标系统的单据头结构各进各的 JSON,不挤占公共骨架列。BillRow 按平台拆表是因为列结构由外部账单决定、不可统一;转换输出是我们自己生成的中间产物,骨架完全可自定义。

  2. columnSchema / editableFields 跟随批次快照落库,不要按脚本实时计算。批次生成即快照——脚本升级、字段重命名、目标系统映射调整都不影响已生成批次。这条约束对前端、对运维、对用户都重要:前端按快照展示、不用管脚本今天是不是升级了;运维不用在每次脚本升级后强制用户「重生成批次」;用户不用担心「我上个月生成的批次今天打开怎么不一样了」。

  3. 金额禁改 + 编辑不留痕是一对约束。金额是追溯链起点,改了整条链断了;其他业务字段改了不需要审计——加审计字段只会把日常编辑流程复杂化。这两条约束的共同前提是「金额调整走改账单/重对账/重新生成」正路,不要图省事在批次里改金额。

  4. columnSchema 的对齐与格式用约定,不要堆属性。align 取 "right" / "center" / "left" 三选一、format 取 "amount" 等格式约定字符串——不要让前端去解析每个目标系统的数字格式(千分位、小数位、币别前缀)。脚本侧统一,导出侧统一,前端拿到 key/value 就能渲染。

  5. 目标系统是配置与脚本的取值,不是代码结构。这是整套设计的元命题。任何时候你想「为目标系统加一张表」「为目标系统加一个枚举值」「为目标系统加一段 if-else」,都该停下来问:这件事是不是该放进脚本与规则表里?

把目标系统从代码结构里剥出来,是 4 类沙箱脚本里转换脚本独有的设计动作——parse / reconcile / allocate 三类的输入输出都有相对固定的形态(业务订单号、核算项目、对账结果),不需要为「换目标系统」做特殊设计;唯独转换脚本的输出端接的是千差万别的外部系统,「剥出来」就成了一道必做的工程动作。这道动作的核心载体,就是 transform_documents × payload JSONB × columnSchema × editableFields 这一组通用骨架——也是轻易云智能对账系统在对账 + 集成一体化上最值得借鉴的产品路径之一。


更多集成转换的开发细节——scriptKind INCOME/EXPENSE 单侧喂数、transform_rules 四元键匹配(脚本层)与五元唯一约束(含 targetSystem)的差异、差额单带符号口径与配平不变式、MANUAL_CONFIRM 分流、matchedSupplyOrderIds 命中子集消费、新平台接入的 SOP——可参考项目内已沉淀的脚本开发 skill。

本文为原创内容,转载请注明出处:https://www.qeasy.cloud/insights/reconciliation/6-1-4-integration-transform-skeleton-payload-columnschema

评论