Files
jiu/docs/design/stock-in-cost-confirm-design.html
T
2026-06-23 20:07:50 +08:00

119 lines
9.1 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>暂估价 → 确认进价 设计 — 酒库管理系统</title>
<style>
:root{
--primary:#2563AC; --primary-dark:#154072; --danger:#D14343; --danger-bg:#FDECEC;
--success:#2E8B57; --warn:#B45309; --accent:#8B2331;
--ink:#232934; --muted:#6E7888; --border:#DCE2EB; --paper:#F5F7FA; --head:#F0F4FF;
}
*{box-sizing:border-box;font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;}
body{margin:0;background:var(--paper);color:var(--ink);padding:28px;line-height:1.65;}
h1{font-size:20px;margin:0 0 4px;}
h2{font-size:16px;margin:26px 0 8px;color:var(--primary-dark);border-left:4px solid var(--primary);padding-left:10px;}
h3{font-size:14px;margin:18px 0 6px;color:var(--accent);}
.sub{color:var(--muted);font-size:13px;margin-bottom:18px;}
.card{background:#fff;border:1px solid var(--border);border-radius:10px;padding:16px 20px;max-width:920px;margin-bottom:16px;}
p{font-size:14px;margin:6px 0;}
code{font-family:ui-monospace,Menlo,monospace;font-size:12.5px;background:#EEF2F8;padding:1px 5px;border-radius:4px;color:var(--primary-dark);}
ol,ul{font-size:14px;margin:6px 0;padding-left:22px;}
li{margin:4px 0;}
table{width:100%;border-collapse:collapse;font-size:13px;max-width:920px;margin:8px 0;}
th{background:var(--head);color:var(--primary-dark);font-weight:600;font-size:12px;text-align:left;padding:9px 10px;border:1px solid var(--border);}
td{padding:9px 10px;border:1px solid #EEF1F5;vertical-align:top;}
.tag{font-size:11px;padding:2px 8px;border-radius:10px;display:inline-block;}
.tag.ok{background:#E6F3EC;color:var(--success);}
.tag.warn{background:#FFF4E5;color:var(--warn);}
.tag.no{background:var(--danger-bg);color:var(--danger);}
.lead{font-size:14px;background:#F0F6FF;border-left:3px solid var(--primary);padding:10px 14px;border-radius:4px;max-width:920px;}
.flow{font-family:ui-monospace,Menlo,monospace;font-size:13px;background:#1d2430;color:#e6edf6;padding:14px 18px;border-radius:8px;max-width:920px;overflow:auto;}
.flow .c{color:#7fd1a0;}
</style>
</head>
<body>
<h1>暂估价(0 价)→ 确认进价 · 设计方案</h1>
<div class="sub">调货「先卖后定价」场景 · 分支 feature/jiu_20260623 · 2026-06-23</div>
<div class="lead">
从朋友处调货,价格未定时以 <b>进价 0(暂估)</b> 入库并照常审核、出库售卖;价格确定后,对该入库单执行
<b>「确认进价」</b>——系统<b>前向补偿</b>真实成本(不反审核、不撤销任何已发生的动作):回填库存成本、已出库成本快照,
并按差额补一条应付流水。售价 / 应收完全不受影响。
</div>
<h2>1. 背景与取舍</h2>
<div class="card">
<p>老系统做法是「入库价填 0 → 出库 → <b>反审核</b>入库单改价 → 重审」。新系统已审核单据只读、无反审核、流水不可变、库存成本一旦落地无修改接口——强行复刻反审核在「货已出库」时几乎无法回滚干净,且违反现有不可变设计。</p>
<p>故改用会计上的「暂估入库 → 实际成本确认」模型:<b>不反审核</b>,以一次前向调整补齐成本与应付。关键前提是新系统的<b>序列号数据模型</b>让这件事变得精确——</p>
<ul>
<li><b>product ↔ 入库明细 1:1</b>:每行入库新建一个独立 product(序列号),出库明细也带 <code>product_id</code>。因此「某次调货的成本」就是「某个 product 的成本」,可凭 <code>product_id</code> 精确回填到它的所有已出库行,<b>无需额外的批次消耗映射表</b></li>
<li><b>库存成本天然可空</b><code>inventories.unit_price</code><code>*float64</code>0 价入库即存 <code>NULL</code><b>天然表示「待定价」</b></li>
</ul>
<p>用户确认的口径:①不填预估、直接填 0;②确认后补成本 + 补应付流水;③调货<b></b>单独建单据类型,复用普通入库单;④利润统计遇到无进价的行须跳过并单独说明。</p>
<p><b>简化决策</b>:不新增 schema 字段(遵循「禁止为每个业务字段改表结构」)。约定 <b>成本待定 = 成本为 0 / <code>unit_price</code> 为 NULL</b>。调货白酒成本不会真为 0,歧义可忽略。</p>
</div>
<h2>2. 核心动作:确认进价</h2>
<div class="card">
<p>接口:<code>POST /api/v1/stock-in/orders/:id/confirm-cost</code>body <code>{"items":[{"item_id":N,"unit_price":50.0}]}</code>
<b>approved</b> 单可确认(draft 直接编辑即可);权限 <b>管理员/超管</b>;后端实现 <code>StockService.ConfirmStockInCost</code></p>
<p>一个事务内,对每个有变化的明细(旧价→新价,差额 <code>diff = (new-old)×qty</code>)前向写补偿:</p>
<div class="flow">
1. <span class="c">入库明细</span> StockInItem.unit_price / total_price 改为真实值
2. <span class="c">剩余库存</span> Inventory.unit_price NULL → 真实值 (按 stock_in_item_id;已全出则无行)
3. <span class="c">已出库行</span> StockOutItem.unit_price / total_price 回填 (按 product_id;不动 sale_price)
4. <span class="c">入库单总额</span> StockInOrder.total_amount += Σ diff
5. <span class="c">应付差额</span> 新增 FinanceRecord{type:payable, amount:Σdiff, ref_type:stock_in_cost_adjust}
6. <span class="c">参考进价</span> Product.purchase_price 同步(按 product_id
</div>
<p><b>幂等 / 可重复</b>:以「旧价→新价」差额计算,多次修正各补一条差额流水(如 0→50 补 +500,再 50→60 补 +100)。</p>
<p><b>应付余额语义</b><code>balance</code> 是该往来单位「应收 + 应付」混合的滚动总账(沿用既有 <code>partnerLastBalance</code>),确认流水在此基础上累加差额。</p>
</div>
<h2>3. 影响矩阵</h2>
<table>
<tr><th>数据</th><th>0 价入库审核后</th><th>确认进价后</th></tr>
<tr><td>入库明细 unit_price</td><td>0</td><td><span class="tag ok">→ 真实价</span></td></tr>
<tr><td>库存批次 unit_price</td><td>NULL(待定)</td><td><span class="tag ok">→ 真实价</span></td></tr>
<tr><td>已出库行 unit_price(成本快照)</td><td>0</td><td><span class="tag ok">→ 真实价(按 product_id</span></td></tr>
<tr><td>出库 sale_price / 应收</td><td>正常</td><td><span class="tag ok">不变</span></td></tr>
<tr><td>入库单 total_amount</td><td>0</td><td><span class="tag ok">→ Σ 真实小计</span></td></tr>
<tr><td>对供应商应付</td><td>+0</td><td><span class="tag ok">补一条 +差额 流水</span></td></tr>
</table>
<h2>4. 边界</h2>
<div class="card">
<ul>
<li><b>未出库就确认</b>:只走第 1/2/4/5/6 步(无已出库行可回填)。</li>
<li><b>部分出库</b>:剩余批次更新 + 已出行回填,各自按 <code>product_id</code> 命中。</li>
<li><b>退货</b><code>returned_quantity</code>):按现有快照同样回填 <code>unit_price</code>,退货金额逻辑不变;如需特殊冲减再议。</li>
<li><b>历史导入单</b>(共享占位 product<code>product_id=0</code>):不向出库行/参考进价传播(避免误伤),仅改本单明细与库存。</li>
<li><b>校验</b>:进价必须 &gt; 0;非管理员 403;非 approved 单 400。</li>
</ul>
</div>
<h2>5. 前端</h2>
<div class="card">
<ul>
<li><b>待定价标注</b>:库存列表、入库单明细单价/金额为 0/NULL 时显示「待定价」(橙色),而非 <code>-</code><code>¥0.00</code></li>
<li><b>确认进价入口</b>:入库单详情弹窗中,单据为 <b>已审核</b> 且含待定行、且当前用户为管理员时,显示「确认进价」按钮 → 弹窗逐行填真实进价 → 调接口 → 刷新详情与列表。</li>
</ul>
</div>
<h2>6. 利润口径(前瞻约定)</h2>
<div class="card">
<p>系统当前<b></b>利润/毛利报表,本次<b></b>新建(YAGNI)。约定:<b>未来任何利润/毛利统计,须排除成本待定(<code>unit_price ≤ 0 / NULL</code>)的出库行,并单独列「N 笔成本待定,未计入利润」。</b> 本设计的「待定价」标识即为该口径提供可识别依据。</p>
</div>
<h2>7. 关键文件</h2>
<div class="card">
<ul>
<li>后端:<code>backend/internal/service/stock.go</code><code>ConfirmStockInCost</code> + <code>CostConfirmItem</code>)、<code>internal/handler/stock_in.go</code><code>ConfirmCost</code>)、<code>internal/router/router.go</code><code>internal/handler/stock_cost_confirm_test.go</code>(4 用例:回填+应付 / 403 / 非 approved / 重复确认)。</li>
<li>前端:<code>client/lib/repositories/stock_in_repository.dart</code><code>confirmCost</code>)、<code>client/lib/screens/stock_in/stock_in_list_screen.dart</code>(确认进价弹窗 + 待定价)、<code>client/lib/screens/inventory/inventory_list_screen.dart</code>(待定价)。</li>
</ul>
</div>
</body>
</html>