# ApproveLeave

## Permission Scope

`approval`

## Overview

Leave approver approves a PENDING `LeaveRequest`, moving it to APPROVED and resolving the mirroring approval-module request.

## Business Rules

- Only reachable from PENDING; the resolver is never the requester (self-approval blocked, ADR-003 SELF_APPROVAL)
- By design, approval does NOT write into `time-tracking`: time-tracking calculates actual worked time from punches and has no reverse dependency on leave-management (a leave day is simply the absence of worked blocks). `LeaveType.timeEntryCodeKey` is the mapping a downstream payroll/reporting consumer uses to render/pay the leave; leave-management never emits into the attendance record, keeping the dependency one-directional (leave-management → time-tracking, pull-based)
- Reserved `LeaveConsumption` rows are left unchanged (not restored) — approving leave keeps the consumption
- Stamps `resolvedBy` and `resolvedAt`; `approverComment` is optional on approval (required only on rejection)
- Resolves the mirroring `approval`-module request as approved (ADR-003)

## Process Flow

```mermaid
flowchart TD
    A[Approver decides on 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[Transition LeaveRequest to APPROVED]
    D --> E[Stamp resolvedBy, resolvedAt]
    E --> G[Resolve mirroring approval request as approved]
    G --> H[LeaveConsumption rows remain unchanged]
```

## External Dependencies

- [leave-management::LeaveRequest](../model/LeaveRequest.md) - PENDING -> APPROVED transition
- time-tracking (cross-module) - not written to; the leave maps via `LeaveType.timeEntryCodeKey` for downstream payroll/reporting consumers (one-directional, pull-based)
- approval module (cross-module, bundled) - resolves the mirroring 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 PENDING request transitions it to APPROVED and stamps resolvedBy/resolvedAt
- approving keeps the reserved LeaveConsumption rows unchanged (not restored)
- a self-approval attempt is rejected
- approving a request that is not PENDING is rejected
- approving a request that does not exist is rejected
- approving by someone who is not an eligible approver is rejected (NOT_ASSIGNEE)

> Note: the approval-module resolution is wired — approveLeave 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 approveApprovalStep with ADR-003 error mapping).
> approveLeave deliberately does NOT write into time-tracking (one-directional boundary — see the
> business rule above); there is no emission to test.

