-- ============================================================ -- 鑫潼智电系统 PostgreSQL 数据库构建脚本 V1.0 -- 基于《鑫潼智电系统需求分析报告 V1.0》+ 数据库设计文档 -- 数据库: PostgreSQL 15+ -- 字符集: UTF8 -- 表数量: 37 张(含 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_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, 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_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.created_at IS '创建时间'; COMMENT ON COLUMN customer.updated_at IS '更新时间'; CREATE INDEX idx_customer_name ON customer (customer_name); 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() ); 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 '创建时间'; CREATE INDEX idx_cust_dept_customer ON customer_department (customer_id); -- 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, created_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.created_at IS '创建时间'; CREATE INDEX idx_visit_customer ON customer_visit (customer_id); CREATE INDEX idx_visit_salesman ON customer_visit (salesman_id); -- 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() ); 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 '创建时间'; CREATE UNIQUE INDEX idx_cust_account_phone ON customer_account (phone); -- ============================================================ -- 三、智电设备域(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); -- ============================================================ -- 六、派单域(4 张表) -- ============================================================ -- 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, remark TEXT, status SMALLINT NOT NULL DEFAULT 0, created_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.remark IS '备注'; COMMENT ON COLUMN survey_order.status IS '状态:0已派发 1已接单 2已完成'; COMMENT ON COLUMN survey_order.created_at IS '创建时间'; CREATE INDEX idx_survey_customer ON survey_order (customer_id); CREATE INDEX idx_survey_worker ON survey_order (worker_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), 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.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); -- ============================================================ -- 十三、初始数据 -- ============================================================ -- 初始角色 INSERT INTO sys_role (role_code, role_name, description, sort_order) VALUES ('ADMIN', '运营管理员', '全部菜单权限,管理所有客户', 1), ('SALESMAN', '业务员', '客户/项目/方案/合同/回访', 2), ('WORKER', '共享电工', 'APP 接单处理,无后台菜单', 3), ('CUSTOMER', '客户', '小程序查看用电/报警/报修', 4); -- 初始菜单 INSERT INTO sys_menu (menu_name, menu_path, menu_type, sort_order, permission) VALUES ('客户管理', '/customer', 2, 1, 'customer:list'), ('项目管理', '/project', 2, 2, 'project:list'), ('运营派单', '/dispatch', 2, 3, 'dispatch:list'), ('设备监控', '/device', 2, 4, 'device:list'), ('统计报表', '/report', 2, 5, 'report:list'), ('系统管理', '/system', 2, 6, 'system:list'); -- 初始字典 INSERT INTO sys_dict (dict_type, dict_label, dict_value, sort_order) VALUES ('customer_status', '意向', '1', 1), ('customer_status', '签约', '2', 2), ('customer_status', '运维中', '3', 3), ('customer_status', '终止', '4', 4), ('equip_type', '配电间', '配电间', 1), ('equip_type', '配电井', '配电井', 2), ('equip_type', '配电箱', '配电箱', 3), ('device_type', '网关', '网关', 1), ('device_type', '探测器', '探测器', 2), ('device_type', '采集器', '采集器', 3), ('dispatch_status', '已派发', '0', 1), ('dispatch_status', '已接单', '1', 2), ('dispatch_status', '已完成', '2', 3), ('pay_cycle', '按月支付', '按月', 1), ('pay_cycle', '按年支付', '按年', 2), ('dispatch_type', '勘察派单', 'survey', 1), ('dispatch_type', '安装派单', 'install', 2), ('dispatch_type', '报警派单', 'alarm', 3), ('dispatch_type', '维修派单', 'repair', 4), ('dispatch_type', '维保派单', 'maintenance', 5); COMMIT; -- ============================================================ -- 完 · 37 张表 · 50+ 索引 · 3 个触发器 · 3 个分区管理函数 · 1 个物化视图 · 2 个 pg_cron 定时任务 · 初始数据 -- ============================================================ -- ============================================================ -- 附录: 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 连接,避免连接认证超时问题。 -- ============================================================