21aa2523b7
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
119 lines
9.1 KiB
HTML
119 lines
9.1 KiB
HTML
<!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>:进价必须 > 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>
|