268 lines
8.0 KiB
JavaScript
268 lines
8.0 KiB
JavaScript
/**
|
||
* Script: Dump và Sync All Teachers
|
||
* Mục đích: Kiểm tra và tạo user_profile cho TẤT CẢ teachers trong hệ thống
|
||
*
|
||
* Usage:
|
||
* node src/scripts/dump-and-sync-teachers.js
|
||
* node src/scripts/dump-and-sync-teachers.js --auto-fix
|
||
*/
|
||
|
||
require('dotenv').config();
|
||
const { sequelize, TeacherDetail, UserProfile, UsersAuth } = require('../models');
|
||
const teacherProfileService = require('../services/teacherProfileService');
|
||
|
||
async function dumpAllTeachers() {
|
||
console.log('📊 Dumping All Teachers Information...\n');
|
||
console.log('='.repeat(80));
|
||
|
||
try {
|
||
// Get all teachers (without join to avoid association issues)
|
||
const teachers = await TeacherDetail.findAll({
|
||
order: [['created_at', 'ASC']],
|
||
});
|
||
|
||
console.log(`\n📈 Total Teachers: ${teachers.length}\n`);
|
||
|
||
const withProfile = [];
|
||
const withoutProfile = [];
|
||
const invalidUserId = [];
|
||
|
||
for (const teacher of teachers) {
|
||
const teacherInfo = {
|
||
id: teacher.id,
|
||
user_id: teacher.user_id,
|
||
teacher_code: teacher.teacher_code,
|
||
teacher_type: teacher.teacher_type,
|
||
status: teacher.status,
|
||
created_at: teacher.created_at,
|
||
};
|
||
|
||
// Check if user_id exists in users_auth
|
||
const userAuth = await UsersAuth.findByPk(teacher.user_id, {
|
||
attributes: ['id', 'username', 'email', 'is_active'],
|
||
});
|
||
|
||
if (!userAuth) {
|
||
invalidUserId.push({
|
||
...teacherInfo,
|
||
issue: 'user_id not found in users_auth',
|
||
});
|
||
continue;
|
||
}
|
||
|
||
teacherInfo.username = userAuth.username;
|
||
teacherInfo.email = userAuth.email;
|
||
teacherInfo.is_active = userAuth.is_active;
|
||
|
||
// Check if profile exists
|
||
const userProfile = await UserProfile.findOne({
|
||
where: { user_id: teacher.user_id },
|
||
});
|
||
|
||
if (userProfile) {
|
||
teacherInfo.profile = {
|
||
id: userProfile.id,
|
||
full_name: userProfile.full_name,
|
||
phone: userProfile.phone,
|
||
needs_update: userProfile.etc?.needs_profile_update,
|
||
};
|
||
withProfile.push(teacherInfo);
|
||
} else {
|
||
withoutProfile.push(teacherInfo);
|
||
}
|
||
}
|
||
|
||
// Display results
|
||
console.log('✅ Teachers WITH Profile:', withProfile.length);
|
||
if (withProfile.length > 0) {
|
||
console.log('\nSample (first 5):');
|
||
withProfile.slice(0, 5).forEach((t, i) => {
|
||
console.log(` ${i + 1}. ${t.teacher_code} (${t.teacher_type}) - ${t.username}`);
|
||
console.log(` Profile: ${t.profile.full_name} | Phone: ${t.profile.phone || 'N/A'}`);
|
||
});
|
||
if (withProfile.length > 5) {
|
||
console.log(` ... and ${withProfile.length - 5} more\n`);
|
||
}
|
||
}
|
||
|
||
console.log('\n❌ Teachers WITHOUT Profile:', withoutProfile.length);
|
||
if (withoutProfile.length > 0) {
|
||
console.log('\nList:');
|
||
withoutProfile.forEach((t, i) => {
|
||
console.log(` ${i + 1}. ${t.teacher_code} (${t.teacher_type}) - ${t.username} - user_id: ${t.user_id}`);
|
||
});
|
||
}
|
||
|
||
console.log('\n⚠️ Teachers with Invalid user_id:', invalidUserId.length);
|
||
if (invalidUserId.length > 0) {
|
||
console.log('\nList:');
|
||
invalidUserId.forEach((t, i) => {
|
||
console.log(` ${i + 1}. ${t.teacher_code} - ${t.issue} - user_id: ${t.user_id}`);
|
||
});
|
||
}
|
||
|
||
console.log('\n' + '='.repeat(80));
|
||
|
||
return {
|
||
total: teachers.length,
|
||
withProfile: withProfile.length,
|
||
withoutProfile: withoutProfile.length,
|
||
invalidUserId: invalidUserId.length,
|
||
teachers: {
|
||
withProfile,
|
||
withoutProfile,
|
||
invalidUserId,
|
||
}
|
||
};
|
||
|
||
} catch (error) {
|
||
console.error('❌ Dump failed:', error.message);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
async function autoFixProfiles(teachersData) {
|
||
console.log('\n🔧 Starting Auto-Fix for Teachers without Profile...\n');
|
||
|
||
const { withoutProfile } = teachersData.teachers;
|
||
|
||
if (withoutProfile.length === 0) {
|
||
console.log('✅ No teachers need fixing. All have profiles!\n');
|
||
return { success: 0, failed: 0 };
|
||
}
|
||
|
||
console.log(`📋 Processing ${withoutProfile.length} teachers...\n`);
|
||
|
||
const results = {
|
||
success: [],
|
||
failed: [],
|
||
};
|
||
|
||
for (const teacher of withoutProfile) {
|
||
try {
|
||
console.log(`Processing: ${teacher.teacher_code} (${teacher.username})...`);
|
||
|
||
// Create profile using service
|
||
const userProfile = await UserProfile.create({
|
||
user_id: teacher.user_id,
|
||
full_name: teacherProfileService._generateFullNameFromCode(teacher.teacher_code),
|
||
first_name: '',
|
||
last_name: '',
|
||
phone: '',
|
||
date_of_birth: null,
|
||
gender: null,
|
||
address: '',
|
||
school_id: null,
|
||
city: '',
|
||
district: '',
|
||
avatar_url: null,
|
||
etc: {
|
||
teacher_code: teacher.teacher_code,
|
||
teacher_type: teacher.teacher_type,
|
||
username: teacher.username,
|
||
synced_from: 'dump-and-sync-script',
|
||
needs_profile_update: true,
|
||
synced_at: new Date().toISOString(),
|
||
}
|
||
});
|
||
|
||
results.success.push({
|
||
teacher_code: teacher.teacher_code,
|
||
username: teacher.username,
|
||
user_id: teacher.user_id,
|
||
profile_id: userProfile.id,
|
||
full_name: userProfile.full_name,
|
||
});
|
||
|
||
console.log(` ✅ Created profile: ${userProfile.full_name}\n`);
|
||
|
||
} catch (error) {
|
||
results.failed.push({
|
||
teacher_code: teacher.teacher_code,
|
||
username: teacher.username,
|
||
error: error.message,
|
||
});
|
||
|
||
console.log(` ❌ Failed: ${error.message}\n`);
|
||
}
|
||
}
|
||
|
||
// Summary
|
||
console.log('\n' + '='.repeat(80));
|
||
console.log('📊 AUTO-FIX SUMMARY');
|
||
console.log('='.repeat(80));
|
||
console.log(`✅ Success: ${results.success.length}`);
|
||
console.log(`❌ Failed: ${results.failed.length}`);
|
||
|
||
if (results.success.length > 0) {
|
||
console.log('\n✅ Successfully Created Profiles:');
|
||
results.success.forEach((item, i) => {
|
||
console.log(` ${i + 1}. ${item.teacher_code} → ${item.full_name}`);
|
||
console.log(` user_id: ${item.user_id} | profile_id: ${item.profile_id}`);
|
||
});
|
||
}
|
||
|
||
if (results.failed.length > 0) {
|
||
console.log('\n❌ Failed:');
|
||
results.failed.forEach((item, i) => {
|
||
console.log(` ${i + 1}. ${item.teacher_code} - ${item.error}`);
|
||
});
|
||
}
|
||
|
||
console.log('\n' + '='.repeat(80));
|
||
|
||
return {
|
||
success: results.success.length,
|
||
failed: results.failed.length,
|
||
details: results,
|
||
};
|
||
}
|
||
|
||
async function main() {
|
||
console.log('\n🚀 Teacher Profile Dump & Sync Tool\n');
|
||
|
||
try {
|
||
// Parse arguments
|
||
const args = process.argv.slice(2);
|
||
const autoFix = args.includes('--auto-fix');
|
||
|
||
// Connect to database
|
||
await sequelize.authenticate();
|
||
console.log('✅ Database connected\n');
|
||
|
||
// Step 1: Dump all teachers
|
||
const dumpResults = await dumpAllTeachers();
|
||
|
||
// Step 2: Ask for auto-fix if not specified
|
||
if (!autoFix && dumpResults.withoutProfile > 0) {
|
||
console.log('\n⚠️ Found teachers without profile!');
|
||
console.log('💡 Run with --auto-fix to create profiles automatically:');
|
||
console.log(' node src/scripts/dump-and-sync-teachers.js --auto-fix\n');
|
||
process.exit(0);
|
||
}
|
||
|
||
// Step 3: Auto-fix if requested
|
||
if (autoFix && dumpResults.withoutProfile > 0) {
|
||
const fixResults = await autoFixProfiles(dumpResults);
|
||
|
||
console.log('\n🎉 COMPLETED!');
|
||
console.log(` Total: ${dumpResults.total} teachers`);
|
||
console.log(` Fixed: ${fixResults.success} profiles`);
|
||
console.log(` Already had: ${dumpResults.withProfile} profiles`);
|
||
console.log(` Failed: ${fixResults.failed} profiles\n`);
|
||
} else if (autoFix) {
|
||
console.log('\n✅ All teachers already have profiles! Nothing to fix.\n');
|
||
}
|
||
|
||
process.exit(0);
|
||
|
||
} catch (error) {
|
||
console.error('\n❌ Error:', error.message);
|
||
console.error(error.stack);
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
// Run
|
||
main();
|