Files
KCGL/db_migrations/add_scan_draft.sql
yueli ec66c33b06 feat(scan-draft): 扫码草稿表与接口,支持暂停后继续扫码
场景
----
扫码出库/借库的作业可能很长(一张单几十项),工人常需中途暂停去处理
更紧急的单据。改造前切换单据会清空已扫内容,刷新/退出页面则全部丢失。

由于库存在申请审批通过时已**预占**,暂停期间货不会被他人抢走 ——
因此草稿只记录「扫到哪了」,**不涉及任何库存操作**。即使草稿丢失也只是
需要重扫,不会造成库存错乱。

隔离粒度
--------
按 (user_id, biz_type, request_id) 一人一单:每个人扫自己的草稿,互不影响;
同一人可同时持有多张单据的草稿(正是「暂停 A 去出 B」的场景)。
user_id 一律取自 JWT,不接受入参覆盖,故不可能读写他人草稿。

为什么整单存一个 JSON(而非每条明细一行)
------------------------------------------
1. 保存是「全量覆盖」语义,逐行存无增量更新的收益;
2. 恢复时需要物料名称/规格/库位等展示字段,逐行方案只能回查申请单的
   items_json —— 而历史单据的 items_json 不含 stock_id,回查会错配。
   整单快照把展示字段一并存下,恢复零依赖,对老单据同样可靠。

接口
----
GET    /api/v1/scan-draft           读取草稿
POST   /api/v1/scan-draft           保存(全量覆盖;空清单则删除)
DELETE /api/v1/scan-draft           清除(提交成功后调用)
GET    /api/v1/scan-draft/overview  各单据进度,供下拉徽标

实现要点
--------
· items_json 列是 jsonb,模型必须用 db.JSON —— 用 db.Text 会让 psycopg2
  拿到 Python list/dict 时无法适配,报 "can't adapt type 'dict'",而异常
  被接口的 except 吞掉后 POST 仍返回"成功",问题极难发现(开发中实际踩到);
· 概览接口做防御:残留的空草稿不参与展示。

实测:保存/读回、跨单据隔离(B 读 A 的草稿为 0 项)、全量覆盖、
      提交后清除,全部符合预期。
2026-09-10 17:21:14 +08:00

43 lines
2.2 KiB
PL/PgSQL
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.

-- =============================================================================
-- 扫码草稿表scan_draft
--
-- 背景
-- 扫码出库/借库的作业过程可能很长(一张单几十项),工人常需中途暂停去处理
-- 更紧急的单据。改造前切换单据会直接清空已扫内容,刷新/退出页面更是全部丢失。
--
-- 库存已在申请审批通过时**预占**,暂停期间不会被他人抢走 —— 因此草稿只需
-- 记录「扫到哪了」,不涉及任何库存操作,纯状态保存。
--
-- 隔离粒度:(user_id, biz_type, request_id)
-- 一人一单 —— 每个人扫自己的草稿,互不影响;同一人可同时持有多张单据的草稿。
--
-- 为什么整单存一个 JSON而非每条明细一行
-- 1. 保存是「全量覆盖」语义,逐行存并无增量更新的收益;
-- 2. 恢复时需要物料名称/规格/库位等展示字段,逐行存方案只能回查申请单
-- 的 items_json —— 而历史单据的 items_json 不含 stock_id回查会错配。
-- 整单快照把展示字段一并存下,恢复时零依赖、对老单据同样可靠。
--
-- 执行: docker exec -i inventory_db psql -U test -d inventory_system < 本文件
-- =============================================================================
BEGIN;
DROP TABLE IF EXISTS scan_draft;
CREATE TABLE scan_draft (
id SERIAL PRIMARY KEY,
user_id integer NOT NULL,
biz_type varchar(20) NOT NULL, -- outbound / borrow
request_id integer NOT NULL, -- 关联的申请单ID
request_no varchar(100), -- 单号快照,便于展示免联表
-- 整单快照:[{stock_id, source_table, sku, name, spec_model,
-- warehouse_location, available_quantity, out_quantity, price}, ...]
items_json jsonb NOT NULL DEFAULT '[]'::jsonb,
updated_at timestamp without time zone NOT NULL DEFAULT timezone('Asia/Shanghai', now()),
created_at timestamp without time zone NOT NULL DEFAULT timezone('Asia/Shanghai', now()),
CONSTRAINT uq_scan_draft UNIQUE (user_id, biz_type, request_id)
);
CREATE INDEX IF NOT EXISTS ix_scan_draft_lookup ON scan_draft(user_id, biz_type);
COMMIT;