Dentolize · Accounting Module Walkthrough
On this pageBusiness viewTechnical view

HR & Leave Management

Business view

A small HR module shipped alongside payroll, because leave and attendance directly affect what payroll owes an employee. It covers:

  • Leave types — the categories of leave a clinic recognizes (Annual, Sick, Unpaid, Permission, Mission, Other), each configurable as day-based (e.g. a full sick day) or hour-based (e.g. a two-hour personal errand).
  • Holidays — the clinic's holiday calendar, factored into leave-day and payroll calculations.
  • Leave requests — an employee requests time off; it's either auto-approved (if a manager is booking it on someone else's behalf) or goes to a manager for approval/rejection. The same person can never approve their own request.
  • My Leave — an employee's self-service view: their remaining balance and their own request history, with the ability to cancel a pending request.
  • Work schedules — per-employee working days and hours, which can follow the branch default, be offset from it, or be fully custom — this is what "worked hours" and "overtime" get measured against in payroll.

Technical view

packages/prisma/schema.prisma:3830-3844 (LeaveKind/LeaveStatus enums), LeaveType (:4066-4084), LeaveRequest (:4087-4116). LeaveKind (ANNUAL | SICK | UNPAID | OTHER | PERMISSION | MISSION), LeaveStatus (PENDING | APPROVED | REJECTED | CANCELLED). LeaveType.hourly marks a type (typically Permission/Mission) as measured in hours within a single day rather than whole days.

Lifecycle

packages/server/src/resolvers/mutations/actions/hr/hrMutations.js:

  • createLeaveRequest (:179) — a self-request lands PENDING; a manager (with APPROVE_LEAVE or DO_ALL) booking leave for someone else is auto-created APPROVED. Day-based requests compute their length schedule-aware (excluding off-days and holidays, halved for a single-day half-day request); hour-based requests require start/end times and must be single-day. Paid leave is capped by the requester's remaining balance regardless of who books it; unpaid leave is uncapped.
  • approveLeaveRequest/rejectLeaveRequest (:290-291) — only from PENDING; the reviewer cannot approve or reject their own request (segregation of duties, same pattern as journal approval).
  • cancelLeaveRequest (:294) — the requester, or anyone with APPROVE_LEAVE.

Balance calculation

computeLeaveBalances (packages/server/src/resolvers/queries/actions/hr/hrQueries.js:93) — entitlement comes from SalaryProfile.annualLeaveDays for annual leave (or LeaveType.defaultAnnualEntitlement for other kinds), plus carriedOverLeaveDays for annual leave specifically. If accrualEnabled, entitlement is prorated by elapsed accrual periods (monthly = 1/12 per month, weekly = 1/52 per week) rather than granted in full up front. Day-based and hour-based balances are tracked in separate, non-interchangeable buckets.

Screens

leave/LeaveTypesScreen.js, leave/HolidaysScreen.js (both gated manageHr), leave/LeaveApprovalsScreen.js (company-wide pending queue), leave/MyLeaveScreen.js (self, gated requestLeave for the "Request" button), leave/NewLeaveRequestScreen.js. Work schedules are configured from salaryHub/EmployeeSalaryScreen.js (gated manageHr for the schedule half of that screen specifically, separate from manageSalaryHub, which gates the compensation half of the same screen).

Known gap

Hourly leave requests cannot actually be submitted from the app. NewLeaveRequestScreen.js has no start-time/end-time fields, and the mobile GraphQL mutation doesn't declare those variables at all — but the server requires exactly those two fields for any leave type with hourly: true and throws otherwise. So a clinic can create an hourly leave type (Permission, Mission) through LeaveTypesScreen.js, but no employee can request that type of leave through the mobile app; the backend support is complete, the request form isn't. If a clinic needs hourly leave working today, this needs a fix before it can be used.