Files
smart_grid_iot/database/db_init.sql
T
andyesfly 6b00f61550 feat: 新增现场勘察派单全流程业务能力
1.  新增共享电工管理模块:包含实体、DTO、控制器、服务以及前后端API
2.  重构勘察工单实体与查询逻辑,完善工单全状态流程与字段
3.  新增勘察SOP模板与工单回执业务体系
4.  更新数据库字典与初始化脚本,补充业务数据
5.  新增勘察派单增删改查、指派、复核、催办等全流程接口
6.  修复用户服务角色关联SQL生成问题
7.  新增勘察派单前后端完整交互链路
2026-09-15 08:50:55 +08:00

1541 lines
77 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- ============================================================
-- 鑫潼智电系统 PostgreSQL 数据库构建脚本 V1.0
-- 基于《鑫潼智电系统需求分析报告 V1.0》+ 数据库设计文档
-- 数据库: PostgreSQL 15+
-- 字符集: UTF8
-- 表数量: 39 张(含 1 张按月分区表)
--
-- 用法:
-- 1. 先安装 pg_cron 扩展并配置 postgresql.conf(见文件末尾说明)
-- 2. psql -U postgres -d xintong -f db_init.sql
--
-- 特性:
-- - 幂等执行(DROP IF EXISTS + CREATE)
-- - updated_at 自动更新触发器
-- - 全部列 COMMENT 注释
-- - 逻辑隔离索引(customer_id)
-- - 用电采集按月 RANGE 分区
-- - 自动分区管理(纯 PG 函数 + pg_cron 定时调度)
-- - 月度汇总物化视图(历史数据分析)
-- - 初始角色/菜单/字典数据
-- ============================================================
BEGIN;
-- 扩展
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- ============================================================
-- updated_at 自动更新触发器函数
-- ============================================================
CREATE OR REPLACE FUNCTION trigger_set_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- ============================================================
-- 一、系统管理域(7 张表)
-- ============================================================
-- 1. 系统用户表
DROP TABLE IF EXISTS sys_user CASCADE;
CREATE TABLE sys_user (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(64) NOT NULL UNIQUE,
password_hash VARCHAR(256) NOT NULL,
real_name VARCHAR(64) NOT NULL,
phone VARCHAR(20),
email VARCHAR(128),
status SMALLINT NOT NULL DEFAULT 1,
last_login_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE sys_user IS '系统用户表(鑫潼内部人员:业务员、运营管理员等)';
COMMENT ON COLUMN sys_user.id IS '自增主键';
COMMENT ON COLUMN sys_user.username IS '登录用户名';
COMMENT ON COLUMN sys_user.password_hash IS '密码哈希值';
COMMENT ON COLUMN sys_user.real_name IS '真实姓名';
COMMENT ON COLUMN sys_user.phone IS '手机号';
COMMENT ON COLUMN sys_user.email IS '邮箱';
COMMENT ON COLUMN sys_user.status IS '状态:1启用 0禁用';
COMMENT ON COLUMN sys_user.last_login_at IS '最后登录时间';
COMMENT ON COLUMN sys_user.created_at IS '创建时间';
COMMENT ON COLUMN sys_user.updated_at IS '更新时间';
CREATE TRIGGER trg_sys_user_updated_at BEFORE UPDATE ON sys_user
FOR EACH ROW EXECUTE FUNCTION trigger_set_updated_at();
-- 2. 角色表
DROP TABLE IF EXISTS sys_role CASCADE;
CREATE TABLE sys_role (
id BIGSERIAL PRIMARY KEY,
role_code VARCHAR(32) NOT NULL UNIQUE,
role_name VARCHAR(64) NOT NULL,
description TEXT,
sort_order INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE sys_role IS '角色表';
COMMENT ON COLUMN sys_role.id IS '自增主键';
COMMENT ON COLUMN sys_role.role_code IS '角色编码';
COMMENT ON COLUMN sys_role.role_name IS '角色名称';
COMMENT ON COLUMN sys_role.description IS '描述';
COMMENT ON COLUMN sys_role.sort_order IS '排序号';
COMMENT ON COLUMN sys_role.created_at IS '创建时间';
-- 3. 用户角色关联表
DROP TABLE IF EXISTS sys_user_role CASCADE;
CREATE TABLE sys_user_role (
user_id BIGINT NOT NULL REFERENCES sys_user(id) ON DELETE CASCADE,
role_id BIGINT NOT NULL REFERENCES sys_role(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, role_id)
);
COMMENT ON TABLE sys_user_role IS '用户角色关联表';
COMMENT ON COLUMN sys_user_role.user_id IS '用户 ID(→ sys_user)';
COMMENT ON COLUMN sys_user_role.role_id IS '角色 ID(→ sys_role)';
-- 4. 菜单表
DROP TABLE IF EXISTS sys_menu CASCADE;
CREATE TABLE sys_menu (
id BIGSERIAL PRIMARY KEY,
parent_id BIGINT REFERENCES sys_menu(id) ON DELETE SET NULL,
menu_name VARCHAR(64) NOT NULL,
menu_path VARCHAR(256),
component VARCHAR(256),
icon VARCHAR(64),
sort_order INT NOT NULL DEFAULT 0,
menu_type SMALLINT NOT NULL DEFAULT 1,
permission VARCHAR(128),
visible SMALLINT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE sys_menu IS '菜单表(RBAC 菜单级权限)';
COMMENT ON COLUMN sys_menu.id IS '自增主键';
COMMENT ON COLUMN sys_menu.parent_id IS '父菜单 ID(自关联)';
COMMENT ON COLUMN sys_menu.menu_name IS '菜单名称';
COMMENT ON COLUMN sys_menu.menu_path IS '路由路径';
COMMENT ON COLUMN sys_menu.component IS '前端组件路径';
COMMENT ON COLUMN sys_menu.icon IS '图标';
COMMENT ON COLUMN sys_menu.sort_order IS '排序号';
COMMENT ON COLUMN sys_menu.menu_type IS '类型:1目录 2菜单 3按钮';
COMMENT ON COLUMN sys_menu.permission IS '权限标识,如 customer:list';
COMMENT ON COLUMN sys_menu.visible IS '是否可见:1是 0否';
COMMENT ON COLUMN sys_menu.created_at IS '创建时间';
-- 5. 角色菜单关联表
DROP TABLE IF EXISTS sys_role_menu CASCADE;
CREATE TABLE sys_role_menu (
role_id BIGINT NOT NULL REFERENCES sys_role(id) ON DELETE CASCADE,
menu_id BIGINT NOT NULL REFERENCES sys_menu(id) ON DELETE CASCADE,
PRIMARY KEY (role_id, menu_id)
);
COMMENT ON TABLE sys_role_menu IS '角色菜单关联表';
COMMENT ON COLUMN sys_role_menu.role_id IS '角色 ID(→ sys_role)';
COMMENT ON COLUMN sys_role_menu.menu_id IS '菜单 ID(→ sys_menu)';
-- 6. 数据字典表
DROP TABLE IF EXISTS sys_dict CASCADE;
CREATE TABLE sys_dict (
id BIGSERIAL PRIMARY KEY,
dict_type VARCHAR(64) NOT NULL,
dict_label VARCHAR(128) NOT NULL,
dict_value VARCHAR(128) NOT NULL,
sort_order INT NOT NULL DEFAULT 0,
status SMALLINT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE sys_dict IS '数据字典表';
COMMENT ON COLUMN sys_dict.id IS '自增主键';
COMMENT ON COLUMN sys_dict.dict_type IS '字典类型';
COMMENT ON COLUMN sys_dict.dict_label IS '字典标签';
COMMENT ON COLUMN sys_dict.dict_value IS '字典值';
COMMENT ON COLUMN sys_dict.sort_order IS '排序号';
COMMENT ON COLUMN sys_dict.status IS '状态:1启用 0禁用';
COMMENT ON COLUMN sys_dict.created_at IS '创建时间';
CREATE INDEX idx_sys_dict_type ON sys_dict (dict_type);
-- 7. 操作日志表
DROP TABLE IF EXISTS sys_operation_log CASCADE;
CREATE TABLE sys_operation_log (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT,
module VARCHAR(64),
action VARCHAR(64),
method VARCHAR(256),
params TEXT,
ip VARCHAR(64),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE sys_operation_log IS '操作日志表';
COMMENT ON COLUMN sys_operation_log.id IS '自增主键';
COMMENT ON COLUMN sys_operation_log.user_id IS '操作用户 ID';
COMMENT ON COLUMN sys_operation_log.module IS '操作模块';
COMMENT ON COLUMN sys_operation_log.action IS '操作动作';
COMMENT ON COLUMN sys_operation_log.method IS '请求方法';
COMMENT ON COLUMN sys_operation_log.params IS '请求参数';
COMMENT ON COLUMN sys_operation_log.ip IS '操作 IP';
COMMENT ON COLUMN sys_operation_log.created_at IS '操作时间';
CREATE INDEX idx_oplog_user ON sys_operation_log (user_id);
CREATE INDEX idx_oplog_time ON sys_operation_log (created_at);
-- ============================================================
-- 二、客户域(7 张表)
-- ============================================================
-- 8. 客户信息表
DROP TABLE IF EXISTS customer CASCADE;
CREATE TABLE customer (
id BIGSERIAL PRIMARY KEY,
customer_no VARCHAR(64) NOT NULL UNIQUE,
customer_name VARCHAR(128) NOT NULL,
province VARCHAR(64),
city VARCHAR(64),
district VARCHAR(64),
address VARCHAR(256),
contact_person VARCHAR(64),
contact_phone VARCHAR(20),
postal_code VARCHAR(10),
fax VARCHAR(20),
customer_level VARCHAR(32),
status SMALLINT NOT NULL DEFAULT 1,
salesman_id BIGINT REFERENCES sys_user(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE customer IS '客户信息表';
COMMENT ON COLUMN customer.id IS '自增主键';
COMMENT ON COLUMN customer.customer_no IS '客户业务编号(KH+YYYYMMDD+3位序号)';
COMMENT ON COLUMN customer.customer_name IS '客户名称';
COMMENT ON COLUMN customer.province IS '省';
COMMENT ON COLUMN customer.city IS '市';
COMMENT ON COLUMN customer.district IS '区/县';
COMMENT ON COLUMN customer.address IS '地址';
COMMENT ON COLUMN customer.contact_person IS '联系人';
COMMENT ON COLUMN customer.contact_phone IS '电话';
COMMENT ON COLUMN customer.postal_code IS '邮编';
COMMENT ON COLUMN customer.fax IS '传真';
COMMENT ON COLUMN customer.customer_level IS '客户级别(学校/医院等)';
COMMENT ON COLUMN customer.status IS '状态:1意向 2签约 3运维中 4终止';
COMMENT ON COLUMN customer.salesman_id IS '负责业务员(→ sys_user.id)';
COMMENT ON COLUMN customer.created_at IS '创建时间';
COMMENT ON COLUMN customer.updated_at IS '更新时间';
CREATE INDEX idx_customer_name ON customer (customer_name);
CREATE INDEX idx_customer_salesman ON customer (salesman_id);
CREATE INDEX idx_customer_status ON customer (status);
CREATE INDEX idx_customer_region ON customer (province, city, district);
CREATE TRIGGER trg_customer_updated_at BEFORE UPDATE ON customer
FOR EACH ROW EXECUTE FUNCTION trigger_set_updated_at();
-- 9. 客户部门表
DROP TABLE IF EXISTS customer_department CASCADE;
CREATE TABLE customer_department (
id BIGSERIAL PRIMARY KEY,
customer_id BIGINT NOT NULL REFERENCES customer(id),
dept_name VARCHAR(128) NOT NULL,
contact_person VARCHAR(64),
contact_phone VARCHAR(20),
function_desc TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE customer_department IS '客户部门信息表';
COMMENT ON COLUMN customer_department.id IS '自增主键';
COMMENT ON COLUMN customer_department.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN customer_department.dept_name IS '部门名称';
COMMENT ON COLUMN customer_department.contact_person IS '联系人';
COMMENT ON COLUMN customer_department.contact_phone IS '电话';
COMMENT ON COLUMN customer_department.function_desc IS '职能';
COMMENT ON COLUMN customer_department.created_at IS '创建时间';
COMMENT ON COLUMN customer_department.updated_at IS '更新时间';
CREATE INDEX idx_dept_customer ON customer_department (customer_id);
CREATE TRIGGER trg_customer_department_updated_at BEFORE UPDATE ON customer_department
FOR EACH ROW EXECUTE FUNCTION trigger_set_updated_at();
-- 10. 客户拜访信息表
DROP TABLE IF EXISTS customer_visit CASCADE;
CREATE TABLE customer_visit (
id BIGSERIAL PRIMARY KEY,
visit_no VARCHAR(64) NOT NULL UNIQUE,
customer_id BIGINT NOT NULL REFERENCES customer(id),
visit_person VARCHAR(64) NOT NULL,
visit_phone VARCHAR(20),
visit_content TEXT,
visit_time DATE,
salesman_id BIGINT,
will_plan SMALLINT DEFAULT 0,
visit_type SMALLINT DEFAULT 1,
visit_mode SMALLINT DEFAULT 1,
priority SMALLINT DEFAULT 3,
subject VARCHAR(128),
plan_time TIMESTAMPTZ,
plan_end_time TIMESTAMPTZ,
checkin_time TIMESTAMPTZ,
checkin_status SMALLINT DEFAULT 0,
checkin_lng DECIMAL(10,6),
checkin_lat DECIMAL(10,6),
space_desc VARCHAR(128),
intent_level SMALLINT,
intent_desc VARCHAR(64),
demands VARCHAR(512),
companion VARCHAR(128),
reception_person VARCHAR(64),
reception_title VARCHAR(64),
reception_phone VARCHAR(20),
result_rating VARCHAR(64),
status SMALLINT DEFAULT 0,
next_visit_time DATE,
next_action VARCHAR(128),
photos VARCHAR(1024),
survey_order_id BIGINT,
project_no VARCHAR(64),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE customer_visit IS '客户拜访信息表';
COMMENT ON COLUMN customer_visit.id IS '自增主键';
COMMENT ON COLUMN customer_visit.visit_no IS '拜访单号';
COMMENT ON COLUMN customer_visit.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN customer_visit.visit_person IS '拜访人';
COMMENT ON COLUMN customer_visit.visit_phone IS '拜访人电话';
COMMENT ON COLUMN customer_visit.visit_content IS '拜访内容/沟通纪要';
COMMENT ON COLUMN customer_visit.visit_time IS '拜访时间';
COMMENT ON COLUMN customer_visit.salesman_id IS '业务员';
COMMENT ON COLUMN customer_visit.will_plan IS '是否做方案:0不同意 1同意';
COMMENT ON COLUMN customer_visit.visit_type IS '拜访类型:1首访摸排 2方案沟通 3商务二访 4维保回访 5应急巡检';
COMMENT ON COLUMN customer_visit.visit_mode IS '拜访方式:1现场拜访 2商务拜访 3电话沟通 4线上会议';
COMMENT ON COLUMN customer_visit.priority IS '优先级:1特急P1 2加急P2 3常规P3';
COMMENT ON COLUMN customer_visit.subject IS '拜访主题/目的';
COMMENT ON COLUMN customer_visit.plan_time IS '计划拜访开始时间';
COMMENT ON COLUMN customer_visit.plan_end_time IS '计划拜访结束时间';
COMMENT ON COLUMN customer_visit.checkin_time IS '到场签到时间';
COMMENT ON COLUMN customer_visit.checkin_status IS '签到状态:0未签到 1已签到';
COMMENT ON COLUMN customer_visit.checkin_lng IS '签到经度';
COMMENT ON COLUMN customer_visit.checkin_lat IS '签到纬度';
COMMENT ON COLUMN customer_visit.space_desc IS '客户空间性质/场所描述';
COMMENT ON COLUMN customer_visit.intent_level IS '意向评级:1=A高意向 2=B中意向 3=C低意向';
COMMENT ON COLUMN customer_visit.intent_desc IS '现场意向说明';
COMMENT ON COLUMN customer_visit.demands IS '诉求痛点(逗号分隔)';
COMMENT ON COLUMN customer_visit.companion IS '陪同人员';
COMMENT ON COLUMN customer_visit.reception_person IS '客户接待人';
COMMENT ON COLUMN customer_visit.reception_title IS '接待人职务';
COMMENT ON COLUMN customer_visit.reception_phone IS '接待人联系方式';
COMMENT ON COLUMN customer_visit.result_rating IS '现场成果评级/结论';
COMMENT ON COLUMN customer_visit.status IS '状态:0待执行 1已完成待复核 2取证待技术复核 3已立项转勘察 4已归档 5已取消';
COMMENT ON COLUMN customer_visit.next_visit_time IS '预计下次回访日期';
COMMENT ON COLUMN customer_visit.next_action IS '下一步行动目标';
COMMENT ON COLUMN customer_visit.photos IS '现场照片对象key(JSON数组字符串)';
COMMENT ON COLUMN customer_visit.survey_order_id IS '关联勘察单ID';
COMMENT ON COLUMN customer_visit.project_no IS '关联工程编号';
COMMENT ON COLUMN customer_visit.created_at IS '创建时间';
COMMENT ON COLUMN customer_visit.updated_at IS '更新时间';
CREATE INDEX idx_visit_customer ON customer_visit (customer_id);
CREATE INDEX idx_visit_salesman ON customer_visit (salesman_id);
CREATE INDEX idx_visit_status ON customer_visit (status);
CREATE INDEX idx_visit_plan_time ON customer_visit (plan_time);
CREATE TRIGGER trg_customer_visit_updated_at BEFORE UPDATE ON customer_visit
FOR EACH ROW EXECUTE FUNCTION trigger_set_updated_at();
-- 11. 客户智电改造建筑信息表
DROP TABLE IF EXISTS customer_building CASCADE;
CREATE TABLE customer_building (
id BIGSERIAL PRIMARY KEY,
customer_id BIGINT NOT NULL REFERENCES customer(id),
building_name VARCHAR(128) NOT NULL,
basic_info TEXT,
purpose VARCHAR(128),
floor_count INT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE customer_building IS '客户智电改造建筑信息表';
COMMENT ON COLUMN customer_building.id IS '自增主键';
COMMENT ON COLUMN customer_building.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN customer_building.building_name IS '建筑名称';
COMMENT ON COLUMN customer_building.basic_info IS '基本情况';
COMMENT ON COLUMN customer_building.purpose IS '用途';
COMMENT ON COLUMN customer_building.floor_count IS '层数';
COMMENT ON COLUMN customer_building.created_at IS '创建时间';
CREATE INDEX idx_building_customer ON customer_building (customer_id);
-- 12. 客户建筑房间信息表
DROP TABLE IF EXISTS customer_room CASCADE;
CREATE TABLE customer_room (
id BIGSERIAL PRIMARY KEY,
room_code VARCHAR(64) NOT NULL,
customer_id BIGINT NOT NULL REFERENCES customer(id),
room_name VARCHAR(128) NOT NULL,
building_id BIGINT NOT NULL REFERENCES customer_building(id),
floor_no VARCHAR(16),
room_purpose VARCHAR(128),
dept_id BIGINT REFERENCES customer_department(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE customer_room IS '客户建筑房间信息表';
COMMENT ON COLUMN customer_room.id IS '自增主键';
COMMENT ON COLUMN customer_room.room_code IS '房间编码';
COMMENT ON COLUMN customer_room.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN customer_room.room_name IS '房间名称';
COMMENT ON COLUMN customer_room.building_id IS '所属建筑 ID(→ customer_building)';
COMMENT ON COLUMN customer_room.floor_no IS '所在楼层';
COMMENT ON COLUMN customer_room.room_purpose IS '房间用途';
COMMENT ON COLUMN customer_room.dept_id IS '房间所属客户部门(→ customer_department)';
COMMENT ON COLUMN customer_room.created_at IS '创建时间';
CREATE INDEX idx_room_customer ON customer_room (customer_id);
CREATE INDEX idx_room_building ON customer_room (building_id);
-- 13. 客户设备信息表(配电间、配电井、配电箱)
DROP TABLE IF EXISTS customer_equipment CASCADE;
CREATE TABLE customer_equipment (
id BIGSERIAL PRIMARY KEY,
equip_code VARCHAR(64) NOT NULL UNIQUE,
customer_id BIGINT NOT NULL REFERENCES customer(id),
equip_name VARCHAR(128) NOT NULL,
equip_type VARCHAR(32) NOT NULL,
building_id BIGINT REFERENCES customer_building(id),
floor_no VARCHAR(16),
location VARCHAR(256),
description TEXT,
remark TEXT,
photo_url VARCHAR(512),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE customer_equipment IS '客户智电改造设备信息表(配电间/井/箱)';
COMMENT ON COLUMN customer_equipment.id IS '自增主键';
COMMENT ON COLUMN customer_equipment.equip_code IS '客户设备编码(唯一)';
COMMENT ON COLUMN customer_equipment.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN customer_equipment.equip_name IS '客户设备名称';
COMMENT ON COLUMN customer_equipment.equip_type IS '设备类型:配电间/配电井/配电箱';
COMMENT ON COLUMN customer_equipment.building_id IS '所属建筑 ID(→ customer_building)';
COMMENT ON COLUMN customer_equipment.floor_no IS '所在楼层';
COMMENT ON COLUMN customer_equipment.location IS '所在位置';
COMMENT ON COLUMN customer_equipment.description IS '设备描述';
COMMENT ON COLUMN customer_equipment.remark IS '备注';
COMMENT ON COLUMN customer_equipment.photo_url IS '未改造前设备照片';
COMMENT ON COLUMN customer_equipment.created_at IS '创建时间';
CREATE INDEX idx_equip_customer ON customer_equipment (customer_id);
CREATE INDEX idx_equip_building ON customer_equipment (building_id);
-- 14. 客户访问账号表
DROP TABLE IF EXISTS customer_account CASCADE;
CREATE TABLE customer_account (
id BIGSERIAL PRIMARY KEY,
customer_id BIGINT NOT NULL REFERENCES customer(id),
phone VARCHAR(20) NOT NULL,
password_hash VARCHAR(256) NOT NULL,
wx_openid VARCHAR(128),
status SMALLINT NOT NULL DEFAULT 1,
last_login_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE customer_account IS '客户访问账号表(小程序登录)';
COMMENT ON COLUMN customer_account.id IS '自增主键';
COMMENT ON COLUMN customer_account.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN customer_account.phone IS '登录电话(唯一)';
COMMENT ON COLUMN customer_account.password_hash IS '密码哈希';
COMMENT ON COLUMN customer_account.wx_openid IS '微信 openid';
COMMENT ON COLUMN customer_account.status IS '状态:1启用 0禁用';
COMMENT ON COLUMN customer_account.last_login_at IS '最后登录时间';
COMMENT ON COLUMN customer_account.created_at IS '创建时间';
COMMENT ON COLUMN customer_account.updated_at IS '更新时间';
CREATE INDEX idx_account_customer ON customer_account (customer_id);
CREATE UNIQUE INDEX uk_account_phone ON customer_account (phone);
CREATE TRIGGER trg_customer_account_updated_at BEFORE UPDATE ON customer_account
FOR EACH ROW EXECUTE FUNCTION trigger_set_updated_at();
-- ============================================================
-- 三、智电设备域(1 张表)
-- ============================================================
-- 15. 智电设备型号表(产品目录)
DROP TABLE IF EXISTS smart_device CASCADE;
CREATE TABLE smart_device (
id BIGSERIAL PRIMARY KEY,
device_model VARCHAR(64) NOT NULL,
device_name VARCHAR(128) NOT NULL,
device_spec VARCHAR(256),
device_type VARCHAR(32) NOT NULL,
device_function TEXT,
supplier VARCHAR(128),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE smart_device IS '鑫潼智电设备信息表(产品目录)';
COMMENT ON COLUMN smart_device.id IS '自增主键';
COMMENT ON COLUMN smart_device.device_model IS '智电设备型号';
COMMENT ON COLUMN smart_device.device_name IS '智电设备名称';
COMMENT ON COLUMN smart_device.device_spec IS '智电设备规格';
COMMENT ON COLUMN smart_device.device_type IS '设备类型:网关/采集器/探测器等';
COMMENT ON COLUMN smart_device.device_function IS '智电设备功能';
COMMENT ON COLUMN smart_device.supplier IS '供应商';
COMMENT ON COLUMN smart_device.created_at IS '创建时间';
-- ============================================================
-- 四、项目方案与合同域(3 张表)
-- ============================================================
-- 16. 智电改造方案表
DROP TABLE IF EXISTS project_plan CASCADE;
CREATE TABLE project_plan (
id BIGSERIAL PRIMARY KEY,
plan_no VARCHAR(64) NOT NULL UNIQUE,
customer_id BIGINT NOT NULL REFERENCES customer(id),
registrar VARCHAR(64),
register_time DATE,
plan_desc TEXT,
plan_file_url VARCHAR(512),
status SMALLINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE project_plan IS '智电改造方案信息表';
COMMENT ON COLUMN project_plan.id IS '自增主键';
COMMENT ON COLUMN project_plan.plan_no IS '方案单号(唯一)';
COMMENT ON COLUMN project_plan.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN project_plan.registrar IS '方案登记人(业务员)';
COMMENT ON COLUMN project_plan.register_time IS '方案登记时间';
COMMENT ON COLUMN project_plan.plan_desc IS '方案说明';
COMMENT ON COLUMN project_plan.plan_file_url IS '方案最终文件';
COMMENT ON COLUMN project_plan.status IS '状态:0草稿 1已确认';
COMMENT ON COLUMN project_plan.created_at IS '创建时间';
CREATE INDEX idx_plan_customer ON project_plan (customer_id);
-- 17. 项目方案智电设备明细表
DROP TABLE IF EXISTS project_plan_detail CASCADE;
CREATE TABLE project_plan_detail (
id BIGSERIAL PRIMARY KEY,
plan_id BIGINT NOT NULL REFERENCES project_plan(id) ON DELETE CASCADE,
customer_id BIGINT NOT NULL REFERENCES customer(id),
equip_id BIGINT REFERENCES customer_equipment(id),
smart_device_id BIGINT REFERENCES smart_device(id),
quantity NUMERIC(10,2) NOT NULL DEFAULT 1,
remark TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE project_plan_detail IS '项目方案智电设备明细信息表';
COMMENT ON COLUMN project_plan_detail.id IS '自增主键';
COMMENT ON COLUMN project_plan_detail.plan_id IS '方案 ID(→ project_plan)';
COMMENT ON COLUMN project_plan_detail.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN project_plan_detail.equip_id IS '客户设备 ID(→ customer_equipment)';
COMMENT ON COLUMN project_plan_detail.smart_device_id IS '智电设备型号 ID(→ smart_device)';
COMMENT ON COLUMN project_plan_detail.quantity IS '数量';
COMMENT ON COLUMN project_plan_detail.remark IS '备注';
COMMENT ON COLUMN project_plan_detail.created_at IS '创建时间';
CREATE INDEX idx_pd_plan ON project_plan_detail (plan_id);
CREATE INDEX idx_pd_customer ON project_plan_detail (customer_id);
-- 18. 智电项目合同表
DROP TABLE IF EXISTS contract CASCADE;
CREATE TABLE contract (
id BIGSERIAL PRIMARY KEY,
contract_no VARCHAR(64) NOT NULL UNIQUE,
xt_contract_no VARCHAR(64),
customer_id BIGINT NOT NULL REFERENCES customer(id),
contract_amount NUMERIC(14,2),
pay_cycle VARCHAR(32),
salesman_id BIGINT,
sign_date DATE,
contract_desc TEXT,
start_date DATE,
end_date DATE,
plan_id BIGINT REFERENCES project_plan(id),
contract_file_url VARCHAR(512),
status SMALLINT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE contract IS '智电项目合同信息表';
COMMENT ON COLUMN contract.id IS '自增主键';
COMMENT ON COLUMN contract.contract_no IS '合同单号(唯一)';
COMMENT ON COLUMN contract.xt_contract_no IS '鑫潼合同号(XT+年份+序号)';
COMMENT ON COLUMN contract.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN contract.contract_amount IS '合同金额';
COMMENT ON COLUMN contract.pay_cycle IS '支付周期:按月/按年';
COMMENT ON COLUMN contract.salesman_id IS '业务员';
COMMENT ON COLUMN contract.sign_date IS '签订日期';
COMMENT ON COLUMN contract.contract_desc IS '合同说明';
COMMENT ON COLUMN contract.start_date IS '开始日期';
COMMENT ON COLUMN contract.end_date IS '结束日期';
COMMENT ON COLUMN contract.plan_id IS '方案 ID(→ project_plan)';
COMMENT ON COLUMN contract.contract_file_url IS '签字盖章电子合同';
COMMENT ON COLUMN contract.status IS '状态:1有效 0终止';
COMMENT ON COLUMN contract.created_at IS '创建时间';
CREATE INDEX idx_contract_customer ON contract (customer_id);
CREATE INDEX idx_contract_xt ON contract (xt_contract_no);
-- ============================================================
-- 五、线路与设备关联域(2 张表,ICC 同步)
-- ============================================================
-- 19. 智电改造线路表(ICC 同步 + 业务分配)
DROP TABLE IF EXISTS electric_line CASCADE;
CREATE TABLE electric_line (
id BIGSERIAL PRIMARY KEY,
line_code VARCHAR(64) NOT NULL UNIQUE,
equip_id BIGINT NOT NULL REFERENCES customer_equipment(id),
icc_device_code VARCHAR(128),
line_name VARCHAR(128),
customer_id BIGINT NOT NULL REFERENCES customer(id),
building_id BIGINT REFERENCES customer_building(id),
floor_no VARCHAR(16),
location VARCHAR(256),
status SMALLINT NOT NULL DEFAULT 1,
synced_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE electric_line IS '智电改造线路信息表(ICC 同步)';
COMMENT ON COLUMN electric_line.id IS '自增主键';
COMMENT ON COLUMN electric_line.line_code IS '线路编码(唯一)';
COMMENT ON COLUMN electric_line.equip_id IS '客户设备 ID(→ customer_equipment)';
COMMENT ON COLUMN electric_line.icc_device_code IS 'ICC 智电设备编号';
COMMENT ON COLUMN electric_line.line_name IS '线路名称(ICC 同步)';
COMMENT ON COLUMN electric_line.customer_id IS '客户 ID(逻辑隔离键)';
COMMENT ON COLUMN electric_line.building_id IS '所属建筑 ID(→ customer_building)';
COMMENT ON COLUMN electric_line.floor_no IS '所在楼层';
COMMENT ON COLUMN electric_line.location IS '所在位置';
COMMENT ON COLUMN electric_line.status IS '状态:1在线 0离线';
COMMENT ON COLUMN electric_line.synced_at IS 'ICC 同步时间';
COMMENT ON COLUMN electric_line.created_at IS '创建时间';
CREATE INDEX idx_line_customer ON electric_line (customer_id);
CREATE INDEX idx_line_equip ON electric_line (equip_id);
CREATE INDEX idx_line_icc ON electric_line (icc_device_code);
-- 20. 线路所属房间关联表
DROP TABLE IF EXISTS line_room CASCADE;
CREATE TABLE line_room (
id BIGSERIAL PRIMARY KEY,
line_id BIGINT NOT NULL REFERENCES electric_line(id) ON DELETE CASCADE,
room_id BIGINT NOT NULL REFERENCES customer_room(id),
customer_id BIGINT NOT NULL REFERENCES customer(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE line_room IS '线路所属房间关联表';
COMMENT ON COLUMN line_room.id IS '自增主键';
COMMENT ON COLUMN line_room.line_id IS '线路 ID(→ electric_line)';
COMMENT ON COLUMN line_room.room_id IS '房间 ID(→ customer_room)';
COMMENT ON COLUMN line_room.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN line_room.created_at IS '创建时间';
CREATE INDEX idx_lr_line ON line_room (line_id);
CREATE INDEX idx_lr_room ON line_room (room_id);
CREATE INDEX idx_lr_customer ON line_room (customer_id);
-- ============================================================
-- 六、派单域(6 张表)
-- ============================================================
-- 21. 勘察派单表
DROP TABLE IF EXISTS survey_order CASCADE;
CREATE TABLE survey_order (
id BIGSERIAL PRIMARY KEY,
survey_no VARCHAR(64) NOT NULL UNIQUE,
customer_id BIGINT NOT NULL REFERENCES customer(id),
surveyor VARCHAR(64),
worker_id BIGINT,
survey_time TIMESTAMPTZ,
priority SMALLINT DEFAULT 3,
task_title VARCHAR(128),
site_location VARCHAR(255),
source VARCHAR(32) DEFAULT 'ledger',
loop_spec VARCHAR(128),
loop_count INTEGER,
progress_done INTEGER DEFAULT 0,
progress_total INTEGER,
drawing_count INTEGER DEFAULT 0,
salesman_id BIGINT,
tech_contact VARCHAR(128),
plan_start TIMESTAMPTZ,
plan_end TIMESTAMPTZ,
expect_minutes INTEGER,
expect_arrival_at TIMESTAMPTZ,
sla_level VARCHAR(32),
urge_count INTEGER DEFAULT 0,
last_urged_at TIMESTAMPTZ,
is_draft SMALLINT NOT NULL DEFAULT 0,
estimated_fee NUMERIC(10,2),
actual_fee NUMERIC(10,2),
photos VARCHAR(1024),
review_remark TEXT,
reviewed_at TIMESTAMPTZ,
cancel_reason VARCHAR(255),
remark TEXT,
status SMALLINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE survey_order IS '客户勘察派单信息表';
COMMENT ON COLUMN survey_order.id IS '自增主键';
COMMENT ON COLUMN survey_order.survey_no IS '勘察单号(唯一)';
COMMENT ON COLUMN survey_order.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN survey_order.surveyor IS '勘察人(共享电工)';
COMMENT ON COLUMN survey_order.worker_id IS '共享电工 ID';
COMMENT ON COLUMN survey_order.survey_time IS '勘察时间';
COMMENT ON COLUMN survey_order.priority IS '优先级:1特急P1 2加急P2 3常规P3';
COMMENT ON COLUMN survey_order.task_title IS '勘察任务标题';
COMMENT ON COLUMN survey_order.site_location IS '具体勘察物理区域';
COMMENT ON COLUMN survey_order.source IS '工单来源:ledger台账派工 visit拜访转勘察 customer客户发起';
COMMENT ON COLUMN survey_order.loop_spec IS '主进线规约与断路器容量';
COMMENT ON COLUMN survey_order.loop_count IS '预估分支测绘回路数';
COMMENT ON COLUMN survey_order.progress_done IS '已完成测绘回路数';
COMMENT ON COLUMN survey_order.progress_total IS '应测绘回路总数';
COMMENT ON COLUMN survey_order.drawing_count IS '待技术复核图纸数';
COMMENT ON COLUMN survey_order.salesman_id IS '责任业务员ID(→ sys_user)';
COMMENT ON COLUMN survey_order.tech_contact IS '现场技术联系人';
COMMENT ON COLUMN survey_order.plan_start IS '计划到场窗口开始';
COMMENT ON COLUMN survey_order.plan_end IS '计划到场窗口结束';
COMMENT ON COLUMN survey_order.expect_minutes IS '要求接单后到场时限(分钟)';
COMMENT ON COLUMN survey_order.expect_arrival_at IS '期望到场时限(指派时按 expect_minutes 推算)';
COMMENT ON COLUMN survey_order.sla_level IS '履约质保要求';
COMMENT ON COLUMN survey_order.urge_count IS '催办次数';
COMMENT ON COLUMN survey_order.last_urged_at IS '最近催办时间';
COMMENT ON COLUMN survey_order.is_draft IS '是否草稿:0正式工单 1草稿';
COMMENT ON COLUMN survey_order.estimated_fee IS '预估劳务费(基础出场费+回路补贴)';
COMMENT ON COLUMN survey_order.actual_fee IS '实际结算劳务费';
COMMENT ON COLUMN survey_order.photos IS '现场取证照片对象key(JSON数组字符串)';
COMMENT ON COLUMN survey_order.review_remark IS '技术复核意见';
COMMENT ON COLUMN survey_order.reviewed_at IS '技术复核时间';
COMMENT ON COLUMN survey_order.cancel_reason IS '取消原因';
COMMENT ON COLUMN survey_order.remark IS '备注';
COMMENT ON COLUMN survey_order.status IS '状态:0待指派 1现场勘察中 2待技术复核 3已归档 4预留 5已取消';
COMMENT ON COLUMN survey_order.created_at IS '创建时间';
COMMENT ON COLUMN survey_order.updated_at IS '更新时间';
CREATE INDEX idx_survey_customer ON survey_order (customer_id);
CREATE INDEX idx_survey_worker ON survey_order (worker_id);
CREATE INDEX idx_survey_status ON survey_order (status);
CREATE INDEX idx_survey_priority ON survey_order (priority);
CREATE INDEX idx_survey_salesman ON survey_order (salesman_id);
CREATE INDEX idx_survey_plan_start ON survey_order (plan_start);
CREATE TRIGGER trg_survey_order_updated_at BEFORE UPDATE ON survey_order
FOR EACH ROW EXECUTE FUNCTION trigger_set_updated_at();
-- 21.1 勘察 SOP 模板检查项表
DROP TABLE IF EXISTS survey_sop_item CASCADE;
CREATE TABLE survey_sop_item (
id BIGSERIAL PRIMARY KEY,
template_code VARCHAR(64) NOT NULL DEFAULT 'standard_dist',
sort_order INTEGER NOT NULL DEFAULT 0,
item_name VARCHAR(128) NOT NULL,
item_desc VARCHAR(255),
receipt_type VARCHAR(16) NOT NULL DEFAULT 'photo',
receipt_require VARCHAR(64),
required BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE survey_sop_item IS '勘察 SOP 模板检查项表';
COMMENT ON COLUMN survey_sop_item.id IS '自增主键';
COMMENT ON COLUMN survey_sop_item.template_code IS 'SOP模板编码:standard_dist标准配电改造模板';
COMMENT ON COLUMN survey_sop_item.sort_order IS '检查项序号';
COMMENT ON COLUMN survey_sop_item.item_name IS '检查项名称';
COMMENT ON COLUMN survey_sop_item.item_desc IS '检查项说明';
COMMENT ON COLUMN survey_sop_item.receipt_type IS '回执形式:photo现场照片 form清单表单 meter测仪截图';
COMMENT ON COLUMN survey_sop_item.receipt_require IS '回执指标要求,如「照片 ≥ 2 张」';
COMMENT ON COLUMN survey_sop_item.required IS '是否必填';
COMMENT ON COLUMN survey_sop_item.created_at IS '创建时间';
CREATE UNIQUE INDEX uk_sop_template_order ON survey_sop_item (template_code, sort_order);
-- 21.2 勘察 SOP 工单回执表
DROP TABLE IF EXISTS survey_sop_result CASCADE;
CREATE TABLE survey_sop_result (
id BIGSERIAL PRIMARY KEY,
survey_order_id BIGINT NOT NULL REFERENCES survey_order(id) ON DELETE CASCADE,
sop_item_id BIGINT NOT NULL REFERENCES survey_sop_item(id),
checked BOOLEAN NOT NULL DEFAULT FALSE,
receipt_value VARCHAR(1024),
remark VARCHAR(255),
checked_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE survey_sop_result IS '勘察 SOP 工单回执表';
COMMENT ON COLUMN survey_sop_result.id IS '自增主键';
COMMENT ON COLUMN survey_sop_result.survey_order_id IS '勘察工单ID(→ survey_order)';
COMMENT ON COLUMN survey_sop_result.sop_item_id IS 'SOP检查项ID(→ survey_sop_item)';
COMMENT ON COLUMN survey_sop_result.checked IS '是否已完成回执';
COMMENT ON COLUMN survey_sop_result.receipt_value IS '回执内容(照片对象key数组/表单值/截图key)';
COMMENT ON COLUMN survey_sop_result.remark IS '回执备注';
COMMENT ON COLUMN survey_sop_result.checked_at IS '回执时间';
COMMENT ON COLUMN survey_sop_result.created_at IS '创建时间';
COMMENT ON COLUMN survey_sop_result.updated_at IS '更新时间';
CREATE UNIQUE INDEX uk_sop_result_order_item ON survey_sop_result (survey_order_id, sop_item_id);
CREATE INDEX idx_sop_result_order ON survey_sop_result (survey_order_id);
-- 22. 安装派单表
DROP TABLE IF EXISTS install_order CASCADE;
CREATE TABLE install_order (
id BIGSERIAL PRIMARY KEY,
install_no VARCHAR(64) NOT NULL UNIQUE,
customer_id BIGINT NOT NULL REFERENCES customer(id),
installer VARCHAR(64),
worker_id BIGINT,
dispatch_date DATE,
start_date DATE,
end_date DATE,
plan_id BIGINT REFERENCES project_plan(id),
remark TEXT,
status SMALLINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE install_order IS '项目安装任务派单信息表';
COMMENT ON COLUMN install_order.id IS '自增主键';
COMMENT ON COLUMN install_order.install_no IS '安装单号(唯一)';
COMMENT ON COLUMN install_order.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN install_order.installer IS '安装人(共享电工)';
COMMENT ON COLUMN install_order.worker_id IS '共享电工 ID';
COMMENT ON COLUMN install_order.dispatch_date IS '派单日期';
COMMENT ON COLUMN install_order.start_date IS '开始日期';
COMMENT ON COLUMN install_order.end_date IS '结束日期';
COMMENT ON COLUMN install_order.plan_id IS '方案 ID(→ project_plan)';
COMMENT ON COLUMN install_order.remark IS '备注';
COMMENT ON COLUMN install_order.status IS '状态:0已派发 1已完成';
COMMENT ON COLUMN install_order.created_at IS '创建时间';
CREATE INDEX idx_install_customer ON install_order (customer_id);
CREATE INDEX idx_install_worker ON install_order (worker_id);
-- 23. 项目验收表
DROP TABLE IF EXISTS acceptance CASCADE;
CREATE TABLE acceptance (
id BIGSERIAL PRIMARY KEY,
accept_no VARCHAR(64) NOT NULL UNIQUE,
customer_id BIGINT NOT NULL REFERENCES customer(id),
acceptor VARCHAR(64),
accept_opinion TEXT,
accept_date DATE,
installer VARCHAR(64),
accept_file_url VARCHAR(512),
contract_id BIGINT REFERENCES contract(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE acceptance IS '项目验收信息表';
COMMENT ON COLUMN acceptance.id IS '自增主键';
COMMENT ON COLUMN acceptance.accept_no IS '验收单号(唯一)';
COMMENT ON COLUMN acceptance.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN acceptance.acceptor IS '验收人';
COMMENT ON COLUMN acceptance.accept_opinion IS '验收意见';
COMMENT ON COLUMN acceptance.accept_date IS '验收日期';
COMMENT ON COLUMN acceptance.installer IS '安装人';
COMMENT ON COLUMN acceptance.accept_file_url IS '验收文件电子版';
COMMENT ON COLUMN acceptance.contract_id IS '合同 ID(→ contract)';
COMMENT ON COLUMN acceptance.created_at IS '创建时间';
CREATE INDEX idx_accept_customer ON acceptance (customer_id);
-- 24. 客户回访信息表
DROP TABLE IF EXISTS visit_record CASCADE;
CREATE TABLE visit_record (
id BIGSERIAL PRIMARY KEY,
visit_no VARCHAR(64) NOT NULL UNIQUE,
customer_id BIGINT NOT NULL REFERENCES customer(id),
contact_person VARCHAR(64),
contact_phone VARCHAR(20),
visitor VARCHAR(64),
visit_time DATE,
customer_opinion TEXT,
photos JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE visit_record IS '客户回访信息表';
COMMENT ON COLUMN visit_record.id IS '自增主键';
COMMENT ON COLUMN visit_record.visit_no IS '回访单号(唯一)';
COMMENT ON COLUMN visit_record.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN visit_record.contact_person IS '联系人';
COMMENT ON COLUMN visit_record.contact_phone IS '电话';
COMMENT ON COLUMN visit_record.visitor IS '回访人';
COMMENT ON COLUMN visit_record.visit_time IS '回访时间';
COMMENT ON COLUMN visit_record.customer_opinion IS '客户意见';
COMMENT ON COLUMN visit_record.photos IS '回访照片(JSONB)';
COMMENT ON COLUMN visit_record.created_at IS '创建时间';
CREATE INDEX idx_visit_rec_customer ON visit_record (customer_id);
-- ============================================================
-- 七、报警域(4 张表)
-- ============================================================
-- 25. 报警故障类型表
DROP TABLE IF EXISTS fault_type CASCADE;
CREATE TABLE fault_type (
id BIGSERIAL PRIMARY KEY,
fault_type_code VARCHAR(64) NOT NULL UNIQUE,
fault_type_name VARCHAR(128) NOT NULL,
description TEXT,
remark TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE fault_type IS '报警故障类型信息表';
COMMENT ON COLUMN fault_type.id IS '自增主键';
COMMENT ON COLUMN fault_type.fault_type_code IS '故障类型编码(唯一)';
COMMENT ON COLUMN fault_type.fault_type_name IS '故障类型名称';
COMMENT ON COLUMN fault_type.description IS '故障类型说明';
COMMENT ON COLUMN fault_type.remark IS '备注';
COMMENT ON COLUMN fault_type.created_at IS '创建时间';
-- 26. 报警故障类型明细表
DROP TABLE IF EXISTS fault_type_detail CASCADE;
CREATE TABLE fault_type_detail (
id BIGSERIAL PRIMARY KEY,
fault_type_id BIGINT NOT NULL REFERENCES fault_type(id) ON DELETE CASCADE,
detail_category VARCHAR(32) NOT NULL,
detail_desc TEXT,
remark TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE fault_type_detail IS '报警故障类型明细信息表';
COMMENT ON COLUMN fault_type_detail.id IS '自增主键';
COMMENT ON COLUMN fault_type_detail.fault_type_id IS '故障类型 ID(→ fault_type)';
COMMENT ON COLUMN fault_type_detail.detail_category IS '明细分类:故障原因/处理流程';
COMMENT ON COLUMN fault_type_detail.detail_desc IS '明细说明';
COMMENT ON COLUMN fault_type_detail.remark IS '备注';
COMMENT ON COLUMN fault_type_detail.created_at IS '创建时间';
CREATE INDEX idx_ftd_type ON fault_type_detail (fault_type_id);
-- 27. 设备报警信息表(ICC 拉取)
DROP TABLE IF EXISTS device_alarm CASCADE;
CREATE TABLE device_alarm (
id BIGSERIAL PRIMARY KEY,
alarm_code VARCHAR(128) NOT NULL UNIQUE,
line_id BIGINT,
equip_id BIGINT,
icc_device_code VARCHAR(128),
line_name VARCHAR(128),
customer_id BIGINT,
building_id BIGINT,
floor_no VARCHAR(16),
location VARCHAR(256),
alarm_date DATE,
fault_type VARCHAR(128),
alarm_value NUMERIC(14,2),
history_count INT DEFAULT 0,
synced_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE device_alarm IS '设备报警信息表(ICC 拉取)';
COMMENT ON COLUMN device_alarm.id IS '自增主键';
COMMENT ON COLUMN device_alarm.alarm_code IS '报警编码(唯一)';
COMMENT ON COLUMN device_alarm.line_id IS '线路 ID';
COMMENT ON COLUMN device_alarm.equip_id IS '客户设备 ID';
COMMENT ON COLUMN device_alarm.icc_device_code IS 'ICC 设备编号';
COMMENT ON COLUMN device_alarm.line_name IS '线路名称';
COMMENT ON COLUMN device_alarm.customer_id IS '客户 ID';
COMMENT ON COLUMN device_alarm.building_id IS '所属建筑 ID';
COMMENT ON COLUMN device_alarm.floor_no IS '所在楼层';
COMMENT ON COLUMN device_alarm.location IS '所在位置';
COMMENT ON COLUMN device_alarm.alarm_date IS '报警日期';
COMMENT ON COLUMN device_alarm.fault_type IS 'ICC 预警类型';
COMMENT ON COLUMN device_alarm.alarm_value IS '报警数值';
COMMENT ON COLUMN device_alarm.history_count IS '历史出现次数';
COMMENT ON COLUMN device_alarm.synced_at IS 'ICC 同步时间';
CREATE INDEX idx_alarm_customer ON device_alarm (customer_id);
CREATE INDEX idx_alarm_date ON device_alarm (alarm_date);
CREATE INDEX idx_alarm_line ON device_alarm (line_id);
-- 28. 报警派单信息表
DROP TABLE IF EXISTS alarm_dispatch CASCADE;
CREATE TABLE alarm_dispatch (
id BIGSERIAL PRIMARY KEY,
dispatch_no VARCHAR(64) NOT NULL UNIQUE,
alarm_id BIGINT,
line_id BIGINT,
equip_id BIGINT,
icc_device_code VARCHAR(128),
line_name VARCHAR(128),
customer_id BIGINT NOT NULL REFERENCES customer(id),
contact_person VARCHAR(64),
contact_phone VARCHAR(20),
building_id BIGINT,
floor_no VARCHAR(16),
location VARCHAR(256),
fault_type_id BIGINT REFERENCES fault_type(id),
fault_desc TEXT,
dispatcher VARCHAR(64),
fault_reason TEXT,
process_flow TEXT,
worker_id BIGINT,
status SMALLINT NOT NULL DEFAULT 0,
process_photos JSONB,
rating SMALLINT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ
);
COMMENT ON TABLE alarm_dispatch IS '报警派单信息表';
COMMENT ON COLUMN alarm_dispatch.id IS '自增主键';
COMMENT ON COLUMN alarm_dispatch.dispatch_no IS '报警派单号(唯一)';
COMMENT ON COLUMN alarm_dispatch.alarm_id IS '报警 ID(→ device_alarm)';
COMMENT ON COLUMN alarm_dispatch.line_id IS '线路 ID';
COMMENT ON COLUMN alarm_dispatch.equip_id IS '客户设备 ID';
COMMENT ON COLUMN alarm_dispatch.icc_device_code IS 'ICC 设备编号';
COMMENT ON COLUMN alarm_dispatch.line_name IS '线路名称';
COMMENT ON COLUMN alarm_dispatch.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN alarm_dispatch.contact_person IS '联系人';
COMMENT ON COLUMN alarm_dispatch.contact_phone IS '电话';
COMMENT ON COLUMN alarm_dispatch.building_id IS '建筑 ID';
COMMENT ON COLUMN alarm_dispatch.floor_no IS '所在楼层';
COMMENT ON COLUMN alarm_dispatch.location IS '所在位置';
COMMENT ON COLUMN alarm_dispatch.fault_type_id IS '故障类型 ID(→ fault_type)';
COMMENT ON COLUMN alarm_dispatch.fault_desc IS '故障事项(类型+数值)';
COMMENT ON COLUMN alarm_dispatch.dispatcher IS '派发人';
COMMENT ON COLUMN alarm_dispatch.fault_reason IS '故障原因(来自明细)';
COMMENT ON COLUMN alarm_dispatch.process_flow IS '处理流程(来自明细)';
COMMENT ON COLUMN alarm_dispatch.worker_id IS '共享电工 ID';
COMMENT ON COLUMN alarm_dispatch.status IS '状态:0已派发 1已处理';
COMMENT ON COLUMN alarm_dispatch.process_photos IS '报警处理照片(JSONB 多张)';
COMMENT ON COLUMN alarm_dispatch.rating IS '派单处理评分';
COMMENT ON COLUMN alarm_dispatch.created_at IS '创建时间';
COMMENT ON COLUMN alarm_dispatch.completed_at IS '完成时间';
CREATE INDEX idx_alarm_disp_customer ON alarm_dispatch (customer_id);
CREATE INDEX idx_alarm_disp_worker ON alarm_dispatch (worker_id);
CREATE INDEX idx_alarm_disp_status ON alarm_dispatch (status);
-- ============================================================
-- 八、报修域(2 张表)
-- ============================================================
-- 29. 报修单信息表
DROP TABLE IF EXISTS repair_request CASCADE;
CREATE TABLE repair_request (
id BIGSERIAL PRIMARY KEY,
repair_no VARCHAR(64) NOT NULL UNIQUE,
customer_id BIGINT NOT NULL REFERENCES customer(id),
requester VARCHAR(64),
requester_phone VARCHAR(20),
request_time TIMESTAMPTZ,
building_id BIGINT,
floor_no VARCHAR(16),
location VARCHAR(256),
repair_content TEXT,
photos JSONB,
status SMALLINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE repair_request IS '报修单信息表';
COMMENT ON COLUMN repair_request.id IS '自增主键';
COMMENT ON COLUMN repair_request.repair_no IS '报修单号(唯一)';
COMMENT ON COLUMN repair_request.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN repair_request.requester IS '报修人';
COMMENT ON COLUMN repair_request.requester_phone IS '报修电话';
COMMENT ON COLUMN repair_request.request_time IS '报修时间';
COMMENT ON COLUMN repair_request.building_id IS '所属建筑 ID';
COMMENT ON COLUMN repair_request.floor_no IS '所在楼层';
COMMENT ON COLUMN repair_request.location IS '所在位置';
COMMENT ON COLUMN repair_request.repair_content IS '报修内容';
COMMENT ON COLUMN repair_request.photos IS '报修照片(JSONB)';
COMMENT ON COLUMN repair_request.status IS '状态:0待处理 1已派单 2已完成';
COMMENT ON COLUMN repair_request.created_at IS '创建时间';
CREATE INDEX idx_repair_customer ON repair_request (customer_id);
CREATE INDEX idx_repair_status ON repair_request (status);
-- 30. 报修派单信息表
DROP TABLE IF EXISTS repair_dispatch CASCADE;
CREATE TABLE repair_dispatch (
id BIGSERIAL PRIMARY KEY,
dispatch_no VARCHAR(64) NOT NULL UNIQUE,
repair_id BIGINT NOT NULL REFERENCES repair_request(id),
customer_id BIGINT NOT NULL REFERENCES customer(id),
requester VARCHAR(64),
requester_phone VARCHAR(20),
request_time TIMESTAMPTZ,
building_id BIGINT,
floor_no VARCHAR(16),
location VARCHAR(256),
repair_content TEXT,
dispatcher VARCHAR(64),
worker_id BIGINT,
status SMALLINT NOT NULL DEFAULT 0,
process_photos JSONB,
rating SMALLINT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ
);
COMMENT ON TABLE repair_dispatch IS '报修派单信息表';
COMMENT ON COLUMN repair_dispatch.id IS '自增主键';
COMMENT ON COLUMN repair_dispatch.dispatch_no IS '报修派单号(唯一)';
COMMENT ON COLUMN repair_dispatch.repair_id IS '报修单 ID(→ repair_request)';
COMMENT ON COLUMN repair_dispatch.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN repair_dispatch.requester IS '报修人';
COMMENT ON COLUMN repair_dispatch.requester_phone IS '报修电话';
COMMENT ON COLUMN repair_dispatch.request_time IS '报修时间';
COMMENT ON COLUMN repair_dispatch.building_id IS '建筑 ID';
COMMENT ON COLUMN repair_dispatch.floor_no IS '所在楼层';
COMMENT ON COLUMN repair_dispatch.location IS '所在位置';
COMMENT ON COLUMN repair_dispatch.repair_content IS '报修内容';
COMMENT ON COLUMN repair_dispatch.dispatcher IS '派发人';
COMMENT ON COLUMN repair_dispatch.worker_id IS '共享电工 ID';
COMMENT ON COLUMN repair_dispatch.status IS '状态:0已派发 1已处理';
COMMENT ON COLUMN repair_dispatch.process_photos IS '处理完成照片(JSONB)';
COMMENT ON COLUMN repair_dispatch.rating IS '报修派单处理评分(客户评)';
COMMENT ON COLUMN repair_dispatch.created_at IS '创建时间';
COMMENT ON COLUMN repair_dispatch.completed_at IS '完成时间';
CREATE INDEX idx_repair_disp_customer ON repair_dispatch (customer_id);
CREATE INDEX idx_repair_disp_worker ON repair_dispatch (worker_id);
-- ============================================================
-- 九、维保域(2 张表)
-- ============================================================
-- 31. 维保单表
DROP TABLE IF EXISTS maintenance_order CASCADE;
CREATE TABLE maintenance_order (
id BIGSERIAL PRIMARY KEY,
maintenance_no VARCHAR(64) NOT NULL UNIQUE,
customer_id BIGINT NOT NULL REFERENCES customer(id),
maintainer VARCHAR(64),
worker_id BIGINT,
maintenance_date DATE,
remark TEXT,
status SMALLINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE maintenance_order IS '维保单信息表';
COMMENT ON COLUMN maintenance_order.id IS '自增主键';
COMMENT ON COLUMN maintenance_order.maintenance_no IS '维保单号(录入,不自动生成)';
COMMENT ON COLUMN maintenance_order.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN maintenance_order.maintainer IS '维保人(共享电工)';
COMMENT ON COLUMN maintenance_order.worker_id IS '共享电工 ID';
COMMENT ON COLUMN maintenance_order.maintenance_date IS '维保日期';
COMMENT ON COLUMN maintenance_order.remark IS '备注';
COMMENT ON COLUMN maintenance_order.status IS '状态:0已派发 1已完成';
COMMENT ON COLUMN maintenance_order.created_at IS '创建时间';
CREATE INDEX idx_maint_customer ON maintenance_order (customer_id);
CREATE INDEX idx_maint_worker ON maintenance_order (worker_id);
-- 32. 维保单明细表
DROP TABLE IF EXISTS maintenance_detail CASCADE;
CREATE TABLE maintenance_detail (
id BIGSERIAL PRIMARY KEY,
maintenance_id BIGINT NOT NULL REFERENCES maintenance_order(id) ON DELETE CASCADE,
equip_id BIGINT,
customer_id BIGINT NOT NULL REFERENCES customer(id),
equip_name VARCHAR(128),
equip_type VARCHAR(32),
building_id BIGINT,
floor_no VARCHAR(16),
location VARCHAR(256),
panel_condition TEXT,
circuit_condition TEXT,
terminal_condition TEXT,
maintenance_desc TEXT,
photos JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE maintenance_detail IS '维保单明细表';
COMMENT ON COLUMN maintenance_detail.id IS '自增主键';
COMMENT ON COLUMN maintenance_detail.maintenance_id IS '维保单 ID(→ maintenance_order)';
COMMENT ON COLUMN maintenance_detail.equip_id IS '客户设备 ID';
COMMENT ON COLUMN maintenance_detail.customer_id IS '客户 ID(→ customer)';
COMMENT ON COLUMN maintenance_detail.equip_name IS '客户设备名称';
COMMENT ON COLUMN maintenance_detail.equip_type IS '设备类型:配电间/井/箱';
COMMENT ON COLUMN maintenance_detail.building_id IS '建筑 ID';
COMMENT ON COLUMN maintenance_detail.floor_no IS '所在楼层';
COMMENT ON COLUMN maintenance_detail.location IS '所在位置';
COMMENT ON COLUMN maintenance_detail.panel_condition IS '配电间或电箱情况';
COMMENT ON COLUMN maintenance_detail.circuit_condition IS '回路情况';
COMMENT ON COLUMN maintenance_detail.terminal_condition IS '终端情况';
COMMENT ON COLUMN maintenance_detail.maintenance_desc IS '维保情况说明';
COMMENT ON COLUMN maintenance_detail.photos IS '维保现场照片(JSONB 水印)';
COMMENT ON COLUMN maintenance_detail.created_at IS '创建时间';
CREATE INDEX idx_md_maint ON maintenance_detail (maintenance_id);
CREATE INDEX idx_md_customer ON maintenance_detail (customer_id);
-- ============================================================
-- 十、共享电工域(4 张表)
-- ============================================================
-- 33. 共享电工信息表
DROP TABLE IF EXISTS worker CASCADE;
CREATE TABLE worker (
id BIGSERIAL PRIMARY KEY,
worker_name VARCHAR(64) NOT NULL,
phone VARCHAR(20) NOT NULL,
id_card VARCHAR(18),
worker_title VARCHAR(32),
rating NUMERIC(3,2) DEFAULT 5.00,
cert_info VARCHAR(128),
cert_verified BOOLEAN DEFAULT TRUE,
completed_count INTEGER DEFAULT 0,
status SMALLINT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE worker IS '共享电工信息表';
COMMENT ON COLUMN worker.id IS '自增主键';
COMMENT ON COLUMN worker.worker_name IS '电工姓名';
COMMENT ON COLUMN worker.phone IS '手机号';
COMMENT ON COLUMN worker.id_card IS '身份证号';
COMMENT ON COLUMN worker.worker_title IS '职衔:电工/资深班组长/特种高压电工';
COMMENT ON COLUMN worker.rating IS '服务评分(0-5)';
COMMENT ON COLUMN worker.cert_info IS '持证资质';
COMMENT ON COLUMN worker.cert_verified IS '资质是否已核验';
COMMENT ON COLUMN worker.completed_count IS '历史累计勘察场次';
COMMENT ON COLUMN worker.status IS '状态:1在岗 0离职';
COMMENT ON COLUMN worker.created_at IS '创建时间';
COMMENT ON COLUMN worker.updated_at IS '更新时间';
CREATE INDEX idx_worker_phone ON worker (phone);
CREATE TRIGGER trg_worker_updated_at BEFORE UPDATE ON worker
FOR EACH ROW EXECUTE FUNCTION trigger_set_updated_at();
-- 34. 派单价格表
DROP TABLE IF EXISTS dispatch_price CASCADE;
CREATE TABLE dispatch_price (
id BIGSERIAL PRIMARY KEY,
price_code VARCHAR(64) NOT NULL UNIQUE,
dispatch_type VARCHAR(32) NOT NULL,
price_type VARCHAR(32),
unit_price NUMERIC(10,2),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE dispatch_price IS '共享电工派单价格信息表';
COMMENT ON COLUMN dispatch_price.id IS '自增主键';
COMMENT ON COLUMN dispatch_price.price_code IS '派单价格编码(唯一)';
COMMENT ON COLUMN dispatch_price.dispatch_type IS '派单类型:勘察/安装/预警/维修/维保';
COMMENT ON COLUMN dispatch_price.price_type IS '价格类型:单价/总价提成';
COMMENT ON COLUMN dispatch_price.unit_price IS '服务单价';
COMMENT ON COLUMN dispatch_price.created_at IS '创建时间';
-- 35. 共享电工派单收入表(月度汇总)
DROP TABLE IF EXISTS worker_income CASCADE;
CREATE TABLE worker_income (
id BIGSERIAL PRIMARY KEY,
income_code VARCHAR(64) NOT NULL UNIQUE,
worker_id BIGINT NOT NULL REFERENCES worker(id),
income_month VARCHAR(7) NOT NULL,
monthly_total NUMERIC(14,2),
rating_summary NUMERIC(5,2),
final_total NUMERIC(14,2),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE worker_income IS '共享电工派单收入信息表(月度汇总)';
COMMENT ON COLUMN worker_income.id IS '自增主键';
COMMENT ON COLUMN worker_income.income_code IS '派单收入编码(唯一)';
COMMENT ON COLUMN worker_income.worker_id IS '共享电工 ID(→ worker)';
COMMENT ON COLUMN worker_income.income_month IS '收入月份(YYYY-MM)';
COMMENT ON COLUMN worker_income.monthly_total IS '月度收入合计';
COMMENT ON COLUMN worker_income.rating_summary IS '评分综合统计';
COMMENT ON COLUMN worker_income.final_total IS '月度最终收入合计';
COMMENT ON COLUMN worker_income.created_at IS '创建时间';
CREATE INDEX idx_wi_worker ON worker_income (worker_id);
CREATE INDEX idx_wi_month ON worker_income (income_month);
-- 36. 共享电工派单收入明细表
DROP TABLE IF EXISTS worker_income_detail CASCADE;
CREATE TABLE worker_income_detail (
id BIGSERIAL PRIMARY KEY,
income_id BIGINT NOT NULL REFERENCES worker_income(id) ON DELETE CASCADE,
worker_id BIGINT NOT NULL REFERENCES worker(id),
income_month VARCHAR(7) NOT NULL,
dispatch_type VARCHAR(32) NOT NULL,
dispatch_no VARCHAR(64),
income_amount NUMERIC(10,2),
rating_level VARCHAR(16),
final_income NUMERIC(10,2),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE worker_income_detail IS '共享电工派单收入明细信息表';
COMMENT ON COLUMN worker_income_detail.id IS '自增主键';
COMMENT ON COLUMN worker_income_detail.income_id IS '收入 ID(→ worker_income)';
COMMENT ON COLUMN worker_income_detail.worker_id IS '共享电工 ID(→ worker)';
COMMENT ON COLUMN worker_income_detail.income_month IS '收入月份';
COMMENT ON COLUMN worker_income_detail.dispatch_type IS '派单类型';
COMMENT ON COLUMN worker_income_detail.dispatch_no IS '各类型派单单号';
COMMENT ON COLUMN worker_income_detail.income_amount IS '本单收入';
COMMENT ON COLUMN worker_income_detail.rating_level IS '评价等级';
COMMENT ON COLUMN worker_income_detail.final_income IS '本单最终收入合计';
COMMENT ON COLUMN worker_income_detail.created_at IS '创建时间';
CREATE INDEX idx_wid_income ON worker_income_detail (income_id);
CREATE INDEX idx_wid_worker ON worker_income_detail (worker_id);
-- ============================================================
-- 十一、用电采集数据(1 张表,按月 RANGE 分区)
-- ============================================================
-- 37. 线路用电信息表(按月分区,时序数据)
DROP TABLE IF EXISTS electricity_data CASCADE;
CREATE TABLE electricity_data (
id BIGSERIAL,
line_id BIGINT NOT NULL,
line_code VARCHAR(64) NOT NULL,
equip_id BIGINT,
icc_device_code VARCHAR(128),
line_name VARCHAR(128),
customer_id BIGINT NOT NULL,
electricity NUMERIC(14,4),
collect_time TIMESTAMPTZ NOT NULL,
PRIMARY KEY (id, collect_time)
) PARTITION BY RANGE (collect_time);
COMMENT ON TABLE electricity_data IS '线路用电信息表(按月分区,时序数据)';
COMMENT ON COLUMN electricity_data.id IS '自增(复合主键含 collect_time)';
COMMENT ON COLUMN electricity_data.line_id IS '线路 ID(→ electric_line)';
COMMENT ON COLUMN electricity_data.line_code IS '线路编码(冗余,加速查询)';
COMMENT ON COLUMN electricity_data.equip_id IS '客户设备 ID';
COMMENT ON COLUMN electricity_data.icc_device_code IS 'ICC 设备编号';
COMMENT ON COLUMN electricity_data.line_name IS '线路名称';
COMMENT ON COLUMN electricity_data.customer_id IS '客户 ID(逻辑隔离键)';
COMMENT ON COLUMN electricity_data.electricity IS '用电量';
COMMENT ON COLUMN electricity_data.collect_time IS '采集时间(分区键)';
-- 初始分区(2026 年 8~12 月)
CREATE TABLE electricity_data_2026_08 PARTITION OF electricity_data
FOR VALUES FROM ('2026-08-01') TO ('2026-09-01');
CREATE TABLE electricity_data_2026_09 PARTITION OF electricity_data
FOR VALUES FROM ('2026-09-01') TO ('2026-10-01');
CREATE TABLE electricity_data_2026_10 PARTITION OF electricity_data
FOR VALUES FROM ('2026-10-01') TO ('2026-11-01');
CREATE TABLE electricity_data_2026_11 PARTITION OF electricity_data
FOR VALUES FROM ('2026-11-01') TO ('2026-12-01');
CREATE TABLE electricity_data_2026_12 PARTITION OF electricity_data
FOR VALUES FROM ('2026-12-01') TO ('2027-01-01');
-- 分区索引(在父表创建,自动继承到各分区)
CREATE INDEX idx_ed_customer ON electricity_data (customer_id);
CREATE INDEX idx_ed_line ON electricity_data (line_id);
CREATE INDEX idx_ed_time ON electricity_data (collect_time);
CREATE INDEX idx_ed_customer_time ON electricity_data (customer_id, collect_time);
-- ============================================================
-- 自动分区管理(纯 PostgreSQL,无需 pg_partman 扩展)
-- ============================================================
-- 1) 确保指定月份的分区存在(不存在则创建)
CREATE OR REPLACE FUNCTION ensure_electricity_partition(p_date DATE DEFAULT CURRENT_DATE)
RETURNS VOID AS $$
DECLARE
v_month_start DATE;
v_month_end DATE;
v_part_name TEXT;
v_exists BOOLEAN;
BEGIN
v_month_start := DATE_TRUNC('month', p_date)::DATE;
v_month_end := (v_month_start + INTERVAL '1 month')::DATE;
v_part_name := 'electricity_data_' || TO_CHAR(v_month_start, 'YYYY_MM');
SELECT EXISTS(
SELECT 1 FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = v_part_name AND n.nspname = 'public'
) INTO v_exists;
IF NOT v_exists THEN
EXECUTE format(
'CREATE TABLE %I PARTITION OF electricity_data FOR VALUES FROM (%L) TO (%L)',
v_part_name, v_month_start, v_month_end
);
END IF;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION ensure_electricity_partition IS '确保指定月份的用电数据分区存在(不存在则自动创建)';
-- 2) 批量预创建未来分区(p_premake = 预创建未来几个月)
CREATE OR REPLACE FUNCTION run_electricity_partition_maintenance(p_premake INT DEFAULT 3)
RETURNS TABLE(created_month TEXT, action TEXT) AS $$
DECLARE
v_date DATE := CURRENT_DATE;
v_i INT;
v_month_start DATE;
v_part_name TEXT;
v_exists BOOLEAN;
BEGIN
FOR v_i IN 0..p_premake LOOP
v_month_start := DATE_TRUNC('month', v_date + (v_i || ' month')::INTERVAL)::DATE;
v_part_name := 'electricity_data_' || TO_CHAR(v_month_start, 'YYYY_MM');
SELECT EXISTS(
SELECT 1 FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = v_part_name AND n.nspname = 'public'
) INTO v_exists;
IF NOT v_exists THEN
EXECUTE format(
'CREATE TABLE %I PARTITION OF electricity_data FOR VALUES FROM (%L) TO (%L)',
v_part_name,
v_month_start,
(v_month_start + INTERVAL '1 month')::DATE
);
created_month := v_part_name;
action := 'CREATED';
ELSE
created_month := v_part_name;
action := 'EXISTS';
END IF;
RETURN NEXT;
END LOOP;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION run_electricity_partition_maintenance IS '批量预创建用电数据分区(默认当前月+未来3个月),返回创建结果';
-- 3) pg_cron 定时调度(数据库全自动管理,无需后端 cron)
-- pg_cron 扩展在数据库内部定时执行,后端服务状态不影响分区管理
-- 注意: PostgreSQL 分区表的 BEFORE INSERT 触发器在分区路由之后才执行,
-- 无法在写入时动态创建分区。因此通过 pg_cron 定时预创建 + 应用层 ensure 兜底。
-- 删除已存在的同名定时任务(幂等)
DO $$
BEGIN
PERFORM cron.unschedule(jobid) FROM cron.job WHERE jobname IN ('electricity_partition_maintenance', 'electricity_monthly_refresh');
EXCEPTION WHEN OTHERS THEN NULL;
END $$;
-- 每月25号凌晨3点:预创建当前月 + 未来3个月分区
SELECT cron.schedule(
'electricity_partition_maintenance',
'0 3 25 * *',
'SELECT run_electricity_partition_maintenance(3)'
);
-- 每月1号凌晨2点:刷新月度汇总物化视图
SELECT cron.schedule(
'electricity_monthly_refresh',
'0 2 1 * *',
'SELECT refresh_electricity_monthly()'
);
-- 4) 立即执行一次维护:确保当前月+未来3个月分区就绪
SELECT * FROM run_electricity_partition_maintenance(3);
-- ============================================================
-- 用电数据月度汇总物化视图(历史数据分析)
-- ============================================================
CREATE MATERIALIZED VIEW IF NOT EXISTS mv_electricity_monthly AS
SELECT
customer_id,
line_id,
TO_CHAR(collect_time, 'YYYY-MM') AS month,
SUM(electricity) AS total_usage,
AVG(electricity) AS avg_usage,
MAX(electricity) AS peak_usage,
MIN(electricity) AS min_usage,
COUNT(*) AS record_count,
MIN(collect_time) AS first_collect,
MAX(collect_time) AS last_collect
FROM electricity_data
GROUP BY customer_id, line_id, TO_CHAR(collect_time, 'YYYY-MM')
WITH NO DATA;
COMMENT ON MATERIALIZED VIEW mv_electricity_monthly IS '用电数据月度汇总(历史分析用,每月刷新)';
CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_em_key
ON mv_electricity_monthly (customer_id, line_id, month);
CREATE INDEX IF NOT EXISTS idx_mv_em_customer
ON mv_electricity_monthly (customer_id);
CREATE INDEX IF NOT EXISTS idx_mv_em_month
ON mv_electricity_monthly (month);
-- 刷新函数(应用层每月初调用,或用 pg_cron 定时)
-- 首次刷新用非并发模式初始化,后续用 CONCURRENTLY 不阻塞查询
CREATE OR REPLACE FUNCTION refresh_electricity_monthly()
RETURNS VOID AS $$
BEGIN
IF NOT pg_is_in_recovery() AND EXISTS (
SELECT 1 FROM pg_matviews WHERE matviewname = 'mv_electricity_monthly'
) THEN
BEGIN
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_electricity_monthly;
EXCEPTION WHEN feature_not_supported THEN
REFRESH MATERIALIZED VIEW mv_electricity_monthly;
END;
ELSE
REFRESH MATERIALIZED VIEW mv_electricity_monthly;
END IF;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION refresh_electricity_monthly IS '刷新用电月度汇总物化视图(自动处理首次初始化,后续用 CONCURRENTLY)';
-- 首次初始化物化视图(WITH NO DATA 创建后必须先刷新一次)
REFRESH MATERIALIZED VIEW mv_electricity_monthly;
-- ============================================================
-- 十二、辅助域(2 张表)
-- ============================================================
-- ICC 同步日志表
DROP TABLE IF EXISTS icc_sync_log CASCADE;
CREATE TABLE icc_sync_log (
id BIGSERIAL PRIMARY KEY,
sync_type VARCHAR(32) NOT NULL,
start_time TIMESTAMPTZ,
end_time TIMESTAMPTZ,
record_count INT,
status SMALLINT NOT NULL DEFAULT 1,
error_message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE icc_sync_log IS 'ICC 数据同步日志表';
COMMENT ON COLUMN icc_sync_log.id IS '自增主键';
COMMENT ON COLUMN icc_sync_log.sync_type IS '同步类型:客户/设备/用电/报警';
COMMENT ON COLUMN icc_sync_log.start_time IS '开始时间';
COMMENT ON COLUMN icc_sync_log.end_time IS '结束时间';
COMMENT ON COLUMN icc_sync_log.record_count IS '同步记录数';
COMMENT ON COLUMN icc_sync_log.status IS '状态:1成功 0失败';
COMMENT ON COLUMN icc_sync_log.error_message IS '错误信息';
COMMENT ON COLUMN icc_sync_log.created_at IS '创建时间';
CREATE INDEX idx_icc_sync_type ON icc_sync_log (sync_type);
CREATE INDEX idx_icc_sync_time ON icc_sync_log (created_at);
-- 文件存储记录表
DROP TABLE IF EXISTS file_record CASCADE;
CREATE TABLE file_record (
id BIGSERIAL PRIMARY KEY,
file_name VARCHAR(256) NOT NULL,
file_url VARCHAR(512) NOT NULL,
file_type VARCHAR(32),
file_size BIGINT,
business_type VARCHAR(64),
business_id BIGINT,
uploaded_by BIGINT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE file_record IS '文件存储记录表';
COMMENT ON COLUMN file_record.id IS '自增主键';
COMMENT ON COLUMN file_record.file_name IS '文件名';
COMMENT ON COLUMN file_record.file_url IS '文件 URL';
COMMENT ON COLUMN file_record.file_type IS '文件类型:取证/合同/验收/方案/月报';
COMMENT ON COLUMN file_record.file_size IS '文件大小(字节)';
COMMENT ON COLUMN file_record.business_type IS '关联业务类型';
COMMENT ON COLUMN file_record.business_id IS '关联业务 ID';
COMMENT ON COLUMN file_record.uploaded_by IS '上传人 ID';
COMMENT ON COLUMN file_record.created_at IS '创建时间';
CREATE INDEX idx_file_business ON file_record (business_type, business_id);
-- ============================================================
-- 十三、初始数据
-- 已迁移至独立脚本 db_seed.sql(幂等执行,便于单独重置种子数据)
-- 执行顺序:先 db_init.sql 建表结构 → 再 db_seed.sql 写入种子数据
-- ============================================================
COMMIT;
-- ============================================================
-- 完 · 37 张表 · 50+ 索引 · 3 个触发器 · 3 个分区管理函数 · 1 个物化视图 · 2 个 pg_cron 定时任务(种子数据见 db_seed.sql)
-- ============================================================
-- ============================================================
-- 附录: pg_cron 安装说明(首次部署时执行一次)
-- ============================================================
--
-- 1. 下载 Windows 预编译包:
-- https://github.com/hakanrw/pg_cron_windows/releases/download/windows/pg_cron_windows_msvc_x64.zip
--
-- 2. 复制文件到 PostgreSQL 目录:
-- pg_cron.dll → C:\Program Files\PostgreSQL\17\lib\
-- pg_cron.control → C:\Program Files\PostgreSQL\17\share\extension\
-- pg_cron--*.sql → C:\Program Files\PostgreSQL\17\share\extension\
--
-- 3. 编辑 postgresql.conf 添加以下内容:
-- shared_preload_libraries = 'pg_cron'
-- cron.database_name = 'xintong'
-- cron.host = '127.0.0.1'
-- cron.use_background_workers = on
-- cron.timezone = 'Asia/Shanghai'
--
-- 4. 编辑 pg_hba.conf,在 IPv4 local connections 之前添加:
-- host all postgres 127.0.0.1/32 trust
-- host all postgres ::1/128 trust
--
-- 5. 重启 PostgreSQL 服务
--
-- 6. 执行本脚本: psql -U postgres -d xintong -f db_init.sql
-- 脚本会自动创建 pg_cron 扩展并注册定时任务
--
-- 注意: cron.use_background_workers = on 是 Windows 上的关键配置,
-- 使用后台工作进程代替 TCP 连接,避免连接认证超时问题。
-- ============================================================