Shift leave seesion date and remove substitute
This commit is contained in:
parent
0914b59e58
commit
2241886ca7
|
|
@ -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,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue