type ReleasableDistribution = { id: string; task_id: 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; }; 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; }) { if (input.publishUrl) return "已回填发布链接的笔记不能释放"; if (input.resultSubmittedAt) return "已提交结果截图的任务不能释放"; return null; } export async function releaseUnfinishedDistribution( db: D1Database, distributionId: string, ) { if (!distributionId) { throw new DistributionReleaseError("请选择需要释放的领取记录", 400); } const distribution = await db .prepare( `SELECT d.id, d.task_id, 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 FROM distributions d JOIN contents c ON c.id = d.content_id JOIN partners p ON p.id = d.partner_id WHERE d.id = ?`, ) .bind(distributionId) .first(); if (!distribution) { throw new DistributionReleaseError("领取记录不存在或已被释放", 404); } const blocked = distributionReleaseBlockReason({ publishUrl: distribution.publish_url, }); if (blocked) throw new DistributionReleaseError(blocked, 409); const statements: D1PreparedStatement[] = [ db .prepare("DELETE FROM collection_runs WHERE distribution_id = ?") .bind(distribution.id), db.prepare("DELETE FROM distributions WHERE id = ?").bind(distribution.id), db .prepare("UPDATE contents SET status = 'available' WHERE id = ?") .bind(distribution.content_id), db .prepare( `UPDATE tasks SET claimed_quantity = MAX(claimed_quantity - 1, 0) WHERE id = ?`, ) .bind(distribution.task_id), db .prepare( `UPDATE partners SET claimed_total = MAX(claimed_total - 1, 0) WHERE id = ?`, ) .bind(distribution.partner_id), ]; if (distribution.delegation_bundle_id) { statements.push( db .prepare( `UPDATE delegation_bundles SET quantity = MAX(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), ); } if (distribution.claim_id) { statements.push( db .prepare( `UPDATE claims SET quantity = MAX(quantity - 1, 0) WHERE id = ?`, ) .bind(distribution.claim_id), 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), ); } await db.batch(statements); return { distributionId: distribution.id, taskId: distribution.task_id, contentId: distribution.content_id, contentTitle: distribution.content_title, partnerName: distribution.partner_name, }; }