# leave-request-approval

## Overview

Leave Request & Approval is the worker-facing leave workflow (leave request to approval): file a request and route it to a leave approver for a decision. It is an **approval wrapper** (ADR-003): each pending decision is mirrored by a bundled `approval` module request (one step, leave-approver role, quorum ANY), and cross-module failures are re-wrapped as this module's errors (NOT_ASSIGNEE / SELF_APPROVAL / APPROVAL_STEP_FAILED).

The redesign unifies the legacy `TimeOffRequest` and `TimeOffCancelRequest` into a **single** `LeaveRequest` lifecycle (ADR-011). A still-PENDING request is withdrawn unilaterally (no approval); an already-APPROVED leave is cancelled through a second approval leg that passes through an intermediate `CANCEL_PENDING` state. Balance-backed leave reserves its days from the `LeaveGrant` ledger the moment the request is filed — not at approval — closing the legacy gap where overlapping requests could together overdraw a balance that looked sufficient at filing; non-deducting leave (SPECIAL, MENSTRUAL) makes no ledger reservation. (The MENSTRUAL gender gate and SPECIAL sub-type checks were dropped in issue #39 along with the LeaveType fields that backed them; they can return when that eligibility plumbing is actually built.)

## Business Purpose

- Give workers a paperless path to statutory, compensatory, special, and menstrual leave with the eligibility rules each category requires
- Reserve balance at filing so an approver is never asked to approve a request the ledger can no longer cover, and never double-books a balance across overlapping requests
- Require the same approval discipline to undo an approved leave as to grant it, since coverage plans may already depend on the approved absence
- Keep an auditable link between every request and the exact grant batches it drew from, restorable on any reversal (ADR-005)
- Map each approved leave to a work-rules TimeEntryCode (via `LeaveType.timeEntryCodeKey`) that downstream payroll/reporting consumers read to render or pay it, without leave-management writing into `time-tracking` (the dependency is one-directional and pull-based)

## Process Flow

Filing, ledger reservation, and the PENDING decision:

```mermaid
flowchart TD
    A[Worker files leaveTypeKey + assignmentId + targetDate + reason] --> B{Eligibility}
    B -- non-terminal request already exists for date --> R3[Reject: DUPLICATE]
    B -- sole eligible approver is requester --> R4[Reject: no valid approver]
    B -- OK --> C{requiresBalance?}
    C -- Yes --> D[Lock grants, allocate FIFO by expiration, record LeaveConsumption]
    C -- No --> E[Validate eligibility only]
    D --> F[Create LeaveRequest PENDING + mirror approval request]
    E --> F
    F --> G{Approver decides}
    G -- approveLeave --> H[LeaveRequest -> APPROVED; resolve mirroring approval; keep reserved days]
    G -- rejectLeave --> I[Restore reserved days to original grants]
    G -- withdrawLeave by requester --> I
```

Cancelling an already-APPROVED leave (the unified cancel sub-flow via CANCEL_PENDING):

```mermaid
flowchart TD
    A[Worker files requestLeaveCancel against an APPROVED leave] --> B[LeaveRequest -> CANCEL_PENDING + mirror approval request]
    B --> C{Approver decides}
    C -- approveLeaveCancel --> D[Restore reserved days to original grants]
    D --> E[LeaveRequest -> CANCELLED]
    C -- rejectLeaveCancel --> G[LeaveRequest -> APPROVED; ledger unchanged]
```

## Scenario Patterns

- **Full-day statutory leave**: PAID_LEAVE_FULL for one date reserves 1.0 day FIFO from statutory grants; on approval the reservation is kept and the leave maps (via `timeEntryCodeKey`) for downstream payroll/reporting
- **Half-day statutory leave**: PAID_LEAVE_HALF reserves 0.5 day; the leave maps to a half-day leave category for downstream consumers
- **Split-batch reservation**: the soonest-expiring grant is insufficient, so the reservation spans two grants, each recorded as its own consumption line
- **Compensatory leave spend**: COMPENSATORY draws only from compensatory grants, never statutory
- **Menstrual leave**: MENSTRUAL is filed with no balance impact (the FEMALE gender gate was dropped in #39 and is not currently enforced)
- **Special leave**: SPECIAL deducts no balance (per-request sub-type validation was dropped in #39 along with LeaveType.specialSubtypeSet)
- **Insufficient balance**: a request exceeding the ledger balance is rejected outright at filing with no partial reservation
- **Duplicate request**: a second request for a date already holding a PENDING, APPROVED, or CANCEL_PENDING request is blocked
- **Withdraw a pending request**: the requester unilaterally withdraws a PENDING request; reserved days are restored, no approval record needed
- **Cancel an approved leave**: `requestLeaveCancel` moves an APPROVED leave to CANCEL_PENDING; on `approveLeaveCancel` the days are restored to their originating grants; on `rejectLeaveCancel` the leave stays APPROVED untouched
- **Approver deadlock avoided**: if the requester is the only active member of the approver role, filing is rejected rather than created unapprovable (ADR-003)
- **Self-approval blocked**: the resolver is never the requester (ADR-003 SELF_APPROVAL)

## Test Cases

- filing a balance-backed request reserves the correct days FIFO and records consumption lines
- filing splits a reservation across grants when the soonest-expiring grant is insufficient alone
- filing a menstrual or special leave performs no ledger reservation
- filing a menstrual leave for a non-FEMALE Worker is rejected
- filing a special leave without a sub-type in the type's set is rejected
- filing fails when requested days exceed the available balance
- filing fails when a non-terminal request already exists for the same worker and date
- filing fails when the sole eligible approver is the requester
- approving a request transitions it to APPROVED and keeps the reserved consumption rows
- rejecting or withdrawing a request restores every reserved day to its original grant
- cancelling an approved leave routes through CANCEL_PENDING; approval restores the reserved days, rejection leaves the APPROVED leave and ledger unchanged
- a rejected or cancelled request does not block a later request for the same date

## Reference Links

- Data-model design research (approval wrapper, request lifecycle, whose = Assignment): https://github.com/tailor-sandbox/Omakase-ERP-attendance/issues/7
- Enterprise gap analysis (legacy split request/cancel entities, balance-at-approval gap): https://github.com/tailor-sandbox/Omakase-ERP-attendance/issues/9
- [KING OF TIME — Leave request and leave cancellation request](https://www.kingtime.jp/) — request and approval-gated cancellation flow benchmarked per ADR-009
- [Jobcan Attendance Management — Leave request](https://jobcan.ne.jp/) — leave-type catalog and request/approval flow benchmarked per ADR-009
