173 lines
5.1 KiB
TypeScript
173 lines
5.1 KiB
TypeScript
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<Record<string, unknown>>;
|
|
};
|
|
|
|
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);
|
|
});
|