Files
KCGL/db_migrations/add_stocktake_session.sql
yueli aeb852c64d feat(stocktake): 新增盘点会话表 StocktakeSession
盘点原本只有 stocktake_draft(草稿行),没有会话实体,导致三个问题:

1. 盲盘/明盘、全盘/抽盘这类**会话级配置**无处存放,塞进草稿表就要每行
   冗余一份,改一次配置得 UPDATE 上万行;
2. 「空会话」无法表示 —— 会话开了但还没扫码时草稿表里没有行,旧实现只能
   靠 max(scan_time) 猜哪个会话活跃,于是新开的空会话会被其他 PDA 忽略、
   反而加入上一轮的旧会话,多端协同直接分裂;
3. 没有状态字段,generate-missing 跑完后旧会话仍被当成活跃。

本表把活跃会话判定变成 company_name + status='active' 的精确查询。

迁移的要点:
- 唯一部分索引 uq_stocktake_session_one_active 保证「一个公司同时只有一个
  活跃会话」,从数据库层挡住多台 PDA 并发开启导致的进度分裂;
  因此 /draft/start-new 必须先把原有活跃会话置为 finished 再插入新记录。
- CHECK 约束限定 mode/scope_type/status 的取值域。
- 不回填存量:旧草稿没有对应会话行,部署后显示「无进行中的盘点」,数据不丢。

执行: docker exec -i inventory_db psql -U test -d inventory_system < db_migrations/add_stocktake_session.sql
2026-09-11 13:28:43 +08:00

58 lines
2.8 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.

-- =============================================================================
-- 一次性迁移盘点会话表stocktake_session
--
-- 背景
-- 盘点原本只有 stocktake_draft草稿行没有任何「会话」实体导致
-- 1. 盲盘/明盘、全盘/抽盘这类**会话级配置**无处存放;
-- 2. 「空会话」无法表示 —— 会话开了但还没扫码时表里没有行,
-- 旧实现只能靠 max(scan_time) 猜哪个会话活跃,于是新开的空会话
-- 会被其他 PDA 忽略、反而加入上一轮的旧会话;
-- 3. 没有状态字段,「已结束」的会话仍会被当成活跃会话返回。
--
-- 本表把活跃会话判定变成 company_name + status='active' 的精确查询。
--
-- 唯一部分索引
-- 保证「一个公司同时只能有一个 status='active' 的会话」。
-- 多台 PDA 同时点「开启新盘点」时,后到的那条会在数据库层被挡住,
-- 不会产生两个并行活跃会话导致进度分裂。
-- 因此 /draft/start-new 必须先把该公司原有的活跃会话置为 finished
-- 再插入新记录(同一事务内完成)。
--
-- 存量数据
-- 不做回填。stocktake_draft 里的历史会话没有对应的会话行,
-- 部署后会显示为「无进行中的盘点」,旧草稿仍留在原表不受影响。
--
-- 执行: docker exec -i inventory_db psql -U test -d inventory_system < 本文件
-- =============================================================================
BEGIN;
CREATE TABLE IF NOT EXISTS stocktake_session (
session_id varchar(100) PRIMARY KEY,
company_name varchar(255) NOT NULL,
-- open=明盘(可看账面数) / blind=盲盘(隐藏账面数与差异)
mode varchar(20) NOT NULL DEFAULT 'open',
-- full=全仓盘点 / active=活跃库位抽盘
scope_type varchar(20) NOT NULL DEFAULT 'full',
-- 范围明细,例: {"days":30,"top_n":50,"locations":[...],"uuids":[...]}
scope_config jsonb NOT NULL DEFAULT '{}'::jsonb,
status varchar(20) NOT NULL DEFAULT 'active',
created_by varchar(100),
created_at timestamp without time zone DEFAULT timezone('Asia/Shanghai', now()),
finished_at timestamp without time zone,
CONSTRAINT ck_stocktake_session_mode CHECK (mode IN ('open', 'blind')),
CONSTRAINT ck_stocktake_session_scope CHECK (scope_type IN ('full', 'active')),
CONSTRAINT ck_stocktake_session_status CHECK (status IN ('active', 'finished'))
);
-- 活跃会话查询的主路径
CREATE INDEX IF NOT EXISTS ix_stocktake_session_company_status
ON stocktake_session(company_name, status);
-- ★ 一个公司同时只能有一个活跃会话
CREATE UNIQUE INDEX IF NOT EXISTS uq_stocktake_session_one_active
ON stocktake_session(company_name)
WHERE status = 'active';
COMMIT;