feat(ticket): record urges in thread, show urge count in admin, mail admins

- aksrt_ticket gains urge_count / last_urge_time (schema 3.1.0)
- cooldown moves from transient to an atomic conditional UPDATE
- urge inserts a system reply rendered as a centered notice (not quotable)
- admin list badge + info card row for urge count / last time
- new ticket_urge_admin mail event via aksrt_ticket_urged

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-10-27 22:17:40 +08:00
1 parent ac3ed9c956
commit 5febdb35c8
14 files changed
+198 -25

No files matched your search

@@ -3,7 +3,7 @@
/**
* 工单表结构契约:
* 1) 6 张 aksrt_ticket* 表全部由 DBMigratorPart2 建出,且被 migrate_ticket_tables() 调用;
* 2) SCHEMA_VERSION 已升到 3.0.0(dbDelta 只在版本变化时重跑);
* 2) SCHEMA_VERSION 已升到 3.1.0(dbDelta 只在版本变化时重跑);
* 3) aksrt_order_aftersale 已从 schema 与表名映射中彻底移除;
* 4) 状态与部门 seeds 幂等,默认状态被标记为不可改删。
*/
@@ -27,10 +27,14 @@ const TABLES = [
test('schema version is bumped so dbDelta re-runs', () => {
const migrator = read('src/Core/DBMigrator.php');
assert.match(migrator, /const SCHEMA_VERSION = '3\.0\.0';/, 'SCHEMA_VERSION must be 3.0.0');
assert.match(migrator, /const SCHEMA_VERSION = '3\.1\.0';/, 'SCHEMA_VERSION must be 3.1.0');
assert.ok(migrator.includes('use DBMigratorTicket;'), 'DBMigratorTicket trait not composed');
// 版本注释要说明 3.0.0 做了什么,否则后人看不懂这次升版的原因。
// 版本注释要说明每次升版做了什么,否则后人看不懂升版原因。
assert.match(migrator, /3\.0\.0: 售后(order_aftersale)整体替换为工单系统/);
assert.match(migrator, /3\.1\.0: aksrt_ticket 新增 urge_count \/ last_urge_time/);
const ticket = read('src/Core/DBMigratorTicket.php');
assert.match(ticket, /urge_count INT UNSIGNED NOT NULL DEFAULT 0,/);
assert.match(ticket, /last_urge_time DATETIME DEFAULT NULL,/);
});
test('order_aftersale is gone from schema and the table map', () => {