const { UsersAuth, UserProfile, TeacherDetail, School } = require('../models'); const bcrypt = require('bcrypt'); const { sequelize } = require('../config/database'); /** * Teacher Profile Service * Xử lý việc tạo user_profile, users_auth cho teacher */ class TeacherProfileService { /** * Tạo full user cho teacher mới (users_auth + user_profile + teacher_detail) * @param {Object} teacherData - Dữ liệu teacher * @returns {Promise} - Teacher đã tạo kèm profile */ async createTeacherWithProfile(teacherData) { const transaction = await sequelize.transaction(); try { const { // Teacher specific teacher_code, teacher_type, qualification, specialization, hire_date, status = 'active', skill_tags, certifications, // User auth (optional - tự generate nếu không có) username, email, password, // Profile (optional - có thể để trống) full_name, first_name, last_name, phone, date_of_birth, gender, address, school_id, city, district, avatar_url, } = teacherData; // Validate required fields if (!teacher_code || !teacher_type) { throw new Error('teacher_code and teacher_type are required'); } // 1. Tạo users_auth const generatedUsername = username || this._generateUsername(teacher_code, teacher_type); const generatedEmail = email || this._generateEmail(teacher_code, teacher_type); const generatedPassword = password || this._generateDefaultPassword(teacher_code); const salt = await bcrypt.genSalt(10); const password_hash = await bcrypt.hash(generatedPassword, salt); const userAuth = await UsersAuth.create({ username: generatedUsername, email: generatedEmail, password_hash: password_hash, salt: salt, is_active: true, qr_version: 1, qr_secret: this._generateQRSecret(), }, { transaction }); // 2. Tạo user_profile với thông tin cơ bản const profileData = { user_id: userAuth.id, full_name: full_name || this._generateFullNameFromCode(teacher_code), first_name: first_name || '', last_name: last_name || '', phone: phone || '', date_of_birth: date_of_birth || null, gender: gender || null, address: address || '', school_id: school_id || null, city: city || '', district: district || '', avatar_url: avatar_url || null, etc: { teacher_code: teacher_code, created_from: 'teacher_import', needs_profile_update: true, // Flag để user update sau } }; const userProfile = await UserProfile.create(profileData, { transaction }); // 3. Tạo teacher_detail const teacherDetail = await TeacherDetail.create({ user_id: userAuth.id, teacher_code: teacher_code, teacher_type: teacher_type, qualification: qualification || '', specialization: specialization || '', hire_date: hire_date || new Date(), status: status, skill_tags: skill_tags || [], certifications: certifications || [], training_hours: 0, last_training_date: null, training_score_avg: null, }, { transaction }); await transaction.commit(); // Return full data return { user_id: userAuth.id, username: generatedUsername, email: generatedEmail, temporary_password: password ? null : generatedPassword, // Only return if auto-generated profile: userProfile, teacher_detail: teacherDetail, }; } catch (error) { await transaction.rollback(); throw error; } } /** * Sync existing teachers: Tạo user_profile cho các teacher đã có sẵn * @param {string} teacherId - Optional: sync specific teacher or all * @returns {Promise} - Results */ async syncExistingTeachers(teacherId = null) { const results = { success: [], failed: [], skipped: [], }; try { // Get teachers without user_profile const whereClause = teacherId ? { id: teacherId } : {}; const teachers = await TeacherDetail.findAll({ where: whereClause, include: [{ model: UserProfile, as: 'profile', required: false, // LEFT JOIN to find teachers without profile }], }); for (const teacher of teachers) { try { // Skip if already has profile if (teacher.profile) { results.skipped.push({ teacher_code: teacher.teacher_code, reason: 'Already has profile', }); continue; } // Check if user_id exists in users_auth const userAuth = await UsersAuth.findByPk(teacher.user_id); if (!userAuth) { results.failed.push({ teacher_code: teacher.teacher_code, reason: 'user_id not found in users_auth', }); continue; } // Create user_profile const userProfile = await UserProfile.create({ user_id: teacher.user_id, full_name: this._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, synced_from: 'sync_script', needs_profile_update: true, synced_at: new Date().toISOString(), } }); results.success.push({ teacher_code: teacher.teacher_code, user_id: teacher.user_id, profile_id: userProfile.id, }); } catch (error) { results.failed.push({ teacher_code: teacher.teacher_code, error: error.message, }); } } return results; } catch (error) { throw new Error(`Sync failed: ${error.message}`); } } /** * Update teacher profile (cho phép user tự update) * @param {string} userId - User ID * @param {Object} profileData - Data to update * @returns {Promise} - Updated profile */ async updateTeacherProfile(userId, profileData) { const transaction = await sequelize.transaction(); try { const userProfile = await UserProfile.findOne({ where: { user_id: userId }, transaction, }); if (!userProfile) { throw new Error('User profile not found'); } // Update allowed fields const allowedFields = [ 'full_name', 'first_name', 'last_name', 'phone', 'date_of_birth', 'gender', 'address', 'city', 'district', 'avatar_url', ]; const updateData = {}; allowedFields.forEach(field => { if (profileData[field] !== undefined) { updateData[field] = profileData[field]; } }); // Update etc to remove needs_profile_update flag if (Object.keys(updateData).length > 0) { updateData.etc = { ...userProfile.etc, needs_profile_update: false, last_updated: new Date().toISOString(), }; } await userProfile.update(updateData, { transaction }); await transaction.commit(); return userProfile; } catch (error) { await transaction.rollback(); throw error; } } // ============ Helper Methods ============ /** * Generate username from teacher_code */ _generateUsername(teacher_code, teacher_type) { const prefix = teacher_type === 'foreign' ? 'ft' : 'gv'; return `${prefix}_${teacher_code.toLowerCase().replace(/[^a-z0-9]/g, '_')}`; } /** * Generate email from teacher_code */ _generateEmail(teacher_code, teacher_type) { const username = this._generateUsername(teacher_code, teacher_type); return `${username}@sena.edu.vn`; } /** * Generate default password (nên đổi sau lần đầu login) */ _generateDefaultPassword(teacher_code) { return `Sena${teacher_code}@2026`; } /** * Generate QR secret for attendance */ _generateQRSecret() { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let secret = ''; for (let i = 0; i < 32; i++) { secret += chars.charAt(Math.floor(Math.random() * chars.length)); } return secret; } /** * Generate full_name from teacher_code * VD: GV001 -> "Giáo viên GV001" * FT001 -> "Teacher FT001" */ _generateFullNameFromCode(teacher_code) { // Detect if foreign teacher const isForeign = teacher_code.toUpperCase().startsWith('FT') || teacher_code.toUpperCase().includes('FOREIGN'); if (isForeign) { return `Teacher ${teacher_code}`; } return `Giáo viên ${teacher_code}`; } } module.exports = new TeacherProfileService();