import { writeFileSync } from 'node:fs'; import { and, eq, isNull } from 'drizzle-orm'; import { crmRoleProfiles, crmUserRoleAssignments, memberships } from '@/db/schema'; import { auditAction } from '@/features/foundation/audit-log/service'; import { db } from '@/lib/db'; type MigrationReport = { mode: 'dry-run' | 'execute'; startedAt: string; completedAt?: string; migratedUsers: number; skippedUsers: number; unknownRoles: Array<{ membershipId: string; organizationId: string; userId: string; businessRole: string }>; duplicateAssignments: Array<{ membershipId: string; assignmentId: string; organizationId: string; userId: string }>; actions: Array>; }; function getArg(name: string) { const match = process.argv.find((arg) => arg === name || arg.startsWith(`${name}=`)); if (!match) { return null; } if (match === name) { return 'true'; } return match.slice(name.length + 1); } async function main() { const execute = getArg('--execute') === 'true'; const reportPath = getArg('--report'); const snapshotPath = getArg('--snapshot'); const report: MigrationReport = { mode: execute ? 'execute' : 'dry-run', startedAt: new Date().toISOString(), migratedUsers: 0, skippedUsers: 0, unknownRoles: [], duplicateAssignments: [], actions: [] }; const membershipRows = await db.query.memberships.findMany(); if (snapshotPath) { writeFileSync(snapshotPath, JSON.stringify(membershipRows, null, 2)); } for (const membership of membershipRows) { const [roleProfile] = await db .select({ id: crmRoleProfiles.id }) .from(crmRoleProfiles) .where( and( eq(crmRoleProfiles.organizationId, membership.organizationId), eq(crmRoleProfiles.code, membership.businessRole), isNull(crmRoleProfiles.deletedAt) ) ) .limit(1); if (!roleProfile) { report.skippedUsers += 1; report.unknownRoles.push({ membershipId: membership.id, organizationId: membership.organizationId, userId: membership.userId, businessRole: membership.businessRole }); continue; } const existing = await db.query.crmUserRoleAssignments.findFirst({ where: and( eq(crmUserRoleAssignments.organizationId, membership.organizationId), eq(crmUserRoleAssignments.userId, membership.userId), eq(crmUserRoleAssignments.roleProfileId, roleProfile.id) ) }); if (existing && !existing.deletedAt) { report.skippedUsers += 1; report.duplicateAssignments.push({ membershipId: membership.id, assignmentId: existing.id, organizationId: membership.organizationId, userId: membership.userId }); continue; } const actionPayload = { membershipId: membership.id, organizationId: membership.organizationId, userId: membership.userId, roleProfileId: roleProfile.id, businessRole: membership.businessRole, branchScopeIds: membership.branchScopeIds ?? [], productTypeScopeIds: membership.productTypeScopeIds ?? [] }; report.actions.push(actionPayload); report.migratedUsers += 1; if (!execute) { continue; } const assignmentId = existing?.id ?? crypto.randomUUID(); if (existing) { await db .update(crmUserRoleAssignments) .set({ branchScopeMode: (membership.branchScopeIds?.length ?? 0) > 0 ? 'selected' : 'all', branchScopeIds: membership.branchScopeIds ?? [], productTypeScopeMode: (membership.productTypeScopeIds?.length ?? 0) > 0 ? 'selected' : 'all', productTypeScopeIds: membership.productTypeScopeIds ?? [], isPrimary: true, isActive: true, assignedBy: 'system:migration', assignedAt: new Date(), updatedAt: new Date(), deletedAt: null }) .where(eq(crmUserRoleAssignments.id, existing.id)); } else { await db.insert(crmUserRoleAssignments).values({ id: assignmentId, organizationId: membership.organizationId, userId: membership.userId, roleProfileId: roleProfile.id, branchScopeMode: (membership.branchScopeIds?.length ?? 0) > 0 ? 'selected' : 'all', branchScopeIds: membership.branchScopeIds ?? [], productTypeScopeMode: (membership.productTypeScopeIds?.length ?? 0) > 0 ? 'selected' : 'all', productTypeScopeIds: membership.productTypeScopeIds ?? [], isPrimary: true, isActive: true, assignedBy: 'system:migration' }); } await auditAction({ organizationId: membership.organizationId, userId: 'system:migration', entityType: 'crm_user_role_assignment', entityId: assignmentId, action: 'migrate', afterData: actionPayload }); } report.completedAt = new Date().toISOString(); if (reportPath) { writeFileSync(reportPath, JSON.stringify(report, null, 2)); } console.info(JSON.stringify(report, null, 2)); } main().catch((error) => { console.error(error); process.exit(1); });