Service Library - scheduleManagement

This document provides a complete reference of the custom code library for the scheduleManagement service. It includes all library functions, edge functions with their REST endpoints, templates, and assets.

Library Functions

Library functions are reusable modules available to all business APIs and other custom code within the service via require("lib/<moduleName>").

detectShiftAssignmentConflicts.js

/*
* Checks if any of the assigned users for the requested shift:
* 1. Are already assigned to another overlapping shift on the same date/time.
* 2. Have an approved leave request that covers the shift date.
* If isUpdate is true, ignores conflict with self (when updating an existing shift).
* Returns: Array of conflict objects with type, id, reason, and details.
*/
const { getShiftListByQuery } = require('dbLayer');
const { convertUserQueryToSequelizeQuery } = require('common/queryBuilder');
const { fetchRemoteListByMQuery } = require('serviceCommon');

function timeOverlap(startA, endA, startB, endB) {
  return (startA < endB && endA > startB);
}

module.exports = async function detectShiftAssignmentConflicts(context, isUpdate) {
  const { shiftDate, startTime, endTime, assignedUserIds = [], assignedDepartmentIds = [], id } = context;
  if (!shiftDate || (!Array.isArray(assignedUserIds) && !Array.isArray(assignedDepartmentIds))) return [];

  const conflicts = [];

  // --- 1. Check overlapping shift assignments ---
  const orConditions = [];
  if (assignedUserIds.length) {
    orConditions.push({ assignedUserIds: { $overlap: assignedUserIds } });
  }
  if (assignedDepartmentIds.length) {
    orConditions.push({ assignedDepartmentIds: { $overlap: assignedDepartmentIds } });
  }
  if (orConditions.length > 0) {
    const mscriptQuery = {
      shiftDate,
      status: { $ne: 'cancelled' },
      $or: orConditions,
      ...(isUpdate && id ? { id: { $ne: id } } : {})
    };
    const sequelizeQuery = convertUserQueryToSequelizeQuery(mscriptQuery);
    const candidateShifts = await getShiftListByQuery(sequelizeQuery);
    for (const s of candidateShifts) {
      if (timeOverlap(startTime, endTime, s.startTime, s.endTime)) {
        for (const uid of assignedUserIds || []) {
          if ((s.assignedUserIds || []).includes(uid)) {
            conflicts.push({ type: 'user', id: uid, reason: 'shiftOverlap', conflictShiftId: s.id, conflictShiftStartTime: s.startTime, conflictShiftEndTime: s.endTime });
          }
        }
        for (const did of assignedDepartmentIds || []) {
          if ((s.assignedDepartmentIds || []).includes(did)) {
            conflicts.push({ type: 'department', id: did, reason: 'shiftOverlap', conflictShiftId: s.id, conflictShiftStartTime: s.startTime, conflictShiftEndTime: s.endTime });
          }
        }
      }
    }
  }

  // --- 2. Check approved leave requests for assigned users ---
  if (assignedUserIds.length > 0 && shiftDate) {
    try {
      // Normalize shiftDate to a comparable date string (YYYY-MM-DD)
      const shiftDateObj = new Date(shiftDate);
      const shiftDateStr = shiftDateObj.toISOString().split('T')[0];

      // Query leaveRequest objects from the leaveManagement service via Elasticsearch.
      // We look for approved leaves where startDate <= shiftDate AND endDate >= shiftDate
      // for any of the assigned users.
      const leaveQuery = {
        status: 'approved',
        userId: { $in: assignedUserIds },
        startDate: { $lte: shiftDateStr + 'T23:59:59.999Z' },
        endDate: { $gte: shiftDateStr + 'T00:00:00.000Z' }
      };

      const approvedLeaves = await fetchRemoteListByMQuery('leaveRequest', leaveQuery, 0, 500);

      for (const leave of approvedLeaves) {
        conflicts.push({
          type: 'user',
          id: leave.userId,
          reason: 'approvedLeave',
          leaveRequestId: leave.id,
          leaveType: leave.leaveType,
          leaveStartDate: leave.startDate,
          leaveEndDate: leave.endDate
        });
      }
    } catch (err) {
      console.error('Error checking approved leaves for shift conflict detection:', err.message);
      // Do not block shift creation if leave service is unavailable; log and continue.
    }
  }

  return conflicts;
};

This document was generated from the service library configuration and should be kept in sync with design changes.