v0.1.0: CRM/ERP 系统内测版本 - 安全加固完成

- Docker bridge 网络隔离(8000 端口封死)
- Gunicorn 4 Worker 多进程
- Alembic 数据库迁移基线
- 日志轮转 20m×3
- JWT 密钥 + DB 密码 + CORS 收紧
- 3-2-1 备份链路(NAS + R740-B 冷备)
- 连接池 pool_pre_ping + pool_recycle=3600
This commit is contained in:
hankin
2026-03-16 07:31:37 +00:00
commit 423baff73b
2578 changed files with 824643 additions and 0 deletions
+539
View File
@@ -0,0 +1,539 @@
-- ============================================================
-- 润滑油行业 B2B ERP/CRM 综合系统 - PostgreSQL 数据库建模
-- 技术栈: Python (FastAPI) + PostgreSQL
-- 生成时间: 2026-02-27
-- ============================================================
-- 启用 uuid-ossp 扩展(用于 UUID 主键生成)
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- ============================================================
-- 通用函数:自动更新 updated_at 触发器
-- ============================================================
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- ************************************************************
-- 1. RBAC 权限域
-- ************************************************************
-- ============================================================
-- 1.1 部门树表 sys_departments
-- ============================================================
CREATE TABLE sys_departments (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
parent_id UUID REFERENCES sys_departments(id),
name VARCHAR(100) NOT NULL,
sort_order INT DEFAULT 0,
status SMALLINT DEFAULT 1, -- 1:启用 0:停用
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_deleted BOOLEAN DEFAULT FALSE
);
COMMENT ON TABLE sys_departments IS '部门树表';
COMMENT ON COLUMN sys_departments.id IS '部门主键 UUID';
COMMENT ON COLUMN sys_departments.parent_id IS '父级部门IDNULL表示顶级';
COMMENT ON COLUMN sys_departments.name IS '部门名称';
COMMENT ON COLUMN sys_departments.sort_order IS '排序序号';
COMMENT ON COLUMN sys_departments.status IS '状态 1:启用 0:停用';
CREATE INDEX idx_dept_parent ON sys_departments(parent_id) WHERE is_deleted = FALSE;
CREATE TRIGGER trg_dept_updated
BEFORE UPDATE ON sys_departments
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================
-- 1.2 角色表 sys_roles
-- ============================================================
CREATE TABLE sys_roles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
role_name VARCHAR(50) NOT NULL UNIQUE,
data_scope VARCHAR(20) NOT NULL DEFAULT 'self', -- all / dept_and_sub / self
menu_keys JSONB DEFAULT '[]'::JSONB, -- 菜单与按钮权限键集合
description VARCHAR(255),
status SMALLINT DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_deleted BOOLEAN DEFAULT FALSE
);
COMMENT ON TABLE sys_roles IS '角色表';
COMMENT ON COLUMN sys_roles.id IS '角色主键 UUID';
COMMENT ON COLUMN sys_roles.role_name IS '角色名称';
COMMENT ON COLUMN sys_roles.data_scope IS '数据权限范围: all=全部 / dept_and_sub=本部门及下属 / self=仅本人';
COMMENT ON COLUMN sys_roles.menu_keys IS '拥有的菜单/按钮权限键列表 (JSONB数组)';
CREATE TRIGGER trg_role_updated
BEFORE UPDATE ON sys_roles
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================
-- 1.3 员工/账号表 sys_users
-- ============================================================
CREATE TABLE sys_users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
dept_id UUID REFERENCES sys_departments(id),
role_id UUID REFERENCES sys_roles(id),
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
real_name VARCHAR(50),
phone VARCHAR(20),
email VARCHAR(100),
avatar_url VARCHAR(500),
status SMALLINT DEFAULT 1, -- 1:在职 0:离职
last_login_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_deleted BOOLEAN DEFAULT FALSE
);
COMMENT ON TABLE sys_users IS '员工/账号表';
COMMENT ON COLUMN sys_users.id IS '用户主键 UUID';
COMMENT ON COLUMN sys_users.dept_id IS '所属部门ID';
COMMENT ON COLUMN sys_users.role_id IS '所属角色ID';
COMMENT ON COLUMN sys_users.username IS '登录账号';
COMMENT ON COLUMN sys_users.password_hash IS '密码哈希值';
COMMENT ON COLUMN sys_users.real_name IS '真实姓名';
COMMENT ON COLUMN sys_users.status IS '状态 1:在职 0:离职';
CREATE INDEX idx_user_dept ON sys_users(dept_id) WHERE is_deleted = FALSE;
CREATE INDEX idx_user_role ON sys_users(role_id) WHERE is_deleted = FALSE;
CREATE TRIGGER trg_user_updated
BEFORE UPDATE ON sys_users
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ************************************************************
-- 2. CRM 客户域
-- ************************************************************
-- ============================================================
-- 2.1 客户主表 crm_customers
-- ============================================================
CREATE TABLE crm_customers (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(200) NOT NULL,
level CHAR(1) DEFAULT 'C', -- A / B / C 三级
industry VARCHAR(100),
contact VARCHAR(50), -- 联系人姓名
phone VARCHAR(30),
email VARCHAR(100),
address TEXT,
ai_score NUMERIC(5,2) DEFAULT 0, -- AI 客情健康度评分 0~100
owner_id UUID REFERENCES sys_users(id), -- 负责销售
status SMALLINT DEFAULT 1, -- 1:活跃 0:冻结
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_deleted BOOLEAN DEFAULT FALSE
);
COMMENT ON TABLE crm_customers IS '客户主表';
COMMENT ON COLUMN crm_customers.id IS '客户主键 UUID';
COMMENT ON COLUMN crm_customers.name IS '客户/公司名称';
COMMENT ON COLUMN crm_customers.level IS '客户等级 A/B/C';
COMMENT ON COLUMN crm_customers.ai_score IS 'AI客情健康度评分 0~100';
COMMENT ON COLUMN crm_customers.owner_id IS '负责销售ID';
CREATE INDEX idx_cust_level ON crm_customers(level) WHERE is_deleted = FALSE;
CREATE INDEX idx_cust_owner ON crm_customers(owner_id) WHERE is_deleted = FALSE;
CREATE INDEX idx_cust_name ON crm_customers(name) WHERE is_deleted = FALSE;
CREATE TRIGGER trg_cust_updated
BEFORE UPDATE ON crm_customers
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================
-- 2.2 客户跟进日志表 crm_follow_up_logs
-- ============================================================
CREATE TABLE crm_follow_up_logs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
customer_id UUID NOT NULL REFERENCES crm_customers(id),
salesperson_id UUID NOT NULL REFERENCES sys_users(id),
content TEXT NOT NULL,
emotion_type VARCHAR(20), -- positive / neutral / negative
next_visit_time TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_deleted BOOLEAN DEFAULT FALSE
);
COMMENT ON TABLE crm_follow_up_logs IS '客户跟进日志表(AI简报数据源)';
COMMENT ON COLUMN crm_follow_up_logs.customer_id IS '关联客户ID';
COMMENT ON COLUMN crm_follow_up_logs.salesperson_id IS '操作销售人员ID';
COMMENT ON COLUMN crm_follow_up_logs.content IS '跟进内容';
COMMENT ON COLUMN crm_follow_up_logs.emotion_type IS '情感标记: positive/neutral/negative';
COMMENT ON COLUMN crm_follow_up_logs.next_visit_time IS '计划下次拜访时间';
CREATE INDEX idx_follow_cust ON crm_follow_up_logs(customer_id) WHERE is_deleted = FALSE;
CREATE INDEX idx_follow_sales ON crm_follow_up_logs(salesperson_id) WHERE is_deleted = FALSE;
CREATE TRIGGER trg_follow_updated
BEFORE UPDATE ON crm_follow_up_logs
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ************************************************************
-- 3. ERP 供应链域
-- ************************************************************
-- ============================================================
-- 3.1 产品分类树 erp_product_categories
-- ============================================================
CREATE TABLE erp_product_categories (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
parent_id UUID REFERENCES erp_product_categories(id),
name VARCHAR(100) NOT NULL,
sort_order INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_deleted BOOLEAN DEFAULT FALSE
);
COMMENT ON TABLE erp_product_categories IS '产品分类树表(左树结构支撑)';
COMMENT ON COLUMN erp_product_categories.parent_id IS '父级分类IDNULL为顶级分类';
CREATE INDEX idx_pcat_parent ON erp_product_categories(parent_id) WHERE is_deleted = FALSE;
CREATE TRIGGER trg_pcat_updated
BEFORE UPDATE ON erp_product_categories
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================
-- 3.2 产品 SKU 表 erp_product_skus
-- ============================================================
CREATE TABLE erp_product_skus (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
category_id UUID REFERENCES erp_product_categories(id),
sku_code VARCHAR(50) NOT NULL UNIQUE,
name VARCHAR(200) NOT NULL,
spec VARCHAR(100), -- 规格,如 200L/桶
standard_price NUMERIC(12,2) NOT NULL DEFAULT 0,
stock_qty NUMERIC(12,2) NOT NULL DEFAULT 0,
warning_threshold NUMERIC(12,2) DEFAULT 0, -- 库存预警阈值
unit VARCHAR(20) DEFAULT '',
status SMALLINT DEFAULT 1, -- 1:在售 0:停售
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_deleted BOOLEAN DEFAULT FALSE
);
COMMENT ON TABLE erp_product_skus IS '产品SKU主档表';
COMMENT ON COLUMN erp_product_skus.sku_code IS 'SKU编号,全局唯一';
COMMENT ON COLUMN erp_product_skus.spec IS '包装规格,如 200L/桶、18L/桶';
COMMENT ON COLUMN erp_product_skus.standard_price IS '标准售价';
COMMENT ON COLUMN erp_product_skus.stock_qty IS '当前库存数量';
COMMENT ON COLUMN erp_product_skus.warning_threshold IS '库存预警阈值,低于此值触发预警';
CREATE INDEX idx_sku_category ON erp_product_skus(category_id) WHERE is_deleted = FALSE;
CREATE INDEX idx_sku_code ON erp_product_skus(sku_code) WHERE is_deleted = FALSE;
CREATE TRIGGER trg_sku_updated
BEFORE UPDATE ON erp_product_skus
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================
-- 3.3 出入库流水表 erp_inventory_flows
-- ============================================================
CREATE TABLE erp_inventory_flows (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
sku_id UUID NOT NULL REFERENCES erp_product_skus(id),
change_qty NUMERIC(12,2) NOT NULL, -- 正数=入库,负数=出库
reason VARCHAR(50) NOT NULL, -- purchase/shipment/loss/adjust
remark TEXT,
operator_id UUID REFERENCES sys_users(id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_deleted BOOLEAN DEFAULT FALSE
);
COMMENT ON TABLE erp_inventory_flows IS '出入库流水账表';
COMMENT ON COLUMN erp_inventory_flows.sku_id IS '关联SKU ID';
COMMENT ON COLUMN erp_inventory_flows.change_qty IS '变动数量:正=入库 负=出库';
COMMENT ON COLUMN erp_inventory_flows.reason IS '变动原因: purchase/shipment/loss/adjust';
COMMENT ON COLUMN erp_inventory_flows.operator_id IS '操作人ID';
CREATE INDEX idx_invflow_sku ON erp_inventory_flows(sku_id) WHERE is_deleted = FALSE;
CREATE INDEX idx_invflow_operator ON erp_inventory_flows(operator_id) WHERE is_deleted = FALSE;
CREATE INDEX idx_invflow_time ON erp_inventory_flows(created_at) WHERE is_deleted = FALSE;
CREATE TRIGGER trg_invflow_updated
BEFORE UPDATE ON erp_inventory_flows
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ************************************************************
-- 4. ERP 交易域
-- ************************************************************
-- ============================================================
-- 4.1 订单主表 erp_orders
-- ============================================================
CREATE TABLE erp_orders (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
order_no VARCHAR(30) NOT NULL UNIQUE, -- 业务单号,如 ORD-20260227-001
customer_id UUID NOT NULL REFERENCES crm_customers(id),
salesperson_id UUID REFERENCES sys_users(id),
total_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
shipping_state VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending / partial / shipped
payment_state VARCHAR(20) NOT NULL DEFAULT 'unpaid', -- unpaid / partial / cleared
paid_amount NUMERIC(14,2) DEFAULT 0,
remark TEXT,
order_date DATE DEFAULT CURRENT_DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_deleted BOOLEAN DEFAULT FALSE
);
COMMENT ON TABLE erp_orders IS '订单主表';
COMMENT ON COLUMN erp_orders.order_no IS '业务订单号';
COMMENT ON COLUMN erp_orders.customer_id IS '下单客户ID';
COMMENT ON COLUMN erp_orders.salesperson_id IS '负责销售ID';
COMMENT ON COLUMN erp_orders.total_amount IS '订单总金额';
COMMENT ON COLUMN erp_orders.shipping_state IS '发货状态: pending/partial/shipped';
COMMENT ON COLUMN erp_orders.payment_state IS '付款状态: unpaid/partial/cleared';
COMMENT ON COLUMN erp_orders.paid_amount IS '已付金额';
CREATE INDEX idx_order_no ON erp_orders(order_no) WHERE is_deleted = FALSE;
CREATE INDEX idx_order_cust ON erp_orders(customer_id) WHERE is_deleted = FALSE;
CREATE INDEX idx_order_sales ON erp_orders(salesperson_id) WHERE is_deleted = FALSE;
CREATE INDEX idx_order_date ON erp_orders(order_date) WHERE is_deleted = FALSE;
CREATE TRIGGER trg_order_updated
BEFORE UPDATE ON erp_orders
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================
-- 4.2 订单明细表 erp_order_items
-- ============================================================
CREATE TABLE erp_order_items (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
order_id UUID NOT NULL REFERENCES erp_orders(id),
sku_id UUID NOT NULL REFERENCES erp_product_skus(id),
qty NUMERIC(12,2) NOT NULL,
unit_price NUMERIC(12,2) NOT NULL, -- 本次成交单价(可能是专属价)
sub_total NUMERIC(14,2) NOT NULL,
shipped_qty NUMERIC(12,2) DEFAULT 0, -- 已发货累计量(用于分批发货扣减)
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_deleted BOOLEAN DEFAULT FALSE
);
COMMENT ON TABLE erp_order_items IS '订单明细行表';
COMMENT ON COLUMN erp_order_items.order_id IS '归属订单ID';
COMMENT ON COLUMN erp_order_items.sku_id IS '关联SKU ID';
COMMENT ON COLUMN erp_order_items.qty IS '下单数量';
COMMENT ON COLUMN erp_order_items.unit_price IS '成交单价(可能为客户专属价)';
COMMENT ON COLUMN erp_order_items.sub_total IS '小计金额 = qty * unit_price';
COMMENT ON COLUMN erp_order_items.shipped_qty IS '已发货累计数量,用于分批发货进度追踪';
CREATE INDEX idx_oitem_order ON erp_order_items(order_id) WHERE is_deleted = FALSE;
CREATE INDEX idx_oitem_sku ON erp_order_items(sku_id) WHERE is_deleted = FALSE;
CREATE TRIGGER trg_oitem_updated
BEFORE UPDATE ON erp_order_items
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ************************************************************
-- 5. ERP 物流域
-- ************************************************************
-- ============================================================
-- 5.1 发货主单表 erp_shipping_records
-- ============================================================
CREATE TABLE erp_shipping_records (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
shipping_no VARCHAR(30) NOT NULL UNIQUE, -- 发货单号 SHP-xxx
order_id UUID NOT NULL REFERENCES erp_orders(id),
carrier VARCHAR(100), -- 承运方,如德邦
tracking_no VARCHAR(100), -- 物流追踪号
status VARCHAR(20) NOT NULL DEFAULT 'transit', -- transit / delivered
ship_date DATE DEFAULT CURRENT_DATE,
remark TEXT,
operator_id UUID REFERENCES sys_users(id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_deleted BOOLEAN DEFAULT FALSE
);
COMMENT ON TABLE erp_shipping_records IS '发货主单表';
COMMENT ON COLUMN erp_shipping_records.shipping_no IS '发货单号';
COMMENT ON COLUMN erp_shipping_records.order_id IS '关联订单ID';
COMMENT ON COLUMN erp_shipping_records.carrier IS '承运方';
COMMENT ON COLUMN erp_shipping_records.tracking_no IS '物流追踪号';
COMMENT ON COLUMN erp_shipping_records.status IS '物流状态: transit=运输中 / delivered=已签收';
CREATE INDEX idx_ship_order ON erp_shipping_records(order_id) WHERE is_deleted = FALSE;
CREATE INDEX idx_ship_no ON erp_shipping_records(shipping_no) WHERE is_deleted = FALSE;
CREATE TRIGGER trg_ship_updated
BEFORE UPDATE ON erp_shipping_records
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================
-- 5.2 发货明细表 erp_shipping_items
-- ============================================================
CREATE TABLE erp_shipping_items (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
shipping_id UUID NOT NULL REFERENCES erp_shipping_records(id),
order_item_id UUID NOT NULL REFERENCES erp_order_items(id),
sku_id UUID NOT NULL REFERENCES erp_product_skus(id),
shipped_qty NUMERIC(12,2) NOT NULL, -- 本次发货数量
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_deleted BOOLEAN DEFAULT FALSE
);
COMMENT ON TABLE erp_shipping_items IS '发货明细行表(承接分批发货扣减)';
COMMENT ON COLUMN erp_shipping_items.shipping_id IS '归属发货单ID';
COMMENT ON COLUMN erp_shipping_items.order_item_id IS '关联订单明细行ID(用于回写已发货量)';
COMMENT ON COLUMN erp_shipping_items.sku_id IS '关联SKU ID';
COMMENT ON COLUMN erp_shipping_items.shipped_qty IS '本次实际发货数量';
CREATE INDEX idx_sitem_shipping ON erp_shipping_items(shipping_id) WHERE is_deleted = FALSE;
CREATE INDEX idx_sitem_oitem ON erp_shipping_items(order_item_id) WHERE is_deleted = FALSE;
CREATE TRIGGER trg_sitem_updated
BEFORE UPDATE ON erp_shipping_items
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ************************************************************
-- 6. 财务票据域
-- ************************************************************
-- ============================================================
-- 6.1 统一发票池表 fin_invoice_pool
-- ============================================================
CREATE TABLE fin_invoice_pool (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
uploader_id UUID REFERENCES sys_users(id),
file_url VARCHAR(500),
merchant_name VARCHAR(200),
amount NUMERIC(14,2) NOT NULL DEFAULT 0,
invoice_date DATE,
type VARCHAR(30) NOT NULL DEFAULT 'expense', -- customer=客户发票 / expense=报销发票
ai_extracted_data JSONB DEFAULT '{}'::JSONB, -- OCR + AI 解析的原始结构化数据
is_used BOOLEAN DEFAULT FALSE, -- 是否已被报销单引用
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_deleted BOOLEAN DEFAULT FALSE -- 软删除=作废
);
COMMENT ON TABLE fin_invoice_pool IS '统一发票池表(上传即OCR提取)';
COMMENT ON COLUMN fin_invoice_pool.uploader_id IS '上传人ID';
COMMENT ON COLUMN fin_invoice_pool.file_url IS '原始票据文件URL';
COMMENT ON COLUMN fin_invoice_pool.merchant_name IS '商户/开票方名称';
COMMENT ON COLUMN fin_invoice_pool.amount IS '票面金额';
COMMENT ON COLUMN fin_invoice_pool.type IS '票据类型: customer=客户发票 / expense=报销发票';
COMMENT ON COLUMN fin_invoice_pool.ai_extracted_data IS 'AI/OCR提取的结构化数据 (JSONB)';
COMMENT ON COLUMN fin_invoice_pool.is_used IS '是否已被报销单引用';
COMMENT ON COLUMN fin_invoice_pool.is_deleted IS '软删除标识(作废,防物理篡改)';
CREATE INDEX idx_inv_uploader ON fin_invoice_pool(uploader_id) WHERE is_deleted = FALSE;
CREATE INDEX idx_inv_type ON fin_invoice_pool(type) WHERE is_deleted = FALSE;
CREATE INDEX idx_inv_date ON fin_invoice_pool(invoice_date) WHERE is_deleted = FALSE;
CREATE TRIGGER trg_inv_updated
BEFORE UPDATE ON fin_invoice_pool
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================
-- 6.2 报销单主表 fin_expense_records
-- ============================================================
CREATE TABLE fin_expense_records (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
system_no VARCHAR(30) NOT NULL UNIQUE, -- 系统单号 EXP-xxx
applicant_id UUID NOT NULL REFERENCES sys_users(id),
total_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'draft', -- draft/pending/approved/rejected
remark TEXT,
approved_by UUID REFERENCES sys_users(id),
approved_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_deleted BOOLEAN DEFAULT FALSE
);
COMMENT ON TABLE fin_expense_records IS '报销单主表';
COMMENT ON COLUMN fin_expense_records.system_no IS '系统报销单号';
COMMENT ON COLUMN fin_expense_records.applicant_id IS '申请人ID';
COMMENT ON COLUMN fin_expense_records.total_amount IS '报销总金额';
COMMENT ON COLUMN fin_expense_records.status IS '报销状态: draft/pending/approved/rejected';
COMMENT ON COLUMN fin_expense_records.approved_by IS '审批人ID';
CREATE INDEX idx_exp_applicant ON fin_expense_records(applicant_id) WHERE is_deleted = FALSE;
CREATE INDEX idx_exp_status ON fin_expense_records(status) WHERE is_deleted = FALSE;
CREATE INDEX idx_exp_no ON fin_expense_records(system_no) WHERE is_deleted = FALSE;
CREATE TRIGGER trg_exp_updated
BEFORE UPDATE ON fin_expense_records
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================
-- 6.3 报销明细行表 fin_expense_details
-- ============================================================
CREATE TABLE fin_expense_details (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
expense_id UUID NOT NULL REFERENCES fin_expense_records(id),
invoice_id UUID REFERENCES fin_invoice_pool(id),
expense_desc VARCHAR(500), -- 费用说明
original_type VARCHAR(50), -- 原始费用类型: fuel/entertainment/travel/office
offset_type VARCHAR(50), -- 冲顶类型(二级类型转换后)
amount NUMERIC(14,2) NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_deleted BOOLEAN DEFAULT FALSE
);
COMMENT ON TABLE fin_expense_details IS '报销明细行表';
COMMENT ON COLUMN fin_expense_details.expense_id IS '归属报销单ID';
COMMENT ON COLUMN fin_expense_details.invoice_id IS '关联发票ID';
COMMENT ON COLUMN fin_expense_details.expense_desc IS '费用说明描述';
COMMENT ON COLUMN fin_expense_details.original_type IS '原始费用类型: fuel/entertainment/travel/office';
COMMENT ON COLUMN fin_expense_details.offset_type IS '冲顶类型(二级票据类型转换后的类型)';
COMMENT ON COLUMN fin_expense_details.amount IS '本行金额';
CREATE INDEX idx_expd_expense ON fin_expense_details(expense_id) WHERE is_deleted = FALSE;
CREATE INDEX idx_expd_invoice ON fin_expense_details(invoice_id) WHERE is_deleted = FALSE;
CREATE TRIGGER trg_expd_updated
BEFORE UPDATE ON fin_expense_details
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================
-- 完成:输出建表结果摘要
-- ============================================================
-- 共创建 13 张业务表:
-- RBAC 权限域: sys_departments, sys_roles, sys_users
-- CRM 客户域: crm_customers, crm_follow_up_logs
-- ERP 供应链域: erp_product_categories, erp_product_skus, erp_inventory_flows
-- ERP 交易域: erp_orders, erp_order_items
-- ERP 物流域: erp_shipping_records, erp_shipping_items
-- 财务票据域: fin_invoice_pool, fin_expense_records, fin_expense_details
--
-- 全局规范落地:
-- ✓ 主键统一 UUID (uuid_generate_v4)
-- ✓ 每表必带 created_at / updated_at / is_deleted 审计三件套
-- ✓ updated_at 自动触发器 (update_updated_at_column)
-- ✓ 灵活字段采用 JSONB (menu_keys, ai_extracted_data)
-- ✓ 外键关联完整,部分索引 (WHERE is_deleted=FALSE) 优化查询