本文整理 DGJ2 中采购结算价、库存移动加权成本、销售出库成本快照、成本调整、盘点成本和利润报表之间的关系。重点不是背字段,而是理解每一种“价格”回答什么问题、在哪个时间点冻结、如何影响库存金额与利润。

阅读后应能回答:

  1. purPrice、costPrice、price、cost_price 分别是什么。
  2. 同一商品为什么在不同仓库有不同成本。
  3. 采购入库如何重新计算移动加权成本。
  4. 负库存后再入库为何会采用特殊公式。
  5. 销售出库保存的是当前动态成本还是实时重算成本。
  6. 成本调整单为何可能改变当前成本,却不自动改历史销售单。
  7. 销售明细报表与月利润表为什么可能不一致。
  8. 成本为零、负毛利、销退、跨月时应怎样排查。

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余数真实表
10019t_bs_cost_price_day_9
999915t_bs_cost_price_day_15
11062t_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() 的规则:

  1. 新组合没有历史成本时,插入今天记录,version=0。
  2. 已有组合今天已经有记录时,更新今天的记录。
  3. 最近记录不是今天时,新增今天记录,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

然后:

  1. 写期初库存流水。
  2. 删除该商品旧动态成本记录(编辑期初场景)。
  3. 调 updateCostPrice() 写入各仓库初始成本。

有非期初业务往来后,代码禁止再修改期初库存。

12.2 CLI 批量初始化

tasks/CostPrice::initBySidWithInvIds(sid, locationId):

  1. 从 /tmp/costSkuIds.txt 读取 SKU。
  2. 每 500 条一批。
  3. 查 SKU 在指定服务站/仓库的商品关系。
  4. 跳过已有动态成本的组合。
  5. 优先取服务站结算价。
  6. 无服务站价时尝试按供应商取公共结算价。
  7. 写当天动态成本。
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():

  1. 收集出库明细的 invId 和 storage_id。
  2. CostPriceModel::setSid(sid)。
  3. 批量读取当前动态成本。
  4. 按 storage_id_invId 取 price。
  5. 写入销售出库明细 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 与原单关联。排查销退毛利异常时要核对:

  1. 退货明细是否正确关联原销售行。
  2. costPrice 是原单快照还是退货当日动态成本。
  3. 数量和金额是否使用负数。
  4. 报表是否对销退取绝对值再重组。
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. 成本调整单详情

草稿详情打开时:

  1. 读取草稿明细。
  2. 按明细 invId + locationId 查询当前动态成本。
  3. 用当前动态成本覆盖返回的 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()。

若传入仓库不是当前站第三方仓:

  1. 优先切到默认第三方仓。
  2. 没有默认仓时取第三方仓列表第一个。
  3. 再查询该仓成本。

如果当前站没有任何第三方仓,代码访问首元素存在风险。


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/coreBizFinReportsale_fee、cost_fee
其他收入/devcenter/reports/otherIncomeReportqtsr_fee
其他支出/devcenter/reports/otherCostReportqtzc_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["查结算价来源和任务日志"]

检查清单:

  1. 不要混用 skuId 和 invId。
  2. 确认查询的是正确分表。
  3. 确认仓库,不要只看商品全站成本。
  4. 按日期列出动态成本版本。
  5. 找对应日期的采购入库、成本调整和期初操作。
  6. 读取入库前实时库存是否为负。
  7. 用代码同样的舍入和分支重算。

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:销售毛利异常

  1. 查销售出库明细 price/disAmount/costPrice/qty。
  2. 确认数量正负号和销退类型。
  3. 手算 costPrice * qty。
  4. 手算 disAmount - cost_amount。
  5. 查页面是否将负毛利率显示为 0。
  6. 若 costPrice=0,查报表是否回退动态成本。
  7. 查同一商品是否跨多个仓库出库。
  8. 查成本调整是否发生在出库之后。
  9. 对比月利润表时确认 Data Service 同步时间。
flowchart TD
  A["毛利异常"] --> B{"costPrice=0?"}
  B -->|"是"| C["查出库时动态成本/报表回退"]
  B -->|"否"| D["核对仓库成本快照"]
  C --> E["核对优惠后应收 disAmount"]
  D --> E
  E --> F["核对销退和负号"]

37. 排查 SOP:月利润表为空或少月份

  1. 校验 JXCSID、startMonth、endMonth。
  2. 总店账号确认 storeId 是否覆盖 sid。
  3. 查 Data Service 三个接口是否都返回。
  4. Provider 非成功时会回调空数组,页面可能无明显错误。
  5. 查 asyncWait() 是否完成全部请求。
  6. 查 dw_month 是毫秒时间戳,月份转换是否正确。
  7. 查 ETL/数据同步延迟。
  8. 分别对比主营、其他收入、其他支出,不只看总利润。

38. 排查 SOP:销售报表与利润表不一致

按以下桥接表核对:

核对层销售报表利润表
单据范围哪些 transTypeData Service cust_type/业务分类
时间字段billDatedw_month
收入disAmountsale_fee
成本本地 cost_amountcost_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-03getCostPrices 空商品/仓库会扩大查询大查询风险强制调用方范围
R45-04CLI 不自动初始化 sid查错表或 where sid 为空参数校验 sid>0
R45-05findInfoBySkuIdAndLocationId 未显式排序多版本时可能取任意行按日期/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-17Data Service 失败返回空数组利润表静默缺数据暴露降级状态和告警
R45-18月利润对金额先 abs负向业务语义可能丢失确认数仓返回符号契约
R45-19sid=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 正常采购到销售

  1. 期初 10 件、成本 80。
  2. 采购入库 5 件、价格 100。
  3. 验证动态成本约 86.6667。
  4. 销售出库 3 件。
  5. 验证销售明细成本快照。
  6. 再次采购改变当前成本。
  7. 验证历史销售成本不变。
  8. 验证销售报表毛利。

43.2 负库存恢复

  1. 将商品销售到负库存。
  2. 记录负库存销售成本快照。
  3. 采购入库恢复正库存。
  4. 验证新动态成本取本次入库价。
  5. 验证历史报表是否发生回退变化。
  6. 评估是否需要修复历史零成本出库。

43.3 成本调整

  1. 创建草稿。
  2. 中途做一次采购入库。
  3. 重开草稿确认 oldPrice 更新。
  4. 生效调整。
  5. 验证当前成本、库存金额和后续销售。
  6. 验证历史销售不变。

44. 上线回归矩阵

变更模块必回归业务
CostPriceModel16 分表、日期版本、批量查询、CLI
InventoryService::updateCost正/零/负库存、修改入库、期初
采购入库新增、修改、撤销、采退
销售出库新旧销售服务、销退、多仓
成本调整草稿、生效、重复提交、删除限制
调拨跨仓成本传递和撤销
盘点元/分、盘盈、盘亏、历史回填
销售报表页面、导出、负毛利、零成本
月利润表三接口、月份、总店切站、导出

45. 待环境确认项

  1. t_bs_cost_price_day_* 生产表结构、价格精度和 UK_SILI 真实字段。
  2. COST_PRICE_INITIAL_DATE 的值与历史迁移背景。
  3. 动态成本扩容到 32 张表时的既定路由方案。
  4. 采购入库调用 updateCost() 的准确事务顺序:库存先加还是成本先算。
  5. 销售退货最终采用原单成本还是退货当日动态成本。
  6. 采购退货、撤销入库对移动加权成本的完整实现。
  7. 调拨目标仓的实际加权公式。
  8. 成本调整是否同步 Data Service、NC 或会计库存金额。
  9. 已生效成本调整单线上是否允许删除。
  10. 盘点明细 cost_price 是否统一以分存储。
  11. 销售报表零成本回退查询的准确日期边界。
  12. Data Service sale_fee/cost_fee 的业务分类与 ETL 延迟。
  13. abs 处理前数据服务金额的正负号契约。
  14. 特殊 sid 9990 -> 1106 的业务原因和适用环境。
  15. 零成本修复任务是否有生产调度,最近执行记录是什么。

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/ConsumerService/Provider汇合点最终业务事实
成本查询/调整scm/Costsid、SKU、日期、调整值/原因Cost ControllerCostService/InventoryServicesid+SKU+cost date当前/日成本记录
入库重算采购/其他入库入库数量、入库金额、原库存成本各入库入口Inventory/Cost Serviceinventory transaction动态成本按加权规则变化
定时固化tasks/CostPrice/UpdateGoodsPdCostPrice日期、站点/SKU 批次Cost taskCost Servicedate+sid+SKU将当前成本固化为日快照/商品成本
利润报表Sale/Profit reports销售日期、状态、组织、成本口径Report ControllerSale/Profit ReportSersale 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_DAYdate+sid+SKU upsert,一日一版本task batch、唯一键、行数
销售成本快照SaOrder/InvSa Service销售/出库事务当时动态成本销售/出库明细成本字段saleCost 固化;后续当前成本变化不应回写历史sale detail、cost source/time
利润聚合/修复ReportSer/ETL只读或受控重算sale amount、历史 cost、returnDWD/report 派生数据profit=sale-return-cost;修当前/历史分别处理单据明细重算、报表数据日期

子模块追踪:cost-read-version 动态成本分表、版本与按日读取

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
当前/按日读业务计算或历史报表取成本sid、SKU、business date、versionapplication/models/bs/CostPriceModel.php当前成本、日成本分表/唯一键、时区和有效版本查询只读 不写;当前/历史按场景选择request/query ID + sid/SKU + date/version历史日期缺快照不自动用当前价;标记待补数据
日成本固化日终任务保存版本task batch、date、sid/SKUapplication/controllers/tasks/CostPrice.php日终当前成本、已有日版本和任务水位每批本地事务按 date+sid+SKU upsert old -> daily snapshottask + date + batch/rows重跑同日稳定键 0 重复;失败不越过水位

子模块追踪:cost-weighted 移动加权与负库存入库成本

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
加权计算有金额的正向入库inventory billNo、sid/SKU、oldQty/cost、inQty/amountapplication/service/bs/InventoryService.php -> application/service/scm/CostService.php入库前数量成本、入库数量金额和边界配置在库存本地事务内计算 newCost=(oldQty*oldCost+inAmount)/(oldQty+inQty),成本 old -> newbillNo + four inputs + formula/result分母 0、负库存、金额异常走代码边界,不静默除零
负库存边界oldQty<0 或入库后仍非正sid/SKU、inventory/cost versionsapplication/service/scm/CostService.php负库存形成原因、旧成本、入库额和当前规则本地事务按明确规则 cost old -> fallback/weightedbillNo + old/new qty/cost + branch规则变更需回归正常/零/负三类;历史成本不批量覆盖

子模块追踪:cost-init-fix 成本初始化与零成本修复

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
候选扫描初始化新品或查零成本task、sid/SKU、exact scopeapplication/controllers/tasks/UpdateGoodsPdCostPrice.php当前/日成本、库存、最近有效入库和销售引用dry-run 查询只读;冻结候选和建议成本task + candidate IDs + source bills无可信来源不修;数量超预期停止
小批修复以已确认来源补成本batch、sid/SKU、before hashapplication/service/scm/CostService.phpbaseline、建议值、版本和幂等标记每项本地事务 cost 0/old -> expected,写调整/版本batch + SKU + before/after + rows当前成本与历史销售成本分别处理;重跑 0 变化

子模块追踪:cost-po-inbound 采购入库成本更新

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
采购入库采购收货写库存与成本po/inventory billNo、SKU、qty/amountapplication/service/scm/InvOiService.php -> application/service/scm/CostService.php采购含税/未税价、入库量、旧库存和旧成本采购/库存/成本本地事务或边界需确认:库存 +n、cost old -> weightedpo/inventory + SKU + inputs/result库存成功成本失败先查事务提交事实;只补成本段
入库对账采购入库与成本版本不符po/inventory、cost version/dateapplication/models/bs/CostPriceModel.php入库流水金额、当前成本、日成本和更新时间查询只读;公式输入可重算billNos + SKU + cost versions不重复入库修成本;用领域成本调整/幂等任务

子模块追踪:cost-other-stock 销退、大客户、调拨与盘点成本

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
非采购库存销退、其他入库、调拨、盘盈盘亏business/inventory billNo、transType、SKUapplication/service/scm/InvCuService.php、application/service/scm/InvTfService.php、application/Services/Gpda/TakeStockSer.php来源类型、历史销售成本/目标站成本、数量方向和当前成本各领域本地事务按来源规则 qty +/-n,成本保持/加权/转移 old -> newbillNo + transType/source + old/new cost不能把所有正向库存都当采购价;各来源分别回归
来源对账同 SKU 成本突变SKU、source bills、cost versionsapplication/Services/Report/InventoryReportSer.php期间各类流水、金额、成本版本和软删查询只读;按时间线重放规则SKU + source bill timeline + versions找到首个异常来源再补偿,不直接改最终值掩盖过程

子模块追踪:cost-sale-snapshot 销售出库成本快照

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
固化成本销售/出库创建sale/invoice billNo、SKU、business timeapplication/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/dateapplication/Services/Report/SaleReportSer.php明细成本、当时日成本、退货和数量符号查询只读;历史快照为销售事实sale/invoice + detail + cost version修历史需受控重算/调整记录,不修改当前成本代替

子模块追踪:cost-adjust 成本调整单

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
创建审核用户/任务提交成本调整adjust billNo、sid/SKU、old/new cost、reasonapplication/controllers/scm/Cost.php -> application/service/scm/CostService.php当前成本版本、权限、调整原因和重复来源调整本地事务 insert 单据,审核后 cost old -> new、版本 +1request ID + adjust + SKU + before/after乐观版本冲突零写入;不物理覆盖调整审计链
反向/回查调整错误或报表未同步adjust billNo、cost versionapplication/service/scm/CostService.php原调整、后续成本变化和报表水位新反向调整事务 current -> corrected;不删原单original/reverse adjust + versions有后续变更需重新计算,不简单恢复旧值;派生层单独刷新

子模块追踪:cost-sale-profit 销售明细毛利

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
明细计算销售明细/毛利查询sale/invoice detail、amount/cost/returnapplication/controllers/reports/SaleReport.php -> application/Services/Report/SaleReportSer.php成交收入、成本快照、退货、税费和数量符号查询只读;profit=sale-return-cost-adjustmentsquery ID + detail/bill + formula inputs负毛利可能合法;先核历史成本和符号,不改当前成本
列表导出页面与导出毛利对比filters、export/query IDapplication/Providers/DataService/ReportProvider.php同一筛选、离线水位和公式版本查询只读;列表/导出相同明细合计query/export + row/amount totals水位不同先等待/补 ETL,公式差异修共享口径

子模块追踪:cost-month-profit 月利润表与报表差异

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
月度聚合利润表按月/组织查询sid set、month、dimensionsapplication/controllers/reports/ProfitReport.php -> application/Services/Report/ProfitReportSer.php销售/退货、历史成本快照、月截止和离线数据查询只读;按月与维度 sum/groupquery ID + month/sid + max data time月边界/时区/转站口径统一;不拿实时当前成本重算历史
差异定位月利润与销售明细合计不符query IDs、business key samplesapplication/models/report/ReportModel.php两侧数据源、过滤、公式、数据日期和 join 粒度查询只读;输出差异主键集合query IDs + source max times + diff IDsOLTP/明细正确只补离线聚合;源快照错走受控历史修复