Shift leave seesion date and remove substitute

This commit is contained in:
Abhijit 2026-10-28 16:11:15 +05:30
parent 0914b59e58
commit 2241886ca7
3 changed files with 60 additions and 48 deletions

View File

@ -5,10 +5,8 @@ import { PrtLeave } from '../models/PrtLeave.js';
import { User } from '../models/User.js';
import { toLeaveDto } from '../utils/serialize.js';
import { toYmd, ymdToDate } from '../services/schedule.js';
import { Cohort } from '../models/Cohort.js';
import { findSubstitutes, openLeaveReassignment } from '../services/matching.js';
import { notifyLeaveDecision } from '../services/notify.js';
import { pushApprovedLeaveSessions } from '../services/programSessions.js';
import { shiftApprovedLeaveSessions } from '../services/programSessions.js';
const objectId = Joi.string().hex().length(24);
@ -136,24 +134,13 @@ export function leaveRoutes(server) {
leave.reviewedBy = creds.user._id;
leave.reviewedAt = new Date();
await leave.save();
let substitutes = [];
let pushResult = null;
let shiftResult = null;
if (leave.status === 'approved') {
const startYmd = toYmd(leave.startDate);
const endYmd = toYmd(leave.endDate);
const cohorts = await Cohort.find({
virtualPrt: leave.user._id,
status: { $in: ['confirmed', 'active'] },
});
substitutes = await findSubstitutes({ excludePrtId: leave.user._id });
pushResult = await pushApprovedLeaveSessions(leave.user._id, startYmd, endYmd, {
substitutePrtId: request.payload.substitutePrtId || undefined,
});
if (!request.payload.substitutePrtId) {
for (const cohort of cohorts) {
await openLeaveReassignment(cohort, leave.user._id);
}
}
shiftResult = await shiftApprovedLeaveSessions(
leave.user._id,
toYmd(leave.startDate),
toYmd(leave.endDate),
);
}
await notifyLeaveDecision({
prt: leave.user,
@ -164,9 +151,7 @@ export function leaveRoutes(server) {
await leave.populate('reviewedBy', 'firstName lastName email');
return {
leave: toLeaveDto(leave),
substitutes,
pushed: pushResult?.pushed?.length || 0,
covered: request.payload.substitutePrtId ? (pushResult?.affected?.length || 0) : 0,
shifted: shiftResult?.shifted?.length || 0,
};
},
});

View File

@ -354,30 +354,59 @@ export async function pushSessionsOnDate(ymd, reason, { prtId, notifyAdmins = fa
return results;
}
export async function pushApprovedLeaveSessions(prtId, startYmd, endYmd, { substitutePrtId } = {}) {
const sessions = await CohortSession.find({
date: { $gte: startYmd, $lte: endYmd },
status: 'scheduled',
// Slides scheduled sessions off approved leave. Earlier meetings stay, and the meeting count stays the same.
export async function shiftApprovedLeaveSessions(prtId, startYmd, endYmd) {
const settings = await getAppSettings();
if (settings.skipPrtLeave === false) return { shifted: [] };
const cohorts = await Cohort.find({
virtualPrt: prtId,
status: { $in: ['confirmed', 'active'] },
}).populate('virtualPrt', 'firstName lastName email google');
const shifted = [];
for (const cohort of cohorts) {
const sessions = await CohortSession.find({ cohort: cohort._id }).sort({ date: 1, index: 1 });
const hit = sessions.find((session) => (
session.status === 'scheduled' && session.date >= startYmd && session.date <= endYmd
));
if (!hit) continue;
const tail = sessions.filter((session) => session.status === 'scheduled' && session.date >= hit.date);
if (!tail.length) continue;
const weekdays = await scheduleWeekdays(cohort.scheduleId);
const blocked = new Set(
sessions.filter((session) => session.status === 'completed').map((session) => session.date),
);
const dates = await collectSessionDates(hit.date, cohort, tail.length, weekdays, blocked);
if (!dates.length) continue;
const fromDate = tail[0].date;
const toDate = dates[0];
const count = Math.min(tail.length, dates.length);
let changed = false;
for (let i = count - 1; i >= 0; i -= 1) { // later dates move first so two rows never share a date
if (tail[i].date === dates[i]) continue;
tail[i].date = dates[i];
await tail[i].save();
changed = true;
}
if (!changed) continue;
const updated = await CohortSession.find({ cohort: cohort._id }).sort({ index: 1 });
await syncExistingMeetSeries(cohort, updated);
const admins = await adminUsers();
await notifySessionPushed({
prt: cohort.virtualPrt,
admins,
cohort,
fromDate,
toDate,
reason: 'Approved PRT leave',
});
const affected = [];
for (const session of sessions) {
const cohort = await Cohort.findById(session.cohort);
if (!cohort || !isLiveCohortStatus(cohort.status)) continue;
if (String(cohort.virtualPrt) !== String(prtId)) continue;
affected.push({ session, cohort });
const { notifyGroupSessionChange } = await import('./whatsapp.js');
await notifyGroupSessionChange(
cohort._id,
`The ${fromDate} session for ${cohort.name} was Approved PRT leave. The replacement date is ${toDate}.`,
);
shifted.push({ cohortId: String(cohort._id), fromDate, toDate });
}
if (substitutePrtId) {
for (const { session } of affected) {
session.coverPrt = substitutePrtId;
await session.save();
}
return { affected: affected.map(({ session }) => session), pushed: [] };
}
const pushed = [];
for (const { session } of affected) {
pushed.push(await pushSession(session, 'Approved PRT leave', { notifyAdmins: true }));
}
return { affected: affected.map(({ session }) => session), pushed };
return { shifted };
}
async function applyMeetToSessions(sessions, event, slot) {

View File

@ -76,9 +76,7 @@ export default function LeavesPage() {
{canPickPrt && (row.original.status || 'pending') === 'pending' ? (
<>
<Button size="sm" variant="success" onClick={async () => {
const substitutes = await api('/api/matching/substitutes');
const pick = window.prompt(`Approve leave. Optional substitute PRT id (leave blank to queue/push):\n${(substitutes.items || []).map((item) => `${item.firstName} ${item.lastName} = ${item.id}`).join('\n')}`);
await api(`/api/leaves/${row.original.id}/review`, { method: 'POST', body: { status: 'approved', substitutePrtId: pick || null } });
await api(`/api/leaves/${row.original.id}/review`, { method: 'POST', body: { status: 'approved' } });
await load();
}}>Approve</Button>
<Button size="sm" variant="outline-danger" onClick={async () => {