task-l.2
This commit is contained in:
172
scripts/migrate-membership-business-roles.ts
Normal file
172
scripts/migrate-membership-business-roles.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user