feat(client): A3 客户与行业目录(两级行业、客户组、字段审批)
背景:A3 需把行业 / 客户 / 客户组 / 客户级字段立为资产根节点;用户确认两级行业树与混搭 UI(行业 B / 客户列表 A / 详情 A / 新建 B / 客户组 B / 字段 A)。 改动: - 新增 ADR-002,行业改为两级树,客户挂末级行业;同步 PRD / 技术架构 / 开发计划。 - 新增 client 模块:industries / customers / customer-groups / customer-fields 接口、真实 PrismaCustomerDirectory、客户状态机与客户转交、客户组数据范围解析、跨客户 404。 - 新增迁移与幂等种子(电商/金融两级行业、2 客户、1 客户组、4 条字段)。 - 前端新增行业/客户/客户组/字段审批页面与路由、导航更名(客户管理 / 行业管理 / 客户组),复用现有 token。 - 补充 shared 契约单测、client 集成用例、A3 浏览器 e2e。 影响面:client 模块与 client 表、shared 契约、web 路由/导航、ScopeContext 数据范围、OpenAPI 与种子;A1 客户转交占位由 client 实现替换。 回滚:回滚本提交;数据库保留新增表或按报告执行 down SQL;CUSTOMER_DIRECTORY 可切回空集实现。 验证:format:check / lint / typecheck / test 全绿(shared 131 / api 90 / web 125,共 346);test:int 5 文件 39 条;A3 e2e 1/1;build 8/8;真实浏览器六页验证通过。
This commit is contained in:
1 parent
8626996bb1
commit
140dc386c1
54 files changed
+9018
-147
No files matched your search
@@ -0,0 +1,157 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "IndustryStatus" AS ENUM ('ACTIVE', 'DISABLED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "CustomerStatus" AS ENUM ('ACTIVE', 'PAUSED', 'ENDED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "CustomerFieldStatus" AS ENUM ('PENDING', 'ACTIVE', 'REJECTED', 'DISABLED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "industries" (
|
||||
"id" CHAR(26) NOT NULL,
|
||||
"parent_id" CHAR(26),
|
||||
"level" INTEGER NOT NULL DEFAULT 1,
|
||||
"name" VARCHAR(64) NOT NULL,
|
||||
"sort_order" INTEGER NOT NULL DEFAULT 10,
|
||||
"status" "IndustryStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"remark" VARCHAR(200) NOT NULL DEFAULT '',
|
||||
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "industries_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "customers" (
|
||||
"id" CHAR(26) NOT NULL,
|
||||
"industry_id" CHAR(26) NOT NULL,
|
||||
"name" VARCHAR(128) NOT NULL,
|
||||
"short_name" VARCHAR(32) NOT NULL,
|
||||
"status" "CustomerStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"owner_user_id" CHAR(26) NOT NULL,
|
||||
"contact_name" VARCHAR(32),
|
||||
"contact_phone" VARCHAR(32),
|
||||
"preferences" JSONB NOT NULL DEFAULT '{}',
|
||||
"custom_fields" JSONB NOT NULL DEFAULT '[]',
|
||||
"remark" VARCHAR(500),
|
||||
"created_by" CHAR(26),
|
||||
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ(3) NOT NULL,
|
||||
"deleted_at" TIMESTAMPTZ(3),
|
||||
|
||||
CONSTRAINT "customers_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "customer_groups" (
|
||||
"id" CHAR(26) NOT NULL,
|
||||
"name" VARCHAR(64) NOT NULL,
|
||||
"description" VARCHAR(255) NOT NULL DEFAULT '',
|
||||
"created_by" CHAR(26),
|
||||
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "customer_groups_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "customer_group_members" (
|
||||
"group_id" CHAR(26) NOT NULL,
|
||||
"customer_id" CHAR(26) NOT NULL,
|
||||
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "customer_group_members_pkey" PRIMARY KEY ("group_id","customer_id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "customer_fields" (
|
||||
"id" CHAR(26) NOT NULL,
|
||||
"customer_id" CHAR(26) NOT NULL,
|
||||
"name" VARCHAR(32) NOT NULL,
|
||||
"code" VARCHAR(32) NOT NULL,
|
||||
"type" VARCHAR(16) NOT NULL,
|
||||
"options" JSONB NOT NULL DEFAULT '[]',
|
||||
"purpose" VARCHAR(200) NOT NULL,
|
||||
"enters_generation" BOOLEAN NOT NULL DEFAULT false,
|
||||
"status" "CustomerFieldStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"applicant_user_id" CHAR(26) NOT NULL,
|
||||
"approver_user_id" CHAR(26),
|
||||
"applied_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"approved_at" TIMESTAMPTZ(3),
|
||||
"reject_reason" VARCHAR(200),
|
||||
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "customer_fields_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "industries_parent_id_sort_order_idx" ON "industries"("parent_id", "sort_order");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "industries_level_status_idx" ON "industries"("level", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "customers_owner_user_id_status_idx" ON "customers"("owner_user_id", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "customers_status_updated_at_idx" ON "customers"("status", "updated_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "customers_deleted_at_idx" ON "customers"("deleted_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "customers_industry_id_name_key" ON "customers"("industry_id", "name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "customer_groups_name_key" ON "customer_groups"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "customer_group_members_customer_id_idx" ON "customer_group_members"("customer_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "customer_fields_status_applied_at_idx" ON "customer_fields"("status", "applied_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "customer_fields_customer_id_status_idx" ON "customer_fields"("customer_id", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "customer_fields_customer_id_code_key" ON "customer_fields"("customer_id", "code");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "industries" ADD CONSTRAINT "industries_parent_id_fkey" FOREIGN KEY ("parent_id") REFERENCES "industries"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "customers" ADD CONSTRAINT "customers_industry_id_fkey" FOREIGN KEY ("industry_id") REFERENCES "industries"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "customers" ADD CONSTRAINT "customers_owner_user_id_fkey" FOREIGN KEY ("owner_user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "customers" ADD CONSTRAINT "customers_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "customer_group_members" ADD CONSTRAINT "customer_group_members_group_id_fkey" FOREIGN KEY ("group_id") REFERENCES "customer_groups"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "customer_group_members" ADD CONSTRAINT "customer_group_members_customer_id_fkey" FOREIGN KEY ("customer_id") REFERENCES "customers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "customer_fields" ADD CONSTRAINT "customer_fields_customer_id_fkey" FOREIGN KEY ("customer_id") REFERENCES "customers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "customer_fields" ADD CONSTRAINT "customer_fields_applicant_user_id_fkey" FOREIGN KEY ("applicant_user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "customer_fields" ADD CONSTRAINT "customer_fields_approver_user_id_fkey" FOREIGN KEY ("approver_user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- A3 / ADR-002:行业名称唯一性(一级全局唯一,二级同父唯一)与两级结构校验
|
||||
CREATE UNIQUE INDEX "industries_root_name_key" ON "industries"("name") WHERE "parent_id" IS NULL;
|
||||
CREATE UNIQUE INDEX "industries_child_name_key" ON "industries"("parent_id", "name") WHERE "parent_id" IS NOT NULL;
|
||||
|
||||
ALTER TABLE "industries"
|
||||
ADD CONSTRAINT "industries_level_check"
|
||||
CHECK (("parent_id" IS NULL AND "level" = 1) OR ("parent_id" IS NOT NULL AND "level" = 2));
|
||||
ALTER TABLE "industries"
|
||||
ADD CONSTRAINT "industries_no_self_parent_check" CHECK ("id" <> "parent_id");
|
||||
Reference in new issue
Block a user