feat(consignment): 收敛寄卖迁移并调整发布页价格区布局
- 142–148 收敛为 142_consignment_platform,旧账本自动清理 - 价格三卡保持一行等宽,其余发布页布局回退 - 修复发布 tab 空白并统一价格文案为系统建议价
This commit is contained in:
@@ -25,24 +25,38 @@ const MIGRATIONS_TABLE = 'schema_migrations'
|
||||
const SHORT_NUMERIC_PREFIX_MESSAGE = /^Can't determine timestamp for \d+$/
|
||||
const MIGRATION_FILE_PATTERN = /^(\d{3})_.+\.sql$/i
|
||||
/**
|
||||
* 合并 main 后寄卖迁移编号顺延:
|
||||
* 合并 main 后寄卖迁移编号顺延,以及 142–148 收敛为单一文件:
|
||||
* 129-131 -> 135-137(main 占用 129-134)
|
||||
* 135-137 -> 142-144(main 又占用 135-141)
|
||||
* 142-148 -> 142_consignment_platform(2026-11-06 合并为单文件)
|
||||
* 已跑过旧编号的环境要清掉旧记录,让新编号按正常顺序重跑(迁移本身幂等)。
|
||||
*/
|
||||
const RENAMED_MIGRATIONS: Array<{ legacyName: string; currentName: string }> = [
|
||||
{ legacyName: '129_consignment_matching', currentName: '142_consignment_matching' },
|
||||
{ legacyName: '135_consignment_matching', currentName: '142_consignment_matching' },
|
||||
{ legacyName: '130_consignment_dynamic_specs', currentName: '143_consignment_dynamic_specs' },
|
||||
{ legacyName: '136_consignment_dynamic_specs', currentName: '143_consignment_dynamic_specs' },
|
||||
{ legacyName: '129_consignment_matching', currentName: '142_consignment_platform' },
|
||||
{ legacyName: '135_consignment_matching', currentName: '142_consignment_platform' },
|
||||
{ legacyName: '130_consignment_dynamic_specs', currentName: '142_consignment_platform' },
|
||||
{ legacyName: '136_consignment_dynamic_specs', currentName: '142_consignment_platform' },
|
||||
{
|
||||
legacyName: '131_consignment_surnan_default_profile',
|
||||
currentName: '144_consignment_surnan_default_profile',
|
||||
currentName: '142_consignment_platform',
|
||||
},
|
||||
{
|
||||
legacyName: '137_consignment_surnan_default_profile',
|
||||
currentName: '144_consignment_surnan_default_profile',
|
||||
currentName: '142_consignment_platform',
|
||||
},
|
||||
{ legacyName: '142_consignment_matching', currentName: '142_consignment_platform' },
|
||||
{ legacyName: '143_consignment_dynamic_specs', currentName: '142_consignment_platform' },
|
||||
{
|
||||
legacyName: '144_consignment_surnan_default_profile',
|
||||
currentName: '142_consignment_platform',
|
||||
},
|
||||
{
|
||||
legacyName: '145_consignment_inventory_multi_binding',
|
||||
currentName: '142_consignment_platform',
|
||||
},
|
||||
{ legacyName: '146_consignment_share_gift', currentName: '142_consignment_platform' },
|
||||
{ legacyName: '147_consignment_match_and_rematch', currentName: '142_consignment_platform' },
|
||||
{ legacyName: '148_consignment_remove_zone_help_text', currentName: '142_consignment_platform' },
|
||||
]
|
||||
|
||||
export async function runDatabaseMigrations() {
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
-- 142_consignment_matching.sql
|
||||
-- 寄卖配置化 + 库存数量/售价 + 内部匹配/自动匹配 + 拼单库存分配。
|
||||
-- 全部语句幂等,可安全重跑;不改动历史迁移。
|
||||
|
||||
-- ===========================================================================
|
||||
-- 1. 分类级寄卖配置(参考 order_service_profiles 的草稿/发布版本模式)
|
||||
-- ===========================================================================
|
||||
CREATE TABLE IF NOT EXISTS consignment_profiles (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
category_id BIGINT NOT NULL REFERENCES work_categories(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
title TEXT NOT NULL DEFAULT '',
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
tab_title TEXT NOT NULL DEFAULT '',
|
||||
config_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
status TEXT NOT NULL DEFAULT 'draft',
|
||||
current_version_id BIGINT,
|
||||
created_by_admin_user_id BIGINT REFERENCES admin_users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT consignment_profiles_status_check
|
||||
CHECK (status IN ('draft', 'published', 'archived'))
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_consignment_profiles_category
|
||||
ON consignment_profiles (category_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_profiles_status
|
||||
ON consignment_profiles (status);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS consignment_profile_versions (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
profile_id BIGINT NOT NULL REFERENCES consignment_profiles(id) ON DELETE CASCADE,
|
||||
version_no INTEGER NOT NULL,
|
||||
config_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
published_by_admin_user_id BIGINT REFERENCES admin_users(id) ON DELETE SET NULL,
|
||||
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT uq_consignment_profile_versions_no UNIQUE (profile_id, version_no)
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'fk_consignment_profiles_current_version'
|
||||
) THEN
|
||||
ALTER TABLE consignment_profiles
|
||||
ADD CONSTRAINT fk_consignment_profiles_current_version
|
||||
FOREIGN KEY (current_version_id)
|
||||
REFERENCES consignment_profile_versions(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 2. 提报与库存:配置快照、数量、寄卖售价
|
||||
-- ===========================================================================
|
||||
ALTER TABLE consignment_submissions
|
||||
ADD COLUMN IF NOT EXISTS profile_id BIGINT REFERENCES consignment_profiles(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS profile_version_id BIGINT
|
||||
REFERENCES consignment_profile_versions(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS profile_snapshot_json JSONB,
|
||||
ADD COLUMN IF NOT EXISTS quantity INTEGER NOT NULL DEFAULT 1,
|
||||
ADD COLUMN IF NOT EXISTS requested_sale_price_fen BIGINT NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE consignment_inventories
|
||||
ADD COLUMN IF NOT EXISTS profile_id BIGINT REFERENCES consignment_profiles(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS profile_version_id BIGINT
|
||||
REFERENCES consignment_profile_versions(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS profile_snapshot_json JSONB,
|
||||
ADD COLUMN IF NOT EXISTS quantity INTEGER NOT NULL DEFAULT 1,
|
||||
ADD COLUMN IF NOT EXISTS remaining_quantity INTEGER NOT NULL DEFAULT 1,
|
||||
ADD COLUMN IF NOT EXISTS sale_price_fen BIGINT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS listed_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS price_updated_at TIMESTAMPTZ;
|
||||
|
||||
-- 存量库存:一件一条,剩余数量 = 总数量,寄卖售价回填收购价(后续可单独改价)。
|
||||
UPDATE consignment_inventories
|
||||
SET remaining_quantity = GREATEST(0, COALESCE(quantity, 1)),
|
||||
sale_price_fen = CASE WHEN COALESCE(sale_price_fen, 0) = 0 THEN COALESCE(cost_price_fen, 0) ELSE sale_price_fen END,
|
||||
listed_at = COALESCE(listed_at, inbound_at)
|
||||
WHERE remaining_quantity IS DISTINCT FROM GREATEST(0, COALESCE(quantity, 1))
|
||||
OR listed_at IS NULL
|
||||
OR (COALESCE(sale_price_fen, 0) = 0 AND COALESCE(cost_price_fen, 0) > 0);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_inventories_profile_matching
|
||||
ON consignment_inventories (profile_id, status, allow_matching, remaining_quantity)
|
||||
WHERE status = 'in_stock';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_inventories_worker_active
|
||||
ON consignment_inventories (worker_id, status, remaining_quantity)
|
||||
WHERE worker_id IS NOT NULL;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 3. 工单:寄卖配置、内部匹配截止、大厅可见时间
|
||||
-- ===========================================================================
|
||||
ALTER TABLE work_orders
|
||||
ADD COLUMN IF NOT EXISTS consignment_profile_id BIGINT
|
||||
REFERENCES consignment_profiles(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS internal_match_deadline_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS hall_visible_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS internal_match_attempts INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_work_orders_internal_matching
|
||||
ON work_orders (internal_match_deadline_at)
|
||||
WHERE status = 'internal_matching';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_work_orders_hall_visible
|
||||
ON work_orders (hall_visible_at)
|
||||
WHERE hall_visible_at IS NOT NULL;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 4. 库存-工单分配(一个工单/份额可占用一条或多条库存)
|
||||
-- ===========================================================================
|
||||
CREATE TABLE IF NOT EXISTS consignment_inventory_allocations (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
inventory_id BIGINT NOT NULL REFERENCES consignment_inventories(id) ON DELETE CASCADE,
|
||||
work_order_id BIGINT NOT NULL REFERENCES work_orders(id) ON DELETE CASCADE,
|
||||
work_order_share_id BIGINT REFERENCES work_order_shares(id) ON DELETE SET NULL,
|
||||
quantity INTEGER NOT NULL DEFAULT 1,
|
||||
unit_price_fen BIGINT NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'reserved',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT consignment_inventory_allocations_status_check
|
||||
CHECK (status IN ('reserved', 'consumed', 'released')),
|
||||
CONSTRAINT consignment_inventory_allocations_quantity_check
|
||||
CHECK (quantity > 0)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_allocations_work_order
|
||||
ON consignment_inventory_allocations (work_order_id, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_allocations_inventory
|
||||
ON consignment_inventory_allocations (inventory_id, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_allocations_share
|
||||
ON consignment_inventory_allocations (work_order_share_id)
|
||||
WHERE work_order_share_id IS NOT NULL;
|
||||
|
||||
ALTER TABLE work_order_shares
|
||||
ADD COLUMN IF NOT EXISTS consignment_inventory_id BIGINT
|
||||
REFERENCES consignment_inventories(id) ON DELETE SET NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_work_order_shares_consignment_inventory
|
||||
ON work_order_shares (consignment_inventory_id)
|
||||
WHERE consignment_inventory_id IS NOT NULL;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 5. 注释
|
||||
-- ===========================================================================
|
||||
COMMENT ON TABLE consignment_profiles IS
|
||||
'分类级寄卖配置:打手端 tab、发布字段、价格、保证金、服务费、数量限制、匹配与超时策略';
|
||||
COMMENT ON TABLE consignment_profile_versions IS
|
||||
'寄卖配置发布版本快照;提报/库存/工单固化 profile_version_id,后续改配置不影响在途数据';
|
||||
COMMENT ON COLUMN consignment_inventories.quantity IS
|
||||
'该寄卖批次总数量;同批次共享卖家账号资料与截图';
|
||||
COMMENT ON COLUMN consignment_inventories.remaining_quantity IS
|
||||
'剩余可匹配数量;部分匹配后继续保留 in_stock,扣到 0 才 consumed';
|
||||
COMMENT ON COLUMN consignment_inventories.sale_price_fen IS
|
||||
'寄卖售价(分):匹配工单时的结算价,可由打手/后台修改';
|
||||
COMMENT ON COLUMN consignment_inventories.listed_at IS
|
||||
'开始计算未售出时长的时间点(审核入库/发布)';
|
||||
COMMENT ON COLUMN work_orders.internal_match_deadline_at IS
|
||||
'内部匹配截止时间;超过后调度任务把工单发回大厅';
|
||||
COMMENT ON COLUMN work_orders.hall_visible_at IS
|
||||
'大厅可见时间;NULL = 立即可见,未来时间 = 延迟进入大厅';
|
||||
COMMENT ON TABLE consignment_inventory_allocations IS
|
||||
'库存占用明细:整单履约一条,拼单时每个份额一条;释放时回补 remaining_quantity';
|
||||
COMMENT ON COLUMN work_order_shares.consignment_inventory_id IS
|
||||
'拼单份额对应的寄卖库存;非寄卖份额为 NULL';
|
||||
@@ -0,0 +1,645 @@
|
||||
-- 142_consignment_platform.sql
|
||||
-- 寄卖平台完整迁移(由 142–148 收敛,2026-11-06)
|
||||
-- 142 寄卖配置/库存/内部匹配
|
||||
-- 143 动态规格
|
||||
-- 144 苏尔南默认寄卖配置
|
||||
-- 145 拼单库存多绑定
|
||||
-- 146 份额级赠送
|
||||
-- 147 重新匹配与匹配日志
|
||||
-- 148 清理旧区服提示文案
|
||||
-- 全部语句幂等;新库直接执行本文件即可。
|
||||
|
||||
-- ===========================================================================
|
||||
-- 原 142_consignment_matching.sql
|
||||
-- ===========================================================================
|
||||
-- 142_consignment_matching.sql
|
||||
-- 寄卖配置化 + 库存数量/售价 + 内部匹配/自动匹配 + 拼单库存分配。
|
||||
-- 全部语句幂等,可安全重跑;不改动历史迁移。
|
||||
|
||||
-- ===========================================================================
|
||||
-- 1. 分类级寄卖配置(参考 order_service_profiles 的草稿/发布版本模式)
|
||||
-- ===========================================================================
|
||||
CREATE TABLE IF NOT EXISTS consignment_profiles (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
category_id BIGINT NOT NULL REFERENCES work_categories(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
title TEXT NOT NULL DEFAULT '',
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
tab_title TEXT NOT NULL DEFAULT '',
|
||||
config_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
status TEXT NOT NULL DEFAULT 'draft',
|
||||
current_version_id BIGINT,
|
||||
created_by_admin_user_id BIGINT REFERENCES admin_users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT consignment_profiles_status_check
|
||||
CHECK (status IN ('draft', 'published', 'archived'))
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_consignment_profiles_category
|
||||
ON consignment_profiles (category_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_profiles_status
|
||||
ON consignment_profiles (status);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS consignment_profile_versions (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
profile_id BIGINT NOT NULL REFERENCES consignment_profiles(id) ON DELETE CASCADE,
|
||||
version_no INTEGER NOT NULL,
|
||||
config_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
published_by_admin_user_id BIGINT REFERENCES admin_users(id) ON DELETE SET NULL,
|
||||
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT uq_consignment_profile_versions_no UNIQUE (profile_id, version_no)
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'fk_consignment_profiles_current_version'
|
||||
) THEN
|
||||
ALTER TABLE consignment_profiles
|
||||
ADD CONSTRAINT fk_consignment_profiles_current_version
|
||||
FOREIGN KEY (current_version_id)
|
||||
REFERENCES consignment_profile_versions(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 2. 提报与库存:配置快照、数量、寄卖售价
|
||||
-- ===========================================================================
|
||||
ALTER TABLE consignment_submissions
|
||||
ADD COLUMN IF NOT EXISTS profile_id BIGINT REFERENCES consignment_profiles(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS profile_version_id BIGINT
|
||||
REFERENCES consignment_profile_versions(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS profile_snapshot_json JSONB,
|
||||
ADD COLUMN IF NOT EXISTS quantity INTEGER NOT NULL DEFAULT 1,
|
||||
ADD COLUMN IF NOT EXISTS requested_sale_price_fen BIGINT NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE consignment_inventories
|
||||
ADD COLUMN IF NOT EXISTS profile_id BIGINT REFERENCES consignment_profiles(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS profile_version_id BIGINT
|
||||
REFERENCES consignment_profile_versions(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS profile_snapshot_json JSONB,
|
||||
ADD COLUMN IF NOT EXISTS quantity INTEGER NOT NULL DEFAULT 1,
|
||||
ADD COLUMN IF NOT EXISTS remaining_quantity INTEGER NOT NULL DEFAULT 1,
|
||||
ADD COLUMN IF NOT EXISTS sale_price_fen BIGINT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS listed_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS price_updated_at TIMESTAMPTZ;
|
||||
|
||||
-- 存量库存:一件一条,剩余数量 = 总数量,寄卖售价回填收购价(后续可单独改价)。
|
||||
UPDATE consignment_inventories
|
||||
SET remaining_quantity = GREATEST(0, COALESCE(quantity, 1)),
|
||||
sale_price_fen = CASE WHEN COALESCE(sale_price_fen, 0) = 0 THEN COALESCE(cost_price_fen, 0) ELSE sale_price_fen END,
|
||||
listed_at = COALESCE(listed_at, inbound_at)
|
||||
WHERE remaining_quantity IS DISTINCT FROM GREATEST(0, COALESCE(quantity, 1))
|
||||
OR listed_at IS NULL
|
||||
OR (COALESCE(sale_price_fen, 0) = 0 AND COALESCE(cost_price_fen, 0) > 0);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_inventories_profile_matching
|
||||
ON consignment_inventories (profile_id, status, allow_matching, remaining_quantity)
|
||||
WHERE status = 'in_stock';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_inventories_worker_active
|
||||
ON consignment_inventories (worker_id, status, remaining_quantity)
|
||||
WHERE worker_id IS NOT NULL;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 3. 工单:寄卖配置、内部匹配截止、大厅可见时间
|
||||
-- ===========================================================================
|
||||
ALTER TABLE work_orders
|
||||
ADD COLUMN IF NOT EXISTS consignment_profile_id BIGINT
|
||||
REFERENCES consignment_profiles(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS internal_match_deadline_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS hall_visible_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS internal_match_attempts INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_work_orders_internal_matching
|
||||
ON work_orders (internal_match_deadline_at)
|
||||
WHERE status = 'internal_matching';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_work_orders_hall_visible
|
||||
ON work_orders (hall_visible_at)
|
||||
WHERE hall_visible_at IS NOT NULL;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 4. 库存-工单分配(一个工单/份额可占用一条或多条库存)
|
||||
-- ===========================================================================
|
||||
CREATE TABLE IF NOT EXISTS consignment_inventory_allocations (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
inventory_id BIGINT NOT NULL REFERENCES consignment_inventories(id) ON DELETE CASCADE,
|
||||
work_order_id BIGINT NOT NULL REFERENCES work_orders(id) ON DELETE CASCADE,
|
||||
work_order_share_id BIGINT REFERENCES work_order_shares(id) ON DELETE SET NULL,
|
||||
quantity INTEGER NOT NULL DEFAULT 1,
|
||||
unit_price_fen BIGINT NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'reserved',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT consignment_inventory_allocations_status_check
|
||||
CHECK (status IN ('reserved', 'consumed', 'released')),
|
||||
CONSTRAINT consignment_inventory_allocations_quantity_check
|
||||
CHECK (quantity > 0)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_allocations_work_order
|
||||
ON consignment_inventory_allocations (work_order_id, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_allocations_inventory
|
||||
ON consignment_inventory_allocations (inventory_id, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_allocations_share
|
||||
ON consignment_inventory_allocations (work_order_share_id)
|
||||
WHERE work_order_share_id IS NOT NULL;
|
||||
|
||||
ALTER TABLE work_order_shares
|
||||
ADD COLUMN IF NOT EXISTS consignment_inventory_id BIGINT
|
||||
REFERENCES consignment_inventories(id) ON DELETE SET NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_work_order_shares_consignment_inventory
|
||||
ON work_order_shares (consignment_inventory_id)
|
||||
WHERE consignment_inventory_id IS NOT NULL;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 5. 注释
|
||||
-- ===========================================================================
|
||||
COMMENT ON TABLE consignment_profiles IS
|
||||
'分类级寄卖配置:打手端 tab、发布字段、价格、保证金、服务费、数量限制、匹配与超时策略';
|
||||
COMMENT ON TABLE consignment_profile_versions IS
|
||||
'寄卖配置发布版本快照;提报/库存/工单固化 profile_version_id,后续改配置不影响在途数据';
|
||||
COMMENT ON COLUMN consignment_inventories.quantity IS
|
||||
'该寄卖批次总数量;同批次共享卖家账号资料与截图';
|
||||
COMMENT ON COLUMN consignment_inventories.remaining_quantity IS
|
||||
'剩余可匹配数量;部分匹配后继续保留 in_stock,扣到 0 才 consumed';
|
||||
COMMENT ON COLUMN consignment_inventories.sale_price_fen IS
|
||||
'寄卖售价(分):匹配工单时的结算价,可由打手/后台修改';
|
||||
COMMENT ON COLUMN consignment_inventories.listed_at IS
|
||||
'开始计算未售出时长的时间点(审核入库/发布)';
|
||||
COMMENT ON COLUMN work_orders.internal_match_deadline_at IS
|
||||
'内部匹配截止时间;超过后调度任务把工单发回大厅';
|
||||
COMMENT ON COLUMN work_orders.hall_visible_at IS
|
||||
'大厅可见时间;NULL = 立即可见,未来时间 = 延迟进入大厅';
|
||||
COMMENT ON TABLE consignment_inventory_allocations IS
|
||||
'库存占用明细:整单履约一条,拼单时每个份额一条;释放时回补 remaining_quantity';
|
||||
COMMENT ON COLUMN work_order_shares.consignment_inventory_id IS
|
||||
'拼单份额对应的寄卖库存;非寄卖份额为 NULL';
|
||||
|
||||
-- ===========================================================================
|
||||
-- 原 143_consignment_dynamic_specs.sql
|
||||
-- ===========================================================================
|
||||
-- 143_consignment_dynamic_specs.sql
|
||||
-- 寄卖动态规格:提报/库存增加 spec_values_json、spec_key、spec_text 与改价上限。
|
||||
-- 全部语句幂等,可安全重跑;不改动历史迁移。
|
||||
|
||||
-- ===========================================================================
|
||||
-- 1. 提报:动态规格值、匹配 key、展示文本、改价上限
|
||||
-- ===========================================================================
|
||||
ALTER TABLE consignment_submissions
|
||||
ADD COLUMN IF NOT EXISTS spec_values_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS spec_key TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS spec_text TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS price_cap_fen BIGINT NOT NULL DEFAULT 0;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 2. 库存:同提报;入库后规格值继续随库存走
|
||||
-- ===========================================================================
|
||||
ALTER TABLE consignment_inventories
|
||||
ADD COLUMN IF NOT EXISTS spec_values_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS spec_key TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS spec_text TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS price_cap_fen BIGINT NOT NULL DEFAULT 0;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 3. 存量回填:内置 5 规格 -> JSON / key / 文本;改价上限取当前售价
|
||||
-- ===========================================================================
|
||||
UPDATE consignment_submissions
|
||||
SET
|
||||
spec_values_json = jsonb_strip_nulls(
|
||||
jsonb_build_object(
|
||||
'system', NULLIF(BTRIM(system), ''),
|
||||
'serverZone', NULLIF(BTRIM(server_zone), ''),
|
||||
'series', NULLIF(BTRIM(series), ''),
|
||||
'diamondLevel', diamond_level,
|
||||
'brillianceCount', brilliance_count
|
||||
)
|
||||
),
|
||||
spec_key = NULLIF(
|
||||
concat_ws(
|
||||
'|',
|
||||
CASE WHEN COALESCE(NULLIF(BTRIM(system), ''), '') <> '' THEN 'system=' || BTRIM(system) END,
|
||||
CASE WHEN COALESCE(NULLIF(BTRIM(server_zone), ''), '') <> '' THEN 'serverZone=' || BTRIM(server_zone) END,
|
||||
CASE WHEN COALESCE(NULLIF(BTRIM(series), ''), '') <> '' THEN 'series=' || BTRIM(series) END,
|
||||
CASE WHEN diamond_level IS NOT NULL THEN 'diamondLevel=' || diamond_level::text END,
|
||||
CASE WHEN brilliance_count IS NOT NULL THEN 'brillianceCount=' || brilliance_count::text END
|
||||
),
|
||||
''
|
||||
),
|
||||
spec_text = COALESCE(NULLIF(BTRIM(item_name), ''), ''),
|
||||
price_cap_fen = CASE
|
||||
WHEN COALESCE(requested_sale_price_fen, 0) > 0 THEN requested_sale_price_fen
|
||||
WHEN COALESCE(asking_price_fen, 0) > 0 THEN asking_price_fen
|
||||
ELSE 0
|
||||
END
|
||||
WHERE spec_key = '';
|
||||
|
||||
UPDATE consignment_inventories
|
||||
SET
|
||||
spec_values_json = jsonb_strip_nulls(
|
||||
jsonb_build_object(
|
||||
'system', NULLIF(BTRIM(system), ''),
|
||||
'serverZone', NULLIF(BTRIM(server_zone), ''),
|
||||
'series', NULLIF(BTRIM(series), ''),
|
||||
'diamondLevel', diamond_level,
|
||||
'brillianceCount', brilliance_count
|
||||
)
|
||||
),
|
||||
spec_key = NULLIF(
|
||||
concat_ws(
|
||||
'|',
|
||||
CASE WHEN COALESCE(NULLIF(BTRIM(system), ''), '') <> '' THEN 'system=' || BTRIM(system) END,
|
||||
CASE WHEN COALESCE(NULLIF(BTRIM(server_zone), ''), '') <> '' THEN 'serverZone=' || BTRIM(server_zone) END,
|
||||
CASE WHEN COALESCE(NULLIF(BTRIM(series), ''), '') <> '' THEN 'series=' || BTRIM(series) END,
|
||||
CASE WHEN diamond_level IS NOT NULL THEN 'diamondLevel=' || diamond_level::text END,
|
||||
CASE WHEN brilliance_count IS NOT NULL THEN 'brillianceCount=' || brilliance_count::text END
|
||||
),
|
||||
''
|
||||
),
|
||||
spec_text = COALESCE(NULLIF(BTRIM(item_name), ''), ''),
|
||||
price_cap_fen = COALESCE(sale_price_fen, 0)
|
||||
WHERE spec_key = '';
|
||||
|
||||
-- ===========================================================================
|
||||
-- 4. 索引:按配置 + 规格 key 快速筛选在库可匹配库存
|
||||
-- ===========================================================================
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_inventories_spec_key
|
||||
ON consignment_inventories (profile_id, spec_key)
|
||||
WHERE status = 'in_stock' AND allow_matching;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_submissions_spec_key
|
||||
ON consignment_submissions (profile_id, spec_key)
|
||||
WHERE status = 'pending_review';
|
||||
|
||||
-- ===========================================================================
|
||||
-- 5. 注释
|
||||
-- ===========================================================================
|
||||
COMMENT ON COLUMN consignment_submissions.spec_values_json IS
|
||||
'动态规格字段值(key -> 选项值/数字/文本),随配置版本快照固化';
|
||||
COMMENT ON COLUMN consignment_submissions.spec_key IS
|
||||
'参与匹配的规格字段稳定 key(按字段 key 排序的 key=value 串),用于候选筛选';
|
||||
COMMENT ON COLUMN consignment_submissions.spec_text IS
|
||||
'规格展示/搜索文本(字段名称 + 选项名称拼接)';
|
||||
COMMENT ON COLUMN consignment_submissions.price_cap_fen IS
|
||||
'打手改价上限(分):默认取接单模板价/系统建议价;0 = 不限制';
|
||||
COMMENT ON COLUMN consignment_inventories.spec_values_json IS
|
||||
'动态规格字段值(key -> 选项值/数字/文本),随配置版本快照固化';
|
||||
COMMENT ON COLUMN consignment_inventories.spec_key IS
|
||||
'参与匹配的规格字段稳定 key(按字段 key 排序的 key=value 串),用于候选筛选';
|
||||
COMMENT ON COLUMN consignment_inventories.spec_text IS
|
||||
'规格展示/搜索文本(字段名称 + 选项名称拼接)';
|
||||
COMMENT ON COLUMN consignment_inventories.price_cap_fen IS
|
||||
'打手改价上限(分):默认取接单模板价/系统建议价;0 = 不限制';
|
||||
|
||||
-- ===========================================================================
|
||||
-- 原 144_consignment_surnan_default_profile.sql
|
||||
-- ===========================================================================
|
||||
-- 144_consignment_surnan_default_profile.sql
|
||||
-- 苏尔南手枪默认寄卖配置:五个规格字段(与旧发布页一致),直接发布 v1。
|
||||
-- 幂等:该分类已有寄卖配置时不做任何事,避免覆盖运营已保存/已发布的配置。
|
||||
|
||||
DO $migration$
|
||||
DECLARE
|
||||
v_category_id BIGINT;
|
||||
v_profile_id BIGINT;
|
||||
v_version_id BIGINT;
|
||||
v_config JSONB;
|
||||
BEGIN
|
||||
SELECT id INTO v_category_id
|
||||
FROM work_categories
|
||||
WHERE category_key = 'shouqiang'
|
||||
LIMIT 1;
|
||||
|
||||
IF v_category_id IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF EXISTS (SELECT 1 FROM consignment_profiles WHERE category_id = v_category_id) THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
v_config := $config${
|
||||
"version": 1,
|
||||
"tabTitle": "苏尔南寄卖",
|
||||
"tabDescription": "苏尔南手枪寄卖:选择规格后按接单模板价带出建议价",
|
||||
"specs": [
|
||||
{
|
||||
"key": "system",
|
||||
"label": "操作系统",
|
||||
"type": "radio",
|
||||
"required": true,
|
||||
"options": [
|
||||
{ "label": "苹果", "value": "苹果", "enabled": true },
|
||||
{ "label": "安卓", "value": "安卓", "enabled": true }
|
||||
],
|
||||
"placeholder": "",
|
||||
"helpText": "",
|
||||
"matchMode": "exact",
|
||||
"wildcardValues": [],
|
||||
"showInItemName": true,
|
||||
"sortOrder": 10
|
||||
},
|
||||
{
|
||||
"key": "serverZone",
|
||||
"label": "游戏区服",
|
||||
"type": "radio",
|
||||
"required": true,
|
||||
"options": [
|
||||
{ "label": "互通(QQ/微信)", "value": "互通", "enabled": true }
|
||||
],
|
||||
"placeholder": "",
|
||||
"helpText": "苏尔南手枪当前统一为互通区服,旧库存的微信区/QQ区仍可参与匹配",
|
||||
"matchMode": "wildcard",
|
||||
"wildcardValues": ["互通", "通用", "全区", "不限"],
|
||||
"showInItemName": false,
|
||||
"sortOrder": 20
|
||||
},
|
||||
{
|
||||
"key": "series",
|
||||
"label": "系列 / 枪系",
|
||||
"type": "radio",
|
||||
"required": true,
|
||||
"options": [
|
||||
{ "label": "暗潮", "value": "暗潮", "enabled": true },
|
||||
{ "label": "怒嚎", "value": "怒嚎", "enabled": true },
|
||||
{ "label": "血宴", "value": "血宴", "enabled": true }
|
||||
],
|
||||
"placeholder": "",
|
||||
"helpText": "",
|
||||
"matchMode": "exact",
|
||||
"wildcardValues": [],
|
||||
"showInItemName": true,
|
||||
"sortOrder": 30
|
||||
},
|
||||
{
|
||||
"key": "diamondLevel",
|
||||
"label": "钻石等级",
|
||||
"type": "radio",
|
||||
"required": true,
|
||||
"options": [
|
||||
{ "label": "1钻", "value": "1", "enabled": true },
|
||||
{ "label": "2钻", "value": "2", "enabled": true },
|
||||
{ "label": "3钻", "value": "3", "enabled": true },
|
||||
{ "label": "4钻", "value": "4", "enabled": true },
|
||||
{ "label": "5钻", "value": "5", "enabled": true },
|
||||
{ "label": "6钻", "value": "6", "enabled": true },
|
||||
{ "label": "7钻", "value": "7", "enabled": true }
|
||||
],
|
||||
"placeholder": "",
|
||||
"helpText": "",
|
||||
"matchMode": "minimum",
|
||||
"wildcardValues": [],
|
||||
"showInItemName": true,
|
||||
"sortOrder": 40
|
||||
},
|
||||
{
|
||||
"key": "brillianceCount",
|
||||
"label": "璀璨数",
|
||||
"type": "radio",
|
||||
"required": true,
|
||||
"options": [
|
||||
{ "label": "无", "value": "0", "enabled": true },
|
||||
{ "label": "1璀", "value": "1", "enabled": true },
|
||||
{ "label": "2璀", "value": "2", "enabled": true },
|
||||
{ "label": "3璀", "value": "3", "enabled": true },
|
||||
{ "label": "4璀", "value": "4", "enabled": true },
|
||||
{ "label": "5璀", "value": "5", "enabled": true },
|
||||
{ "label": "6璀", "value": "6", "enabled": true },
|
||||
{ "label": "7璀", "value": "7", "enabled": true }
|
||||
],
|
||||
"placeholder": "",
|
||||
"helpText": "",
|
||||
"matchMode": "minimum",
|
||||
"wildcardValues": [],
|
||||
"showInItemName": true,
|
||||
"sortOrder": 50
|
||||
}
|
||||
],
|
||||
"nameTemplate": "{system}{series}{diamondLevel}钻{brillianceCount}璀璨 苏尔南",
|
||||
"pricing": {
|
||||
"priceType": "template_discount",
|
||||
"discount": 10,
|
||||
"fixedPriceFen": 0,
|
||||
"allowWorkerPrice": true,
|
||||
"minPriceFen": 0,
|
||||
"missingPricePolicy": "manual"
|
||||
},
|
||||
"fields": {
|
||||
"system": true,
|
||||
"serverZone": true,
|
||||
"series": true,
|
||||
"diamondLevel": true,
|
||||
"brillianceCount": true,
|
||||
"quantity": true
|
||||
},
|
||||
"deposit": {
|
||||
"enabled": false,
|
||||
"mode": "fixed",
|
||||
"fixedAmountFen": 0,
|
||||
"unitAmountFen": 0
|
||||
},
|
||||
"serviceFee": {
|
||||
"enabled": false,
|
||||
"mode": "percent",
|
||||
"percent": 0,
|
||||
"amountFen": 0
|
||||
},
|
||||
"inventoryLimit": {
|
||||
"enabled": false,
|
||||
"maxActiveQuantity": 0
|
||||
},
|
||||
"matching": {
|
||||
"mode": "manual",
|
||||
"autoMatchEnabled": false,
|
||||
"exactOnly": true,
|
||||
"preferLowReward": true
|
||||
},
|
||||
"internalMatch": {
|
||||
"delayMinutes": 30,
|
||||
"fallbackToHall": true
|
||||
},
|
||||
"timeout": {
|
||||
"minutes": 30,
|
||||
"policy": "reopen"
|
||||
},
|
||||
"priceDrop": {
|
||||
"enabled": false,
|
||||
"hours": 72,
|
||||
"mode": "percent",
|
||||
"value": 10
|
||||
}
|
||||
}$config$::jsonb;
|
||||
|
||||
INSERT INTO consignment_profiles (
|
||||
category_id, name, title, description, tab_title, config_json, status, created_at, updated_at
|
||||
) VALUES (
|
||||
v_category_id,
|
||||
'苏尔南手枪寄卖',
|
||||
'苏尔南手枪寄卖',
|
||||
'苏尔南手枪默认配置:选择规格后按接单模板价带出建议价',
|
||||
'苏尔南寄卖',
|
||||
v_config,
|
||||
'published',
|
||||
now(),
|
||||
now()
|
||||
)
|
||||
RETURNING id INTO v_profile_id;
|
||||
|
||||
INSERT INTO consignment_profile_versions (
|
||||
profile_id, version_no, config_json, published_at, created_at
|
||||
) VALUES (
|
||||
v_profile_id, 1, v_config, now(), now()
|
||||
)
|
||||
RETURNING id INTO v_version_id;
|
||||
|
||||
UPDATE consignment_profiles
|
||||
SET current_version_id = v_version_id, updated_at = now()
|
||||
WHERE id = v_profile_id;
|
||||
END
|
||||
$migration$;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 原 145_consignment_inventory_multi_binding.sql
|
||||
-- ===========================================================================
|
||||
-- 145_consignment_inventory_multi_binding.sql
|
||||
-- 拼单匹配允许同一张工单绑定多条寄卖库存;旧的“一单一件库存”唯一索引会挡住第二个份额。
|
||||
-- 保留普通索引用于按工单反查库存;数据完整性由分配表和拼单份额表保证。
|
||||
DROP INDEX IF EXISTS uniq_consignment_inventories_bound_work_order;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_inventories_bound_work_order
|
||||
ON consignment_inventories (bound_work_order_id)
|
||||
WHERE bound_work_order_id IS NOT NULL;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 原 146_consignment_share_gift.sql
|
||||
-- ===========================================================================
|
||||
-- 146_consignment_share_gift.sql
|
||||
--
|
||||
-- 拼单寄卖:每个卖家的库存对应一条 work_order_share,好友赠送的“加好友时间/冷却截止”
|
||||
-- 必须落在份额上,不能再用 work_orders.friend_added_at 这种整单字段。
|
||||
-- 这样 A 确认加好友只影响 A 的份额,B 仍可独立确认、独立提交赠送凭证。
|
||||
ALTER TABLE work_order_shares
|
||||
ADD COLUMN IF NOT EXISTS friend_added_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS gift_available_at TIMESTAMPTZ;
|
||||
|
||||
COMMENT ON COLUMN work_order_shares.friend_added_at IS
|
||||
'寄卖拼单份额:卖家确认加好友时间(冷却起点);非寄卖份额为 NULL';
|
||||
COMMENT ON COLUMN work_order_shares.gift_available_at IS
|
||||
'寄卖拼单份额:赠送冷却结束时间;到点后按时间实时推导为可赠送';
|
||||
|
||||
-- ===========================================================================
|
||||
-- 原 147_consignment_match_and_rematch.sql
|
||||
-- ===========================================================================
|
||||
-- 147_consignment_match_and_rematch.sql
|
||||
-- 1) 超时「重新匹配」排除原卖家/原库存;
|
||||
-- 2) 手动/自动匹配统一留痕,供后台排查问题。
|
||||
-- 全部语句幂等,可安全重跑;不改动历史迁移。
|
||||
|
||||
ALTER TABLE work_orders
|
||||
ADD COLUMN IF NOT EXISTS rematch_excluded_worker_id BIGINT,
|
||||
ADD COLUMN IF NOT EXISTS rematch_excluded_inventory_id BIGINT;
|
||||
|
||||
COMMENT ON COLUMN work_orders.rematch_excluded_worker_id IS
|
||||
'超时重新匹配时排除的原接单打手;匹配成功后清空';
|
||||
COMMENT ON COLUMN work_orders.rematch_excluded_inventory_id IS
|
||||
'超时重新匹配时排除的原寄卖库存;匹配成功后清空';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS consignment_match_logs (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
work_order_id BIGINT,
|
||||
work_order_no TEXT NOT NULL DEFAULT '',
|
||||
profile_id BIGINT,
|
||||
category_id BIGINT,
|
||||
trigger_type TEXT NOT NULL,
|
||||
result TEXT NOT NULL,
|
||||
reason_code TEXT NOT NULL DEFAULT '',
|
||||
reason_text TEXT NOT NULL DEFAULT '',
|
||||
candidate_count INTEGER NOT NULL DEFAULT 0,
|
||||
matched_count INTEGER NOT NULL DEFAULT 0,
|
||||
actor_type TEXT NOT NULL DEFAULT 'system',
|
||||
actor_id TEXT NOT NULL DEFAULT '',
|
||||
duration_ms INTEGER NOT NULL DEFAULT 0,
|
||||
payload_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT consignment_match_logs_trigger_check
|
||||
CHECK (trigger_type IN ('auto', 'manual', 'rematch')),
|
||||
CONSTRAINT consignment_match_logs_result_check
|
||||
CHECK (result IN ('matched', 'failed', 'skipped'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_match_logs_created
|
||||
ON consignment_match_logs (created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_match_logs_work_order
|
||||
ON consignment_match_logs (work_order_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_match_logs_profile
|
||||
ON consignment_match_logs (profile_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_match_logs_type_result
|
||||
ON consignment_match_logs (trigger_type, result, created_at DESC);
|
||||
|
||||
COMMENT ON TABLE consignment_match_logs IS
|
||||
'寄卖匹配日志:手动/自动/重新匹配的每次尝试(成功、失败、跳过)与原因,用于留痕和问题分析';
|
||||
COMMENT ON COLUMN consignment_match_logs.trigger_type IS
|
||||
'触发方式:auto 自动调度 / manual 后台手动 / rematch 超时重新匹配';
|
||||
COMMENT ON COLUMN consignment_match_logs.reason_code IS
|
||||
'机器可读原因码,例如 no_candidate / matching_disabled / spec_mismatch / exception';
|
||||
|
||||
-- ===========================================================================
|
||||
-- 原 148_consignment_remove_zone_help_text.sql
|
||||
-- ===========================================================================
|
||||
-- 148_consignment_remove_zone_help_text.sql
|
||||
-- 删除「游戏区服」字段上的旧提示文案:
|
||||
-- 「苏尔南手枪当前统一为互通区服,旧库存的微信区/QQ区仍可参与匹配」。
|
||||
-- 只清理已发布配置的当前快照,不改动历史版本与历史提报/库存数据;幂等可重跑。
|
||||
|
||||
UPDATE consignment_profiles AS p
|
||||
SET config_json = jsonb_set(
|
||||
p.config_json,
|
||||
'{specs}',
|
||||
COALESCE(
|
||||
(
|
||||
SELECT jsonb_agg(
|
||||
CASE
|
||||
WHEN elem->>'key' = 'serverZone'
|
||||
AND BTRIM(COALESCE(elem->>'helpText', '')) =
|
||||
'苏尔南手枪当前统一为互通区服,旧库存的微信区/QQ区仍可参与匹配'
|
||||
THEN jsonb_set(elem, '{helpText}', '""'::jsonb)
|
||||
ELSE elem
|
||||
END
|
||||
ORDER BY ord
|
||||
)
|
||||
FROM jsonb_array_elements(p.config_json->'specs') WITH ORDINALITY AS t(elem, ord)
|
||||
),
|
||||
'[]'::jsonb
|
||||
)
|
||||
)
|
||||
WHERE jsonb_typeof(p.config_json->'specs') = 'array';
|
||||
|
||||
UPDATE consignment_profile_versions AS v
|
||||
SET config_json = jsonb_set(
|
||||
v.config_json,
|
||||
'{specs}',
|
||||
COALESCE(
|
||||
(
|
||||
SELECT jsonb_agg(
|
||||
CASE
|
||||
WHEN elem->>'key' = 'serverZone'
|
||||
AND BTRIM(COALESCE(elem->>'helpText', '')) =
|
||||
'苏尔南手枪当前统一为互通区服,旧库存的微信区/QQ区仍可参与匹配'
|
||||
THEN jsonb_set(elem, '{helpText}', '""'::jsonb)
|
||||
ELSE elem
|
||||
END
|
||||
ORDER BY ord
|
||||
)
|
||||
FROM jsonb_array_elements(v.config_json->'specs') WITH ORDINALITY AS t(elem, ord)
|
||||
),
|
||||
'[]'::jsonb
|
||||
)
|
||||
)
|
||||
FROM consignment_profiles AS p
|
||||
WHERE v.id = p.current_version_id
|
||||
AND jsonb_typeof(v.config_json->'specs') = 'array';
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
-- 143_consignment_dynamic_specs.sql
|
||||
-- 寄卖动态规格:提报/库存增加 spec_values_json、spec_key、spec_text 与改价上限。
|
||||
-- 全部语句幂等,可安全重跑;不改动历史迁移。
|
||||
|
||||
-- ===========================================================================
|
||||
-- 1. 提报:动态规格值、匹配 key、展示文本、改价上限
|
||||
-- ===========================================================================
|
||||
ALTER TABLE consignment_submissions
|
||||
ADD COLUMN IF NOT EXISTS spec_values_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS spec_key TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS spec_text TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS price_cap_fen BIGINT NOT NULL DEFAULT 0;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 2. 库存:同提报;入库后规格值继续随库存走
|
||||
-- ===========================================================================
|
||||
ALTER TABLE consignment_inventories
|
||||
ADD COLUMN IF NOT EXISTS spec_values_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS spec_key TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS spec_text TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS price_cap_fen BIGINT NOT NULL DEFAULT 0;
|
||||
|
||||
-- ===========================================================================
|
||||
-- 3. 存量回填:内置 5 规格 -> JSON / key / 文本;改价上限取当前售价
|
||||
-- ===========================================================================
|
||||
UPDATE consignment_submissions
|
||||
SET
|
||||
spec_values_json = jsonb_strip_nulls(
|
||||
jsonb_build_object(
|
||||
'system', NULLIF(BTRIM(system), ''),
|
||||
'serverZone', NULLIF(BTRIM(server_zone), ''),
|
||||
'series', NULLIF(BTRIM(series), ''),
|
||||
'diamondLevel', diamond_level,
|
||||
'brillianceCount', brilliance_count
|
||||
)
|
||||
),
|
||||
spec_key = NULLIF(
|
||||
concat_ws(
|
||||
'|',
|
||||
CASE WHEN COALESCE(NULLIF(BTRIM(system), ''), '') <> '' THEN 'system=' || BTRIM(system) END,
|
||||
CASE WHEN COALESCE(NULLIF(BTRIM(server_zone), ''), '') <> '' THEN 'serverZone=' || BTRIM(server_zone) END,
|
||||
CASE WHEN COALESCE(NULLIF(BTRIM(series), ''), '') <> '' THEN 'series=' || BTRIM(series) END,
|
||||
CASE WHEN diamond_level IS NOT NULL THEN 'diamondLevel=' || diamond_level::text END,
|
||||
CASE WHEN brilliance_count IS NOT NULL THEN 'brillianceCount=' || brilliance_count::text END
|
||||
),
|
||||
''
|
||||
),
|
||||
spec_text = COALESCE(NULLIF(BTRIM(item_name), ''), ''),
|
||||
price_cap_fen = CASE
|
||||
WHEN COALESCE(requested_sale_price_fen, 0) > 0 THEN requested_sale_price_fen
|
||||
WHEN COALESCE(asking_price_fen, 0) > 0 THEN asking_price_fen
|
||||
ELSE 0
|
||||
END
|
||||
WHERE spec_key = '';
|
||||
|
||||
UPDATE consignment_inventories
|
||||
SET
|
||||
spec_values_json = jsonb_strip_nulls(
|
||||
jsonb_build_object(
|
||||
'system', NULLIF(BTRIM(system), ''),
|
||||
'serverZone', NULLIF(BTRIM(server_zone), ''),
|
||||
'series', NULLIF(BTRIM(series), ''),
|
||||
'diamondLevel', diamond_level,
|
||||
'brillianceCount', brilliance_count
|
||||
)
|
||||
),
|
||||
spec_key = NULLIF(
|
||||
concat_ws(
|
||||
'|',
|
||||
CASE WHEN COALESCE(NULLIF(BTRIM(system), ''), '') <> '' THEN 'system=' || BTRIM(system) END,
|
||||
CASE WHEN COALESCE(NULLIF(BTRIM(server_zone), ''), '') <> '' THEN 'serverZone=' || BTRIM(server_zone) END,
|
||||
CASE WHEN COALESCE(NULLIF(BTRIM(series), ''), '') <> '' THEN 'series=' || BTRIM(series) END,
|
||||
CASE WHEN diamond_level IS NOT NULL THEN 'diamondLevel=' || diamond_level::text END,
|
||||
CASE WHEN brilliance_count IS NOT NULL THEN 'brillianceCount=' || brilliance_count::text END
|
||||
),
|
||||
''
|
||||
),
|
||||
spec_text = COALESCE(NULLIF(BTRIM(item_name), ''), ''),
|
||||
price_cap_fen = COALESCE(sale_price_fen, 0)
|
||||
WHERE spec_key = '';
|
||||
|
||||
-- ===========================================================================
|
||||
-- 4. 索引:按配置 + 规格 key 快速筛选在库可匹配库存
|
||||
-- ===========================================================================
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_inventories_spec_key
|
||||
ON consignment_inventories (profile_id, spec_key)
|
||||
WHERE status = 'in_stock' AND allow_matching;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_submissions_spec_key
|
||||
ON consignment_submissions (profile_id, spec_key)
|
||||
WHERE status = 'pending_review';
|
||||
|
||||
-- ===========================================================================
|
||||
-- 5. 注释
|
||||
-- ===========================================================================
|
||||
COMMENT ON COLUMN consignment_submissions.spec_values_json IS
|
||||
'动态规格字段值(key -> 选项值/数字/文本),随配置版本快照固化';
|
||||
COMMENT ON COLUMN consignment_submissions.spec_key IS
|
||||
'参与匹配的规格字段稳定 key(按字段 key 排序的 key=value 串),用于候选筛选';
|
||||
COMMENT ON COLUMN consignment_submissions.spec_text IS
|
||||
'规格展示/搜索文本(字段名称 + 选项名称拼接)';
|
||||
COMMENT ON COLUMN consignment_submissions.price_cap_fen IS
|
||||
'打手改价上限(分):默认取接单模板价/系统建议价;0 = 不限制';
|
||||
COMMENT ON COLUMN consignment_inventories.spec_values_json IS
|
||||
'动态规格字段值(key -> 选项值/数字/文本),随配置版本快照固化';
|
||||
COMMENT ON COLUMN consignment_inventories.spec_key IS
|
||||
'参与匹配的规格字段稳定 key(按字段 key 排序的 key=value 串),用于候选筛选';
|
||||
COMMENT ON COLUMN consignment_inventories.spec_text IS
|
||||
'规格展示/搜索文本(字段名称 + 选项名称拼接)';
|
||||
COMMENT ON COLUMN consignment_inventories.price_cap_fen IS
|
||||
'打手改价上限(分):默认取接单模板价/系统建议价;0 = 不限制';
|
||||
@@ -1,203 +0,0 @@
|
||||
-- 144_consignment_surnan_default_profile.sql
|
||||
-- 苏尔南手枪默认寄卖配置:五个规格字段(与旧发布页一致),直接发布 v1。
|
||||
-- 幂等:该分类已有寄卖配置时不做任何事,避免覆盖运营已保存/已发布的配置。
|
||||
|
||||
DO $migration$
|
||||
DECLARE
|
||||
v_category_id BIGINT;
|
||||
v_profile_id BIGINT;
|
||||
v_version_id BIGINT;
|
||||
v_config JSONB;
|
||||
BEGIN
|
||||
SELECT id INTO v_category_id
|
||||
FROM work_categories
|
||||
WHERE category_key = 'shouqiang'
|
||||
LIMIT 1;
|
||||
|
||||
IF v_category_id IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF EXISTS (SELECT 1 FROM consignment_profiles WHERE category_id = v_category_id) THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
v_config := $config${
|
||||
"version": 1,
|
||||
"tabTitle": "苏尔南寄卖",
|
||||
"tabDescription": "苏尔南手枪寄卖:选择规格后按接单模板价带出建议价",
|
||||
"specs": [
|
||||
{
|
||||
"key": "system",
|
||||
"label": "操作系统",
|
||||
"type": "radio",
|
||||
"required": true,
|
||||
"options": [
|
||||
{ "label": "苹果", "value": "苹果", "enabled": true },
|
||||
{ "label": "安卓", "value": "安卓", "enabled": true }
|
||||
],
|
||||
"placeholder": "",
|
||||
"helpText": "",
|
||||
"matchMode": "exact",
|
||||
"wildcardValues": [],
|
||||
"showInItemName": true,
|
||||
"sortOrder": 10
|
||||
},
|
||||
{
|
||||
"key": "serverZone",
|
||||
"label": "游戏区服",
|
||||
"type": "radio",
|
||||
"required": true,
|
||||
"options": [
|
||||
{ "label": "互通(QQ/微信)", "value": "互通", "enabled": true }
|
||||
],
|
||||
"placeholder": "",
|
||||
"helpText": "苏尔南手枪当前统一为互通区服,旧库存的微信区/QQ区仍可参与匹配",
|
||||
"matchMode": "wildcard",
|
||||
"wildcardValues": ["互通", "通用", "全区", "不限"],
|
||||
"showInItemName": false,
|
||||
"sortOrder": 20
|
||||
},
|
||||
{
|
||||
"key": "series",
|
||||
"label": "系列 / 枪系",
|
||||
"type": "radio",
|
||||
"required": true,
|
||||
"options": [
|
||||
{ "label": "暗潮", "value": "暗潮", "enabled": true },
|
||||
{ "label": "怒嚎", "value": "怒嚎", "enabled": true },
|
||||
{ "label": "血宴", "value": "血宴", "enabled": true }
|
||||
],
|
||||
"placeholder": "",
|
||||
"helpText": "",
|
||||
"matchMode": "exact",
|
||||
"wildcardValues": [],
|
||||
"showInItemName": true,
|
||||
"sortOrder": 30
|
||||
},
|
||||
{
|
||||
"key": "diamondLevel",
|
||||
"label": "钻石等级",
|
||||
"type": "radio",
|
||||
"required": true,
|
||||
"options": [
|
||||
{ "label": "1钻", "value": "1", "enabled": true },
|
||||
{ "label": "2钻", "value": "2", "enabled": true },
|
||||
{ "label": "3钻", "value": "3", "enabled": true },
|
||||
{ "label": "4钻", "value": "4", "enabled": true },
|
||||
{ "label": "5钻", "value": "5", "enabled": true },
|
||||
{ "label": "6钻", "value": "6", "enabled": true },
|
||||
{ "label": "7钻", "value": "7", "enabled": true }
|
||||
],
|
||||
"placeholder": "",
|
||||
"helpText": "",
|
||||
"matchMode": "minimum",
|
||||
"wildcardValues": [],
|
||||
"showInItemName": true,
|
||||
"sortOrder": 40
|
||||
},
|
||||
{
|
||||
"key": "brillianceCount",
|
||||
"label": "璀璨数",
|
||||
"type": "radio",
|
||||
"required": true,
|
||||
"options": [
|
||||
{ "label": "无", "value": "0", "enabled": true },
|
||||
{ "label": "1璀", "value": "1", "enabled": true },
|
||||
{ "label": "2璀", "value": "2", "enabled": true },
|
||||
{ "label": "3璀", "value": "3", "enabled": true },
|
||||
{ "label": "4璀", "value": "4", "enabled": true },
|
||||
{ "label": "5璀", "value": "5", "enabled": true },
|
||||
{ "label": "6璀", "value": "6", "enabled": true },
|
||||
{ "label": "7璀", "value": "7", "enabled": true }
|
||||
],
|
||||
"placeholder": "",
|
||||
"helpText": "",
|
||||
"matchMode": "minimum",
|
||||
"wildcardValues": [],
|
||||
"showInItemName": true,
|
||||
"sortOrder": 50
|
||||
}
|
||||
],
|
||||
"nameTemplate": "{system}{series}{diamondLevel}钻{brillianceCount}璀璨 苏尔南",
|
||||
"pricing": {
|
||||
"priceType": "template_discount",
|
||||
"discount": 10,
|
||||
"fixedPriceFen": 0,
|
||||
"allowWorkerPrice": true,
|
||||
"minPriceFen": 0,
|
||||
"missingPricePolicy": "manual"
|
||||
},
|
||||
"fields": {
|
||||
"system": true,
|
||||
"serverZone": true,
|
||||
"series": true,
|
||||
"diamondLevel": true,
|
||||
"brillianceCount": true,
|
||||
"quantity": true
|
||||
},
|
||||
"deposit": {
|
||||
"enabled": false,
|
||||
"mode": "fixed",
|
||||
"fixedAmountFen": 0,
|
||||
"unitAmountFen": 0
|
||||
},
|
||||
"serviceFee": {
|
||||
"enabled": false,
|
||||
"mode": "percent",
|
||||
"percent": 0,
|
||||
"amountFen": 0
|
||||
},
|
||||
"inventoryLimit": {
|
||||
"enabled": false,
|
||||
"maxActiveQuantity": 0
|
||||
},
|
||||
"matching": {
|
||||
"mode": "manual",
|
||||
"autoMatchEnabled": false,
|
||||
"exactOnly": true,
|
||||
"preferLowReward": true
|
||||
},
|
||||
"internalMatch": {
|
||||
"delayMinutes": 30,
|
||||
"fallbackToHall": true
|
||||
},
|
||||
"timeout": {
|
||||
"minutes": 30,
|
||||
"policy": "reopen"
|
||||
},
|
||||
"priceDrop": {
|
||||
"enabled": false,
|
||||
"hours": 72,
|
||||
"mode": "percent",
|
||||
"value": 10
|
||||
}
|
||||
}$config$::jsonb;
|
||||
|
||||
INSERT INTO consignment_profiles (
|
||||
category_id, name, title, description, tab_title, config_json, status, created_at, updated_at
|
||||
) VALUES (
|
||||
v_category_id,
|
||||
'苏尔南手枪寄卖',
|
||||
'苏尔南手枪寄卖',
|
||||
'苏尔南手枪默认配置:选择规格后按接单模板价带出建议价',
|
||||
'苏尔南寄卖',
|
||||
v_config,
|
||||
'published',
|
||||
now(),
|
||||
now()
|
||||
)
|
||||
RETURNING id INTO v_profile_id;
|
||||
|
||||
INSERT INTO consignment_profile_versions (
|
||||
profile_id, version_no, config_json, published_at, created_at
|
||||
) VALUES (
|
||||
v_profile_id, 1, v_config, now(), now()
|
||||
)
|
||||
RETURNING id INTO v_version_id;
|
||||
|
||||
UPDATE consignment_profiles
|
||||
SET current_version_id = v_version_id, updated_at = now()
|
||||
WHERE id = v_profile_id;
|
||||
END
|
||||
$migration$;
|
||||
@@ -1,8 +0,0 @@
|
||||
-- 145_consignment_inventory_multi_binding.sql
|
||||
-- 拼单匹配允许同一张工单绑定多条寄卖库存;旧的“一单一件库存”唯一索引会挡住第二个份额。
|
||||
-- 保留普通索引用于按工单反查库存;数据完整性由分配表和拼单份额表保证。
|
||||
DROP INDEX IF EXISTS uniq_consignment_inventories_bound_work_order;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_inventories_bound_work_order
|
||||
ON consignment_inventories (bound_work_order_id)
|
||||
WHERE bound_work_order_id IS NOT NULL;
|
||||
@@ -1,13 +0,0 @@
|
||||
-- 146_consignment_share_gift.sql
|
||||
--
|
||||
-- 拼单寄卖:每个卖家的库存对应一条 work_order_share,好友赠送的“加好友时间/冷却截止”
|
||||
-- 必须落在份额上,不能再用 work_orders.friend_added_at 这种整单字段。
|
||||
-- 这样 A 确认加好友只影响 A 的份额,B 仍可独立确认、独立提交赠送凭证。
|
||||
ALTER TABLE work_order_shares
|
||||
ADD COLUMN IF NOT EXISTS friend_added_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS gift_available_at TIMESTAMPTZ;
|
||||
|
||||
COMMENT ON COLUMN work_order_shares.friend_added_at IS
|
||||
'寄卖拼单份额:卖家确认加好友时间(冷却起点);非寄卖份额为 NULL';
|
||||
COMMENT ON COLUMN work_order_shares.gift_available_at IS
|
||||
'寄卖拼单份额:赠送冷却结束时间;到点后按时间实时推导为可赠送';
|
||||
@@ -1,52 +0,0 @@
|
||||
-- 147_consignment_match_and_rematch.sql
|
||||
-- 1) 超时「重新匹配」排除原卖家/原库存;
|
||||
-- 2) 手动/自动匹配统一留痕,供后台排查问题。
|
||||
-- 全部语句幂等,可安全重跑;不改动历史迁移。
|
||||
|
||||
ALTER TABLE work_orders
|
||||
ADD COLUMN IF NOT EXISTS rematch_excluded_worker_id BIGINT,
|
||||
ADD COLUMN IF NOT EXISTS rematch_excluded_inventory_id BIGINT;
|
||||
|
||||
COMMENT ON COLUMN work_orders.rematch_excluded_worker_id IS
|
||||
'超时重新匹配时排除的原接单打手;匹配成功后清空';
|
||||
COMMENT ON COLUMN work_orders.rematch_excluded_inventory_id IS
|
||||
'超时重新匹配时排除的原寄卖库存;匹配成功后清空';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS consignment_match_logs (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
work_order_id BIGINT,
|
||||
work_order_no TEXT NOT NULL DEFAULT '',
|
||||
profile_id BIGINT,
|
||||
category_id BIGINT,
|
||||
trigger_type TEXT NOT NULL,
|
||||
result TEXT NOT NULL,
|
||||
reason_code TEXT NOT NULL DEFAULT '',
|
||||
reason_text TEXT NOT NULL DEFAULT '',
|
||||
candidate_count INTEGER NOT NULL DEFAULT 0,
|
||||
matched_count INTEGER NOT NULL DEFAULT 0,
|
||||
actor_type TEXT NOT NULL DEFAULT 'system',
|
||||
actor_id TEXT NOT NULL DEFAULT '',
|
||||
duration_ms INTEGER NOT NULL DEFAULT 0,
|
||||
payload_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT consignment_match_logs_trigger_check
|
||||
CHECK (trigger_type IN ('auto', 'manual', 'rematch')),
|
||||
CONSTRAINT consignment_match_logs_result_check
|
||||
CHECK (result IN ('matched', 'failed', 'skipped'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_match_logs_created
|
||||
ON consignment_match_logs (created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_match_logs_work_order
|
||||
ON consignment_match_logs (work_order_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_match_logs_profile
|
||||
ON consignment_match_logs (profile_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_match_logs_type_result
|
||||
ON consignment_match_logs (trigger_type, result, created_at DESC);
|
||||
|
||||
COMMENT ON TABLE consignment_match_logs IS
|
||||
'寄卖匹配日志:手动/自动/重新匹配的每次尝试(成功、失败、跳过)与原因,用于留痕和问题分析';
|
||||
COMMENT ON COLUMN consignment_match_logs.trigger_type IS
|
||||
'触发方式:auto 自动调度 / manual 后台手动 / rematch 超时重新匹配';
|
||||
COMMENT ON COLUMN consignment_match_logs.reason_code IS
|
||||
'机器可读原因码,例如 no_candidate / matching_disabled / spec_mismatch / exception';
|
||||
@@ -89,9 +89,9 @@ test('降价提示:未到时长不提示,到点后按比例给出建议售
|
||||
assert.equal(suggestion?.dropHours, 24)
|
||||
})
|
||||
|
||||
test('迁移 142 必须包含寄卖配置、库存数量/售价与分配表', async () => {
|
||||
test('合并迁移 142 必须包含寄卖配置、库存数量/售价与分配表', async () => {
|
||||
const source = await readFile(
|
||||
new URL('../../db/migrations/142_consignment_matching.sql', import.meta.url),
|
||||
new URL('../../db/migrations/142_consignment_platform.sql', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
for (const fragment of [
|
||||
@@ -103,6 +103,6 @@ test('迁移 142 必须包含寄卖配置、库存数量/售价与分配表', as
|
||||
'internal_match_deadline_at',
|
||||
'consignment_inventory_id',
|
||||
]) {
|
||||
assert.ok(source.includes(fragment), `迁移 142 缺少 ${fragment}`)
|
||||
assert.ok(source.includes(fragment), `合并迁移 142 缺少 ${fragment}`)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -35,7 +35,7 @@ function option(label: string, value: string) {
|
||||
return { label, value, enabled: true }
|
||||
}
|
||||
|
||||
/** 苏尔南手枪种子配置的五个字段(与迁移 144 一致)。 */
|
||||
/** 苏尔南手枪种子配置的五个字段(与合并迁移 142 一致)。 */
|
||||
function surnanSpecs(): ConsignmentSpecField[] {
|
||||
return [
|
||||
{
|
||||
@@ -457,9 +457,9 @@ test('动态规格:旧列仍由兼容 key 同步', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('迁移 143 必须包含 JSON、匹配 key、文本与改价上限', async () => {
|
||||
test('合并迁移 142 必须包含动态规格的 JSON、匹配 key、文本与改价上限', async () => {
|
||||
const sql = await readFile(
|
||||
new URL('../../db/migrations/143_consignment_dynamic_specs.sql', import.meta.url),
|
||||
new URL('../../db/migrations/142_consignment_platform.sql', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
for (const fragment of [
|
||||
@@ -469,13 +469,13 @@ test('迁移 143 必须包含 JSON、匹配 key、文本与改价上限', async
|
||||
'price_cap_fen',
|
||||
'CREATE INDEX IF NOT EXISTS idx_consignment_inventories_spec_key',
|
||||
]) {
|
||||
assert.ok(sql.includes(fragment), `迁移 143 缺少 ${fragment}`)
|
||||
assert.ok(sql.includes(fragment), `合并迁移 142 缺少 ${fragment}`)
|
||||
}
|
||||
})
|
||||
|
||||
test('迁移 144 必须为苏尔南手枪写入已发布的五个单选字段配置', async () => {
|
||||
test('合并迁移 142 必须为苏尔南手枪写入已发布的五个单选字段配置', async () => {
|
||||
const sql = await readFile(
|
||||
new URL('../../db/migrations/144_consignment_surnan_default_profile.sql', import.meta.url),
|
||||
new URL('../../db/migrations/142_consignment_platform.sql', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
for (const fragment of [
|
||||
@@ -485,6 +485,6 @@ test('迁移 144 必须为苏尔南手枪写入已发布的五个单选字段配
|
||||
"'published'",
|
||||
'"key": "brillianceCount"',
|
||||
]) {
|
||||
assert.ok(sql.includes(fragment), `迁移 144 缺少 ${fragment}`)
|
||||
assert.ok(sql.includes(fragment), `合并迁移 142 缺少 ${fragment}`)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
*
|
||||
* 设计约定:
|
||||
* - 没有任何「内置字段」:配置初始 specs 为空,字段全部由运营在寄卖配置里维护;
|
||||
* 苏尔南手枪的五个字段由迁移 131 作为种子配置写入;
|
||||
* 苏尔南手枪的五个字段由迁移 142 作为种子配置写入;
|
||||
* - 字段类型支持:文本 / 长文本 / 下拉 / 单选 / 数字 / 日期;
|
||||
* - 价格只有两种:接单模板价打折、模板价固定下调;打手只能按系统建议价往下调;
|
||||
* - 价格只有两种:接单模板价打折、模板价固定下调;打手只能按系统建议价格往下调;
|
||||
* - 旧列兼容:字段 key 仍为 system/serverZone/series/diamondLevel/brillianceCount 时,
|
||||
* 继续同步旧列并沿用旧匹配器,保证存量库存与工单不回归。
|
||||
*/
|
||||
|
||||
@@ -1045,7 +1045,7 @@ export async function submitWorkerConsignmentGiftEvidence(
|
||||
/**
|
||||
* 发布寄卖的建议价:
|
||||
* - 模板折扣:接单模板价 × 折扣;
|
||||
* - 固定价格:直接使用固定价,有模板价时校验不得高于模板价;
|
||||
* - 模板价固定下调:接单模板价 − 固定下调金额,有模板价才允许发布;
|
||||
* - 同时返回改价上限(等于建议价,打手只能往下调)。
|
||||
*/
|
||||
export async function getWorkerConsignmentPriceSuggestion(payload: {
|
||||
|
||||
@@ -1312,7 +1312,7 @@ export default function ConsignmentConfigPanel() {
|
||||
label="允许打手改价"
|
||||
name={['pricing', 'allowWorkerPrice']}
|
||||
valuePropName="checked"
|
||||
extra="打手只能按系统建议价往下调"
|
||||
extra="打手只能按系统建议价格往下调"
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<Switch checkedChildren="允许" unCheckedChildren="不允许" />
|
||||
|
||||
@@ -162,7 +162,7 @@ export default function WorkerConsignmentPage() {
|
||||
const [cooldownNow, setCooldownNow] = useState(() => Date.now())
|
||||
const [form] = Form.useForm()
|
||||
const currentDiamondLevel = Form.useWatch('diamondLevel', form)
|
||||
// 寄售价:按后台配置(模板折扣价 / 固定价)自动带出,打手只能往下调。
|
||||
// 寄售价:按后台配置(模板折扣价 / 模板价固定下调)自动带出,打手只能往下调。
|
||||
const [priceState, setPriceState] = useState<
|
||||
| { status: 'idle' }
|
||||
| { status: 'loading' }
|
||||
@@ -740,14 +740,6 @@ export default function WorkerConsignmentPage() {
|
||||
}
|
||||
/>
|
||||
|
||||
{!consignmentContactsQuery.isLoading && contactSupporters.length === 0 ? (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
message="暂无可选的对接客服,请联系管理员在「配置 - 对接客服」中添加后再发布"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Divider plain className="worker-consignment-divider">
|
||||
规格与价格
|
||||
</Divider>
|
||||
@@ -846,12 +838,16 @@ export default function WorkerConsignmentPage() {
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="worker-consignment-price-grid">
|
||||
<div
|
||||
className={`worker-consignment-price-grid${
|
||||
selectedProfile?.config.fields.quantity === false ? ' is-two' : ''
|
||||
}`}
|
||||
>
|
||||
<Form.Item
|
||||
label="寄售价格"
|
||||
tooltip={
|
||||
manualPriceAllowed
|
||||
? `默认按后台配置带出(模板折扣价 / 固定价),可下调,不能高于系统建议价${selectedProfile?.config.pricing.minPriceFen ? `;最低 ¥${(selectedProfile.config.pricing.minPriceFen / 100).toFixed(2)}` : ''}。`
|
||||
? `默认按后台配置带出(模板折扣价 / 模板价固定下调),可下调,不能高于系统建议价${selectedProfile?.config.pricing.minPriceFen ? `;最低 ¥${(selectedProfile.config.pricing.minPriceFen / 100).toFixed(2)}` : ''}。`
|
||||
: '由后台寄卖配置自动带出,卖家不可修改;需要调整请联系客服。'
|
||||
}
|
||||
className="worker-consignment-spec-card is-price"
|
||||
@@ -1126,33 +1122,12 @@ export default function WorkerConsignmentPage() {
|
||||
<Card bordered={false} className="worker-orders-card">
|
||||
<Tabs
|
||||
className="worker-order-tabs"
|
||||
activeKey={
|
||||
activeTab === 'records'
|
||||
? 'records'
|
||||
: selectedProfile
|
||||
? `publish-${selectedProfile.profileId}`
|
||||
: 'publish'
|
||||
}
|
||||
activeKey={activeTab === 'records' ? 'records' : 'publish'}
|
||||
onChange={(key) => {
|
||||
if (key === 'records') {
|
||||
setActiveTab('records')
|
||||
return
|
||||
}
|
||||
const profileId = Number(String(key).replace('publish-', ''))
|
||||
if (Number.isInteger(profileId) && profileId > 0) {
|
||||
if (profileId !== selectedProfileId) {
|
||||
const nextProfile = consignmentProfiles.find((item) => item.profileId === profileId)
|
||||
const nextSpecs = nextProfile?.config.specs ?? []
|
||||
const cleared: Record<string, undefined> = {}
|
||||
for (const field of nextSpecs) cleared[field.key] = undefined
|
||||
cleared.askingPrice = undefined
|
||||
cleared.itemName = undefined
|
||||
form.setFieldsValue(cleared)
|
||||
setPriceState({ status: 'idle' })
|
||||
lastPriceQueryKeyRef.current = ''
|
||||
}
|
||||
setSelectedProfileId(profileId)
|
||||
}
|
||||
setActiveTab('publish')
|
||||
}}
|
||||
items={[
|
||||
|
||||
@@ -615,7 +615,7 @@ export function fetchWorkerConsignmentPriceSuggestion(params: {
|
||||
}>('/api/v1/worker/consignment/price-suggestion', params)
|
||||
}
|
||||
|
||||
/** 动态规格发布:按 profileId + specValues 算价(模板折扣价 / 固定价)。 */
|
||||
/** 动态规格发布:按 profileId + specValues 算价(模板折扣价 / 模板价固定下调)。 */
|
||||
export function fetchWorkerConsignmentPricePreview(payload: {
|
||||
profileId?: number
|
||||
specValues: ConsignmentSpecValues
|
||||
|
||||
@@ -4498,11 +4498,16 @@ body {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 寄售价格 / 寄售数量 / 履约押金始终一行三列等宽 */
|
||||
.worker-consignment-price-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 240px 240px;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
/* 寄售数量被后台关闭时只剩两张卡,按两列等宽,避免第三列留空 */
|
||||
.worker-consignment-price-grid.is-two {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.worker-consignment-spec-card {
|
||||
@@ -4635,9 +4640,14 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.worker-consignment-spec-grid,
|
||||
@media (max-width: 1024px) {
|
||||
.worker-consignment-price-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.worker-consignment-spec-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -4646,6 +4656,13 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.worker-consignment-price-grid,
|
||||
.worker-consignment-price-grid.is-two {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.worker-consignment-filter-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -20,7 +20,7 @@ test('后台全量选项:系统含通用,区服含互通', () => {
|
||||
assert.ok(source.includes("value: '互通'"), '区服选项必须包含「互通」')
|
||||
})
|
||||
|
||||
test('苏尔南默认配置:取消代码内置字段,由迁移 144 提供五个单选字段', () => {
|
||||
test('苏尔南默认配置:取消代码内置字段,由合并迁移 142 提供五个单选字段', () => {
|
||||
const utils = readFileSync(
|
||||
new URL('../src/utils/consignment-spec.ts', import.meta.url).pathname,
|
||||
'utf8',
|
||||
|
||||
Reference in New Issue
Block a user