6.6 KiB
6.6 KiB
Customer Migration Script - Simplified Approach (Updated)
Date: 2025-01-14
Summary of Changes
The customer migration script has been updated with a simplified approach that eliminates username conflicts by simply updating the parent references of sub-accounts instead of trying to move or merge them.
Previous Approach (Removed)
The old approach attempted to:
- Move sub-accounts from source to destination customer
- Detect username conflicts between source and destination sub-accounts
- Offer a
--merge-conflictsflag to merge conflicting accounts - Mark old accounts as deleted and update all references
Problem: Since usernames are globally unique in the system, attempting to move accounts between customers created conflicts.
New Approach (Current)
The new simplified approach:
- Sub-accounts stay in place with their original usernames
- Only the
.parentfield is updated to point to the destination customer - No username conflicts because accounts aren't moving, just being re-parented
- Much simpler code with no merging logic needed
Key Changes
1. Conflict Detection Simplified
- Before: Checked for username conflicts across all sub-account types
- After: Only checks for circular references (migrating a customer to itself)
// Old: Complex username conflict checking
// New: Simple validation
async function detectConflicts(sourceCustomers, targetCustomer, migrationRecord) {
const conflicts = [];
// Only check for circular reference
for (const sourceCustomer of sourceCustomers) {
if (sourceCustomer._id.toString() === targetCustomer._id.toString()) {
conflicts.push({
type: 'circular_reference',
message: `Cannot migrate customer ${sourceCustomer.username} to itself`
});
}
}
return conflicts;
}
2. Sub-Account Migration Rewritten
- Before:
migrateSubAccounts()with conflict map, merging logic, reference updates - After:
updateSubAccountsParent()with simple parent field update
// Old: ~100 lines of merge/conflict handling
// New: ~40 lines of simple parent update
async function updateSubAccountsParent(sourceId, targetId, session, migrationRecord) {
const subAccounts = await models.User.find({
parent: sourceId,
kind: { $in: [UserTypes.CLIENT, UserTypes.PILOT, UserTypes.DEVICE,
UserTypes.OFFICER, UserTypes.INSPECTOR] }
}).session(session);
for (const account of subAccounts) {
// Simply update the parent reference
account.parent = targetId;
await account.save({ session });
// Track in migration record
migrationRecord.details.changes.push({
action: 'update_parent_reference',
kind: account.kind,
username: account.username,
accountId: account._id,
fromParent: sourceId,
toParent: targetId
});
}
}
3. Removed Options
- Removed:
--merge-conflictsflag (no longer needed) - Removed: All conflict merging logic
- Removed: Reference update functions for merged accounts
4. Documentation Updates
- Updated header comments to explain the parent-reference approach
- Simplified help text
- Removed merge-related warnings and examples
Migration Behavior
What Happens During Migration
-
Source Customer Account:
- If
--convert-to-adminflag: Converted to an admin (kind='2') under destination - Otherwise: Marked as deleted and inactive
- If
-
Sub-Accounts (Clients, Pilots, Vehicles, Officers, Inspectors):
- Parent field updated from sourceCustomerId to targetCustomerId
- Usernames remain unchanged (globally unique)
- All other fields unchanged
-
Jobs, Products, Crops:
byPuidfield updated from sourceCustomerId to targetCustomerId
-
Invoices (if
--update-invoicesflag):customerfield updated from sourceCustomerId to targetCustomerId
Example
Before migration:
Customer: trungh1@agnav.com (ID: 67ae...)
├─ Client: client1@example.com (parent: 67ae...)
├─ Pilot: pilot1@example.com (parent: 67ae...)
└─ Vehicle: vehicle1 (parent: 67ae...)
Customer: trungh@agnav.com (ID: 6786...)
└─ (existing sub-accounts)
After migration:
Customer: trungh1@agnav.com (ID: 67ae...) [DELETED]
Customer: trungh@agnav.com (ID: 6786...)
├─ Client: client1@example.com (parent: 6786...) ← Updated
├─ Pilot: pilot1@example.com (parent: 6786...) ← Updated
├─ Vehicle: vehicle1 (parent: 6786...) ← Updated
└─ (existing sub-accounts)
Testing Results
Successfully tested with:
# Preview mode
node scripts/migrateCustomerData.js \
--sources trungh1@agnav.com \
--destination trungh@agnav.com \
--preview
# Execution
node scripts/migrateCustomerData.js \
--sources trungh1@agnav.com \
--destination trungh@agnav.com
Results:
- ✅ No conflicts detected
- ✅ 2 clients migrated (parent updated)
- ✅ 1 pilot migrated (parent updated)
- ✅ 5 vehicles migrated (parent updated)
- ✅ 1 job migrated (byPuid updated)
- ✅ 1 product migrated (byPuid updated)
- ✅ 1 crop migrated (byPuid updated)
- ✅ Transaction completed successfully
- ✅ Migration history saved
Benefits of New Approach
- Eliminates Conflicts: No username conflicts since accounts stay in place
- Simpler Code: Removed ~200 lines of conflict/merge logic
- Safer: No account deletion or reference rewiring
- Clearer Intent: Sub-accounts are "re-parented", not "moved"
- Faster Execution: No conflict checking for thousands of sub-accounts
Files Modified
scripts/migrateCustomerData.js:- Simplified
detectConflicts()function - Replaced
migrateSubAccounts()withupdateSubAccountsParent() - Removed
updateAccountReferences()function - Removed
--merge-conflictsoption parsing - Updated documentation strings
- Added display name sanitization for corrupt data
- Simplified
Backward Compatibility
This is a breaking change if anyone was using the --merge-conflicts flag. However:
- The new approach is the correct approach for this data model
- Old approach would have caused data inconsistencies
- Migration history format remains compatible
Related Files
scripts/README_CUSTOMER_MIGRATION.md- Full documentation (needs minor update)scripts/MIGRATION_QUICK_REFERENCE.md- Quick reference (needs minor update)MIGRATION_SUMMARY.md- Implementation overview (needs minor update)
Next Steps
Consider updating the documentation files to reflect:
- The simplified conflict detection
- Removal of merge-conflicts flag
- Clarification that sub-accounts are "re-parented" not "moved"