From 658df690f685b75180230f4062b19ab8347969e6 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Fri, 10 Jul 2026 17:29:19 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=8E=86=E5=8F=B2=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E5=87=BA=E5=BA=93=E4=BB=B7=E6=A0=BC=E9=94=99=E5=88=97=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=A4=8D=E7=9B=98=20runbook=EF=BC=882026-07-10?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因/修复设计/执行结果/遗留脏数据清单/回滚方式,登记 docs/index.html Co-Authored-By: Claude Fable 5 --- docs/index.html | 1 + .../runbooks/fix-history-prices-20260710.html | 130 ++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 docs/runbooks/fix-history-prices-20260710.html diff --git a/docs/index.html b/docs/index.html index f203ca3..32bde72 100644 --- a/docs/index.html +++ b/docs/index.html @@ -62,6 +62,7 @@

🧪 测试 · 审计报告

diff --git a/docs/runbooks/fix-history-prices-20260710.html b/docs/runbooks/fix-history-prices-20260710.html new file mode 100644 index 0000000..4cd8a3d --- /dev/null +++ b/docs/runbooks/fix-history-prices-20260710.html @@ -0,0 +1,130 @@ + + + + + +历史导入出库价格错列修复复盘(2026-07-10) — 酒库管理系统 + + + +

历史导入出库价格错列修复复盘

+
门店:鼎晟酒行(shop_id=8, S000008)· 生产库 ali/jiu_mysql · 2026-07-10 执行 · 修复工具 backend/cmd/fix-history-prices · commit e19c498
+ +
一句话结论:2026-06-20 历史进销存导入(cmd/import-history)把旧系统出库明细的「单价/金额」(售价口径)误写入成本列 cost_price/cost_amount,售价列留 0,导致全部历史出库单显示「待定价」、成本列实为售价。本次用一次性工具修复 26,335 行明细 / 4,927 张出库单:售价归位、成本按序列号回查真实进价回填、利润重算;应收合计不动,财务零影响。
+ +

一、症状与发现路径

+
+ +
+ +

二、根因

+
+

backend/cmd/import-history/main.go 的出库明细映射(修复前):

+
CostPrice:   parseFloatLoose(get(dr, dCols, "单价")),   // ✗ 源出库「单价」是售价口径
+CostAmount:  parseFloatLoose(get(dr, dCols, "金额")),   // ✗ 同上;sale_price / sale_amount 留 0
+ +

映射已随修复同步改对(出库明细「单价/金额」→ sale_price/sale_amount,成本留 0 待回查),以后再导不会复发。

+
+ +

三、修复设计(cmd/fix-history-prices)

+
+ + + + + + + + +
要点说明
修复范围仅历史导入明细:stock_out_items.product_id = 占位商品 HIST-PLACEHOLDER(shop8 id=4809)sale_price=0 AND cost_price>0注意占位商品已被界面软删(2026-06-20),工具定位时不过滤 deleted_at
售价归位sale_price ← 原 cost_pricesale_amount ← 原 cost_amount(忠实保留源售价,不做任何归一)。
成本回填按商品编号(序列号,一物一码)逐级回查:① stock_in_items 同店同编号入库价 → ② inventories.unit_price 库存快照价(快照列或经 product 关联)→ ③ products.purchase_price;均查不到 → 0(成本待定)。与库存列表展示链 COALESCE(sii.cost_price, inv.unit_price, p.purchase_price) 同序。
利润重算涉及单据 profit_totalservice.recalcStockOutProfit 同口径:Σ(sale_price>0 ? (sale-cost)×qty : 0)。成本待定行暂按全售价计(与系统既有"成本待定"语义一致)。
应收不动sale_total 保持源单头合计——财务应收/KPI 直接 SUM 单头列,修复对财务零影响、无需冲账。修复后校验 Σ明细售价小计 vs 单头合计,不一致只报不改。
安全机制默认 dry-run(事务内试算后回滚);--apply 才落库;幂等(已修行 sale_price>0 自动跳过,可重跑)。SQLite 内存库单测覆盖四种成本来源 / dry-run 回滚 / 幂等 / 非占位明细不波及 / 占位商品软删仍可定位。
+
# 用法(本机交叉编译 → scp 到 ali 宿主执行;jiu_mysql 容器映射 127.0.0.1:3306)
+cd backend && GOOS=linux GOARCH=amd64 go build -o /tmp/fix-history-prices ./cmd/fix-history-prices
+scp /tmp/fix-history-prices ali:/tmp/
+ssh ali '/tmp/fix-history-prices --shop-id 8 --env-file /opt/jiu/config/production.env'          # dry-run
+ssh ali '/tmp/fix-history-prices --shop-id 8 --env-file /opt/jiu/config/production.env --apply'  # 落库
+
+ +

四、执行记录与结果(2026-07-10)

+
+ + + + + + +
步骤结果
1. 全库备份(改库前)完成 本机 ~/jiu-db-backups/jiu_db_prefix_fix_20260710.sql.gz(2.8MB,gzip -t 校验通过)
2. dry-run待修 26,335 行 / 4,927 单;成本回填:入库价 25,139 / 库存价 32 / 商品进价 0 / 查无 1,164;合计不一致单 2 张
3. --apply 落库完成 数字与 dry-run 完全一致
4. 核验全绿 待修行剩 0;抽查 ZXZ027628:成本 13,600 / 售价 14,300 ✓;CK202606150001:应收 128,700 / 利润 22,900 ✓
+

成本回填命中率 95.5%(25,139/26,335 走入库明细价,最精确来源);1,164 行查无进价 = 该序列号在导出的历史窗口内无入库记录(更早的老库存),成本置 0 待定。

+
+ +

五、遗留脏数据清单(源数据问题,忠实保留,待业务决策)

+
+ + + + + + +
#问题影响建议处置
1数量脏 CK202403050009 行 ZXZ010370 茅台白皮2003 数量=50,902(该序列号入库仅 1 瓶),明细金额 2.62 亿该单 profit_total 虚高至 3,312 万;单头应收 542,950 正常业务核实真实数量(大概率 1)后单行修正并重算该单利润。同族问题见「装箱数当数量」脏数据(库存侧 784 行,另案待决策)
2合计差 CK202401170005 单头 95,400 vs 明细合计 95,500(差 100)轻微,应收按单头可不处理;或对账时人工核
3零额单 出库 47 张 / 入库 13 张 合计 ≤ 0可能是赠送/内部调拨/源缺失保留原貌
4成本待定 出库明细 1,164 行查无进价(cost=0);入库明细 59 行进价=0所在单利润暂按全售价计,偏高后续补录进价可自动纠正利润(确认进价/再跑回填)
+
+ +

六、回滚方式

+
+
回滚会连带覆盖备份时刻之后的所有新业务数据,仅在修复本身被证实有误时使用;局部纠错优先用幂等工具或单行 SQL。
+
# 全库恢复(备份 = 修复前全库快照)
+gunzip -c ~/jiu-db-backups/jiu_db_prefix_fix_20260710.sql.gz | ssh ali 'PW=...; docker exec -i -e MYSQL_PWD="$PW" jiu_mysql mysql -uroot jiu_db'
+

密码取法同 scripts/ci/backup-db.sh:从 /opt/jiu/config/production.envDATABASE_DSN 解析。

+
+ +

七、关联索引

+
+ +
+ + +