gongxiangp2/scripts/collapse-performance-survey-template-versions.mjs
2026-07-29 19:35:55 +08:00

187 lines
6.1 KiB
JavaScript

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()
}