# RejectLeave

## Permission Scope

`approval`

## Overview

Leave approver rejects a PENDING `LeaveRequest`, moving it to REJECTED and restoring any reserved ledger days to their originating grants.

## Business Rules

- Only reachable from PENDING; the resolver is never the requester
- Restores all non-restored `LeaveConsumption` rows for this request in the same transaction: each row's `days` are credited back to its exact `leaveGrantId` and `restoredAt` is stamped — this is the side effect this command has on the `LeaveGrant`/`LeaveConsumption` ledger
- REJECTED is terminal and does not block a later request for the same worker/date
- `approverComment` is required and non-empty on rejection
- Stamps `resolvedBy` and `resolvedAt`; resolves the mirroring `approval`-module request as rejected

## Process Flow

```mermaid
flowchart TD
    A[Approver rejects a PENDING LeaveRequest] --> B{Resolver is the requester?}
    B -- Yes --> R1[Reject: SELF_APPROVAL]
    B -- No --> C{Request status is PENDING?}
    C -- No --> R2[Reject: INVALID_STATE_TRANSITION]
    C -- Yes --> D{approverComment provided?}
    D -- No --> R3[Reject: MISSING_APPROVER_COMMENT]
    D -- Yes --> E[Transition LeaveRequest to REJECTED]
    E --> F[Restore all non-restored LeaveConsumption rows to their originating grants, stamp restoredAt]
    F --> G[Stamp resolvedBy, resolvedAt]
    G --> H[Resolve mirroring approval request as rejected]
```

## External Dependencies

- [leave-management::LeaveRequest](../model/LeaveRequest.md) - PENDING -> REJECTED transition
- [leave-management::LeaveGrant](../model/LeaveGrant.md) / [leave-management::LeaveConsumption](../model/LeaveConsumption.md) - days restored to the exact grants drawn from, as a side effect of rejection
- approval module (cross-module, bundled) - resolves the mirroring approval request as rejected (ADR-003)

## Error Scenarios

- **LEAVE_REQUEST_NOT_FOUND**: no LeaveRequest exists for the given id
- **INVALID_STATE_TRANSITION**: the LeaveRequest's current status does not permit this transition
- **SELF_APPROVAL**: the resolver is the requester
- **MISSING_APPROVER_COMMENT**: `approverComment` is empty
- **NOT_ASSIGNEE**: no eligible approver exists other than the requester, or the resolver is not an eligible approver
- **APPROVAL_STEP_FAILED**: the bundled approval-module step failed to resolve

## Test Cases

- rejecting a request restores every reserved day to its original grant
- a rejected request does not block a later request for the same date
- rejecting a request that is not PENDING is rejected
- rejecting without an approverComment is rejected
- a self-rejection attempt is rejected
- rejecting by someone who is not an eligible approver is rejected (NOT_ASSIGNEE)

> Note: the approval-module resolution is now wired — rejectLeave resolves the mirrored request
> through the injected approval seam (locates the active request by the LeaveRequest id, checks the
> resolver owns a pending assignee → NOT_ASSIGNEE, then sendBackApprovalStep with ADR-003 error
> mapping).

