盘点原本只有 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
58 lines
2.8 KiB
PL/PgSQL
58 lines
2.8 KiB
PL/PgSQL
-- =============================================================================
|
||
-- 一次性迁移:盘点会话表(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;
|