diff --git a/components/classes/classModel.js b/components/classes/classModel.js index fe4cd1a..eda3967 100644 --- a/components/classes/classModel.js +++ b/components/classes/classModel.js @@ -39,6 +39,10 @@ const classSchema = new mongoose.Schema({ isActive: { type: Boolean, default: true + }, + adminNotes: { + type: [String], + default: [] } }, { timestamps: true diff --git a/components/classes/classService.js b/components/classes/classService.js index 3995732..beb170f 100644 --- a/components/classes/classService.js +++ b/components/classes/classService.js @@ -19,6 +19,7 @@ const getAll = async (query) => { const [items, total] = await Promise.all([ Class.find(filter) + .select('name course professor students capacity tuitionFee startDate endDate isActive adminNotes createdAt updatedAt') .populate({ path: 'course', select: 'title type price' }) .populate({ path: 'professor', select: 'name surname' }) .skip(skip).limit(limit).sort({ createdAt: -1 }).lean(), diff --git a/components/sessions/sessionModel.js b/components/sessions/sessionModel.js index 67a33fb..af8819c 100644 --- a/components/sessions/sessionModel.js +++ b/components/sessions/sessionModel.js @@ -81,6 +81,10 @@ const sessionSchema = new mongoose.Schema({ reminderSentAt: { type: Date, default: null + }, + adminNotes: { + type: [String], + default: [] } }, { timestamps: true diff --git a/components/users/userModel.js b/components/users/userModel.js index 77689aa..5a7ff39 100644 --- a/components/users/userModel.js +++ b/components/users/userModel.js @@ -106,6 +106,10 @@ const userSchema = new mongoose.Schema({ isActive: { type: Boolean, default: true + }, + adminNotes: { + type: [String], + default: [] } }, { timestamps: true diff --git a/components/users/userService.js b/components/users/userService.js index 8f9050c..0e31bb7 100644 --- a/components/users/userService.js +++ b/components/users/userService.js @@ -3,6 +3,7 @@ const User = require('./userModel'); const Role = require('../roles/roleModel'); +const Class = require('../classes/classModel'); const bcrypt = require('bcryptjs'); const AppError = require('../../utils/AppError'); const { calculateMeta } = require('../../utils/pagination'); @@ -11,6 +12,29 @@ const { sendAccountCreatedSms } = require('../../utils/senders/smsMessages'); const logger = require('../../utils/logger'); const { mergeFullName, normalizeGender } = require('../../utils/userProfile'); +const normalizeAdminNotes = (value) => { + if (value == null) return undefined; + if (!Array.isArray(value)) return undefined; + return value.map((n) => String(n).trim()).filter(Boolean); +}; + +const withUserListExtras = async (users) => { + if (!users.length) return users; + const userIds = users.map((u) => u._id); + const counts = await Class.aggregate([ + { $match: { students: { $in: userIds } } }, + { $unwind: '$students' }, + { $match: { students: { $in: userIds } } }, + { $group: { _id: '$students', count: { $sum: 1 } } } + ]); + const countMap = Object.fromEntries(counts.map((c) => [String(c._id), c.count])); + return users.map((u) => ({ + ...u, + nationalId: u.nationalIdCode, + registeredClassesCount: countMap[String(u._id)] || 0 + })); +}; + const POPULATE_ROLE = { path: 'role', select: 'name permissions' }; const SAFE_FIELDS = '-passwordHash -refreshTokens'; const ALLOWED_MESSENGERS = ['Bale', 'WhatsApp', 'Telegram', 'SMS', 'Email']; @@ -54,7 +78,8 @@ const pickProfileFields = (body = {}) => { birthDate: body.birthDate || undefined, education: body.education, parentPhoneNumber: body.parentPhoneNumber, - preferredMessenger: normalizePreferredMessengers(body.preferredMessenger) + preferredMessenger: normalizePreferredMessengers(body.preferredMessenger), + adminNotes: normalizeAdminNotes(body.adminNotes) }; Object.keys(fields).forEach((key) => { @@ -87,7 +112,8 @@ const signUp = async (body) => { const getUserById = async (id) => { const user = await User.findById(id).select(SAFE_FIELDS).populate(POPULATE_ROLE).lean(); if (!user) throw new AppError('USER_NOT_FOUND'); - return user; + const [enriched] = await withUserListExtras([user]); + return enriched; }; const getAllUsers = async (query) => { @@ -111,7 +137,7 @@ const getAllUsers = async (query) => { User.countDocuments(filter) ]); - return { data: items, meta: calculateMeta(total, page, limit) }; + return { data: await withUserListExtras(items), meta: calculateMeta(total, page, limit) }; }; const searchUsers = async (query) => getAllUsers(query); @@ -204,6 +230,9 @@ const updateUser = async (id, body) => { if (preferredMessenger !== undefined) { update.preferredMessenger = normalizePreferredMessengers(preferredMessenger) || []; } + if (body.adminNotes !== undefined) { + update.adminNotes = normalizeAdminNotes(body.adminNotes) || []; + } if (typeof username === 'string' && username.trim()) { update.username = username.trim(); @@ -213,7 +242,7 @@ const updateUser = async (id, body) => { delete update.surname; Object.keys(update).forEach((key) => { - if (key === 'preferredMessenger') return; + if (key === 'preferredMessenger' || key === 'adminNotes') return; if (update[key] === '' || update[key] === null || update[key] === undefined) { delete update[key]; }