chore(db): 收敛订单服务数据库迁移

This commit is contained in:
yml2213
2026-11-04 13:13:53 +08:00
parent 3969703ac8
commit 7b75c27c85
11 changed files with 138 additions and 280 deletions
@@ -0,0 +1,138 @@
-- 128_order_service.sql
--
-- 分类级订单服务配置、客户资料提交、自动发布任务与客户通知。
-- 本迁移收敛了原 128–137,直接创建最终结构,并保留历史数据修复逻辑。
CREATE TABLE order_service_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 '',
guide_image_json JSONB NOT NULL DEFAULT '{}'::jsonb,
fields_json JSONB NOT NULL DEFAULT '[]'::jsonb,
screenshots_json JSONB NOT NULL DEFAULT '{}'::jsonb,
auto_publish_json JSONB NOT NULL DEFAULT '{"enabled":false}'::jsonb,
material_confirmation_json JSONB NOT NULL DEFAULT '{}'::jsonb,
submit_text TEXT NOT NULL DEFAULT '提交资料',
success_text TEXT NOT NULL DEFAULT '资料已提交,请等待处理',
post_submit_text TEXT NOT NULL DEFAULT '资料提交后如需修改,请联系客服。',
status_steps_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 order_service_profiles_status_check
CHECK (status IN ('draft', 'published', 'archived'))
);
CREATE UNIQUE INDEX uq_order_service_profiles_category
ON order_service_profiles (category_id);
CREATE INDEX idx_order_service_profiles_status
ON order_service_profiles (status);
CREATE TABLE order_service_profile_versions (
id BIGSERIAL PRIMARY KEY,
profile_id BIGINT NOT NULL REFERENCES order_service_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_order_service_profile_versions_no
UNIQUE (profile_id, version_no)
);
ALTER TABLE order_service_profiles
ADD CONSTRAINT fk_order_service_profiles_current_version
FOREIGN KEY (current_version_id)
REFERENCES order_service_profile_versions(id) ON DELETE SET NULL;
ALTER TABLE work_orders
ADD COLUMN service_profile_id BIGINT REFERENCES order_service_profiles(id) ON DELETE SET NULL,
ADD COLUMN service_profile_version_id BIGINT
REFERENCES order_service_profile_versions(id) ON DELETE SET NULL,
ADD COLUMN service_profile_snapshot_json JSONB;
CREATE INDEX idx_work_orders_service_profile
ON work_orders (service_profile_id, service_profile_version_id)
WHERE service_profile_id IS NOT NULL;
CREATE TABLE order_service_auto_publish_jobs (
id BIGSERIAL PRIMARY KEY,
work_order_id BIGINT NOT NULL REFERENCES work_orders(id) ON DELETE CASCADE,
status TEXT NOT NULL DEFAULT 'pending',
attempt_count INTEGER NOT NULL DEFAULT 0,
max_attempts INTEGER NOT NULL DEFAULT 3,
next_run_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_error TEXT NOT NULL DEFAULT '',
result_json JSONB NOT NULL DEFAULT '{}'::jsonb,
locked_until TIMESTAMPTZ,
locked_by TEXT NOT NULL DEFAULT '',
finished_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT order_service_auto_publish_jobs_status_check
CHECK (status IN ('pending', 'processing', 'succeeded', 'failed', 'skipped'))
);
CREATE UNIQUE INDEX uq_order_service_auto_publish_active
ON order_service_auto_publish_jobs (work_order_id)
WHERE status IN ('pending', 'processing');
CREATE INDEX idx_order_service_auto_publish_due
ON order_service_auto_publish_jobs (next_run_at)
WHERE status = 'pending';
CREATE INDEX idx_order_service_auto_publish_lease
ON order_service_auto_publish_jobs (locked_until)
WHERE status = 'processing';
CREATE TABLE customer_order_notifications (
id BIGSERIAL PRIMARY KEY,
order_id BIGINT REFERENCES orders(id) ON DELETE CASCADE,
order_item_id BIGINT REFERENCES order_items(id) ON DELETE SET NULL,
work_order_id BIGINT REFERENCES work_orders(id) ON DELETE CASCADE,
event_type TEXT NOT NULL,
channel TEXT NOT NULL DEFAULT 'sms',
status TEXT NOT NULL DEFAULT 'skipped',
phone TEXT NOT NULL DEFAULT '',
template_key TEXT NOT NULL DEFAULT '',
payload_json JSONB NOT NULL DEFAULT '{}'::jsonb,
dedupe_key TEXT NOT NULL,
attempt_count INTEGER NOT NULL DEFAULT 0,
last_error TEXT NOT NULL DEFAULT '',
sent_at TIMESTAMPTZ,
next_run_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT customer_order_notifications_channel_check
CHECK (channel IN ('web', 'sms')),
CONSTRAINT customer_order_notifications_status_check
CHECK (status IN ('pending', 'sent', 'failed', 'skipped'))
);
CREATE UNIQUE INDEX uq_customer_order_notifications_dedupe
ON customer_order_notifications (dedupe_key);
CREATE INDEX idx_customer_order_notifications_pending
ON customer_order_notifications (next_run_at)
WHERE status = 'pending';
COMMENT ON TABLE order_service_profiles IS
'分类级订单服务配置:资料字段、引导图、截图规则与自动发布策略';
COMMENT ON COLUMN order_service_profiles.material_confirmation_json IS
'提交资料确认:enabled/title/content/confirmText/cancelText';
COMMENT ON COLUMN order_service_profiles.post_submit_text IS
'客户资料页底部的提交后说明,支持多行文本';
COMMENT ON COLUMN order_service_profiles.status_steps_json IS
'客户状态进度文案:submitted/scheduled/processing/completed 的 title 与 description';
COMMENT ON COLUMN work_orders.service_profile_snapshot_json IS
'工单创建时固化的订单服务配置快照;配置后续变更不影响该工单';
COMMENT ON TABLE order_service_auto_publish_jobs IS
'客户资料提交后的自动发布任务;核销失败可按配置重试';
COMMENT ON COLUMN order_service_auto_publish_jobs.locked_until IS
'任务处理租约到期时间;进程中断后可被其它 worker 重新领取';
COMMENT ON COLUMN order_service_auto_publish_jobs.locked_by IS
'领取任务的进程或实例标识';
@@ -1,74 +0,0 @@
-- 128_order_service_profiles.sql
--
-- 分类级订单服务配置:资料收集字段、引导图、截图规则与自动发布策略。
-- 工单创建时固化配置快照,后续修改配置不影响已创建工单。
ALTER TABLE work_categories
ADD COLUMN IF NOT EXISTS default_work_rule_id BIGINT REFERENCES work_product_rules(id) ON DELETE SET NULL;
COMMENT ON COLUMN work_categories.default_work_rule_id IS
'分类默认接单模板;商品级映射可覆盖,未命中时整个分类使用该模板';
CREATE TABLE IF NOT EXISTS order_service_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 '',
guide_image_json JSONB NOT NULL DEFAULT '{}'::jsonb,
fields_json JSONB NOT NULL DEFAULT '[]'::jsonb,
screenshots_json JSONB NOT NULL DEFAULT '{}'::jsonb,
auto_publish_json JSONB NOT NULL DEFAULT '{"enabled":false}'::jsonb,
submit_text TEXT NOT NULL DEFAULT '提交资料',
success_text TEXT NOT NULL DEFAULT '资料已提交,请等待处理',
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 order_service_profiles_status_check
CHECK (status IN ('draft', 'published', 'archived'))
);
CREATE UNIQUE INDEX IF NOT EXISTS uq_order_service_profiles_category
ON order_service_profiles (category_id);
CREATE INDEX IF NOT EXISTS idx_order_service_profiles_status
ON order_service_profiles (status);
CREATE TABLE IF NOT EXISTS order_service_profile_versions (
id BIGSERIAL PRIMARY KEY,
profile_id BIGINT NOT NULL REFERENCES order_service_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_order_service_profile_versions_no
UNIQUE (profile_id, version_no)
);
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'fk_order_service_profiles_current_version'
) THEN
ALTER TABLE order_service_profiles
ADD CONSTRAINT fk_order_service_profiles_current_version
FOREIGN KEY (current_version_id)
REFERENCES order_service_profile_versions(id) ON DELETE SET NULL;
END IF;
END $$;
ALTER TABLE work_orders
ADD COLUMN IF NOT EXISTS service_profile_id BIGINT REFERENCES order_service_profiles(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS service_profile_version_id BIGINT REFERENCES order_service_profile_versions(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS service_profile_snapshot_json JSONB;
CREATE INDEX IF NOT EXISTS idx_work_orders_service_profile
ON work_orders (service_profile_id, service_profile_version_id)
WHERE service_profile_id IS NOT NULL;
COMMENT ON TABLE order_service_profiles IS
'分类级订单服务配置:资料字段、引导图、截图规则与自动发布策略';
COMMENT ON COLUMN work_orders.service_profile_snapshot_json IS
'工单创建时固化的订单服务配置快照;配置后续变更不影响该工单';
@@ -1,29 +0,0 @@
-- 129_order_service_auto_publish_jobs.sql
--
-- 资料提交后自动发布工单。任务先落库,外部电子凭证核销失败时可以重试。
CREATE TABLE IF NOT EXISTS order_service_auto_publish_jobs (
id BIGSERIAL PRIMARY KEY,
work_order_id BIGINT NOT NULL REFERENCES work_orders(id) ON DELETE CASCADE,
status TEXT NOT NULL DEFAULT 'pending',
attempt_count INTEGER NOT NULL DEFAULT 0,
max_attempts INTEGER NOT NULL DEFAULT 3,
next_run_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_error TEXT NOT NULL DEFAULT '',
result_json JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT order_service_auto_publish_jobs_status_check
CHECK (status IN ('pending', 'succeeded', 'failed', 'skipped'))
);
CREATE UNIQUE INDEX IF NOT EXISTS uq_order_service_auto_publish_pending
ON order_service_auto_publish_jobs (work_order_id)
WHERE status = 'pending';
CREATE INDEX IF NOT EXISTS idx_order_service_auto_publish_due
ON order_service_auto_publish_jobs (next_run_at)
WHERE status = 'pending';
COMMENT ON TABLE order_service_auto_publish_jobs IS
'客户资料提交后的自动发布任务;核销失败可按配置重试';
@@ -1,35 +0,0 @@
-- 130_customer_notifications.sql
--
-- 客户订单通知事件模型。首版只落事件,不发送短信;
-- 后续接入 SMS Provider 时消费 pending 任务即可。
CREATE TABLE IF NOT EXISTS customer_order_notifications (
id BIGSERIAL PRIMARY KEY,
order_id BIGINT REFERENCES orders(id) ON DELETE CASCADE,
order_item_id BIGINT REFERENCES order_items(id) ON DELETE SET NULL,
work_order_id BIGINT REFERENCES work_orders(id) ON DELETE CASCADE,
event_type TEXT NOT NULL,
channel TEXT NOT NULL DEFAULT 'sms',
status TEXT NOT NULL DEFAULT 'skipped',
phone TEXT NOT NULL DEFAULT '',
template_key TEXT NOT NULL DEFAULT '',
payload_json JSONB NOT NULL DEFAULT '{}'::jsonb,
dedupe_key TEXT NOT NULL,
attempt_count INTEGER NOT NULL DEFAULT 0,
last_error TEXT NOT NULL DEFAULT '',
sent_at TIMESTAMPTZ,
next_run_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT customer_order_notifications_channel_check
CHECK (channel IN ('web', 'sms')),
CONSTRAINT customer_order_notifications_status_check
CHECK (status IN ('pending', 'sent', 'failed', 'skipped'))
);
CREATE UNIQUE INDEX IF NOT EXISTS uq_customer_order_notifications_dedupe
ON customer_order_notifications (dedupe_key);
CREATE INDEX IF NOT EXISTS idx_customer_order_notifications_pending
ON customer_order_notifications (next_run_at)
WHERE status = 'pending';
@@ -1,34 +0,0 @@
-- 131_order_service_hardening.sql
--
-- 订单服务配置以“分类 -> 当前发布版本”为线上唯一读取源。
-- 分类与接单模板的绑定沿用 work_product_rules.category_id,不再维护反向默认模板。
ALTER TABLE work_categories
DROP COLUMN IF EXISTS default_work_rule_id;
ALTER TABLE order_service_auto_publish_jobs
ADD COLUMN IF NOT EXISTS locked_until TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS locked_by TEXT NOT NULL DEFAULT '',
ADD COLUMN IF NOT EXISTS finished_at TIMESTAMPTZ;
ALTER TABLE order_service_auto_publish_jobs
DROP CONSTRAINT IF EXISTS order_service_auto_publish_jobs_status_check;
ALTER TABLE order_service_auto_publish_jobs
ADD CONSTRAINT order_service_auto_publish_jobs_status_check
CHECK (status IN ('pending', 'processing', 'succeeded', 'failed', 'skipped'));
DROP INDEX IF EXISTS uq_order_service_auto_publish_pending;
CREATE UNIQUE INDEX IF NOT EXISTS uq_order_service_auto_publish_active
ON order_service_auto_publish_jobs (work_order_id)
WHERE status IN ('pending', 'processing');
CREATE INDEX IF NOT EXISTS idx_order_service_auto_publish_lease
ON order_service_auto_publish_jobs (locked_until)
WHERE status = 'processing';
COMMENT ON COLUMN order_service_auto_publish_jobs.locked_until IS
'任务处理租约到期时间;进程中断后可被其它 worker 重新领取';
COMMENT ON COLUMN order_service_auto_publish_jobs.locked_by IS
'领取任务的进程或实例标识';
@@ -1,26 +0,0 @@
-- 132_backfill_pending_order_service_snapshots.sql
--
-- 旧工单创建于“配置快照”上线之前,打开 /o 时会回退到旧的 requirement_json。
-- 对本分类仍待客户提交资料、且未指派打手的工单,回填当前发布版本快照。
UPDATE work_orders wo
SET service_profile_id = p.id,
service_profile_version_id = v.id,
service_profile_snapshot_json = jsonb_build_object(
'profileId', p.id,
'categoryId', p.category_id,
'versionId', v.id,
'versionNo', v.version_no,
'config', v.config_json
),
requirement_json = jsonb_build_object(
'fields', COALESCE(v.config_json -> 'fields', '[]'::jsonb)
),
updated_at = NOW()
FROM order_service_profiles p
JOIN order_service_profile_versions v ON v.id = p.current_version_id
WHERE wo.category_id = p.category_id
AND p.status <> 'archived'
AND wo.status = 'pending_material'
AND wo.assigned_worker_id IS NULL
AND wo.service_profile_snapshot_json IS NULL;
@@ -1,8 +0,0 @@
-- 133_order_service_profile_announcement.sql
-- 订单服务客户页公告:草稿保存公告内容与展示样式,发布时随配置写入版本快照。
ALTER TABLE order_service_profiles
ADD COLUMN IF NOT EXISTS announcement_json JSONB NOT NULL DEFAULT '{}'::jsonb;
COMMENT ON COLUMN order_service_profiles.announcement_json IS
'客户页公告:enabled/title/content/fontSize/color/backgroundColor/fontWeight/textAlign';
@@ -1,9 +0,0 @@
-- 134_order_service_material_confirmation.sql
-- 客户提交资料前的确认弹窗配置;同时移除已废弃的页面公告字段。
ALTER TABLE order_service_profiles
ADD COLUMN IF NOT EXISTS material_confirmation_json JSONB NOT NULL DEFAULT '{}'::jsonb,
DROP COLUMN IF EXISTS announcement_json;
COMMENT ON COLUMN order_service_profiles.material_confirmation_json IS
'提交资料确认:enabled/title/content/confirmText/cancelText';
@@ -1,9 +0,0 @@
-- 135_order_service_post_submit_text.sql
-- 客户提交资料后,页面底部展示的自定义说明。
ALTER TABLE order_service_profiles
ADD COLUMN IF NOT EXISTS post_submit_text TEXT NOT NULL
DEFAULT '资料提交后如需修改,请联系客服。';
COMMENT ON COLUMN order_service_profiles.post_submit_text IS
'客户资料页底部的提交后说明,支持多行文本';
@@ -1,48 +0,0 @@
-- 136_backfill_mock_order_service_snapshots.sql
-- 修复历史 Mock 工单未绑定订单服务版本,导致资料提交后无法自动发布的问题。
UPDATE work_orders wo
SET service_profile_id = p.id,
service_profile_version_id = v.id,
service_profile_snapshot_json = jsonb_build_object(
'profileId', p.id,
'categoryId', p.category_id,
'versionId', v.id,
'versionNo', v.version_no,
'config', v.config_json
),
requirement_json = jsonb_build_object(
'fields', COALESCE(v.config_json -> 'fields', '[]'::jsonb)
),
updated_at = NOW()
FROM order_service_profiles p
JOIN order_service_profile_versions v ON v.id = p.current_version_id
WHERE wo.category_id = p.category_id
AND wo.service_profile_snapshot_json IS NULL
AND wo.status IN ('pending_material', 'unassigned')
AND wo.assigned_worker_id IS NULL
AND wo.material_json -> 'source' ->> 'mock' = 'true'
AND NOT EXISTS (
SELECT 1
FROM work_order_shares share
WHERE share.work_order_id = wo.id
AND share.status <> 'cancelled'
);
INSERT INTO order_service_auto_publish_jobs (work_order_id, max_attempts)
SELECT
wo.id,
GREATEST(
1,
COALESCE(
NULLIF(wo.service_profile_snapshot_json -> 'config' -> 'autoPublish' ->> 'retryMax', '')::int,
3
) + 1
)
FROM work_orders wo
WHERE wo.material_json -> 'source' ->> 'mock' = 'true'
AND wo.status = 'unassigned'
AND wo.assigned_worker_id IS NULL
AND wo.material_json -> 'collect' ->> 'complete' = 'true'
AND wo.service_profile_snapshot_json -> 'config' -> 'autoPublish' ->> 'enabled' = 'true'
ON CONFLICT DO NOTHING;
@@ -1,8 +0,0 @@
-- 137_order_service_status_steps.sql
-- 客户订单状态进度的四个节点文案,可按分类独立配置。
ALTER TABLE order_service_profiles
ADD COLUMN IF NOT EXISTS status_steps_json JSONB NOT NULL DEFAULT '{}'::jsonb;
COMMENT ON COLUMN order_service_profiles.status_steps_json IS
'客户状态进度文案:submitted/scheduled/processing/completed 的 title 与 description';