import type { DatabaseClient } from "./database"; 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); } return 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(); 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, }; }); }