# GrantLeave

## Permission Scope

`grant`

## Overview

Administrator-initiated manual grant that writes a `LeaveGrant` record for a STATUTORY adjustment, a COMPENSATORY (compensatory) leave grant, or discretionary MANUAL days.

## Business Rules

- `grantType` records provenance only (STATUTORY adjustment, COMPENSATORY leave, or discretionary MANUAL); all three take a caller-supplied positive `grantedDays` (0.5 increments allowed) and an explicit `expirationDate`. The caller/policy decides the amount and expiration window — e.g. for STATUTORY the admin/UI resolves `expirationDate` from the effective `AccrualPlan`'s `expirationMonths`, while COMPENSATORY/MANUAL expiration follows company policy. This command validates but does not itself query `AccrualPlan`
- `grantType` COMPENSATORY (compensatory leave) is deliberately never auto-linked to holiday-work approval — always an explicit administrator action
- `grantedDays` must be a positive decimal in half-day increments; `remainingDays` is initialized equal to `grantedDays`
- Any grant created by this command always has `grantSource` MANUAL (HIRE and ANNIVERSARY are produced only by the batch commands)
- `expirationDate` must be strictly after `grantedDate`

## Process Flow

```mermaid
flowchart TD
    A[Admin submits workerId, leaveTypeKey, grantType, grantedDays, expirationDate] --> B[Validate positive half-day grantedDays]
    B --> C[Validate expirationDate strictly after grantedDate]
    C --> F[Insert LeaveGrant: grantSource = MANUAL, remainingDays = grantedDays]
    F --> G[Grant immediately counts toward getLeaveBalance]
```

## External Dependencies

- [leave-management::LeaveGrant](../model/LeaveGrant.md) - the ledger record created by this command
- [leave-management::LeaveType](../model/LeaveType.md) - `leaveTypeKey` must resolve to an existing leave type
- workforce Worker (cross-module) - `workerId` is the ledger owner

## Error Scenarios

- **LEAVE_TYPE_NOT_FOUND**: no LeaveType exists for the given id/key
- **WORKER_NOT_FOUND**: no workforce Worker exists for the given id
- **INVALID_GRANTED_DAYS**: `grantedDays` is non-positive or not a valid half-day increment
- **INVALID_EXPIRATION_DATE**: `expirationDate` is not strictly after `grantedDate`

## Test Cases

- a manually granted STATUTORY adjustment allows arbitrary positive days including 0.5 increments
- a COMPENSATORY grant accepts a caller-supplied day amount and explicit expiration
- a MANUAL grant accepts a discretionary day amount and an explicit expiration
- compensatory leave is never auto-linked to holiday-work approval; `grantLeave` requires an explicit administrator action
- granting against a leaveTypeKey with no matching LeaveType is rejected
- a non-positive or non-half-day grantedDays amount is rejected
- a grant with a missing or non-strictly-after expirationDate is rejected
- granting to a non-existent worker is rejected (WORKER_NOT_FOUND)

