HEL-222: 整合自动提醒引擎(212b1f9)到现行测试版 d6f39e0

- 迁移链追加 0009_reminders_engine:旧 manual reminders 表改名
  reminders_legacy_manual 保留历史,新建 reminders/reminder_events/
  reminder_settings(append-only 触发器)
- server.py:新提醒 API(pending/scan/send/manual/resend/settings/
  公司端收件与状态流转)替换旧 manual-send 端点
- web:admin 待提醒清单+扫描+详情抽屉,公司端通知动态化+
  去处理事件委托;设置保存同步提醒扫描参数
- 移除被取代的 settings.pending_items/send_reminders 与旧 UI 逻辑
- 全量测试 346 项通过(5 项浏览器跳过与历史一致)

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
总工
2026-08-28 15:21:27 +00:00
co-authored by multica-agent
18 changed files with 2215 additions and 698 deletions
+85
View File
@@ -1011,6 +1011,91 @@ MIGRATIONS: tuple[Migration, ...] = (
DROP TABLE IF EXISTS closed_periods;
""",
),
Migration(
version=9,
name="0009_reminders_engine",
# HEL-195/215 auto-reminder engine. The old manual reminders table
# from 0007 is preserved verbatim under reminders_legacy_manual so
# existing test-env history stays queryable; the new engine uses its
# own append-only reminders/reminder_events schema.
up="""
ALTER TABLE reminders RENAME TO reminders_legacy_manual;
DROP INDEX IF EXISTS idx_reminders_company;
CREATE INDEX idx_reminders_legacy_company
ON reminders_legacy_manual (company_id);
CREATE TABLE reminder_settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TEXT NOT NULL
);
INSERT INTO reminder_settings (key, value, updated_at) VALUES
('monthly_start_day', '5', datetime('now')),
('gap_days', '5', datetime('now')),
('scan_time', '08:00', datetime('now'));
CREATE TABLE reminders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
company_id INTEGER NOT NULL REFERENCES companies (id),
rule_key TEXT NOT NULL CHECK (rule_key IN (
'unsubmitted', 'gap', 'pending_review', 'manual'
)),
dedupe_key TEXT NOT NULL UNIQUE,
rule_params TEXT NOT NULL DEFAULT '{}',
title TEXT NOT NULL,
content TEXT NOT NULL,
deadline TEXT,
source TEXT NOT NULL CHECK (source IN ('auto', 'manual')),
status TEXT NOT NULL DEFAULT 'open'
CHECK (status IN ('open', 'acknowledged', 'resolved')),
send_count INTEGER NOT NULL DEFAULT 0,
first_sent_at TEXT,
last_sent_at TEXT,
created_by TEXT NOT NULL,
created_at TEXT NOT NULL,
action_link TEXT
);
CREATE INDEX idx_reminders_company ON reminders (company_id);
CREATE INDEX idx_reminders_status ON reminders (status);
CREATE TABLE reminder_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
reminder_id INTEGER NOT NULL REFERENCES reminders (id),
event_type TEXT NOT NULL CHECK (event_type IN (
'sent', 'acknowledged', 'resolved', 'escalated'
)),
actor TEXT NOT NULL,
detail TEXT,
created_at TEXT NOT NULL
);
CREATE INDEX idx_reminder_events_reminder ON reminder_events (reminder_id);
CREATE TRIGGER reminder_events_no_update BEFORE UPDATE ON reminder_events
BEGIN SELECT RAISE (ABORT, 'reminder_events rows are append-only'); END;
CREATE TRIGGER reminder_events_no_delete BEFORE DELETE ON reminder_events
BEGIN SELECT RAISE (ABORT, 'reminder_events rows are append-only'); END;
CREATE TRIGGER reminders_no_delete BEFORE DELETE ON reminders
BEGIN SELECT RAISE (ABORT, 'reminders rows are immutable history'); END;
""",
down="""
DROP TRIGGER IF EXISTS reminders_no_delete;
DROP TRIGGER IF EXISTS reminder_events_no_delete;
DROP TRIGGER IF EXISTS reminder_events_no_update;
DROP INDEX IF EXISTS idx_reminder_events_reminder;
DROP TABLE IF EXISTS reminder_events;
DROP INDEX IF EXISTS idx_reminders_status;
DROP INDEX IF EXISTS idx_reminders_company;
DROP TABLE IF EXISTS reminders;
DROP TABLE IF EXISTS reminder_settings;
DROP INDEX IF EXISTS idx_reminders_legacy_company;
ALTER TABLE reminders_legacy_manual RENAME TO reminders;
CREATE INDEX idx_reminders_company ON reminders (company_id);
""",
),
)