feat(collections): 自动同步作品交付集验收状态

This commit is contained in:
yuzhe
2026-07-21 19:23:58 +08:00
parent e4d1d3bcea
commit 721e971dd8
22 changed files with 309 additions and 35 deletions

View File

@@ -0,0 +1,46 @@
import type { CollectionStatus } from '../../shared/types.js';
import { database, databaseDialect, type QueryContext } from '../database.js';
export interface CollectionStatusResult {
status: CollectionStatus;
workCount: number;
approvedCount: number;
completedAt: string | null;
}
export function deriveCollectionStatus(workCount: number, approvedCount: number): Exclude<CollectionStatus, 'archived'> {
if (workCount === 0) return 'draft';
if (approvedCount === workCount) return 'completed';
return 'reviewing';
}
export async function recalculateCollectionStatus(
collectionId: number,
tx: QueryContext = database,
): Promise<CollectionStatusResult | null> {
const collection = await tx.one<{ status: CollectionStatus; completed_at: string | null }>(
`SELECT status, completed_at FROM collections WHERE id = ?${databaseDialect === 'postgres' ? ' FOR NO KEY UPDATE' : ''}`,
[collectionId],
);
if (!collection) return null;
const counts = await tx.one<{ work_count: number | string; approved_count: number | string }>(
`SELECT COUNT(*) AS work_count,
SUM(CASE WHEN review_status = 'approved' THEN 1 ELSE 0 END) AS approved_count
FROM notes WHERE collection_id = ? AND review_status != 'draft'`,
[collectionId],
);
const workCount = Number(counts?.work_count ?? 0);
const approvedCount = Number(counts?.approved_count ?? 0);
if (collection.status === 'archived') {
return { status: 'archived', workCount, approvedCount, completedAt: collection.completed_at };
}
const status = deriveCollectionStatus(workCount, approvedCount);
const completedAt = status === 'completed'
? collection.completed_at ?? new Date().toISOString()
: null;
await tx.execute('UPDATE collections SET status = ?, completed_at = ? WHERE id = ?', [status, completedAt, collectionId]);
return { status, workCount, approvedCount, completedAt };
}