# ApproveLeaveCancel

## Permission Scope

`approval`

## Overview

Leave approver approves a leave cancellation, moving the `LeaveRequest` from CANCEL_PENDING to CANCELLED: reserved days are restored to their originating grants.

## Business Rules

- Only reachable from CANCEL_PENDING; the resolver is never the requester
- Restores all non-restored `LeaveConsumption` rows for this request, crediting each row's `days` back to its exact `leaveGrantId` and stamping `restoredAt`, in the same transaction as the state transition — this is the side effect this command has on the `LeaveGrant`/`LeaveConsumption` ledger
- There is nothing to revert in time-tracking: leave-management never wrote a leave day into the attendance record (one-directional boundary — see `approveLeave`)
- CANCELLED is terminal and does not block a later request for the same worker/date
- Stamps `resolvedBy` and `resolvedAt`; resolves the mirroring `approval`-module request as approved

## Process Flow

```mermaid
flowchart TD
    A[Approver approves a CANCEL_PENDING LeaveRequest] --> B{Resolver is the requester?}
    B -- Yes --> R1[Reject: SELF_APPROVAL]
    B -- No --> C{Request status is CANCEL_PENDING?}
    C -- No --> R2[Reject: INVALID_STATE_TRANSITION]
    C -- Yes --> D[Transition LeaveRequest to CANCELLED]
    D --> E[Restore all non-restored LeaveConsumption rows to their originating grants, stamp restoredAt]
    E --> G[Stamp resolvedBy, resolvedAt; resolve mirroring approval request as approved]
```

## External Dependencies

- [leave-management::LeaveRequest](../model/LeaveRequest.md) - CANCEL_PENDING -> CANCELLED 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 approving the cancellation
- approval module (cross-module, bundled) - resolves the mirroring cancellation approval request (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
- **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

- approving a cancellation restores the reserved days to their grants
- a cancelled request does not block a later request for the same date
- approving a cancellation on a request that is not CANCEL_PENDING is rejected
- a self-approval attempt on the cancellation decision is rejected
- approving a cancellation by someone who is not an eligible approver is rejected (NOT_ASSIGNEE)

> Note: the approval-module resolution is wired — approveLeaveCancel resolves the mirrored
> cancellation request through the injected approval seam (locates the active request by the
> LeaveRequest id, checks the resolver owns a pending assignee → NOT_ASSIGNEE, then approveApprovalStep
> with ADR-003 error mapping). There is nothing to revert in time-tracking: approveLeave never wrote
> a leave day into the attendance record (one-directional boundary — see approveLeave), so restoring
> the LeaveConsumption ledger is the only side effect of an approved cancellation.

