- Add service timeline builder for appointments to manage available slots. - Create a hook to fetch resource booking services with effective durations. - Develop ResourceBookingSlotController to handle API requests for resource booking slots. - Implement ResourceBookingSlotService to calculate available time slots based on resource occupancy and service durations. - Add tests for resource appointment creation and booking slot functionality to ensure correct behavior and edge cases.
68 lines
2.5 KiB
TypeScript
68 lines
2.5 KiB
TypeScript
import { formatTime } from '../../lib/utils';
|
|
import { CANCELLED_STATUSES } from './turnStatus';
|
|
import type { Appointment } from '../../types';
|
|
import type { TimelineSlot } from './types';
|
|
|
|
/** یک بازهٔ کاری: شیفتِ منبع یا سشنِ برنامهٔ هفتگیِ پزشک. */
|
|
export interface WorkingWindow {
|
|
start: number;
|
|
end: number;
|
|
}
|
|
|
|
/**
|
|
* تایملاینِ نوبتدهی **سرویسی**: نوبتهای رزروشده + بازههای خالیِ بینشان.
|
|
*
|
|
* حالت سرویسی اسلاتِ ثابت ندارد — طول هر نوبت از سرویسهایش میآید — پس ردیفها از
|
|
* روی بازهٔ کاری و نوبتهای واقعی ساخته میشوند، نه از شبکهٔ اسلات.
|
|
*
|
|
* پزشک و منبع همین یک الگوریتم را دارند: بازهٔ کاری یکی از برنامهٔ هفتگی میآید و
|
|
* دیگری از تقویم منبع، ولی چیدنِ کارتها فرقی نمیکند و دو نسخهاش یعنی دو رفتار.
|
|
*/
|
|
export function buildServiceTimeline(
|
|
windows: WorkingWindow[],
|
|
appointments: Appointment[],
|
|
now: number = Math.floor(Date.now() / 1000),
|
|
): TimelineSlot[] {
|
|
const booked = appointments
|
|
.filter(a => !CANCELLED_STATUSES.has(a.status) && !a.is_reserve)
|
|
.map(a => ({
|
|
appointment: a,
|
|
start: Number(a.slot_start),
|
|
end: Number(a.slot_end),
|
|
}))
|
|
.sort((a, b) => a.start - b.start);
|
|
|
|
const out: TimelineSlot[] = [];
|
|
|
|
/** بازهٔ خالی؛ تکهٔ گذشتهاش بریده میشود چون قابل رزرو نیست. */
|
|
const pushFree = (start: number, end: number) => {
|
|
const from = start < now ? now : start;
|
|
if (end <= from) return;
|
|
out.push({
|
|
start: from, end,
|
|
start_time: formatTime(from), end_time: formatTime(end),
|
|
is_available: true, appointment: null, cancelled_appointment: null,
|
|
});
|
|
};
|
|
|
|
windows.forEach(({ start: winStart, end: winEnd }) => {
|
|
let cursor = winStart;
|
|
|
|
booked
|
|
.filter(b => b.start >= winStart && b.start < winEnd)
|
|
.forEach(({ appointment, start, end }) => {
|
|
if (start > cursor) pushFree(cursor, start);
|
|
out.push({
|
|
start, end,
|
|
start_time: formatTime(start), end_time: formatTime(end),
|
|
is_available: false, appointment, cancelled_appointment: null,
|
|
});
|
|
cursor = Math.max(cursor, end);
|
|
});
|
|
|
|
if (cursor < winEnd) pushFree(cursor, winEnd);
|
|
});
|
|
|
|
return out;
|
|
}
|