feat: 任务模型物理合并,发布+回收并为单任务双阶段

- tasks 新增 recovery_enabled/recovery_due_at/recovery_instructions/recovery_example_images,回收挂自己名下(先发布后交截图),不再置 complete
- 存量 screenshot_collect 迁移并入父任务后删除(mysql/0020 含 DELETE,生产执行前务必备份)
- 新 action task_enable_recovery:ghost 面板一键开通回收,截止默认=发布截止
- 企微催办双阶段(待发布/截图待提交);超期自动释放仅发布阶段
- admin 删 groupTaskMasters 聚合改单卡双面板;portal 详情页回收截图区块+批次回收进度
- 测试 309 全绿 + Playwright E2E 20/20
This commit is contained in:
ABAPPLO
2026-09-24 15:52:45 +08:00
parent ab78b187c0
commit e2a93e22cf
29 changed files with 2219 additions and 2195 deletions

View File

@@ -62,7 +62,7 @@ test("runDueSoonWecomNotifications filters by taskId when given", async () => {
);
assert.equal(executed.length, 1);
assert.match(executed[0].sql, /AND t\.id = \?/);
assert.deepEqual(executed[0].values, ["2026-08-21", "task-9"]);
assert.deepEqual(executed[0].values, ["2026-08-21", "2026-08-21", "task-9"]);
assert.equal(summary.dueSoonAttempted, 0);
assert.equal(summary.digestSent, false);
});
@@ -73,5 +73,5 @@ test("runDueSoonWecomNotifications keeps global scan without taskId", async () =
await runDueSoonWecomNotifications(db, NOTIFY_CONFIG, undefined, now);
assert.equal(executed.length, 1);
assert.ok(!/AND t\.id = \?/.test(executed[0].sql));
assert.deepEqual(executed[0].values, ["2026-08-21"]);
assert.deepEqual(executed[0].values, ["2026-08-21", "2026-08-21"]);
});