本文整理 DGJ2 中采购结算价、库存移动加权成本、销售出库成本快照、成本调整、盘点成本和利润报表之间的关系。重点不是背字段,而是理解每一种“价格”回答什么问题、在哪个时间点冻结、如何影响库存金额与利润。
阅读后应能回答:
purPrice、costPrice、price、cost_price分别是什么。- 同一商品为什么在不同仓库有不同成本。
- 采购入库如何重新计算移动加权成本。
- 负库存后再入库为何会采用特殊公式。
- 销售出库保存的是当前动态成本还是实时重算成本。
- 成本调整单为何可能改变当前成本,却不自动改历史销售单。
- 销售明细报表与月利润表为什么可能不一致。
- 成本为零、负毛利、销退、跨月时应怎样排查。
1. 业务目标
| 目标 | 说明 |
|---|---|
| 库存计价 | 为每个服务站、仓库、商品保存当前单位成本 |
| 入库重算 | 采购入库按移动加权公式更新当前成本 |
| 出库冻结 | 销售出库把出库时成本写入单据明细 |
| 历史追溯 | 按日期读取当时有效的动态成本 |
| 人工纠偏 | 通过成本调整单修正异常当前成本 |
| 经营分析 | 计算销售成本、毛利、毛利率和月利润 |
| 盘点估值 | 盘盈盘亏按成本计算库存金额差异 |
2. 先区分七类价格
| 名称 | 常见字段 | 业务问题 | 是否随库存移动变化 |
|---|---|---|---|
| 采购结算价 | purPrice、purprice | 向供应方按什么价格采购/结算 | 由采购与价格体系变化 |
| 采购入库单价 | price | 本次实际入库的单位价格 | 每张入库单不同 |
| 动态成本 | t_bs_cost_price_day_*.price | 当前仓库这件商品平均价值多少 | 会随入库/调整变化 |
| 销售出库成本快照 | sa_invoice_info.costPrice | 这次出库按什么单位成本计价 | 写单后原则上冻结 |
| 库存流水成本/金额 | inventory_info.costPrice/amount | 本次库存变化的价值是多少 | 每条流水固定 |
| 销售价 | price、disAmount | 客户支付/应收多少 | 与成本独立 |
| 指导价/零售价 | lowPrice、salePrice 等 | 销售定价参考 | 不直接代表库存成本 |
flowchart TD
P["采购结算价"] --> I["采购入库单价"]
I --> W["移动加权动态成本"]
W --> S["销售出库成本快照"]
S --> C["销售成本"]
SP["销售应收金额"] --> G["毛利"]
C --> G
GP["指导价/零售价"] --> SP
采购结算价是动态成本的可能初始来源,但不等于当前动态成本;销售价也不参与移动加权成本计算。
3. 成本对象的粒度
动态成本不是只按 SKU 保存,而是按以下组合区分:
sid + locationId + invId + insertDate/version
| 维度 | 含义 | 为什么需要 |
|---|---|---|
sid | 服务站 | 各站采购和库存独立 |
locationId | 仓库 | 同一商品在不同仓成本可能不同 |
invId | 服务站商品 ID | 业务库存实体,不只是中心 SKU |
insertDate | 成本生效日期 | 支持历史日期查询 |
version | 跨日版本序号 | 区分连续日期版本 |
flowchart LR
SKU["中心 SKU"] --> I1["站点 A invId"]
SKU --> I2["站点 B invId"]
I1 --> W1["仓库 1 成本"]
I1 --> W2["仓库 2 成本"]
I2 --> W3["仓库 3 成本"]
排查成本时仅用 skuId 查询是不够的,至少要明确 sid + invId + locationId + 业务日期。
4. 代码地图
4.1 成本读写
| 文件 | 责任 |
|---|---|
application/models/bs/CostPriceModel.php | 动态成本分表选择、写版本、按日期取成本 |
application/service/bs/InventoryService.php | 移动加权公式、成本调整写入、期初成本 |
application/controllers/scm/Cost.php | 成本调整页面与接口 |
application/service/scm/CostService.php | 成本调整单主明细与生效 |
application/controllers/tasks/CostPrice.php | 初始化/修复零成本 |
application/controllers/tasks/UpdateGoodsPdCostPrice.php | 历史盘点成本回填 |
4.2 业务取成本
| 业务 | 文件 |
|---|---|
| 新销售出库 | application/Services/SaOrders/SaOrderSer.php |
| 老销售出库 | application/service/scm/InvSaService.php |
| 大客户销售 | application/service/scm/InvCuService.php |
| 调拨 | application/service/scm/InvTfService.php |
| 盘点 | application/service/scm/InvOiService.php、application/Services/Gpda/TakeStockSer.php |
| 库存金额 | application/Services/Report/InventoryReportSer.php、ReportModel.php |
4.3 利润报表
| 文件 | 责任 |
|---|---|
application/controllers/reports/SaleReport.php | 销售明细毛利展示与导出 |
application/Services/Report/SaleReportSer.php | 销售报表汇总 |
application/controllers/reports/ProfitReport.php | 月利润表接口与导出 |
application/Services/Report/ProfitReportSer.php | 月度主营/其他收入支出合并 |
application/Providers/DataService/ReportProvider.php | 调数据服务获取利润口径 |
application/models/report/ReportModel.php | 老报表本地 SQL 与成本回退 |
5. 核心表
5.1 动态成本
| 常量/表 | 用途 |
|---|---|
BS_COST_PRICE / t_bs_cost_price | 历史主表常量,现代码大量逻辑已注释不用 |
BS_COST_PRICE_DAY / t_bs_cost_price_day_ | 动态成本分表前缀 |
BS_COST_PRICE_DAY_NUM=16 | 当前分表数量 |
t_bs_cost_price_day_{sid % 16} | 某服务站真实成本表 |
5.2 业务事实
| 表 | 成本意义 |
|---|---|
t_scm_pu_invoice_info | 采购入库数量和本次入库价格 |
t_scm_sa_invoice_info | 销售/销退成本快照 |
t_scm_big_order_info | 大客户销售/销退成本和结算信息 |
t_scm_inventory_info | 库存变化数量、成本和金额事实 |
t_scm_inventory_real_time | 当前库存数量,参与加权计算 |
th_scm_inventory | 往月库存流水归档 |
5.3 人工调整与盘点
| 表 | 用途 |
|---|---|
t_scm_update_cost | 成本调整主单 |
t_scm_update_cost_info | 调整前后成本明细 |
t_scm_inventory_check | 盘点主单 |
t_scm_inventory_check_info{sid末位} | 盘点明细和分单位成本 |
erDiagram
COST_PRICE_DAY {
int sid
int invId
int locationId
decimal price
date insertDate
int version
}
PU_INVOICE_INFO }o--|| COST_PRICE_DAY : recalculates
SA_INVOICE_INFO }o--|| COST_PRICE_DAY : snapshots
UPDATE_COST_INFO }o--|| COST_PRICE_DAY : overrides
INVENTORY_CHECK_INFO }o--|| COST_PRICE_DAY : values
INVENTORY_INFO }o--|| COST_PRICE_DAY : traces
6. 动态成本分表规则
table = t_bs_cost_price_day_ + (sid % 16)
示例:
| sid | 余数 | 真实表 |
|---|---|---|
1001 | 9 | t_bs_cost_price_day_9 |
9999 | 15 | t_bs_cost_price_day_15 |
1106 | 2 | t_bs_cost_price_day_2 |
flowchart TD
S["sid"] --> M["sid % 16"]
M --> T0["cost_price_day_0 ..."]
M --> T15["cost_price_day_15"]
tables.php 注释说明未来扩容必须按倍数扩到 32,并考虑历史最大 sid 的分布。不能直接把常量从 16 改为 32,否则同一历史 sid 会被路由到另一张表。
7. 动态成本版本模型
CostPriceModel::modify() 的规则:
- 新组合没有历史成本时,插入今天记录,
version=0。 - 已有组合今天已经有记录时,更新今天的记录。
- 最近记录不是今天时,新增今天记录,
version=上次版本+1。
stateDiagram-v2
[*] --> NoRecord
NoRecord --> TodayV0: 首次写入
TodayV0 --> TodayV0: 当日再次调整
TodayV0 --> NextDayV1: 次日变化
NextDayV1 --> NextDayV1: 当日再次变化
NextDayV1 --> LaterVersion: 后续日期变化
7.1 同日覆盖与跨日留痕
| 场景 | 结果 | 历史可见性 |
|---|---|---|
| 同一天两次采购入库 | 更新同一天记录 | 无法从日表区分日内两次成本 |
| 第二天再次入库 | 新增版本 | 可按日期回看 |
| 同日人工调整 | 覆盖同日动态成本 | 当日采购后的中间成本不保留 |
因此动态成本日表是“日级最终状态”,不是每次成本变化的完整流水。完整过程要结合采购入库、成本调整和库存流水追溯。
8. 按日期读取成本
逻辑入口:CostPriceModel::getCostPrices(invIds, locationIds, date, sid)。
返回键:
locationId_invId => {invId, locationId, price}
flowchart TD
A["输入 sid/invIds/locationIds/date"] --> B["定位 sid % 16 分表"]
B --> C{"date 早于 COST_PRICE_INITIAL_DATE?"}
C -->|"是"| D["只查 version=0"]
C -->|"否"| E["查初始化日至目标日期"]
D --> F["按 id 倒序"]
E --> F
F --> G["按 locationId,invId 分组取最新"]
G --> H["返回 locationId_invId map"]
8.1 读取契约
| 参数 | 空值行为 |
|---|---|
date | 默认为今天 |
sid | 优先使用模型已 setSid 的值,否则取入参 |
invIds | 空时不限制商品,可能扩大查询 |
locationIds | 空时不限制仓库 |
调用前应明确设置 sid。CLI 模式不会自动从登录会话初始化 sid。
9. 移动加权成本公式
9.1 正常正库存入库
设:
Q_old = 入库前库存数量
C_old = 入库前动态单位成本
Q_in = 本次入库数量
P_in = 本次入库单位价格
新成本:
C_new = (Q_old * C_old + Q_in * P_in) / (Q_old + Q_in)
flowchart LR
A["旧库存金额 Q_old*C_old"] --> S["金额相加"]
B["本次入库金额 Q_in*P_in"] --> S
C["旧库存数量 Q_old"] --> Q["数量相加"]
D["入库数量 Q_in"] --> Q
S --> R["C_new = 总金额/总数量"]
Q --> R
9.2 示例
旧库存 10 件,单位成本 80 元
本次入库 5 件,单价 100 元
新成本 = (10*80 + 5*100) / 15
= 86.666666...
业务展示通常格式化两位小数,但底层保存精度和后续计算精度要以表结构确认。
10. 负库存入库规则
InventoryService::updateCost() 对入库前库存小于 0 的分支:
如果 Q_old < 0:
总金额 = 本次入库金额
总数量 = 本次入库数量
C_new = P_in
flowchart TD
A["读取实时库存 Q_old"] --> B{"Q_old < 0?"}
B -->|"否"| C["正常移动加权"]
B -->|"是"| D["忽略旧负库存金额"]
D --> E["新成本直接取本次入库价"]
原因是负库存没有可靠的在库金额基础。若继续将负数量乘旧成本参与加权,可能得到极端甚至负成本。
10.1 风险
- 先负库存销售、后高价入库,会让后续当前成本跳到高价。
- 历史负库存销售单的
costPrice已冻结,不会自动追溯改成本。 - 报表若对零成本历史单做当前成本回退,可能与出库时口径不同。
11. 修改采购入库的特殊规则
updateCost($data, true) 用于修改采购入库单时有额外分支:
若当前库存数量 < 被修改入库数量:
说明部分入库量可能已经出库
代码按差额重新计算 allAmount/allPrice
并支持 errQty 将错误数量加回,用于还原修改前真实库存基数。
flowchart TD
A["修改历史采购入库"] --> B["读取当前实时库存"]
B --> C["用 errQty 还原原场景"]
C --> D{"当前库存 < 原入库数量?"}
D -->|"是"| E["按已消耗后的差额重算"]
D -->|"否"| F["按普通移动加权重算"]
修改历史入库是高风险操作,因为当前库存已经包含之后所有出入库。仅改采购明细而不重新推导成本,会导致当前成本与历史业务不一致。
12. 成本初始化
12.1 新商品期初库存
商品期初库存保存时:
初始单位成本 = iniAmount / iniQty
然后:
- 写期初库存流水。
- 删除该商品旧动态成本记录(编辑期初场景)。
- 调
updateCostPrice()写入各仓库初始成本。
有非期初业务往来后,代码禁止再修改期初库存。
12.2 CLI 批量初始化
tasks/CostPrice::initBySidWithInvIds(sid, locationId):
- 从
/tmp/costSkuIds.txt读取 SKU。 - 每 500 条一批。
- 查 SKU 在指定服务站/仓库的商品关系。
- 跳过已有动态成本的组合。
- 优先取服务站结算价。
- 无服务站价时尝试按供应商取公共结算价。
- 写当天动态成本。
flowchart TD
A["/tmp/costSkuIds.txt"] --> B["按 500 分批"]
B --> C["找 sid 下 invId/locationId"]
C --> D{"已有成本?"}
D -->|"是"| E["跳过"]
D -->|"否"| F["服务站结算价"]
F --> G{"有价格?"}
G -->|"否"| H["供应商公共价"]
G -->|"是"| I["写成本"]
H --> I
该任务依赖本地 /tmp 文件,只适合受控一次性初始化,不应作为长期生产调度。
13. 零成本修复任务
tasks/CostPrice::fixPrice(sid, limit):
- 分页找最新记录
price=0的商品/仓库组合。 - 排除同商品已有其他正成本记录的情况。
- 通过商品与供应商关系重新取结算价。
- 当天记录直接更新;历史记录则新增今天版本。
flowchart TD
A["扫描零成本组合"] --> B["同 invId 是否已有正成本?"]
B -->|"是"| C["跳过当前修复路径"]
B -->|"否"| D["获取 sku/供应商/结算价"]
D --> E{"今天记录?"}
E -->|"是"| F["更新 price"]
E -->|"否"| G["新增今日版本"]
修复任务不是历史成本重算器,只是把当前零成本初始化为可用结算价。
14. 采购入库如何影响成本
sequenceDiagram
participant U as 仓库人员
participant PU as 采购入库服务
participant INV as 实时库存
participant CS as InventoryService::updateCost
participant CP as CostPriceModel
U->>PU: 确认采购入库
PU->>INV: 写入库流水/增加数量
PU->>CS: 入库数量、价格、仓库、商品
CS->>CP: 查询旧动态成本
CS->>INV: 查询当前仓库库存
CS->>CS: 正库存/负库存/修改单公式
CS->>CP: modify 今日版本
14.1 成本更新输入
| 字段 | 含义 |
|---|---|
sid | 服务站 |
invId | 服务站商品 |
skuId | 中心 SKU,用于追溯 |
locationId | 入库仓库 |
qty | 入库数量 |
price | 本次入库单位价 |
amount | 本次入库总金额 |
errQty | 修改入库时的还原数量 |
必须验证 amount 与 qty * price 的舍入口径一致。
15. 哪些库存动作会改变当前成本
| 动作 | 通常是否重算动态成本 | 说明 |
|---|---|---|
| 采购入库 | 是 | 移动加权核心入口 |
| 期初库存 | 是 | 以期初金额/数量初始化 |
| 成本调整单 | 是 | 直接覆盖目标成本 |
| 销售出库 | 否 | 读取并冻结成本,不重新加权 |
| 销售退货 | 需按实现确认 | 理想上回原成本,实际看服务 |
| 采购退货 | 需按实现确认 | 可能影响库存金额与当前成本 |
| 调拨 | 出入仓成本传递 | 两仓成本可能变化 |
| 盘盈/盘亏 | 以盘点成本估值 | 是否回写动态成本依实现 |
不要凭会计直觉推断代码行为,应从具体业务服务是否调用 updateCost() / modify() 验证。
16. 销售出库成本快照
新销售链 SaOrderSer::addInvoiceInfo():
- 收集出库明细的
invId和storage_id。 CostPriceModel::setSid(sid)。- 批量读取当前动态成本。
- 按
storage_id_invId取price。 - 写入销售出库明细
costPrice;取不到写0。
sequenceDiagram
participant S as 销售出库
participant CP as 动态成本
participant DB as sa_invoice_info
S->>CP: getCostPrices(invIds, storageIds)
CP-->>S: location_inv -> price
loop 每个出库明细
S->>S: costPrice = 当前动态成本或 0
S->>DB: 写成本快照
end
16.1 快照原则
历史销售成本 = 出库明细上保存的 costPrice * qty
后续采购入库或成本调整改变当前动态成本,原则上不应修改已出库单据的 costPrice。
17. 销退成本
销售退货需要同时处理两种价值:
| 价值 | 业务意义 |
|---|---|
| 退给客户的销售金额 | 取原销售价/退货价和折扣 |
| 退回库存的成本 | 应关联原出库成本或按实现取当前成本 |
新销售服务在构造销退发票明细时仍批量读取动态成本,同时建立 srcOrderEntryId/srcOrderNo/srcOrderId 与原单关联。排查销退毛利异常时要核对:
- 退货明细是否正确关联原销售行。
costPrice是原单快照还是退货当日动态成本。- 数量和金额是否使用负数。
- 报表是否对销退取绝对值再重组。
flowchart LR
O["原销售明细"] -->|"srcOrderEntryId"| R["销退明细"]
O --> P["原销售价/成本快照"]
C["退货日动态成本"] --> R
P --> Q{"实际采用哪个成本口径"}
R --> Q
该口径必须结合实际服务版本和样例单确认,不能只看字段名。
18. 大客户成本
大客户渠道订单建单时已经保存:
costPrice = 服务站成本/结算成本
costAmount = qty * costPrice
profit = salePrice - costPrice
profitAmount = qty * profit
在老 InvCuService 中还存在 prevPrice、含税结算价、无税价和税额计算。大客户“配送费/利润”不一定等于普通销售报表的毛利。
flowchart TD
A["大客户销售价"] --> P["价差/配送费"]
B["大客户结算/成本价"] --> P
B --> T["含税/无税/税额"]
P --> O["OPS/NC 大客户口径"]
A --> R["DGJ 销售事实"]
19. 调拨成本
调拨是从一个仓库减少、另一个仓库增加:
出库仓:按出库仓动态成本形成调拨成本
入库仓:将调拨成本作为入库价值参与目标仓成本
InvTfService 按 outLocationId_invId 查询成本并写 cost_price。需要保证:
- 出库仓成本不是零。
- 调拨在途和入库数量一致。
- 目标仓加权时使用同一批调拨成本。
- 撤销调拨能对称恢复两仓数量和价值。
flowchart LR
A["仓 A: Q/C"] -->|"调拨出库,携带 C"| T["在途"]
T -->|"调拨入库"| B["仓 B 重新移动加权"]
20. 盘点成本
盘点金额:
盘点差异数量 = checkQty - sysQty
盘点差异金额 = 盘点差异数量 * cost_price
盘点明细 cost_price 存在分单位差异。UpdateGoodsPdCostPrice 中把动态成本乘以 100 后写入盘点明细,表明该字段可能以“分”为单位。
flowchart TD
A["盘点系统数 sysQty"] --> D["diffQty"]
B["盘点实数 checkQty"] --> D
C["动态成本 price 元"] --> E["*100 写 cost_price 分"]
D --> F["盘盈/盘亏金额"]
E --> F
成本单位不确认时,最典型错误是金额放大或缩小 100 倍。
21. 成本调整单状态
从 CostService 当前实现可推断:
billStatus | 语义 | 行为 |
|---|---|---|
1 | 草稿 | 保存主明细,不更新动态成本;详情重新读取实时旧成本 |
0 | 确认/生效 | 保存单据并调用 updateCostPrice() |
2 | 列表查询的“全部”条件 | 不作为实际单据状态过滤 |
stateDiagram-v2
[*] --> Draft: billStatus=1
Draft --> Draft: 编辑保存
Draft --> Effective: billStatus=0
Effective --> [*]: 当前实现无正式反审核状态
22. 新增成本调整单接口
逻辑入口:scm/Cost::addCostDoc(),参数通过表单字段 postData 传 JSON 字符串。
POST /scm/cost/addCostDoc
Content-Type: application/x-www-form-urlencoded
postData={
"id": -1,
"billNo": "<成本调整单号>",
"date": "2026-07-16",
"billStatus": 1,
"description": "成本核对",
"entries": [
{
"invId": 123,
"skuId": "<SKU>",
"invNumber": "<商品编号>",
"invName": "<商品名称>",
"locationId": 10000,
"locationName": "主仓",
"oldPrice": "80.00",
"price": "85.00",
"description": "按财务确认调整"
}
]
}
22.1 生效时序
sequenceDiagram
participant U as 用户
participant C as CostService
participant DB as 调整单表
participant I as InventoryService
participant CP as CostPriceModel
U->>C: addCostDoc(postData)
C->>DB: 按 billNo 防重复
C->>DB: 开事务写/更新主单
alt billStatus=0
C->>I: updateCostPrice(entries)
I->>CP: modify 当前动态成本
end
C->>DB: 删除旧明细并插入新明细
C->>DB: 提交
22.2 调整单不是库存金额重估流水
当前代码直接改变动态单位成本,没有显式生成一条库存数量变化。是否需要生成会计“成本调整金额”事实,取决于下游财务设计;不能默认成本调整单已经同步修正所有报表与 NC。
23. 成本调整单详情
草稿详情打开时:
- 读取草稿明细。
- 按明细
invId + locationId查询当前动态成本。 - 用当前动态成本覆盖返回的
oldPrice。
这意味着草稿保存时的“原成本”和再次打开时展示的“原成本”可能不同:
flowchart LR
A["周一建草稿 oldPrice=80"] --> B["周二采购入库,当前成本=83"]
B --> C["周三打开草稿"]
C --> D["页面 oldPrice=83"]
确认前应再次比较当前成本,避免基于过期预期直接覆盖。
24. 删除成本调整单的语义风险
deleteCost() 只把主表和明细 isDelete=1,没有反向恢复动态成本。
| 被删单据 | 结果 |
|---|---|
| 草稿 | 仅删除草稿,合理 |
| 已生效调整单 | 调整记录不可见,但动态成本仍是调整后值 |
flowchart TD
A["已生效调整单"] --> B["动态成本已改"]
A --> C["deleteCost"]
C --> D["主明细 isDelete=1"]
B --> E["动态成本不回退"]
生产上应限制已生效单删除,改为反向调整单或显式反审核流程。
25. 当前成本查询接口
25.1 批量查询
逻辑入口:scm/Cost::getCostPrice()。
POST /scm/cost/getCostPrice
Content-Type: application/json
{
"locationId": 10000,
"invIds": [123, 456]
}
返回按商品组织的当前仓库成本。
25.2 商品与首选仓库
逻辑入口:scm/Cost::getGoodsInfo()。
若传入仓库不是当前站第三方仓:
- 优先切到默认第三方仓。
- 没有默认仓时取第三方仓列表第一个。
- 再查询该仓成本。
如果当前站没有任何第三方仓,代码访问首元素存在风险。
26. 销售明细毛利公式
SaleReportSer / SaleReport 的主要口径:
销售成本 = cost_amount
销售毛利 = dis_amount - cost_amount
毛利率 = 销售毛利 / dis_amount * 100%
其中 dis_amount 是优惠后应收金额,而不是原订单金额。
flowchart LR
A["订单原金额"] --> B["减优惠/折扣"]
B --> R["应收 dis_amount"]
C["出库成本 cost_amount"] --> G["毛利"]
R --> G
G --> P["毛利率 = 毛利/应收"]
R --> P
26.1 负毛利显示差异
部分代码只有 gross_profit > 0 时才计算毛利率,否则显示 0.00%。因此:
真实毛利 = -20
真实毛利率 = -10%
页面可能显示毛利率 = 0.00%
这会掩盖负毛利,不应据此判断没有亏损。
27. 月利润表口径
月利润表不是直接在 DGJ 本地把销售明细相加,而是通过 Data Service 并行取三组数据:
| 类型 | 数据服务接口 | 字段 |
|---|---|---|
| 主营业务 | /devcenter/reports/coreBizFinReport | sale_fee、cost_fee |
| 其他收入 | /devcenter/reports/otherIncomeReport | qtsr_fee |
| 其他支出 | /devcenter/reports/otherCostReport | qtzc_fee |
公式:
主营利润 = 主营收入 - 主营成本
主营利润率 = 主营利润 / 主营收入
其他利润 = 其他收入 - 其他支出
总利润 = 主营利润 + 其他利润
flowchart TD
A["Data Service 主营收入/成本"] --> B["主营利润"]
C["其他收入"] --> D["其他利润"]
E["其他支出"] --> D
B --> T["总利润"]
D --> T
28. 月利润表接口
28.1 月汇总
POST /reports/profitReport/getProfitReport
Content-Type: application/json
{
"JXCSID": 1001,
"startMonth": "2026-01",
"endMonth": "2026-06"
}
总店账号可通过 storeId 切换服务站,控制器会覆盖 JXCSID。
28.2 月明细
POST /reports/profitReport/getProfitReportDetail
Content-Type: application/json
{
"JXCSID": 1001,
"startMonth": "2026-06"
}
28.3 时序
sequenceDiagram
participant U as 页面
participant P as ProfitReportSer
participant D as Data Service
U->>P: sid + 月份范围
par 三个异步请求
P->>D: coreBizFinReport
P->>D: otherIncomeReport
P->>D: otherCostReport
end
D-->>P: 各月数据
P->>P: 按月归组、abs 金额、计算利润
P-->>U: 月列表 + 合计
29. 为什么销售报表与利润表可能不同
| 差异来源 | 销售明细报表 | 月利润表 |
|---|---|---|
| 数据源 | DGJ 本地销售/库存表 | Data Service 汇总 |
| 时间 | 单据日期/出库日期 | 数据仓库月份 |
| 收入口径 | 优惠后应收 | sale_fee 的数仓口径 |
| 成本 | 出库快照或本地回退 | cost_fee 数仓口径 |
| 其他收入支出 | 通常不含 | 纳入总利润 |
| 退货 | 明细负向/绝对值处理 | 数仓聚合处理 |
| 数据延迟 | 近实时本地 | ETL/同步延迟 |
flowchart TD
L["DGJ 本地销售事实"] --> SR["销售明细报表"]
L --> MQ["同步/ETL"]
MQ --> DW["数据服务"]
DW --> PR["月利润表"]
O["其他收入支出"] --> DW
两张报表数字不一致时,先比较口径和数据时间,不要直接判断其中一张错误。
30. 成本为零时的报表回退
ReportModel 的部分查询逻辑:
若销售明细 costPrice != 0:使用单据成本快照
否则:关联目标日期可用的动态成本作为回退
flowchart TD
A["销售明细"] --> B{"costPrice != 0?"}
B -->|"是"| C["使用历史快照"]
B -->|"否"| D["关联动态成本日表"]
D --> E["使用查询时可得到的最近成本"]
这会导致:
- 原先零成本销售单在初始化成本后,报表成本发生变化。
- 不同查询日期或不同 SQL 可能选到不同成本。
- 本地销售报表和数据仓库历史口径不一致。
长期方案应修复出库时成本快照,而不是依赖报表层静默回退。
31. 成本与库存金额守恒
对某仓库商品:
理论库存金额 = 当前库存数量 * 当前动态单位成本
但移动加权系统中,精确核对应从流水推导:
期初金额
+ 采购入库金额
+ 销退入库成本
+ 调拨入库成本
+ 盘盈金额
- 销售出库成本
- 采购退货成本
- 调拨出库成本
- 盘亏金额
+/- 成本调整影响
= 期末库存金额
flowchart TD
O["期初库存金额"] --> E["期末金额"]
I["各类入库金额"] --> E
U["成本调整"] --> E
X["各类出库金额"] -->|"减"| E
当前成本乘数量可能因舍入、日内覆盖、历史调整和负库存规则与流水累计有小额或显著差异。
32. 常用查询 SQL
以下为只读模板,先确认环境、分表和字段精度。
32.1 查某商品当前成本历史
SELECT id, sid, invId, locationId, price, insertDate, version
FROM t_bs_cost_price_day_<sid_mod_16>
WHERE sid = <sid>
AND invId = <invId>
AND locationId = <locationId>
ORDER BY insertDate DESC, id DESC;
32.2 查同商品各仓成本
SELECT locationId, price, insertDate, version, id
FROM t_bs_cost_price_day_<sid_mod_16>
WHERE sid = <sid>
AND invId = <invId>
ORDER BY locationId, insertDate DESC, id DESC;
32.3 查采购入库价格
SELECT iid, billNo, billDate, invId, locationId,
qty, price, amount, createTime
FROM t_scm_pu_invoice_info
WHERE sid = <sid>
AND invId = <invId>
AND locationId = <locationId>
ORDER BY billDate, id;
32.4 查销售成本快照
SELECT iid, billNo, billDate, transType, invId, locationId,
qty, price, disAmount, costPrice,
costPrice * qty AS cost_amount
FROM t_scm_sa_invoice_info
WHERE sid = <sid>
AND invId = <invId>
ORDER BY billDate, id;
32.5 查零成本销售
SELECT billNo, billDate, invId, locationId, qty, price, disAmount, costPrice
FROM t_scm_sa_invoice_info
WHERE sid = <sid>
AND isDelete = 0
AND costPrice = 0
ORDER BY billDate DESC;
32.6 查成本调整单
SELECT a.id, a.billNo, a.billDate, a.billStatus, a.userName,
b.invId, b.locationId, b.oldPrice, b.price,
a.isDelete, b.isDelete, a.createTime, a.modifyTime
FROM t_scm_update_cost a
JOIN t_scm_update_cost_info b ON b.iid = a.id
WHERE a.sid = <sid>
AND b.invId = <invId>
AND b.locationId = <locationId>
ORDER BY a.id DESC;
32.7 查当前实时库存
SELECT sid, inv_id, location_id, SUM(qty) AS qty
FROM t_scm_inventory_real_time
WHERE sid = <sid>
AND inv_id = <invId>
AND location_id = <locationId>
GROUP BY sid, inv_id, location_id;
32.8 查库存流水金额
SELECT billDate, billNo, billType, transType,
qty, costPrice, amount, locationId, locationAreaId
FROM t_scm_inventory_info
WHERE sid = <sid>
AND invId = <invId>
AND locationId = <locationId>
ORDER BY billDate, id;
33. 常用代码检索
rg -n "BS_COST_PRICE_DAY|BS_COST_PRICE_DAY_NUM|COST_PRICE_INITIAL_DATE" application
rg -n "function (getCostPrices|modify|updateCost|updateCostPrice)" application
rg -n "CostPriceModel->(setSid|getCostPrices|modify)" application
rg -n "costPrice|cost_price|costAmount|cost_amount" application/Services application/service
rg -n "gross_profit|rate_margin|saleProfit|profit_rate" application/controllers/reports application/Services/Report
rg -n "coreBizFinReport|otherIncomeReport|otherCostReport|profitReport" application
rg -n "SCM_UPDATE_COST|billStatus" application/controllers/scm/Cost.php application/service/scm/CostService.php
34. 排查 SOP:当前成本不对
flowchart TD
A["当前成本异常"] --> B["确认 sid/invId/locationId"]
B --> C["查成本日表全部版本"]
C --> D["定位最后一次变化日期"]
D --> E{"来源是什么?"}
E --> F["采购入库"]
E --> G["成本调整"]
E --> H["期初/修复任务"]
F --> I["按入库前库存重算公式"]
G --> J["查调整单 oldPrice/price"]
H --> K["查结算价来源和任务日志"]
检查清单:
- 不要混用
skuId和invId。 - 确认查询的是正确分表。
- 确认仓库,不要只看商品全站成本。
- 按日期列出动态成本版本。
- 找对应日期的采购入库、成本调整和期初操作。
- 读取入库前实时库存是否为负。
- 用代码同样的舍入和分支重算。
35. 排查 SOP:采购入库后成本没更新
| 检查点 | 说明 |
|---|---|
| 入库是否真正完成 | 草稿/待审核不一定触发成本 |
| 入库仓库 | 成本按 locationId 分开 |
| 输入字段 | qty/price/amount/invId/locationId 是否完整 |
| 实时库存 | 调用时读取的数量是否符合预期 |
updateCost() 返回 | 事务是否成功 |
| 日表记录 | 今天是更新旧行还是新增行 |
| 分表唯一键 | UK_SILI 是否冲突 |
flowchart LR
A["采购入库单"] --> B["库存已增加?"]
B --> C["updateCost 被调用?"]
C --> D["CostPriceModel::modify 成功?"]
D --> E["正确分表今天记录?"]
36. 排查 SOP:销售毛利异常
- 查销售出库明细
price/disAmount/costPrice/qty。 - 确认数量正负号和销退类型。
- 手算
costPrice * qty。 - 手算
disAmount - cost_amount。 - 查页面是否将负毛利率显示为 0。
- 若
costPrice=0,查报表是否回退动态成本。 - 查同一商品是否跨多个仓库出库。
- 查成本调整是否发生在出库之后。
- 对比月利润表时确认 Data Service 同步时间。
flowchart TD
A["毛利异常"] --> B{"costPrice=0?"}
B -->|"是"| C["查出库时动态成本/报表回退"]
B -->|"否"| D["核对仓库成本快照"]
C --> E["核对优惠后应收 disAmount"]
D --> E
E --> F["核对销退和负号"]
37. 排查 SOP:月利润表为空或少月份
- 校验
JXCSID、startMonth、endMonth。 - 总店账号确认
storeId是否覆盖 sid。 - 查 Data Service 三个接口是否都返回。
- Provider 非成功时会回调空数组,页面可能无明显错误。
- 查
asyncWait()是否完成全部请求。 - 查
dw_month是毫秒时间戳,月份转换是否正确。 - 查 ETL/数据同步延迟。
- 分别对比主营、其他收入、其他支出,不只看总利润。
38. 排查 SOP:销售报表与利润表不一致
按以下桥接表核对:
| 核对层 | 销售报表 | 利润表 |
|---|---|---|
| 单据范围 | 哪些 transType | Data Service cust_type/业务分类 |
| 时间字段 | billDate | dw_month |
| 收入 | disAmount | sale_fee |
| 成本 | 本地 cost_amount | cost_fee |
| 退货 | 负向明细 | 数据仓库净额 |
| 其他项 | 无/部分 | qtsr_fee/qtzc_fee |
先对齐一个月、一个站、一个客户类型,再扩大范围。
39. 成本调整安全 SOP
flowchart TD
A["发现成本异常"] --> B["冻结商品相关高风险操作"]
B --> C["导出动态成本版本/库存/业务流水"]
C --> D["计算正确目标成本"]
D --> E["先保存草稿"]
E --> F["复核 oldPrice、仓库和单位"]
F --> G["生效调整"]
G --> H["验证动态成本与库存金额"]
H --> I["验证销售/盘点/报表影响"]
禁止:
- 直接 update 成本日表而不留调整单。
- 删除已生效调整单当作回滚。
- 用另一个仓库成本覆盖目标仓库。
- 把分单位成本当元写入。
- 修改历史销售成本前不评估报表和财务影响。
40. 已识别代码风险
| 编号 | 风险 | 后果 | 建议 |
|---|---|---|---|
| R45-01 | 分表规则直接依赖 sid % 16 | 扩容改常量会迁错历史路由 | 建立稳定分片映射 |
| R45-02 | 同日成本只保留最终值 | 日内过程不可追溯 | 增加成本变更流水 |
| R45-03 | getCostPrices 空商品/仓库会扩大查询 | 大查询风险 | 强制调用方范围 |
| R45-04 | CLI 不自动初始化 sid | 查错表或 where sid 为空 | 参数校验 sid>0 |
| R45-05 | findInfoBySkuIdAndLocationId 未显式排序 | 多版本时可能取任意行 | 按日期/id 倒序 |
| R45-06 | 零成本修复取结算价 | 不一定等于真实库存成本 | 标记初始化来源 |
| R45-07 | /tmp 初始化脚本依赖外部文件 | 不可审计/不可重放 | 任务参数表和 checksum |
| R45-08 | 负库存入库直接用本次价 | 当前成本突跳 | 监控负库存并复核历史毛利 |
| R45-09 | 历史入库修改依赖当前库存和 errQty | 易重算错误 | 建立完整重放算法 |
| R45-10 | 销售取不到成本写 0 | 零成本、虚高毛利 | 出库前阻断或告警 |
| R45-11 | 报表对零成本回退当前/历史动态成本 | 历史数字漂移 | 修复单据快照并固化口径 |
| R45-12 | 负毛利率部分页面显示 0 | 掩盖亏损 | 显示真实负值 |
| R45-13 | 已生效成本单可软删但不回退成本 | 审计记录消失 | 禁止删除,使用反向单 |
| R45-14 | 成本调整可能重复生效 | 多次编辑确认覆盖 | 状态机 + 乐观锁 |
| R45-15 | 调整只改单位成本,不见金额重估事实 | 财务库存金额难对账 | 明确成本调整金额流水 |
| R45-16 | 盘点成本乘 100 | 元/分混用 | 类型/字段名标明单位 |
| R45-17 | Data Service 失败返回空数组 | 利润表静默缺数据 | 暴露降级状态和告警 |
| R45-18 | 月利润对金额先 abs | 负向业务语义可能丢失 | 确认数仓返回符号契约 |
| R45-19 | sid=9990 被映射为 1106 | 特殊站口径难理解 | 配置化并记录原因 |
| R45-20 | 多套销售服务并存 | 新旧链成本行为不同 | 建立同场景契约测试 |
41. 监控建议
| 指标 | 阈值/用途 |
|---|---|
| 零成本在库 SKU 数 | 大于 0 告警 |
| 零成本销售出库行数 | 立即告警 |
| 负动态成本数 | 立即告警 |
| 单日成本涨跌幅 | 超阈值复核 |
| 负库存商品数 | 持续监控 |
| 成本调整单金额影响 | 高金额人工审批 |
| 动态成本与流水估值差 | 超容差告警 |
| Data Service 三接口失败率 | 月利润表可用性 |
| 负毛利销售占比 | 经营风险 |
42. 单元测试清单
42.1 动态成本
- 首次入库创建
version=0。 - 同日第二次入库更新同一日记录。
- 次日入库新增版本。
- 两个仓库成本独立。
- 两个服务站同 SKU 成本独立。
- 正库存移动加权公式正确。
- 零库存入库成本等于入库价。
- 负库存入库成本等于本次入库价。
- 修改入库
errQty分支正确。 - 目标总数量小于等于零时成本为 0。
42.2 成本调整
- 草稿不修改动态成本。
- 生效单修改目标仓库成本。
- 草稿重开刷新
oldPrice。 - 重复
billNo不新增。 - 已生效单禁止普通删除。
- 同一商品多仓调整互不影响。
42.3 销售与报表
- 出库保存当时成本快照。
- 后续成本变化不修改历史快照。
- 零成本出库触发告警。
- 优惠后毛利公式正确。
- 负毛利率显示真实负数。
- 销退正确冲减收入和成本。
- Data Service 单接口失败可见,不静默当 0。
43. 集成回归场景
43.1 正常采购到销售
- 期初 10 件、成本 80。
- 采购入库 5 件、价格 100。
- 验证动态成本约 86.6667。
- 销售出库 3 件。
- 验证销售明细成本快照。
- 再次采购改变当前成本。
- 验证历史销售成本不变。
- 验证销售报表毛利。
43.2 负库存恢复
- 将商品销售到负库存。
- 记录负库存销售成本快照。
- 采购入库恢复正库存。
- 验证新动态成本取本次入库价。
- 验证历史报表是否发生回退变化。
- 评估是否需要修复历史零成本出库。
43.3 成本调整
- 创建草稿。
- 中途做一次采购入库。
- 重开草稿确认
oldPrice更新。 - 生效调整。
- 验证当前成本、库存金额和后续销售。
- 验证历史销售不变。
44. 上线回归矩阵
| 变更模块 | 必回归业务 |
|---|---|
CostPriceModel | 16 分表、日期版本、批量查询、CLI |
InventoryService::updateCost | 正/零/负库存、修改入库、期初 |
| 采购入库 | 新增、修改、撤销、采退 |
| 销售出库 | 新旧销售服务、销退、多仓 |
| 成本调整 | 草稿、生效、重复提交、删除限制 |
| 调拨 | 跨仓成本传递和撤销 |
| 盘点 | 元/分、盘盈、盘亏、历史回填 |
| 销售报表 | 页面、导出、负毛利、零成本 |
| 月利润表 | 三接口、月份、总店切站、导出 |
45. 待环境确认项
t_bs_cost_price_day_*生产表结构、价格精度和UK_SILI真实字段。COST_PRICE_INITIAL_DATE的值与历史迁移背景。- 动态成本扩容到 32 张表时的既定路由方案。
- 采购入库调用
updateCost()的准确事务顺序:库存先加还是成本先算。 - 销售退货最终采用原单成本还是退货当日动态成本。
- 采购退货、撤销入库对移动加权成本的完整实现。
- 调拨目标仓的实际加权公式。
- 成本调整是否同步 Data Service、NC 或会计库存金额。
- 已生效成本调整单线上是否允许删除。
- 盘点明细
cost_price是否统一以分存储。 - 销售报表零成本回退查询的准确日期边界。
- Data Service
sale_fee/cost_fee的业务分类与 ETL 延迟。 abs处理前数据服务金额的正负号契约。- 特殊 sid
9990 -> 1106的业务原因和适用环境。 - 零成本修复任务是否有生产调度,最近执行记录是什么。
46. 一页式成本脉络
flowchart TD
A["期初成本 / 结算价初始化"] --> C["动态成本日表"]
B["采购入库价"] --> D{"入库前库存"}
D -->|"正/零库存"| E["移动加权"]
D -->|"负库存"| F["取本次入库价"]
E --> C
F --> C
G["成本调整单"] --> C
C --> H["销售出库读取"]
H --> I["sa_invoice_info.costPrice 快照"]
I --> J["销售成本"]
K["优惠后应收"] --> L["销售毛利"]
J --> L
I --> M["本地销售报表"]
M --> N["同步/ETL"]
N --> O["Data Service 月利润表"]
P["其他收入/支出"] --> O
C --> Q["盘点与库存估值"]
47. 结论
成本问题要沿四个时间点拆开:
采购/期初发生时:本次入库价值是什么
动态成本变化时:当前仓库平均成本是什么
销售出库发生时:这张单冻结了什么成本
报表查询时:报表采用快照、回退还是数仓口径
排查路径应固定为:
sid + invId + locationId + 日期
-> 动态成本版本
-> 入库/调整来源
-> 销售成本快照
-> 库存数量和流水金额
-> 本地销售报表
-> Data Service 月利润表
只改当前动态成本不能自动修复历史销售毛利;只改历史销售成本也不能修复当前库存估值。两类问题必须分开判断,再决定是否需要业务补偿、报表重算或财务调整。
请求-日志-数据变更追踪卡
多入口请求链路
| 场景 | 调用方与入口 | 请求载荷/上下文 | Controller/Consumer | Service/Provider | 汇合点 | 最终业务事实 |
|---|---|---|---|---|---|---|
| 成本查询/调整 | scm/Cost | sid、SKU、日期、调整值/原因 | Cost Controller | CostService/InventoryService | sid+SKU+cost date | 当前/日成本记录 |
| 入库重算 | 采购/其他入库 | 入库数量、入库金额、原库存成本 | 各入库入口 | Inventory/Cost Service | inventory transaction | 动态成本按加权规则变化 |
| 定时固化 | tasks/CostPrice/UpdateGoodsPdCostPrice | 日期、站点/SKU 批次 | Cost task | Cost Service | date+sid+SKU | 将当前成本固化为日快照/商品成本 |
| 利润报表 | Sale/Profit reports | 销售日期、状态、组织、成本口径 | Report Controller | Sale/Profit ReportSer | sale detail ID | 销售价减历史成本得毛利 |
日志证据矩阵
| 链路段 | 日志来源 | 可检索锚点 | 成功信号 | 失败信号 | 与下一段关联方式 | | --- | --- | --- | --- | --- | --- | --- | | 入库成本 | Inventory/Cost Service | request_id、入库单、sid、SKU、qty/amount | 库存和成本同业务边界更新 | 数量 0、金额单位错、部分成功 | 入库单查库存/成本前后值 | | 人工调整 | Cost Controller/Service | adjust ID、operator、reason、before/after | 调整记录可审计 | 直接覆盖无原因/无快照 | sid+SKU+time 查成本历史 | | 日快照任务 | CostPrice task | task date、batch、sid/SKU、counts | 每个目标键唯一快照 | 漏站/漏 SKU、重复覆盖历史 | date key 对比当前成本 | | 报表 | ProfitReport/Provider | query ID、sale detail、cost date/source | 单据可追溯成本来源 | 用当前成本算历史、ETL 未追平 | sale detail 关联成本快照 |
环节数据变更台账
| 步骤 | 代码位置 | 事务 | 读取事实 | 写入表/缓存/MQ | 字段或数量变化 | 回查证据 |
|---|---|---|---|---|---|---|
| 入库前 | Inventory/Cost Service | 业务事务 | oldQty、oldCost、inQty、inAmount | 无 | 计算 newCost=(oldQty*oldCost+inAmount)/(oldQty+inQty),边界规则按代码 | 输入四项、公式结果 |
| 更新当前成本 | CostService/CostPriceModel | 与库存事务边界需确认 | 计算结果、当前版本 | BS_COST_PRICE/商品成本字段 | cost old -> new,qty 同步 old -> old+inQty | 入库单、影响行数、前后快照 |
| 固化日成本 | CostPrice task | 每批事务 | 日终当前成本 | BS_COST_PRICE_DAY | date+sid+SKU upsert,一日一版本 | task batch、唯一键、行数 |
| 销售成本快照 | SaOrder/InvSa Service | 销售/出库事务 | 当时动态成本 | 销售/出库明细成本字段 | saleCost 固化;后续当前成本变化不应回写历史 | sale detail、cost source/time |
| 利润聚合/修复 | ReportSer/ETL | 只读或受控重算 | sale amount、历史 cost、return | DWD/report 派生数据 | profit=sale-return-cost;修当前/历史分别处理 | 单据明细重算、报表数据日期 |
子模块追踪:cost-read-version 动态成本分表、版本与按日读取
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 当前/按日读 | 业务计算或历史报表取成本 | sid、SKU、business date、version | application/models/bs/CostPriceModel.php | 当前成本、日成本分表/唯一键、时区和有效版本 | 查询只读 不写;当前/历史按场景选择 | request/query ID + sid/SKU + date/version | 历史日期缺快照不自动用当前价;标记待补数据 |
| 日成本固化 | 日终任务保存版本 | task batch、date、sid/SKU | application/controllers/tasks/CostPrice.php | 日终当前成本、已有日版本和任务水位 | 每批本地事务按 date+sid+SKU upsert old -> daily snapshot | task + date + batch/rows | 重跑同日稳定键 0 重复;失败不越过水位 |
子模块追踪:cost-weighted 移动加权与负库存入库成本
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 加权计算 | 有金额的正向入库 | inventory billNo、sid/SKU、oldQty/cost、inQty/amount | application/service/bs/InventoryService.php -> application/service/scm/CostService.php | 入库前数量成本、入库数量金额和边界配置 | 在库存本地事务内计算 newCost=(oldQty*oldCost+inAmount)/(oldQty+inQty),成本 old -> new | billNo + four inputs + formula/result | 分母 0、负库存、金额异常走代码边界,不静默除零 |
| 负库存边界 | oldQty<0 或入库后仍非正 | sid/SKU、inventory/cost versions | application/service/scm/CostService.php | 负库存形成原因、旧成本、入库额和当前规则 | 本地事务按明确规则 cost old -> fallback/weighted | billNo + old/new qty/cost + branch | 规则变更需回归正常/零/负三类;历史成本不批量覆盖 |
子模块追踪:cost-init-fix 成本初始化与零成本修复
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 候选扫描 | 初始化新品或查零成本 | task、sid/SKU、exact scope | application/controllers/tasks/UpdateGoodsPdCostPrice.php | 当前/日成本、库存、最近有效入库和销售引用 | dry-run 查询只读;冻结候选和建议成本 | task + candidate IDs + source bills | 无可信来源不修;数量超预期停止 |
| 小批修复 | 以已确认来源补成本 | batch、sid/SKU、before hash | application/service/scm/CostService.php | baseline、建议值、版本和幂等标记 | 每项本地事务 cost 0/old -> expected,写调整/版本 | batch + SKU + before/after + rows | 当前成本与历史销售成本分别处理;重跑 0 变化 |
子模块追踪:cost-po-inbound 采购入库成本更新
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 采购入库 | 采购收货写库存与成本 | po/inventory billNo、SKU、qty/amount | application/service/scm/InvOiService.php -> application/service/scm/CostService.php | 采购含税/未税价、入库量、旧库存和旧成本 | 采购/库存/成本本地事务或边界需确认:库存 +n、cost old -> weighted | po/inventory + SKU + inputs/result | 库存成功成本失败先查事务提交事实;只补成本段 |
| 入库对账 | 采购入库与成本版本不符 | po/inventory、cost version/date | application/models/bs/CostPriceModel.php | 入库流水金额、当前成本、日成本和更新时间 | 查询只读;公式输入可重算 | billNos + SKU + cost versions | 不重复入库修成本;用领域成本调整/幂等任务 |
子模块追踪:cost-other-stock 销退、大客户、调拨与盘点成本
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 非采购库存 | 销退、其他入库、调拨、盘盈盘亏 | business/inventory billNo、transType、SKU | application/service/scm/InvCuService.php、application/service/scm/InvTfService.php、application/Services/Gpda/TakeStockSer.php | 来源类型、历史销售成本/目标站成本、数量方向和当前成本 | 各领域本地事务按来源规则 qty +/-n,成本保持/加权/转移 old -> new | billNo + transType/source + old/new cost | 不能把所有正向库存都当采购价;各来源分别回归 |
| 来源对账 | 同 SKU 成本突变 | SKU、source bills、cost versions | application/Services/Report/InventoryReportSer.php | 期间各类流水、金额、成本版本和软删 | 查询只读;按时间线重放规则 | SKU + source bill timeline + versions | 找到首个异常来源再补偿,不直接改最终值掩盖过程 |
子模块追踪:cost-sale-snapshot 销售出库成本快照
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 固化成本 | 销售/出库创建 | sale/invoice billNo、SKU、business time | application/Services/SaOrders/SaOrderSer.php -> application/service/scm/InvSaService.php | 当时动态/日成本、商品和数量 | 销售本地事务将 saleCost none -> current snapshot 写入明细 | request ID + sale/invoice + SKU + cost source/time | 当前成本后变不回写历史;缺成本按规则阻断/标记 |
| 快照回查 | 销售毛利异常 | sale detail、cost version/date | application/Services/Report/SaleReportSer.php | 明细成本、当时日成本、退货和数量符号 | 查询只读;历史快照为销售事实 | sale/invoice + detail + cost version | 修历史需受控重算/调整记录,不修改当前成本代替 |
子模块追踪:cost-adjust 成本调整单
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 创建审核 | 用户/任务提交成本调整 | adjust billNo、sid/SKU、old/new cost、reason | application/controllers/scm/Cost.php -> application/service/scm/CostService.php | 当前成本版本、权限、调整原因和重复来源 | 调整本地事务 insert 单据,审核后 cost old -> new、版本 +1 | request ID + adjust + SKU + before/after | 乐观版本冲突零写入;不物理覆盖调整审计链 |
| 反向/回查 | 调整错误或报表未同步 | adjust billNo、cost version | application/service/scm/CostService.php | 原调整、后续成本变化和报表水位 | 新反向调整事务 current -> corrected;不删原单 | original/reverse adjust + versions | 有后续变更需重新计算,不简单恢复旧值;派生层单独刷新 |
子模块追踪:cost-sale-profit 销售明细毛利
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 明细计算 | 销售明细/毛利查询 | sale/invoice detail、amount/cost/return | application/controllers/reports/SaleReport.php -> application/Services/Report/SaleReportSer.php | 成交收入、成本快照、退货、税费和数量符号 | 查询只读;profit=sale-return-cost-adjustments | query ID + detail/bill + formula inputs | 负毛利可能合法;先核历史成本和符号,不改当前成本 |
| 列表导出 | 页面与导出毛利对比 | filters、export/query ID | application/Providers/DataService/ReportProvider.php | 同一筛选、离线水位和公式版本 | 查询只读;列表/导出相同明细合计 | query/export + row/amount totals | 水位不同先等待/补 ETL,公式差异修共享口径 |
子模块追踪:cost-month-profit 月利润表与报表差异
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 月度聚合 | 利润表按月/组织查询 | sid set、month、dimensions | application/controllers/reports/ProfitReport.php -> application/Services/Report/ProfitReportSer.php | 销售/退货、历史成本快照、月截止和离线数据 | 查询只读;按月与维度 sum/group | query ID + month/sid + max data time | 月边界/时区/转站口径统一;不拿实时当前成本重算历史 |
| 差异定位 | 月利润与销售明细合计不符 | query IDs、business key samples | application/models/report/ReportModel.php | 两侧数据源、过滤、公式、数据日期和 join 粒度 | 查询只读;输出差异主键集合 | query IDs + source max times + diff IDs | OLTP/明细正确只补离线聚合;源快照错走受控历史修复 |