业务架构
价值链 → 销售 / 采购 / 仓储成本 / 财务 → 商城 → 横切机制
B1业务价值链:标准商贸进销存 + 业财一体 + B2B 订货商城
B2销售链 Order-to-Cash
flowchart TB Q["报价 2220
quotation
与订单解耦 · 可选"] -. "前端预填(ADR-0001)
后端无源单关联" .-> O["销售订单 2221
order
draft → approved"] O -- "审核:只占用
不出库 · 不写应收(ADR-0011)" --> O2["order approved"] subgraph ENTRY["出库三入口"] E1["ERP 手动转单
POST /salesOutBills/fromOrder"] E2["商城 Checkout
支付成功后建单送审"] E3["WMS 验货完成
adapter 注入"] end O2 --> E1 E1 --> S["销售出库 2222
salesout"] E2 --> S E3 --> S S -- "终审同事务" --> X1["inventory.ApplyMovement
扣减 + 台账"] S --> X2["costing.Outbound 结转成本"] S --> X3["finance.CreateReceivableTx 应收
条件:有客户 · 金额>0 · settlement_basis≠invoice"] S --> X4["order.ApplyFulfillment
回写 order_items.fulfilled_qty"] S --> AS["payment.AutoSettleForBillTx
按已有 2323 自动核销"] PAY["收款 2323 payment
创建即 ReceivePaymentTx 减应收"] -- "手动选单" --> SET["核销 settlement_details"] AS --> SET S -.可选.-> INV["发票 invoice"] RQ["退货申请 3308
returnorder
终审仅标记通过"] -. "快照引用 · 不联动" .-> RB["销售退货 2224
salesreturn
可独立手建"] S -. "快照引用" .-> RB RB -- "终审同事务" --> Y["加库存 + 入库成本
+ ReverseReceivableTx 冲应收"] classDef deepc fill:#0f172a,color:#fff,stroke:#0f172a; class S deepc
- 在销售链内,2222 终审是扣库存与产生应收的触发点;订单审核不触发。其它单据族(采购入库、销售退货、库内单据等)各有自己的库存写入,统一经 inventory.ApplyMovement。订单审核默认不占库存(StockBalIncludeOrder=0),=1 时订单预留量计入占用。
- 取价管线(pricing):促销价 → 客户专属价 → 客户指定策略价 → 默认策略价 → 最新有效报价价 → 历史成交价(排除促销来源)→ 零售价。「客户等级价」是旧系统称呼(PRD 明令不得使用),阶梯价与品牌 / 分类价目前未实现(pricing PRD R3 / R7)。
- 价格守卫:低于最低售价时 block 阻断,或 warn 并需特批权限 sale.order.underprice_override(UnderPriceAction 默认 warn)。旧系统的「低于参考成本价(负毛利)」守卫在新系统未实现,如需保留须先在 PRD 立项。
- 信用:占用额 = 应收余额 + 未出库订单额 + 未审核出库额;超限按 FinanceCreditControl block / warn(warn 需 override),客户信用冻结已实现。客户 / 业务员的「最大欠款单数、最大欠款天数」(max_debt_days,按最老未结出库单)已在 order/credit_check.go 落地;负毛利(进价×(1+毛利率%) 最底价)守卫未实现,XsProfitrate 仅登记未接线。
- 红冲:仅 approved && !is_red 可建红字单,方向取反,建单 + 自动审核同事务(salesout v0.11)。
B3采购链 Procure-to-Pay
flowchart TB PR["采购申请 / 报价 1207
purchase_requests
两者合一(ADR-0045)"] -- "下推" --> PO["采购订单 1211
purchase_orders
draft → approved → completed"] PO -- "选单生成入库(手工)" --> IN["采购入库 1212
purchasein"] IN -. "终审 / 反审回写 received_qty
syncPurchaseOrderCompletion
自动置 / 回退 completed" .-> PO RET -. "1214 终审 / 反审同样重判完成态" .-> PO IN -- "终审同事务" --> A1["inventory.ApplyMovement
加库存 + 台账"] IN --> A2["costing.Inbound
FIFO 租户建层 · 月加权由关账统一处理
其它策略保留手录成本"] IN -- "仅赊账 credit 且 settlement_basis≠invoice" --> AP["应付台账
finance.CreatePayableTx"] IN --> A4["回写订单 received_qty
仅带 ref_order_id 的行"] AP --> PAY["付款 1313
payment"] PAY --> SET["应付核销"] FR["采购运费 1217
freight"] -- "按金额 / 数量 / 重量 / 手工分摊
AddUnitCost 回写 1212 行成本" --> IN SE["供应商费用 1315
internal/finance"] -- "审核写应付
不进入库成本" --> AP IN -. "可选快照引用原入库行" .-> RET["采购退货 1214
purchasereturnout
可独立手建 · 扣库存 + ReversePayableTx"] DS["直运单 1209
direct_bills
独立建单:无采购订单引用列"] -- "终审同事务:同仓 1212 + 2222
瞬时过账,净额为零" --> IN DS --> D2["2222 销售出库 → 采购 / 销售发票 1318 / 2328
→ billchain → 回写"] classDef deepc fill:#0f172a,color:#fff,stroke:#0f172a; class IN deepc
- 采购订单没有手工「完成」动作:UpdateStatus("completed") 会被拒绝(purchase/service.go:892);completed 由 1212 终审 / 反审以及 1214 终审 / 反审回写 received_qty 后按净入库量自动判定(purchase PRD v0.10.47 / v0.10.48,方案 A);未收齐用手工关闭(is_closed)。
- 应付与成本都有条件:入库应付仅「赊账且 settlement_basis=stock_bill」时同事务写;cash / invoice 跳过。入库成本只有 FIFO 租户当场建层,月加权在月末关账统一处理。
- 退货 1214:单层(ADR-0045);cash / invoice 跳过冲应付;仅引用了原入库行、且入库行带订单引用的行才回冲 received_qty。
- 直运 1209:与采购订单无数据关联,可独立创建;终审在同一事务内按序生成 1212 → 2222 → 发票 → billchain → 回写。货物不形成本地期末库存,但系统内会产生同仓 1212 + 2222 的零净额台账(BUSINESS_DOMAIN:154「不产生本地出入库流水」已过时)。
- 代码与预期口径偏离:直运 2222 的行成本先预填采购价,但 salesout.applyCosting 随后按租户成本策略重算并覆盖,不保证等于直运采购价(salesout/costing.go:19-32、costing/outbound_costing.go:84-98)。若业务要求「采购金额直接结转销售成本」,需改代码。
B4仓储、库存与成本
flowchart TB
subgraph DOCS["出入库单据模块:只陈述业务事实"]
D1["销售侧
salesout 2222 · salesreturn 2224"]
D3["采购侧
purchasein 1212 · purchasereturnout 1214"]
D5["库内单据
warehouse 3315/3325 · 盘盈 3311 · 盘亏 3321
transfer 3331 · damage 3322 · assembly 3317/3327"]
end
DV["directvendor
三方供应商虚拟仓库存 · 无单据 · ledger=3318"]
DOCS -- "costing.Inbound / Outbound
(directvendor 不入 costing)" --> CO
DOCS -- "inventory.ApplyMovement" --> INV
DV -- "ApplyMovement" --> INV
D1 & D3 -- "反审 ReverseByLedgerNet
仅 salesout · salesreturn · purchasereturnout" --> INV
subgraph CO["costing(deep)"]
direction LR
M1["月加权平均(默认)"]
M2["先进先出 FIFO"]
M3["最后进价"]
M4["最后进价 + 运费"]
M5["指定成本价"]
M6["不处理"]
end
subgraph INV["inventory(deep · 唯一写入方 ADR-0056)"]
direction LR
I1["策略解析:deny / warn / allow"]
I2["原子条件 UPDATE(持行级锁)"]
I3["余额 upsert"]
I4["台账追加(带符号金额 = 余额变动)"]
end
CO -- "FIFO 层归 costing" --> FL[("fifo_in_layers
fifo_out_layers")]
INV --> IB[("inventory")]
INV --> IL[("inventory_ledger")]
ST["stocktake 3300
Confirm 编排生成 3311 / 3321
经 warehouse Create → Submit → Approve
各步独立事务,失败可留中间态"] --> D5
WMS["wms 拣货 / 验货 / 上下架
不直接写库存"] -. "adapter → salesout" .-> D1
SRC["各出入库专表
(19 张 UNION,期间聚合源)"] --> CL
IL -. "库存金额 ↔ 台账对账" .-> CL
CL["closing(编排)→ costing.ClosePeriod → 期间 → gl
预检:未审核 / 未过账凭证 + 金额-台账对账
月加权重算 · FIFO 对账 · 其它策略只写快照
当期 end_* 落 cost_periods,下期读取为 begin_*"] --> CP[("cost_periods")]
classDef deepc fill:#0f172a,color:#fff,stroke:#0f172a;
class M1,M2,I2,I4 deepc
月加权平均(默认,空值同)· 先进先出 · 最后进价 · 最后进价+运费 · 指定成本价 · 不处理。未知值:关账入口拒绝(fail-closed),日常出库暂按月加权兜底并告警。调用方不再选策略(SSOT §4.1)。真实签名是 costing.Inbound(ctx, db, tx, billID, lines),DESIGN_PATTERNS 里的 Inbound(ctx, tx, bill) 是目标态示意。
仓库级三态回退到租户级 stock_policy.allow_negative:deny 返回类型化错误 · warn 条件扣减后记日志 · allow 不检查。迁移期兼容例外(movement.go:31-50):damage 仅租户级、purchasereturnout strict warn(缺行 fail-loud、不写日志)、purchasein 不检查;收敛另立 PRD 规则。
期间数量 / 金额台账 → cost_periods;实时快照 → inventory;逐笔事件 → inventory_ledger;日结高频缓存 → 无直接对应。
- 批次 / 近效期:提供批次维度与近效期分析查询;无 FEFO 强制取批(inventory PRD R11 明确不做;旧系统也只有批次必填与按批号 / 生产日期排序);BUSINESS_DOMAIN 写的「近效期优先推荐」尚未实现。
- 序列号 serial:1212 入库登记、2222 出库消费、2224 退货回库、3331 调拨已接线;warehouse / damage / assembly 暂不流转序列号(serial PRD Q8)。
- 成本关账(costing.ClosePeriod)与总账结账(gl.ClosePeriod)是两条独立链路,由 closing 编排并靠期间指针对齐(GL-33)。
B5财务闭环:往来 → 核销 → 出纳 → 总账
flowchart LR
subgraph DOC["单据终审"]
OUT["销售出库 2222"]
SR["销售退货 2224"]
IN["采购入库 1212"]
PRT["采购退货 1214"]
end
subgraph INVO["发票 / 费用"]
INV28["销项发票 2328"]
INV18["进项发票 1318"]
SE["供应商费用 1315"]
end
OUT -- "CreateReceivableTx
有客户 · 金额>0 · basis≠invoice" --> AR["应收台账
finance"]
SR -- "ReverseReceivableTx" --> AR
INV28 -- "仅 basis=invoice" --> AR
IN -- "CreatePayableTx
赊账 credit 且 basis≠invoice" --> AP["应付台账
finance"]
PRT -- "ReversePayableTx(credit)" --> AP
INV18 -- "仅 basis=invoice" --> AP
SE -- "审核写应付" --> AP
RECV["收款 2323
创建即生效"] -- "ReceivePaymentTx 减应收" --> AR
PAYB["付款 1313
创建即生效"] -- "MakePaymentTx 减应付" --> AP
RECV & PAYB -- "AutoSettle / 手动选单" --> SD["核销记录
settlement_details"]
RECV -- "writeCashierFlows
2323→6001 · 退款冲回→6002" --> CASH["出纳日记账
cashier"]
PAYB -- "1313→6002 · 冲回→6001" --> CASH
AR -. "同一单位既是客户又是供应商" .- OFF["往来对冲 1323
arapoffset"]
AP -.- OFF
图一:往来、核销与出纳的子账层。三种「核销」分属 payment / finance / arapoffset 三个模块(见下方卡片)。
flowchart LR SRC["push 来源:采购入库 1212 · 收款 2323 · 付款 1313
往来对冲 1323 · 出纳 · 发票 1318 / 2328 · 供应商费用 1315"] -- "commit 后 push:TryAutoGenerate
失败仅记日志 · 出纳需 AtCNGenVoucher=1" --> V["机制凭证
gl.is_machine = true"] OUT["销售出库 2222"] -. "无 push · 走 pull
GenerateFromBills" .-> V V -- "PostVouchers" --> BAL["科目余额
gl_balances"] BAL -. "独立操作(关账前)" .-> PNL["CarryForwardProfitLoss
结转损益"] BAL -- "gl.ClosePeriod:余额结转下期
+ 总账期间指针推进" --> CLS["总账期间关闭"] classDef deepc fill:#0f172a,color:#fff,stroke:#0f172a; class V,BAL deepc
payment:收付款单 ↔ 业务单据(settlement_details);finance:台账本身(余额 / 坏账 / 佣金 / 供应商费用);arapoffset:应收 ↔ 应付互抵。账龄分析在 report(五桶 0-30 / 31-60 / 61-90 / 91-180 / 180+,参数 FinanceAgingBuckets 默认 30,60,90,180),不在 finance。
gl 不 import 任何 internal 包(11 个业务包依赖它);push 调用方为 payment / invoice / finance / arapoffset / cashier / freight / purchasein / asset / prepaid(cmd/server/main.go:633-652),2222 没有 push,走 pull。机制凭证限制手改,删除先删关联再软删。
纯 CNY;金额路径禁 float64,经 pkg/money;草稿服务端重算,审核后金额冻结。历史 float64 为已登记债务。出纳 6003 是现金调拨,与收付款无关(出纳域 6001–6007)。
B6商城与多端:B2B 订货
flowchart TB C["客户
PC 商城 / 小程序 / 移动 App"] --> CART["购物车 · 目录
mall"] CART --> PRC["pricing 取价管线"] PRC --> PRM["promotion 促销引擎
单品特价 · 搭赠 · 整单满减 / 满赠 · 组合套餐
时段折扣 / 特价 · 抢购 · 混搭满量送减
优惠券为独立子系统"] PRM --> CHK["Checkout 编排
mall:只编排,不发明规则"] CHK -- "创建订单" --> ORD["order 2221"] CHK --> PI["payintegration
微信支付 / 支付宝 / 银联(ChannelProvider 可插拔)
cash / member_card 为线下渠道 · 租户开关门控"] PI -- "支付成功回调
幂等:status=paid 即返回" --> OUT["salesout:建单 + 送审
禁止未付先出库"] OUT --> APP["终审事务内:CreatePaymentTx 建 2323
无默认现金账户则失败
+ 应收 + AutoSettle"] CRON["每 5 分钟
取消超时未支付订单"] -.-> ORD CO["cloudorder
商城配置展示 · 不做订单交易"] -.配置.-> CART classDef deepc fill:#0f172a,color:#fff,stroke:#0f172a; class CHK deepc
- 商城与 ERP 共用同一套 salesout / finance / payment,不是两套平行实现(TestE2E_MallCheckout_PayBeforeOutbound)。顺序是:支付成功 → salesout 建单 + 送审 → 终审事务内建 2323(mall/service.go:1184-1197、salesout/service.go:1604),而不是回调直接建 2323。
- 商城客户是「租户内的客户账号」(ADR-0046);商城 API 契约以 MALL_API_CONTRACT_SSOT.md 为准(ADR-0047)。
- cloudorder 只管商城配置与展示,不做订单交易(cloudorder PRD Non-Goal);items.ydh_* 经 item.UpdateItemMallFields、customers.ydh_* 经 master 写入,cloudorder 不直写 items / item_categories / units(ADR-0029);分类表没有 ydh_* 列。
B7横切业务机制:审批、权限、期间、编号、红冲
stateDiagram-v2
[*] --> unsubmitted
unsubmitted --> pending: Submit
rejected --> pending: 修改后 Submit(编辑保持 rejected)
pending --> approved: Approve level 1(业务闸门)
pending --> rejected: Reject
approved --> approved: Approve level 2/3(可选叠加签字)
approved --> approved: 反审 level 3/2(仅删签)
approved --> pending: 反审 level 1(业务回退)
level=1 即业务终审(库存 / 成本 / 信用等副作用)并置 approved;level=2/3 是 approved 之后可选叠加的签字,不改主表。权限码 bill.{domain}.{billType}.approve{level}。「去央批化」(2026-09-06)后无中心审批引擎;反审按 3→2→1 逐级删签,只有 level 1 反审才把主表退回 pending。approval_records 并非只追加:level 1 把 pending 行 UPDATE 为 approved,Reject 同样 UPDATE,反审 DELETE(approvalbook/service.go:193-295),与 SSOT 的 Append-Only 清单冲突,见观察页。
- 模块访问:RequireModuleAccess
- 按钮动作:查看 / 新增 / 修改 / 删除 / 作废 / 一二三审 / 反审 / 打印导出
- 行级数据范围:仓库、部门、业务员(白名单 / 黑名单模式)
- 列级脱敏:成本价、进价等敏感列服务端掩码
供应链期间指针(CurrentYear/Period)与总账期间指针(GLCurrentYear/Period)分别维护;关账后该期间单据禁止审核、数据冻结。
pkg/billno 统一取号(事务版 GenerateTx);billchain 记 LinkBills / GetChain;明细行 ref_*_item_id 行级匹配回写源单执行量,全执行完关闭源单。
completed / cancelled 为硬终态(ADR-0009);作废仅限 status=draft 且未过一审(warehouse / damage 等族允许被驳回的单据作废),已审核先反审;冲回库存 / 成本 / 流水 / 履行量只在反审里做;已关账期间只能走红字。