feat: auto generate placeholder nationalId for users and professors, auto calculate class end date

This commit is contained in:
2026-08-21 15:03:18 +03:30
parent cbcf3f55db
commit 30db6582b7
6 changed files with 157 additions and 21 deletions
+74 -17
View File
@@ -63,10 +63,6 @@ const findExistingUser = async ({ nationalIdCode, phoneNumber, name }) => {
const byId = await User.findOne({ nationalIdCode });
if (byId) return byId;
}
if (phoneNumber) {
const byPhone = await User.findOne({ phoneNumber });
if (byPhone) return byPhone;
}
const target = normalizePersonName(name);
if (target) {
const firstToken = target.split(' ')[0];
@@ -76,6 +72,14 @@ const findExistingUser = async ({ nationalIdCode, phoneNumber, name }) => {
const match = candidates.find((user) => namesMatch(user.name, target));
if (match) return match;
}
if (phoneNumber) {
const byPhone = await User.findOne({ phoneNumber });
if (byPhone) {
if (!nationalIdCode || !byPhone.nationalIdCode || byPhone.nationalIdCode === nationalIdCode) {
return byPhone;
}
}
}
return null;
};
@@ -108,7 +112,7 @@ const applyStudentProfile = (user, student) => {
};
const upsertStudent = async (student, userRole, stats, warnings) => {
const phoneNumber = normalizePhone(student.phoneNumber || student.phone || student.parentPhoneNumber);
let phoneNumber = normalizePhone(student.phoneNumber || student.phone || student.parentPhoneNumber);
let nationalIdCode = normalizeNationalId(student.nationalIdCode || student.nationalId);
if (!phoneNumber && !nationalIdCode) {
@@ -129,23 +133,22 @@ const upsertStudent = async (student, userRole, stats, warnings) => {
return user;
}
if (!phoneNumber) {
warnings.push({
reason: 'missing_phone',
student: { name: student.name, nationalIdCode }
});
stats.studentsSkipped += 1;
return null;
}
if (!nationalIdCode) {
nationalIdCode = await allocatePlaceholderNationalId(phoneNumber);
nationalIdCode = await allocatePlaceholderNationalId(phoneNumber || student.name);
warnings.push({
reason: 'placeholder_national_id',
student: { name: student.name, phoneNumber, nationalIdCode }
});
}
if (!phoneNumber) {
phoneNumber = await allocatePlaceholderPhone(nationalIdCode || student.name);
warnings.push({
reason: 'placeholder_phone',
student: { name: student.name, nationalIdCode, placeholderPhone: phoneNumber }
});
}
const conflictById = await User.findOne({ nationalIdCode });
if (conflictById) {
applyStudentProfile(conflictById, student);
@@ -154,6 +157,17 @@ const upsertStudent = async (student, userRole, stats, warnings) => {
return conflictById;
}
const conflictByPhone = await User.findOne({ phoneNumber });
if (conflictByPhone) {
const originalPhone = phoneNumber;
phoneNumber = await allocatePlaceholderPhone(nationalIdCode || student.name);
if (!student.parentPhoneNumber) student.parentPhoneNumber = originalPhone;
warnings.push({
reason: 'phone_conflict_placeholder_allocated',
student: { name: student.name, originalPhone, placeholderPhone: phoneNumber }
});
}
const username = await allocateUniqueUsername();
const plainPassword = generateSimplePassword();
const passwordHash = await bcrypt.hash(plainPassword, 10);
@@ -194,13 +208,56 @@ const enrollUserInClass = async (user, course, cls, stats) => {
}
};
const WEEKDAY_STR_TO_NUM = {
sunday: 0,
monday: 1,
tuesday: 2,
wednesday: 3,
thursday: 4,
friday: 5,
saturday: 6,
'یکشنبه': 0,
'یک شنبه': 0,
'دوشنبه': 1,
'دو شنبه': 1,
'سه شنبه': 2,
'سه‌شنبه': 2,
'سهشنبه': 2,
'چهارشنبه': 3,
'چهار شنبه': 3,
'پنجشنبه': 4,
'پنج شنبه': 4,
'جمعه': 5,
'شنبه': 6
};
const normalizeWeekdaysInput = (days) => {
if (!Array.isArray(days)) return [];
const res = [];
for (const d of days) {
if (typeof d === 'number' && d >= 0 && d <= 6) {
if (!res.includes(d)) res.push(d);
} else if (typeof d === 'string') {
const lower = d.trim().toLowerCase();
if (WEEKDAY_STR_TO_NUM[lower] != null) {
const num = WEEKDAY_STR_TO_NUM[lower];
if (!res.includes(num)) res.push(num);
} else if (!Number.isNaN(Number(d))) {
const num = Number(d);
if (num >= 0 && num <= 6 && !res.includes(num)) res.push(num);
}
}
}
return res;
};
const applyClassSchedule = (cls, classInput) => {
if (classInput.startDate && !cls.startDate) {
const startDate = parseImportDate(classInput.startDate);
if (startDate) cls.startDate = startDate;
}
if (Array.isArray(classInput.days) && classInput.days.length) {
cls.days = classInput.days;
cls.days = normalizeWeekdaysInput(classInput.days);
}
if (classInput.startTime) cls.startTime = String(classInput.startTime).trim();
if (classInput.endTime) cls.endTime = String(classInput.endTime).trim();
@@ -581,7 +638,7 @@ const importData = async (rawPayload) => {
course: course._id,
startDate: parseImportDate(classInput.startDate) || undefined,
tuitionFee: Number(classInput.tuitionFee) || Number(courseInput.price) || 0,
days: Array.isArray(classInput.days) ? classInput.days : [],
days: normalizeWeekdaysInput(classInput.days),
startTime: classInput.startTime || '',
endTime: classInput.endTime || '',
isActive: true