feat: 新增可配置通知中心,支持扫描式/埋点式通知规则与手动群发

新增通知规则实体、扫描任务、API与管理界面,重构站内通知支持用户/电工双类型,移除硬编码的证照到期提醒,新增工单派单、电工审核结果等通知触发场景,调整菜单与数据库结构适配新通知体系。
This commit is contained in:
andyesfly committed 2026-10-06 11:53:02 +08:00
1 parent 91aa3c06f0
commit 6cf6a3d5b9
36 files changed
+3309 -246

No files matched your search

@@ -138,14 +138,14 @@ CREATE UNIQUE INDEX IF NOT EXISTS uk_sys_notice_biz ON sys_notice (notice_type,
-- ------------------------------------------------------------
-- 6) 菜单与权限
-- 「运营派单」由顶级菜单项改为目录,运维费定价/成本价/电工登记作为其子菜单
-- 「运营派单」由顶级菜单项改为目录,运维费定价/电工登记作为其子菜单(勘察费用归属「项目管理」)
-- ------------------------------------------------------------
UPDATE sys_menu SET menu_type = 1 WHERE id = 3;
INSERT INTO sys_menu (id, parent_id, menu_name, menu_path, component, icon, sort_order, menu_type, permission, visible) VALUES
(34, 3, '运维费定价', '/dispatch/price', 'dispatch/price/index', 'MoneyCollectOutlined', 1, 2, 'dispatch:price', 1),
(35, 3, '电工派单成本价', '/dispatch/cost', 'dispatch/cost/index', 'AccountBookOutlined', 2, 2, 'dispatch:cost', 1),
(36, 3, '电工登记管理', '/dispatch/worker', 'dispatch/worker/index', 'ToolOutlined', 3, 2, 'dispatch:worker', 1)
(34, 3, '运维费定价', '/dispatch/price', 'dispatch/price/index', 'MoneyCollectOutlined', 2, 2, 'dispatch:price', 1),
(35, 2, '勘察费用', '/dispatch/cost', 'dispatch/cost/index', 'AccountBookOutlined', 8, 2, 'dispatch:cost', 1),
(36, 3, '电工登记管理', '/dispatch/worker', 'dispatch/worker/index', 'ToolOutlined', 1, 2, 'dispatch:worker', 1)
ON CONFLICT (id) DO UPDATE SET
parent_id = EXCLUDED.parent_id,
menu_name = EXCLUDED.menu_name,
@@ -162,9 +162,9 @@ INSERT INTO sys_menu (id, parent_id, menu_name, menu_path, component, icon, sort
(37, 3, '新增价目', NULL, NULL, NULL, 1, 3, 'price:create', 1),
(38, 3, '编辑价目', NULL, NULL, NULL, 2, 3, 'price:update', 1),
(39, 3, '删除价目', NULL, NULL, NULL, 3, 3, 'price:delete', 1),
(40, 3, '新增成本价', NULL, NULL, NULL, 4, 3, 'cost:create', 1),
(41, 3, '编辑成本价', NULL, NULL, NULL, 5, 3, 'cost:update', 1),
(42, 3, '删除成本价', NULL, NULL, NULL, 6, 3, 'cost:delete', 1),
(40, 2, '新增勘察费用', NULL, NULL, NULL, 9, 3, 'cost:create', 1),
(41, 2, '编辑勘察费用', NULL, NULL, NULL, 10, 3, 'cost:update', 1),
(42, 2, '删除勘察费用', NULL, NULL, NULL, 11, 3, 'cost:delete', 1),
(43, 3, '新增电工', NULL, NULL, NULL, 7, 3, 'worker:create', 1),
(44, 3, '编辑电工', NULL, NULL, NULL, 8, 3, 'worker:update', 1),
(45, 3, '删除电工', NULL, NULL, NULL, 9, 3, 'worker:delete', 1),
@@ -0,0 +1,161 @@
-- 可配置消息通知中心
-- 背景:现有站内通知为硬编码单点(仅「证照到期」一种、标题模板写死、接收人写死为 ADMIN),
-- 且仅 Web 管理端可见;两个移动端(管理端 App / 电工端 App)没有消息中心。
-- 本期建立「可配置 + 两端统一」的站内消息中心:
-- - 改造 sys_notice:新增 receiver_type(user|worker),两端共用一套消息数据;
-- - 新增 sys_notice_rule(通知规则模板 + 接收对象配置);
-- - 新增 sys_notice_batch(手动群发批次/发送记录);
-- - 新增内置任务 notice_scan(扫描式规则统一入口)与首批规则种子;
-- - 挂载系统管理下的「通知管理」菜单与按钮权限。
-- 幂等:新增列带 DEFAULT + IF NOT EXISTS;建表/索引用 IF NOT EXISTS;菜单/绑定用 ON CONFLICT;
-- 任务与规则种子用 ON CONFLICT DO NOTHING。
-- 注意:菜单 id 51-54,避开 20261001(34-46)与 20261002(47-50)已占用区间。
BEGIN;
-- ------------------------------------------------------------
-- 1) 改造 sys_notice:增加接收人类型与来源引用
-- ------------------------------------------------------------
ALTER TABLE sys_notice ADD COLUMN IF NOT EXISTS receiver_type VARCHAR(16) NOT NULL DEFAULT 'user'; -- 接收人类型:user-管理端用户 worker-电工
ALTER TABLE sys_notice ADD COLUMN IF NOT EXISTS rule_id BIGINT; -- 来源通知规则(→ sys_notice_rule.id)
ALTER TABLE sys_notice ADD COLUMN IF NOT EXISTS batch_id BIGINT; -- 来源群发批次(→ sys_notice_batch.id)
COMMENT ON COLUMN sys_notice.receiver_type IS '接收人类型:user-管理端用户 worker-电工';
COMMENT ON COLUMN sys_notice.rule_id IS '来源通知规则(→ sys_notice_rule.id)';
COMMENT ON COLUMN sys_notice.batch_id IS '来源群发批次(→ sys_notice_batch.id)';
-- 幂等键升级:加入 receiver_type(user 与 worker 的 id 空间不同,必须区分)
DROP INDEX IF EXISTS uk_sys_notice_biz;
CREATE UNIQUE INDEX IF NOT EXISTS uk_sys_notice_deliver
ON sys_notice (notice_type, biz_type, biz_id, receiver_type, receiver_id);
CREATE INDEX IF NOT EXISTS idx_sys_notice_receiver_type ON sys_notice (receiver_type, receiver_id, is_read);
-- ------------------------------------------------------------
-- 2) 通知规则表(规则模板 + 接收对象配置)
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS sys_notice_rule (
id BIGSERIAL PRIMARY KEY,
rule_key VARCHAR(64) NOT NULL, -- 规则键(唯一)
rule_name VARCHAR(64) NOT NULL, -- 规则名称
event_key VARCHAR(64) NOT NULL, -- 事件键(对应代码事件注册表)
trigger_mode SMALLINT NOT NULL DEFAULT 1, -- 触发方式:1扫描式 2埋点式
enabled SMALLINT NOT NULL DEFAULT 1, -- 状态:1启用 0停用
advance_days INT, -- 提前天数(扫描式)
title_tpl VARCHAR(128) NOT NULL, -- 标题模板(支持 {变量})
content_tpl VARCHAR(512) NOT NULL, -- 正文模板(支持 {变量})
receiver_config JSONB NOT NULL DEFAULT '{"users":{"roles":[],"ids":[]},"workers":{"mode":"none","ids":[]}}'::jsonb, -- 接收对象配置
remark VARCHAR(255), -- 备注
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE sys_notice_rule IS '通知规则表(模板 + 接收对象)';
COMMENT ON COLUMN sys_notice_rule.id IS '自增主键';
COMMENT ON COLUMN sys_notice_rule.rule_key IS '规则键(唯一)';
COMMENT ON COLUMN sys_notice_rule.rule_name IS '规则名称';
COMMENT ON COLUMN sys_notice_rule.event_key IS '事件键(对应代码事件注册表)';
COMMENT ON COLUMN sys_notice_rule.trigger_mode IS '触发方式:1扫描式 2埋点式';
COMMENT ON COLUMN sys_notice_rule.enabled IS '状态:1启用 0停用';
COMMENT ON COLUMN sys_notice_rule.advance_days IS '提前天数(扫描式)';
COMMENT ON COLUMN sys_notice_rule.title_tpl IS '标题模板(支持 {变量})';
COMMENT ON COLUMN sys_notice_rule.content_tpl IS '正文模板(支持 {变量})';
COMMENT ON COLUMN sys_notice_rule.receiver_config IS '接收对象配置:users{roles,ids} + workers{mode:none|event|all|ids|status,ids,auditStatus,status}';
COMMENT ON COLUMN sys_notice_rule.remark IS '备注';
COMMENT ON COLUMN sys_notice_rule.created_at IS '创建时间';
COMMENT ON COLUMN sys_notice_rule.updated_at IS '更新时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_sys_notice_rule_key ON sys_notice_rule (rule_key);
CREATE INDEX IF NOT EXISTS idx_sys_notice_rule_event ON sys_notice_rule (event_key, trigger_mode, enabled);
-- ------------------------------------------------------------
-- 3) 手动群发批次表(发送记录)
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS sys_notice_batch (
id BIGSERIAL PRIMARY KEY,
title VARCHAR(128) NOT NULL, -- 消息标题
content VARCHAR(512) NOT NULL, -- 消息正文
receiver_config JSONB NOT NULL DEFAULT '{"users":{"roles":[],"ids":[]},"workers":{"mode":"none","ids":[]}}'::jsonb, -- 接收对象快照
receiver_count INT NOT NULL DEFAULT 0, -- 实际接收人数
sent_by BIGINT, -- 发送人(→ sys_user.id)
sent_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -- 发送时间
);
COMMENT ON TABLE sys_notice_batch IS '手动群发批次表(发送记录)';
COMMENT ON COLUMN sys_notice_batch.id IS '自增主键';
COMMENT ON COLUMN sys_notice_batch.title IS '消息标题';
COMMENT ON COLUMN sys_notice_batch.content IS '消息正文';
COMMENT ON COLUMN sys_notice_batch.receiver_config IS '接收对象快照';
COMMENT ON COLUMN sys_notice_batch.receiver_count IS '实际接收人数';
COMMENT ON COLUMN sys_notice_batch.sent_by IS '发送人(→ sys_user.id)';
COMMENT ON COLUMN sys_notice_batch.sent_at IS '发送时间';
-- ------------------------------------------------------------
-- 4) 菜单与权限(系统管理 parent_id=6 下新增「通知管理」+ 3 个按钮权限)
-- ------------------------------------------------------------
INSERT INTO sys_menu (id, parent_id, menu_name, menu_path, component, icon, sort_order, menu_type, permission, visible) VALUES
(51, 6, '通知管理', '/system/notice', 'system/notice/index', 'BellOutlined', 7, 2, 'notice:rule', 1),
(52, 51, '保存规则', NULL, NULL, NULL, 1, 3, 'notice:rule:update', 1),
(53, 51, '删除规则', NULL, NULL, NULL, 2, 3, 'notice:rule:delete', 1),
(54, 51, '群发消息', NULL, NULL, NULL, 3, 3, 'notice:batch', 1)
ON CONFLICT (id) DO UPDATE SET
parent_id = EXCLUDED.parent_id,
menu_name = EXCLUDED.menu_name,
menu_path = EXCLUDED.menu_path,
component = EXCLUDED.component,
icon = EXCLUDED.icon,
sort_order = EXCLUDED.sort_order,
menu_type = EXCLUDED.menu_type,
permission = EXCLUDED.permission,
visible = EXCLUDED.visible;
-- 角色绑定:ADMIN(role_id=1)拥有 51~54
INSERT INTO sys_role_menu (role_id, menu_id) VALUES
(1, 51), (1, 52), (1, 53), (1, 54)
ON CONFLICT (role_id, menu_id) DO NOTHING;
-- 重置 sys_menu 自增序列,避免后续插入 id 冲突
SELECT setval('sys_menu_id_seq', 54, true);
-- ------------------------------------------------------------
-- 5) 内置任务种子:通知规则扫描(扫描式规则的统一入口)
-- ------------------------------------------------------------
INSERT INTO sys_job (job_key, job_name, cron_expression, params, retry_times, retry_interval, status, remark) VALUES
('notice_scan', '通知规则扫描', '0 0 8 * * *', '{}'::jsonb, 1, 300, 1,
'遍历启用的扫描式通知规则,按模板与接收对象生成站内通知(幂等,默认每日 08:00)')
ON CONFLICT (job_key) DO NOTHING;
-- ------------------------------------------------------------
-- 6) 首批通知规则种子(按 rule_key 幂等;接收对象均可在「通知管理」页调整)
-- ------------------------------------------------------------
INSERT INTO sys_notice_rule (rule_key, rule_name, event_key, trigger_mode, enabled, advance_days, title_tpl, content_tpl, receiver_config, remark) VALUES
('cert_expire', '电工资质证照到期提醒', 'cert_expire', 1, 1, 30,
'电工【{workerName}】的{certType}{expireState}',
'证书编号:{certNo};发证机关:{issueAuthority};有效期至:{expireDate};{remainDaysText},请及时提醒电工办理续期',
'{"users":{"roles":["ADMIN"],"ids":[]},"workers":{"mode":"event","ids":[]}}'::jsonb,
'扫描 worker_cert 中 advance_days 天内到期或已过期的证照,通知 ADMIN 与证照所属电工'),
('worker_audit_result', '电工入驻审核结果通知', 'worker_audit_result', 2, 1, NULL,
'您的入驻审核{auditState}',
'审核{auditState};{auditRemarkText}如资料有误,请修改后重新提交。',
'{"users":{"roles":[],"ids":[]},"workers":{"mode":"event","ids":[]}}'::jsonb,
'审核通过/驳回时实时通知电工本人(业务埋点触发)'),
('dispatch_assign', '工单派单通知', 'dispatch_assign', 2, 1, NULL,
'您有新的{orderTypeName}工单',
'工单编号:{orderNo};客户:{customerName};{detailText}请及时在电工端查看并接单。',
'{"users":{"roles":[],"ids":[]},"workers":{"mode":"event","ids":[]}}'::jsonb,
'勘察/安装/维保/报修派单时实时通知被指派电工(业务埋点触发)'),
('contract_expire', '客户合同到期提醒', 'contract_expire', 1, 1, 30,
'客户【{customerName}】的合同{expireState}',
'合同编号:{contractNo};合同名称:{contractName};到期日:{endDate};{remainDaysText},请及时跟进续签。',
'{"users":{"roles":["ADMIN"],"ids":[]},"workers":{"mode":"none","ids":[]}}'::jsonb,
'扫描 contract 中 advance_days 天内到期或已过期的合同,通知 ADMIN'),
('visit_plan', '客户拜访计划提醒', 'visit_plan', 1, 1, 0,
'客户拜访计划待执行:{customerName}',
'计划拜访日期:{planDate};{remainDaysText}。请及时完成拜访。',
'{"users":{"roles":["ADMIN"],"ids":[]},"workers":{"mode":"none","ids":[]}}'::jsonb,
'扫描 customer_visit 中未执行且计划日期已到/超期的拜访计划,通知 ADMIN'),
('order_timeout', '工单超时未接单提醒', 'order_timeout', 1, 1, 1,
'工单超时未接单:{orderTypeName}',
'工单编号:{orderNo};客户:{customerName};已派单 {staleDays} 天仍未接单,请及时跟进。',
'{"users":{"roles":["ADMIN"],"ids":[]},"workers":{"mode":"none","ids":[]}}'::jsonb,
'扫描派单后超过 advance_days 天仍未接单的工单,通知 ADMIN')
ON CONFLICT (rule_key) DO NOTHING;
COMMIT;