// /components/professors/professorMigration.js 'use strict'; const mongoose = require('mongoose'); const Professor = require('./professorModel'); const User = require('../users/userModel'); const Role = require('../roles/roleModel'); const logger = require('../../utils/logger'); const { allocatePlaceholderNationalId } = require('../../utils/nationalId'); const { generateUsername, generateSimplePassword } = require('../../utils/credentials'); const bcrypt = require('bcryptjs'); const ensureProfessorRole = async () => { let role = await Role.findOne({ name: 'Professor' }); if (!role) { role = await Role.create({ name: 'Professor', description: 'استاد و مدرس دوره‌ها', permissions: ['professors:read', 'classes:read', 'sessions:read', 'attendances:read'], isSystem: true }); } return role; }; /** * Automatically migrate and link all legacy Professor records to the Users table. * Ensures every Professor document has a valid, populated `user` reference. */ const migrateAndLinkProfessorsToUsers = async () => { try { const professorRole = await ensureProfessorRole(); const professors = await Professor.find({}).setOptions({ _skipUserPopulate: true }); if (!professors.length) { logger.info('Professor migration: No professor records found.'); return { total: 0, migrated: 0, alreadyLinked: 0 }; } let migratedCount = 0; let alreadyLinkedCount = 0; for (const prof of professors) { const rawDoc = prof._doc || prof; let linkedUser = null; // 1. Check if professor already has a valid user reference if (prof.user && mongoose.Types.ObjectId.isValid(prof.user)) { linkedUser = await User.findById(prof.user); } if (linkedUser) { // Ensure user role is Professor if not an admin if (!linkedUser.role || (String(linkedUser.role) !== String(professorRole._id) && linkedUser.role?.name !== 'SuperAdmin' && linkedUser.role?.name !== 'Admin')) { linkedUser.role = professorRole._id; await linkedUser.save(); } alreadyLinkedCount++; continue; } // 2. Professor is not linked to a user. Find matching user by phone or national ID const rawPhone = String(rawDoc.phoneNumber || rawDoc.phone || '').trim(); const rawNationalId = String(rawDoc.nationalIdCode || rawDoc.nationalId || '').trim(); const rawName = String(rawDoc.name || '').trim(); const rawSurname = String(rawDoc.surname || '').trim(); const fullName = [rawName, rawSurname].filter(Boolean).join(' ').trim() || 'استاد بدون نام'; if (rawPhone) { linkedUser = await User.findOne({ phoneNumber: rawPhone }); } if (!linkedUser && rawNationalId) { linkedUser = await User.findOne({ nationalIdCode: rawNationalId }); } // 3. If matching user found, link and update role if (linkedUser) { if (!linkedUser.role || (String(linkedUser.role) !== String(professorRole._id) && linkedUser.role?.name !== 'SuperAdmin' && linkedUser.role?.name !== 'Admin')) { linkedUser.role = professorRole._id; } if (!linkedUser.name && fullName) { linkedUser.name = fullName; } if (!linkedUser.cardNumber && rawDoc.cardNumber) { linkedUser.cardNumber = String(rawDoc.cardNumber).trim(); } if (!linkedUser.shabaNumber && (rawDoc.shabaNumber || rawDoc.iban)) { linkedUser.shabaNumber = String(rawDoc.shabaNumber || rawDoc.iban).trim(); } await linkedUser.save(); prof.user = linkedUser._id; await prof.save(); migratedCount++; logger.info(`Professor migration: Linked professor "${fullName}" (${prof._id}) to existing user (${linkedUser._id}).`); continue; } // 4. If no matching user found, create a new User for this professor let nationalIdCode = rawNationalId; if (!nationalIdCode) { nationalIdCode = await allocatePlaceholderNationalId(rawPhone || fullName); } const generatedUsername = await generateUsername(fullName); const plainPassword = generateSimplePassword(); const passwordHash = await bcrypt.hash(plainPassword, 10); const newUser = await User.create({ name: fullName, nationalIdCode, phoneNumber: rawPhone || undefined, email: rawDoc.email ? String(rawDoc.email).trim().toLowerCase() : undefined, role: professorRole._id, username: generatedUsername, passwordHash, cardNumber: rawDoc.cardNumber ? String(rawDoc.cardNumber).trim() : undefined, shabaNumber: (rawDoc.shabaNumber || rawDoc.iban) ? String(rawDoc.shabaNumber || rawDoc.iban).trim() : undefined, isActive: prof.isActive !== false }); prof.user = newUser._id; await prof.save(); migratedCount++; logger.info(`Professor migration: Created new user (${newUser._id}) for legacy professor "${fullName}" (${prof._id}).`); } logger.info(`Professor migration complete: ${professors.length} total, ${migratedCount} newly linked/created, ${alreadyLinkedCount} already linked.`); return { total: professors.length, migrated: migratedCount, alreadyLinked: alreadyLinkedCount }; } catch (error) { logger.error('Error during migrateAndLinkProfessorsToUsers:', error); return { error: error.message }; } }; module.exports = { migrateAndLinkProfessorsToUsers, ensureProfessorRole };