库存三态改造:在售 / 库存 / 已售

2026-07-07 设计 · 用户需求:库存行持久化三态 + 公开酒单只挂「在售」+ 卖光留痕「已售」+ 抽屉 switch 切换(桌面/移动)

一、现状与目标

现状:库存「状态」是显示时派生的(在售/预警/缺货 = 数量 vs 安全库存),数据库无字段;出库扣减到 0 的批次被软删除,从列表彻底消失;公开店铺页展示全部有库存商品(3841 件全裸奔)。

目标:状态落库为三态,门店可控地把商品挂上/撤下公开酒单,卖光的序列号留痕可查。

状态DB 值语义进入方式公开酒单
库存stock在库、不对外展示(默认)入库审核默认 / switch 撤售 / 退单恢复不显示
在售on_sale在库、公开酒单可见抽屉 switch 手动挂售显示
已售sold出库卖光,留痕台账出库审核扣减到 0 自动置入(不再软删不显示

流转:stock ⇄ on_sale(手动)→ sold(出库审核自动,不可手动进出);出库退单恢复数量时置回 stock(保守,需重新手动挂售)。盘点盘亏归零维持现状软删(灭失≠售出,不算已售)。

⚠️ 上线即时影响,需确认:存量 3841 行全部迁移为「库存」(你拍板的口径)→ 公开店铺页上线瞬间清空,之后靠门店逐个把商品切到「在售」才会出现在酒单里。这是预期行为(酒单从「全量裸奔」变「人工精选」),但要提前告知门店有这步运营动作。

二、数据模型

三、后端改动

#位置改动
1model/stock.go + schema/schema.sqlInventory 加 Status 字段;docs/db-schema.html 同步
2service/stock.go:414(出库 FIFO)批次扣到 0:quantity=0 + status='sold'不再打 deleted_at;部分扣减不改状态
3service/stock.go(出库退单)恢复数量的行若为 sold → 置回 stock(并清 quantity 归还)
4新接口PUT /api/v1/inventory/:id/status,body {"status":"on_sale"|"stock"}——只收这两值(sold 拒 400);shop_id 归属校验;受 ReadOnly() 保护,operator 可用(店员挂酒单是日常操作)
5库存 List返回 status;新增 status= 筛选参数(可多值逗号分隔);默认排除已售(2026-07-07 用户拍板:仅点「已售」KPI 卡或状态筛选选已售时才显示),显式传 status=sold 才返回已售行
6库存 Summarysku_count / in_stock_qty / stock_value / 月初快照 一律排除 sold;新增 sold_countshortage_count(qty≤0 未删行)语义被 sold 取代 → 保留字段但预期归零
7公开接口 + SSR 店铺页ListShopProducts/queryShopProducts 的库存子查询加 status='on_sale'(qty>0 保留)——公开酒单只剩在售;商品单页不过滤(历史标签扫码永远可看,已售商品批次块自然消失)

四、设计系统(L1 先行——switch 组件不存在)

  1. 原型 design/prototype/atoms.css 登记 .switch 组件(38×22 pill 轨道 + 圆点滑块,on=--primary、off=--border,禁用态 faint;全走 tokens)+ index.html 组件清单登记;mobile-atoms.css 如需尺寸变体一并登记。
  2. Flutter 对照新建 client/lib/widgets/ds/ds_switch.dart(颜色单源过 check_ds_code 闸)。
  3. 状态徽章三态色复用现有 DsBadge tone:在售=ok 绿、库存=info 蓝;「已售」需中性灰 tone——确认 DsBadge 有无 neutral,无则原型 badge 先补 neutral 变体再同步。
  4. 原型 inventory.html / m-inventory.html(同步态屏)状态列/徽章文案同步为三态。

五、前端改动(client)

#位置改动
1models/inventory.dartstatus 解析(缺省 'stock' 容错旧响应)
2库存列表(桌面+移动)状态列/卡片徽章改三态显示;状态筛选改「全部/在售/库存/已售」走后端参数;预警/缺货退出状态列——低于安全库存仍由数量列染色(既有 qty.low)承担;KPI 第四卡「缺货预警」改为「已售」(主数 sold_count,点击筛选已售;副行保留「需补货 N 项」预警数)
3product_editor_drawer.dart「状态」行 = 三态徽章 + 徽章后紧跟 DsSwitch(在售⇄库存);已售时只显示灰徽章无 switch;切换调 PUT 接口,成功即时更新徽章并刷新列表;readonly 用户 WriteGuard 禁用。桌面 drawer 与移动 sheet 同组件,一处生效
4_openEditor 调用传入库存行 id 与 status(switch 操作对象=该 product 的库存行;序列号模型下 1 product≈1 行)
5golden库存列表桌面/移动 + 抽屉相关 golden 重录(管理员形态)

六、兼容与发版顺序

  1. server-v1.1.10 先发:AutoMigrate 加列默认 stock;旧 client 无 switch、仍显示旧派生状态(升级窗口内两套口径并存,可接受);公开酒单即刻只剩在售(上线即空,见顶部提醒)。
  2. client-v1.1.6 跟发:三态列表 + 抽屉 switch。
  3. 回滚:server 回退上一 tag——新列留在库里无害;已置 sold 的行回退后在旧逻辑下表现为「qty=0 未删」即旧「缺货」态,数据不损。

七、测试与验收

八、明确不做(本期)