import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() const applyChanges = process.argv.includes('--apply') const actorUserId = 'system-template-consolidation' const actorName = '系统模板归并' function chooseRetainedVersion(versions) { const byNewest = [...versions].sort((left, right) => ( Number(right.version || 0) - Number(left.version || 0) || new Date(right.updatedAt).getTime() - new Date(left.updatedAt).getTime() )) return byNewest.find((version) => version.status === 'published') || byNewest.find((version) => version.status === 'draft') || byNewest[0] || null } function readDefinition(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : {} } async function buildPlan() { const templates = await prisma.performanceSurveyTemplate.findMany({ include: { versions: { orderBy: [{ version: 'desc' }, { updatedAt: 'desc' }], }, }, orderBy: { createdAt: 'asc' }, }) return templates.map((template) => { const retained = chooseRetainedVersion(template.versions) const removed = retained ? template.versions.filter((version) => version.id !== retained.id) : [] return { template, retained, removed, } }) } async function applyPlan(plan) { return prisma.$transaction(async (tx) => { const summary = { templates: 0, retainedVersions: 0, removedVersions: 0, reassignedPlanItems: 0, deduplicatedPlanItems: 0, } for (const item of plan) { const { template, retained, removed } = item if (!retained) continue summary.templates += 1 summary.retainedVersions += 1 const allVersionIds = template.versions.map((version) => version.id) const planItems = await tx.performanceSurveyPositionPlanItem.findMany({ where: { templateVersionId: { in: allVersionIds } }, orderBy: [{ positionPlanId: 'asc' }, { sortOrder: 'asc' }, { createdAt: 'asc' }], }) const itemsByPlan = new Map() for (const planItem of planItems) { const current = itemsByPlan.get(planItem.positionPlanId) || [] current.push(planItem) itemsByPlan.set(planItem.positionPlanId, current) } for (const groupedItems of itemsByPlan.values()) { const retainedItem = groupedItems.find((planItem) => planItem.templateVersionId === retained.id) const primary = retainedItem || groupedItems[0] const redundantIds = groupedItems .filter((planItem) => planItem.id !== primary.id) .map((planItem) => planItem.id) if (redundantIds.length > 0) { await tx.performanceSurveyPositionPlanItem.deleteMany({ where: { id: { in: redundantIds } }, }) summary.deduplicatedPlanItems += redundantIds.length } const nextSortOrder = Math.min(...groupedItems.map((planItem) => planItem.sortOrder)) const nextEnabled = groupedItems.some((planItem) => planItem.isEnabled) if ( primary.templateVersionId !== retained.id || primary.sortOrder !== nextSortOrder || primary.isEnabled !== nextEnabled ) { await tx.performanceSurveyPositionPlanItem.update({ where: { id: primary.id }, data: { templateVersionId: retained.id, sortOrder: nextSortOrder, isEnabled: nextEnabled, }, }) summary.reassignedPlanItems += 1 } } if (removed.length > 0) { await tx.performanceSurveyTemplateVersion.deleteMany({ where: { id: { in: removed.map((version) => version.id) } }, }) summary.removedVersions += removed.length } if (retained.version !== 1) { await tx.performanceSurveyTemplateVersion.update({ where: { id: retained.id }, data: { version: 1 }, }) } const definition = readDefinition(retained.definition) await tx.performanceSurveyTemplate.update({ where: { id: template.id }, data: { name: String(definition.name || template.name), description: String(definition.description || ''), surveyType: String(definition.surveyType || template.surveyType), weight: Number(definition.weight || template.weight || 1), questions: Array.isArray(definition.questions) ? definition.questions : [], scoreBands: Array.isArray(definition.scoreBands) ? definition.scoreBands : [], }, }) await tx.performanceSurveyConfigAuditLog.create({ data: { action: 'template.versions_collapsed', targetType: 'template', targetId: template.id, actorUserId, actorName, reason: '模板改为单配置模式', beforeSnapshot: { versionIds: template.versions.map((version) => version.id), versions: template.versions.map((version) => ({ id: version.id, version: version.version, status: version.status, })), }, afterSnapshot: { retainedVersionId: retained.id, status: retained.status, }, metadata: { removedVersionIds: removed.map((version) => version.id), }, }, }) } return summary }, { maxWait: 10_000, timeout: 60_000 }) } try { const plan = await buildPlan() const preview = plan.map(({ template, retained, removed }) => ({ templateId: template.id, templateCode: template.code, templateName: template.name, retained: retained ? { id: retained.id, version: retained.version, status: retained.status } : null, removed: removed.map((version) => ({ id: version.id, version: version.version, status: version.status, })), })) if (!applyChanges) { console.log(JSON.stringify({ mode: 'preview', templates: preview }, null, 2)) } else { const summary = await applyPlan(plan) console.log(JSON.stringify({ mode: 'applied', summary }, null, 2)) } } finally { await prisma.$disconnect() }