
# 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`

```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.*
