235 lines
8.1 KiB
JavaScript
235 lines
8.1 KiB
JavaScript
// /components/professors/professorService.js
|
|
|
|
const Professor = require('./professorModel');
|
|
const User = require('../users/userModel');
|
|
const Role = require('../roles/roleModel');
|
|
const AppError = require('../../utils/AppError');
|
|
const eventEmitter = require('../../events/eventEmitter');
|
|
const EVENT_NAMES = require('../../constants/eventNames');
|
|
const { parsePaginationAndSort, buildFilterQuery, calculateMeta } = require('../../utils/pagination');
|
|
const { allocatePlaceholderNationalId } = require('../../utils/nationalId');
|
|
|
|
const createProfessor = async (data) => {
|
|
let nationalIdCode = String(data.nationalIdCode || data.nationalId || '').trim();
|
|
const phoneNumber = String(data.phoneNumber || data.phone || '').trim();
|
|
|
|
if (!nationalIdCode) {
|
|
nationalIdCode = await allocatePlaceholderNationalId(phoneNumber);
|
|
}
|
|
|
|
const payload = {
|
|
...data,
|
|
name: String(data.name || '').trim(),
|
|
surname: String(data.surname || '').trim(),
|
|
nationalIdCode,
|
|
phoneNumber,
|
|
email: data.email ? String(data.email).trim().toLowerCase() : undefined,
|
|
cardNumber: data.cardNumber ? String(data.cardNumber).trim() : undefined,
|
|
shabaNumber: data.shabaNumber || data.iban ? String(data.shabaNumber || data.iban).trim() : undefined,
|
|
bio: data.bio ? String(data.bio).trim() : undefined
|
|
};
|
|
|
|
const existing = await Professor.findOne({
|
|
$or: [
|
|
{ nationalIdCode: payload.nationalIdCode },
|
|
{ phoneNumber: payload.phoneNumber },
|
|
...(payload.email ? [{ email: payload.email }] : [])
|
|
]
|
|
});
|
|
if (existing) {
|
|
throw new AppError('PROFESSOR_ALREADY_EXISTS');
|
|
}
|
|
|
|
const professor = await Professor.create(payload);
|
|
eventEmitter.emit(EVENT_NAMES.PROFESSOR_CREATED, { professorId: professor._id, name: `${professor.name} ${professor.surname}` });
|
|
return professor;
|
|
};
|
|
|
|
const createProfessorFromUser = async (userId, additionalData = {}) => {
|
|
if (!userId) {
|
|
throw new AppError('VALIDATION_FAILED', null, 'User ID is required');
|
|
}
|
|
|
|
const user = await User.findById(userId);
|
|
if (!user) {
|
|
throw new AppError('USER_NOT_FOUND');
|
|
}
|
|
|
|
const professorRole = await Role.findOne({ name: 'Professor' });
|
|
if (!professorRole) {
|
|
throw new AppError('DEFAULT_ROLE_NOT_FOUND', null, 'Professor role not found');
|
|
}
|
|
|
|
// Update user role to Professor if not already
|
|
if (String(user.role) !== String(professorRole._id)) {
|
|
user.role = professorRole._id;
|
|
await user.save();
|
|
}
|
|
|
|
// Determine name and surname
|
|
let firstName = String(additionalData.name || '').trim();
|
|
let lastName = String(additionalData.surname || '').trim();
|
|
|
|
if (!firstName && !lastName) {
|
|
const rawName = String(user.name || '').trim();
|
|
const parts = rawName.split(/\s+/);
|
|
if (parts.length > 1) {
|
|
firstName = parts[0];
|
|
lastName = parts.slice(1).join(' ');
|
|
} else {
|
|
firstName = rawName || 'استاد';
|
|
lastName = rawName || 'استاد';
|
|
}
|
|
} else if (!lastName && firstName) {
|
|
lastName = firstName;
|
|
} else if (!firstName && lastName) {
|
|
firstName = lastName;
|
|
}
|
|
|
|
let nationalIdCode = String(additionalData.nationalIdCode || additionalData.nationalId || user.nationalIdCode || '').trim();
|
|
const phoneNumber = String(additionalData.phoneNumber || additionalData.phone || user.phoneNumber || '').trim();
|
|
|
|
if (!nationalIdCode) {
|
|
nationalIdCode = await allocatePlaceholderNationalId(phoneNumber);
|
|
}
|
|
|
|
const email = additionalData.email ? String(additionalData.email).trim().toLowerCase() : (user.email ? String(user.email).trim().toLowerCase() : undefined);
|
|
const cardNumber = additionalData.cardNumber ? String(additionalData.cardNumber).trim() : (user.cardNumber ? String(user.cardNumber).trim() : undefined);
|
|
const shabaNumber = additionalData.shabaNumber || additionalData.iban ? String(additionalData.shabaNumber || additionalData.iban).trim() : (user.shabaNumber ? String(user.shabaNumber).trim() : undefined);
|
|
const bio = additionalData.bio ? String(additionalData.bio).trim() : undefined;
|
|
|
|
let expertise = [];
|
|
if (Array.isArray(additionalData.expertise)) {
|
|
expertise = additionalData.expertise.map(String).map(s => s.trim()).filter(Boolean);
|
|
} else if (typeof additionalData.expertise === 'string' && additionalData.expertise.trim()) {
|
|
expertise = additionalData.expertise.split(',').map(s => s.trim()).filter(Boolean);
|
|
}
|
|
|
|
// Check if a Professor record already exists for this nationalIdCode or phoneNumber
|
|
let professor = await Professor.findOne({
|
|
$or: [
|
|
{ nationalIdCode },
|
|
{ phoneNumber }
|
|
]
|
|
});
|
|
|
|
if (professor) {
|
|
professor.name = firstName;
|
|
professor.surname = lastName;
|
|
if (email) professor.email = email;
|
|
if (cardNumber) professor.cardNumber = cardNumber;
|
|
if (shabaNumber) professor.shabaNumber = shabaNumber;
|
|
if (bio) professor.bio = bio;
|
|
if (expertise.length > 0) professor.expertise = expertise;
|
|
professor.isActive = true;
|
|
await professor.save();
|
|
} else {
|
|
professor = await Professor.create({
|
|
name: firstName,
|
|
surname: lastName,
|
|
nationalIdCode,
|
|
phoneNumber,
|
|
email,
|
|
cardNumber,
|
|
shabaNumber,
|
|
bio,
|
|
expertise,
|
|
isActive: true
|
|
});
|
|
}
|
|
|
|
eventEmitter.emit(EVENT_NAMES.PROFESSOR_CREATED, {
|
|
professorId: professor._id,
|
|
name: `${professor.name} ${professor.surname}`
|
|
});
|
|
|
|
const updatedUser = await User.findById(user._id)
|
|
.select('-passwordHash -refreshTokens')
|
|
.populate({ path: 'role', select: 'name permissions' })
|
|
.lean();
|
|
|
|
return { professor, user: updatedUser };
|
|
};
|
|
|
|
const getProfessorById = async (id) => {
|
|
const professor = await Professor.findById(id).populate('courses', 'title type price');
|
|
if (!professor) {
|
|
throw new AppError('PROFESSOR_NOT_FOUND');
|
|
}
|
|
return professor;
|
|
};
|
|
|
|
const getAllProfessors = async (queryParams) => {
|
|
const { page, limit, skip, sort } = parsePaginationAndSort(queryParams);
|
|
const filter = buildFilterQuery(queryParams, ['name', 'surname', 'nationalIdCode', 'phoneNumber', 'email', 'expertise']);
|
|
|
|
const [professors, totalCount] = await Promise.all([
|
|
Professor.find(filter).populate('courses', 'title').sort(sort).skip(skip).limit(limit),
|
|
Professor.countDocuments(filter)
|
|
]);
|
|
|
|
const meta = calculateMeta(totalCount, page, limit);
|
|
return { data: professors, meta };
|
|
};
|
|
|
|
const updateProfessor = async (id, updateData) => {
|
|
const professor = await Professor.findById(id);
|
|
if (!professor) {
|
|
throw new AppError('PROFESSOR_NOT_FOUND');
|
|
}
|
|
|
|
const payload = { ...updateData };
|
|
if (payload.nationalIdCode !== undefined || payload.nationalId !== undefined) {
|
|
payload.nationalIdCode = String(payload.nationalIdCode || payload.nationalId || '').trim();
|
|
delete payload.nationalId;
|
|
}
|
|
if (payload.phoneNumber !== undefined || payload.phone !== undefined) {
|
|
payload.phoneNumber = String(payload.phoneNumber || payload.phone || '').trim();
|
|
delete payload.phone;
|
|
}
|
|
if (payload.name !== undefined) payload.name = String(payload.name).trim();
|
|
if (payload.surname !== undefined) payload.surname = String(payload.surname).trim();
|
|
if (payload.email !== undefined) {
|
|
payload.email = payload.email ? String(payload.email).trim().toLowerCase() : undefined;
|
|
}
|
|
if (payload.cardNumber !== undefined) {
|
|
payload.cardNumber = payload.cardNumber ? String(payload.cardNumber).trim() : undefined;
|
|
}
|
|
if (payload.shabaNumber !== undefined || payload.iban !== undefined) {
|
|
const val = payload.shabaNumber || payload.iban;
|
|
payload.shabaNumber = val ? String(val).trim() : undefined;
|
|
delete payload.iban;
|
|
}
|
|
if (payload.bio !== undefined) {
|
|
payload.bio = payload.bio ? String(payload.bio).trim() : undefined;
|
|
}
|
|
|
|
Object.assign(professor, payload);
|
|
await professor.save();
|
|
return professor;
|
|
};
|
|
|
|
const deleteProfessor = async (id) => {
|
|
const professor = await Professor.findById(id);
|
|
if (!professor) {
|
|
throw new AppError('PROFESSOR_NOT_FOUND');
|
|
}
|
|
|
|
await Professor.findByIdAndDelete(id);
|
|
return null;
|
|
};
|
|
|
|
const searchProfessors = async (queryParams) => {
|
|
return getAllProfessors(queryParams);
|
|
};
|
|
|
|
module.exports = {
|
|
createProfessor,
|
|
createProfessorFromUser,
|
|
getProfessorById,
|
|
getAllProfessors,
|
|
updateProfessor,
|
|
deleteProfessor,
|
|
searchProfessors
|
|
};
|