|
|
|
@@ -18,6 +18,28 @@ const applyScheduleFields = (payload, body) => {
|
|
|
|
|
return payload;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const CLASS_LIST_FIELDS = 'name course professor students capacity tuitionFee hasDiscount discount freeSpots showOnFrontend startDate endDate days startTime endTime isActive adminNotes createdAt updatedAt';
|
|
|
|
|
|
|
|
|
|
const normalizePricingFields = (body = {}) => {
|
|
|
|
|
const tuitionFee = Math.max(0, Number(body.tuitionFee) || 0);
|
|
|
|
|
const hasDiscount = Boolean(body.hasDiscount);
|
|
|
|
|
const discount = hasDiscount
|
|
|
|
|
? Math.min(Math.max(0, Number(body.discount) || 0), tuitionFee)
|
|
|
|
|
: 0;
|
|
|
|
|
return { tuitionFee, hasDiscount, discount };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const enrichClassForDisplay = (cls) => {
|
|
|
|
|
const tuitionFee = cls.tuitionFee || 0;
|
|
|
|
|
const discount = cls.hasDiscount ? (cls.discount || 0) : 0;
|
|
|
|
|
const finalTuitionFee = Math.max(0, tuitionFee - discount);
|
|
|
|
|
let daysUntilStart = null;
|
|
|
|
|
if (cls.startDate) {
|
|
|
|
|
daysUntilStart = Math.ceil((new Date(cls.startDate).getTime() - Date.now()) / (1000 * 60 * 60 * 24));
|
|
|
|
|
}
|
|
|
|
|
return { ...cls, finalTuitionFee, daysUntilStart };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getAll = async (query) => {
|
|
|
|
|
const page = parseInt(query.page) || 1;
|
|
|
|
|
const limit = Math.min(parseInt(query.limit) || 20, 200);
|
|
|
|
@@ -34,14 +56,14 @@ const getAll = async (query) => {
|
|
|
|
|
|
|
|
|
|
const [items, total] = await Promise.all([
|
|
|
|
|
Class.find(filter)
|
|
|
|
|
.select('name course professor students capacity tuitionFee startDate endDate days startTime endTime isActive adminNotes createdAt updatedAt')
|
|
|
|
|
.select(CLASS_LIST_FIELDS)
|
|
|
|
|
.populate({ path: 'course', select: 'title type price' })
|
|
|
|
|
.populate({ path: 'professor', select: 'name surname' })
|
|
|
|
|
.skip(skip).limit(limit).sort({ createdAt: -1 }).lean(),
|
|
|
|
|
Class.countDocuments(filter)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return { data: items, meta: calculateMeta(total, page, limit) };
|
|
|
|
|
return { data: items.map(enrichClassForDisplay), meta: calculateMeta(total, page, limit) };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getOne = async (id) => {
|
|
|
|
@@ -51,22 +73,32 @@ const getOne = async (id) => {
|
|
|
|
|
.populate({ path: 'students', select: 'name phoneNumber gender' })
|
|
|
|
|
.lean();
|
|
|
|
|
if (!cls) throw new AppError('CLASS_NOT_FOUND');
|
|
|
|
|
return cls;
|
|
|
|
|
return enrichClassForDisplay(cls);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const create = async (body) => {
|
|
|
|
|
const payload = applyScheduleFields({ ...body }, body);
|
|
|
|
|
const payload = applyScheduleFields({ ...body, ...normalizePricingFields(body) }, body);
|
|
|
|
|
if (body.freeSpots === '' || body.freeSpots === null || body.freeSpots === undefined) {
|
|
|
|
|
payload.freeSpots = null;
|
|
|
|
|
} else {
|
|
|
|
|
payload.freeSpots = Math.max(0, Number(body.freeSpots) || 0);
|
|
|
|
|
}
|
|
|
|
|
const cls = await Class.create(payload);
|
|
|
|
|
return getOne(cls._id);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const update = async (id, body) => {
|
|
|
|
|
const payload = applyScheduleFields({ ...body }, body);
|
|
|
|
|
const payload = applyScheduleFields({ ...body, ...normalizePricingFields(body) }, body);
|
|
|
|
|
if (body.freeSpots === '' || body.freeSpots === null) {
|
|
|
|
|
payload.freeSpots = null;
|
|
|
|
|
} else if (body.freeSpots !== undefined) {
|
|
|
|
|
payload.freeSpots = Math.max(0, Number(body.freeSpots) || 0);
|
|
|
|
|
}
|
|
|
|
|
const cls = await Class.findByIdAndUpdate(id, payload, { new: true, runValidators: true })
|
|
|
|
|
.populate({ path: 'course', select: 'title' })
|
|
|
|
|
.lean();
|
|
|
|
|
if (!cls) throw new AppError('CLASS_NOT_FOUND');
|
|
|
|
|
return cls;
|
|
|
|
|
return enrichClassForDisplay(cls);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const remove = async (id) => {
|
|
|
|
@@ -144,7 +176,30 @@ const getMyClasses = async (userId, query = {}) => {
|
|
|
|
|
Class.countDocuments(filter)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return { data: items, meta: calculateMeta(total, page, limit) };
|
|
|
|
|
return { data: items.map(enrichClassForDisplay), meta: calculateMeta(total, page, limit) };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
module.exports = { getAll, getOne, create, update, remove, registerUsers, removeUser, getMyClasses };
|
|
|
|
|
const getPublicClasses = async (query = {}) => {
|
|
|
|
|
const page = parseInt(query.page) || 1;
|
|
|
|
|
const limit = Math.min(parseInt(query.limit) || 50, 200);
|
|
|
|
|
const skip = (page - 1) * limit;
|
|
|
|
|
|
|
|
|
|
const filter = {
|
|
|
|
|
isActive: { $ne: false },
|
|
|
|
|
showOnFrontend: { $ne: false }
|
|
|
|
|
};
|
|
|
|
|
if (query.courseId) filter.course = query.courseId;
|
|
|
|
|
|
|
|
|
|
const [items, total] = await Promise.all([
|
|
|
|
|
Class.find(filter)
|
|
|
|
|
.select('name course professor capacity tuitionFee hasDiscount discount freeSpots startDate endDate days startTime endTime')
|
|
|
|
|
.populate({ path: 'course', select: 'title type description' })
|
|
|
|
|
.populate({ path: 'professor', select: 'name surname' })
|
|
|
|
|
.skip(skip).limit(limit).sort({ startDate: 1, createdAt: -1 }).lean(),
|
|
|
|
|
Class.countDocuments(filter)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return { data: items.map(enrichClassForDisplay), meta: calculateMeta(total, page, limit) };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
module.exports = { getAll, getOne, create, update, remove, registerUsers, removeUser, getMyClasses, getPublicClasses };
|
|
|
|
|