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

@@ -93,7 +93,7 @@ async function handlePost(request: Request) {
const assignment = upload.delegationToken
? await getRawDb()
.prepare(
`SELECT d.id, d.publish_url, d.result_screenshot_key, t.task_type
`SELECT d.id, d.publish_url, d.result_screenshot_key, t.recovery_enabled
FROM distributions d
JOIN delegation_bundles b ON b.id = d.delegation_bundle_id
JOIN tasks t ON t.id = d.task_id
@@ -102,10 +102,10 @@ async function handlePost(request: Request) {
AND b.status = 'active'`,
)
.bind(upload.distributionId, upload.delegationToken)
.first<{ id: string; publish_url: string | null; result_screenshot_key: string | null; task_type: string }>()
.first<{ id: string; publish_url: string | null; result_screenshot_key: string | null; recovery_enabled: number }>()
: await getRawDb()
.prepare(
`SELECT d.id, d.publish_url, d.result_screenshot_key, t.task_type
`SELECT d.id, d.publish_url, d.result_screenshot_key, t.recovery_enabled
FROM distributions d
JOIN claims c ON c.id = d.claim_id
JOIN tasks t ON t.id = d.task_id
@@ -115,7 +115,7 @@ async function handlePost(request: Request) {
AND c.task_id = t.id`,
)
.bind(upload.distributionId, upload.claimToken, upload.taskToken)
.first<{ id: string; publish_url: string | null; result_screenshot_key: string | null; task_type: string }>();
.first<{ id: string; publish_url: string | null; result_screenshot_key: string | null; recovery_enabled: number }>();
if (!assignment) {
return Response.json({ error: "笔记与领取凭证不匹配" }, { status: 403 });
}
@@ -125,11 +125,8 @@ async function handlePost(request: Request) {
{ status: 409 },
);
}
if (isTaskResult && assignment.task_type !== "screenshot_collect") {
return Response.json({ error: "当前任务不支持截图结果回填" }, { status: 400 });
}
if (!isTaskResult && assignment.task_type === "screenshot_collect") {
return Response.json({ error: "截图回收任务请上传任务结果截图" }, { status: 400 });
if (isTaskResult && Number(assignment.recovery_enabled) !== 1) {
return Response.json({ error: "该任务未开通截图回收" }, { status: 400 });
}
const existingResultKeys = isTaskResult
? parseResultScreenshotKeys(assignment.result_screenshot_key)