- 任务/项目负责人 union scope:任务中心分组视图、工作台项目进度卡、收录验证列 - 项目负责人可管理自己项目:编辑/删除项目、编辑任务、分配负责人、推送、项目内建任务;堵死转移归属/越权新建/任务迁移 - 只读依赖放开给已登录:inspect_feishu、wecom_list_internal_contacts - 企微 OAuth 自助绑定:/api/wecom-bind(state 签名校验+登录态一致性+归属冲突检查),未绑定 banner 增加入口 - 工作台项目进度卡与未绑定 banner 排版修复 - 修 user-auth 判空分支被 Turbopack dev 折叠删除导致匿名请求 500
158 lines
4.4 KiB
TypeScript
158 lines
4.4 KiB
TypeScript
import type { DatabaseClient } from "./database";
|
||
import { refreshTaskStatus } from "./task-service";
|
||
|
||
type ReleasableDistribution = {
|
||
id: string;
|
||
task_id: string;
|
||
task_type: string;
|
||
content_id: string;
|
||
content_title: string;
|
||
partner_id: string;
|
||
partner_name: string;
|
||
claim_id: string | null;
|
||
delegation_bundle_id: string | null;
|
||
publish_url: string | null;
|
||
result_submitted_at: string | null;
|
||
};
|
||
|
||
export class DistributionReleaseError extends Error {
|
||
readonly status: number;
|
||
|
||
constructor(message: string, status: number) {
|
||
super(message);
|
||
this.name = "DistributionReleaseError";
|
||
this.status = status;
|
||
}
|
||
}
|
||
|
||
export function distributionReleaseBlockReason(input: {
|
||
publishUrl?: string | null;
|
||
resultSubmittedAt?: string | null;
|
||
taskType?: string | null;
|
||
}) {
|
||
if (input.publishUrl) return "已回填发布链接的笔记不能释放";
|
||
if (input.resultSubmittedAt) return "已提交结果截图的任务不能释放";
|
||
return null;
|
||
}
|
||
|
||
export async function releaseUnfinishedDistribution(
|
||
database: DatabaseClient,
|
||
distributionId: string,
|
||
) {
|
||
if (!distributionId) {
|
||
throw new DistributionReleaseError("请选择需要释放的领取记录", 400);
|
||
}
|
||
|
||
const result = await database.transaction(async (db) => {
|
||
const distribution = await db
|
||
.prepare(
|
||
`SELECT
|
||
d.id,
|
||
d.task_id,
|
||
t.task_type,
|
||
d.content_id,
|
||
c.title AS content_title,
|
||
d.partner_id,
|
||
p.name AS partner_name,
|
||
d.claim_id,
|
||
d.delegation_bundle_id,
|
||
d.publish_url,
|
||
d.result_submitted_at
|
||
FROM distributions d
|
||
JOIN tasks t ON t.id = d.task_id
|
||
JOIN contents c ON c.id = d.content_id
|
||
JOIN partners p ON p.id = d.partner_id
|
||
WHERE d.id = ?
|
||
FOR UPDATE`,
|
||
)
|
||
.bind(distributionId)
|
||
.first<ReleasableDistribution>();
|
||
|
||
if (!distribution) {
|
||
throw new DistributionReleaseError("领取记录不存在或已被释放", 404);
|
||
}
|
||
const blocked = distributionReleaseBlockReason({
|
||
publishUrl: distribution.publish_url,
|
||
resultSubmittedAt: distribution.result_submitted_at,
|
||
taskType: distribution.task_type,
|
||
});
|
||
if (blocked) throw new DistributionReleaseError(blocked, 409);
|
||
|
||
await db
|
||
.prepare("DELETE FROM collection_runs WHERE distribution_id = ?")
|
||
.bind(distribution.id)
|
||
.run();
|
||
await db
|
||
.prepare("DELETE FROM distributions WHERE id = ?")
|
||
.bind(distribution.id)
|
||
.run();
|
||
await db
|
||
.prepare("UPDATE contents SET status = 'available' WHERE id = ?")
|
||
.bind(distribution.content_id)
|
||
.run();
|
||
await db
|
||
.prepare(
|
||
`UPDATE tasks
|
||
SET claimed_quantity = GREATEST(claimed_quantity - 1, 0)
|
||
WHERE id = ?`,
|
||
)
|
||
.bind(distribution.task_id)
|
||
.run();
|
||
await db
|
||
.prepare(
|
||
`UPDATE partners
|
||
SET claimed_total = GREATEST(claimed_total - 1, 0)
|
||
WHERE id = ?`,
|
||
)
|
||
.bind(distribution.partner_id)
|
||
.run();
|
||
|
||
if (distribution.delegation_bundle_id) {
|
||
await db
|
||
.prepare(
|
||
`UPDATE delegation_bundles
|
||
SET quantity = GREATEST(quantity - 1, 0),
|
||
status = CASE WHEN quantity <= 1 THEN 'revoked' ELSE status END,
|
||
revoked_at = CASE WHEN quantity <= 1 THEN CURRENT_TIMESTAMP ELSE revoked_at END,
|
||
updated_at = CURRENT_TIMESTAMP
|
||
WHERE id = ?`,
|
||
)
|
||
.bind(distribution.delegation_bundle_id)
|
||
.run();
|
||
}
|
||
|
||
if (distribution.claim_id) {
|
||
await db
|
||
.prepare(
|
||
`UPDATE claims
|
||
SET quantity = GREATEST(quantity - 1, 0)
|
||
WHERE id = ?`,
|
||
)
|
||
.bind(distribution.claim_id)
|
||
.run();
|
||
await db
|
||
.prepare(
|
||
`DELETE FROM claims
|
||
WHERE id = ?
|
||
AND quantity <= 0
|
||
AND NOT EXISTS (
|
||
SELECT 1 FROM distributions WHERE claim_id = ?
|
||
)`,
|
||
)
|
||
.bind(distribution.claim_id, distribution.claim_id)
|
||
.run();
|
||
}
|
||
|
||
return {
|
||
distributionId: distribution.id,
|
||
taskId: distribution.task_id,
|
||
contentId: distribution.content_id,
|
||
contentTitle: distribution.content_title,
|
||
partnerName: distribution.partner_name,
|
||
};
|
||
});
|
||
// 释放会让任务从 completed 回到 active(content 重新 available)
|
||
await refreshTaskStatus(result.taskId, database).catch(() => undefined);
|
||
return result;
|
||
}
|