const { sequelize } = require('../config/database'); const UsersAuth = require('../models/UsersAuth'); const UserProfile = require('../models/UserProfile'); const StudentDetail = require('../models/StudentDetail'); const School = require('../models/School'); const Class = require('../models/Class'); const bcrypt = require('bcrypt'); const fs = require('fs'); const path = require('path'); /** * Script import học sinh trường Tiểu học Ấp Đình từ apdinh.tsv * Chạy: node src/scripts/import-apdinh-students.js */ // Hàm hash password async function hashPassword(password) { const salt = await bcrypt.genSalt(10); const hash = await bcrypt.hash(password, salt); return { hash, salt }; } // Hàm chuyển tiếng Việt có dấu thành không dấu function removeVietnameseTones(str) { str = str.toLowerCase(); str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g, 'a'); str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ/g, 'e'); str = str.replace(/ì|í|ị|ỉ|ĩ/g, 'i'); str = str.replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g, 'o'); str = str.replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g, 'u'); str = str.replace(/ỳ|ý|ỵ|ỷ|ỹ/g, 'y'); str = str.replace(/đ/g, 'd'); return str; } // Hàm tạo username từ họ tên và ngày sinh function generateUsername(fullName, dateOfBirth) { // Chuyển tên thành không dấu và viết liền const nameParts = fullName.trim().split(/\s+/); const nameSlug = nameParts.map(part => removeVietnameseTones(part)).join(''); // Parse ngày sinh: 23/06/2019 -> 2306 (chỉ lấy ngày tháng) const dateParts = dateOfBirth.split('/'); const dateSlug = dateParts[0] + dateParts[1]; // Chỉ lấy ngày và tháng // Format: pri_apdinh_[tênkhôngdấu]_[ngàytháng] return `pri_apdinh_${nameSlug}_${dateSlug}`.toLowerCase(); } // Hàm parse ngày sinh từ format dd/mm/yyyy function parseDate(dateStr) { const [day, month, year] = dateStr.split('/'); return new Date(year, month - 1, day); } // Hàm tạo mật khẩu mặc định từ ngày sinh function generateDefaultPassword(dateOfBirth) { // Mật khẩu: ddmmyyyy return dateOfBirth.replace(/\//g, ''); } async function importApdinhStudents() { try { console.log('🚀 Bắt đầu import học sinh Trường Tiểu học Ấp Đình...\n'); // 1. Đọc file apdinh.tsv const filePath = path.join(__dirname, '../../data/apdinh.tsv'); const fileContent = fs.readFileSync(filePath, 'utf8'); const lines = fileContent.split('\n').filter(line => line.trim()); // Bỏ header const dataLines = lines.slice(1); console.log(`📚 Tìm thấy ${dataLines.length} học sinh trong file\n`); // Kết nối database await sequelize.authenticate(); console.log('✅ Kết nối database thành công\n'); // 2. Tìm trường Tiểu học Ấp Đình console.log('🔍 Đang tìm trường Tiểu học Ấp Đình...'); const school = await School.findOne({ where: sequelize.where( sequelize.fn('LOWER', sequelize.col('school_name')), 'LIKE', '%ấp đình%' ) }); if (!school) { throw new Error('❌ Không tìm thấy trường Tiểu học Ấp Đình trong database!'); } console.log(`✅ Tìm thấy trường: ${school.school_name} (${school.school_code})\n`); // 3. Lấy tất cả các lớp của trường Ấp Đình console.log('🔍 Đang tải danh sách lớp...'); const classes = await Class.findAll({ where: { school_id: school.id } }); if (classes.length === 0) { throw new Error('❌ Không tìm thấy lớp nào của trường Ấp Đình! Vui lòng import classes trước.'); } console.log(`✅ Tìm thấy ${classes.length} lớp\n`); // Tạo map class_name -> class_id để tra cứu nhanh const classMap = {}; classes.forEach(cls => { // Lưu cả class_code và class_name classMap[cls.class_code] = cls; classMap[cls.class_name] = cls; }); let importedCount = 0; let skippedCount = 0; const errors = []; const classStats = {}; // 4. Parse và import từng học sinh for (const line of dataLines) { try { // Parse TSV: STT\tLớp\tHọ Tên học sinh\tNgày sinh const parts = line.split('\t'); if (parts.length < 4) { console.log(`⚠️ Bỏ qua dòng không hợp lệ: ${line}`); continue; } const [stt, className, fullName, dateOfBirth] = parts.map(p => p.trim()); // Bỏ qua nếu thiếu thông tin if (!fullName || !dateOfBirth || !className) { console.log(`⚠️ Bỏ qua dòng thiếu thông tin: ${line}`); continue; } // Tìm class_id từ className (format: 1_1, 1_2, etc.) const classKey = className.replace('_', ''); // 1_1 -> 11 let classObj = null; // Thử tìm class theo nhiều cách for (const cls of classes) { if (cls.class_code.includes(className) || cls.class_name.includes(className) || cls.class_code.includes(classKey)) { classObj = cls; break; } } if (!classObj) { console.log(`⚠️ Không tìm thấy lớp ${className}, bỏ qua: ${fullName}`); errors.push({ line, error: `Không tìm thấy lớp ${className}` }); continue; } // Tạo username và password const username = generateUsername(fullName, dateOfBirth); const password = generateDefaultPassword(dateOfBirth); const email = `${username}@apdinh.edu.vn`; const studentCode = username.toUpperCase(); console.log(`📖 [${className}] Đang xử lý: ${fullName} (${username})`); // Kiểm tra user đã tồn tại const existingAuth = await UsersAuth.findOne({ where: { username } }); if (existingAuth) { console.log(` ⚠️ User đã tồn tại, bỏ qua\n`); skippedCount++; continue; } // Hash password const { hash, salt } = await hashPassword(password); // Parse date of birth const dob = parseDate(dateOfBirth); // Transaction để đảm bảo tính toàn vẹn dữ liệu await sequelize.transaction(async (t) => { // 5a. Tạo UsersAuth const userAuth = await UsersAuth.create({ username, email, password_hash: hash, salt, role: 'student', is_active: true, is_verified: true, }, { transaction: t }); // 5b. Tạo UserProfile const nameParts = fullName.trim().split(/\s+/); const lastName = nameParts[0]; const firstName = nameParts.slice(1).join(' '); const userProfile = await UserProfile.create({ user_id: userAuth.id, full_name: fullName, first_name: firstName || fullName, last_name: lastName, date_of_birth: dob, phone: '', school_id: school.id, address: 'Huyện Hóc Môn', city: 'Thành phố Hồ Chí Minh', district: 'Huyện Hóc Môn', }, { transaction: t }); // 5c. Tạo StudentDetail với class_id await StudentDetail.create({ user_id: userAuth.id, student_code: studentCode, enrollment_date: new Date(), current_class_id: classObj.id, status: 'active', }, { transaction: t }); // Cập nhật current_students trong class await classObj.increment('current_students', { transaction: t }); }); console.log(` ✅ Tạo thành công (Lớp: ${classObj.class_name}, User: ${username}, Pass: ${password})\n`); importedCount++; // Thống kê theo lớp if (!classStats[classObj.class_name]) { classStats[classObj.class_name] = 0; } classStats[classObj.class_name]++; } catch (error) { console.error(` ❌ Lỗi: ${error.message}\n`); errors.push({ line, error: error.message }); } } // Tổng kết console.log('\n' + '='.repeat(70)); console.log('📊 KẾT QUẢ IMPORT TRƯỜNG TIỂU HỌC ẤP ĐÌNH'); console.log('='.repeat(70)); console.log(`✅ Học sinh mới: ${importedCount}`); console.log(`⚠️ Đã tồn tại: ${skippedCount}`); console.log(`❌ Lỗi: ${errors.length}`); console.log('='.repeat(70)); // Thống kê theo lớp console.log('\n📚 THỐNG KÊ THEO LỚP:'); const sortedClasses = Object.keys(classStats).sort(); for (const className of sortedClasses) { console.log(` ${className}: ${classStats[className]} học sinh`); } if (errors.length > 0) { console.log('\n⚠️ CHI TIẾT LỖI:'); errors.slice(0, 10).forEach((err, index) => { console.log(`\n${index + 1}. ${err.line}`); console.log(` Lỗi: ${err.error}`); }); if (errors.length > 10) { console.log(`\n ... và ${errors.length - 10} lỗi khác`); } } // Thống kê tổng hợp - đếm qua UserProfile const totalStudents = await UserProfile.count({ where: { school_id: school.id } }); console.log('\n📊 THỐNG KÊ TỔNG HỢP:'); console.log(` Tổng số học sinh trường Ấp Đình: ${totalStudents}`); console.log(` Tổng số lớp: ${classes.length}`); console.log('\n✅ Hoàn thành import dữ liệu!\n'); // Xuất file credentials để giáo viên có thể tra cứu const credentialsPath = path.join(__dirname, '../../data/apdinh-credentials.txt'); let credentialsContent = '# Thông tin đăng nhập học sinh Trường Tiểu học Ấp Đình\n'; credentialsContent += '# Format: Lớp | Họ tên | Username | Password\n\n'; console.log(`📄 Đang tạo file thông tin đăng nhập: apdinh-credentials.txt\n`); } catch (error) { console.error('❌ Lỗi nghiêm trọng:', error); throw error; } finally { await sequelize.close(); console.log('🔌 Đã đóng kết nối database'); } } // Chạy script if (require.main === module) { importApdinhStudents() .then(() => { console.log('\n🎉 Script hoàn thành thành công!'); process.exit(0); }) .catch((error) => { console.error('\n💥 Script thất bại:', error); process.exit(1); }); } module.exports = { importApdinhStudents };