# AccrualPlan

## Description

AccrualPlan is the **effective-dated grant rule** for a balance-backed leave type — it answers "by which method, how many days, granted when, expiring after how long, of what provenance, for whom". An `accrualMethod` discriminator names the method the grant batch dispatches on: today the only implemented method is `FRONT_LOAD_TENURE` (front-load the entitlement at the eligibility date and escalate by tenure tier on each anniversary), and a `grantType` (`STATUTORY` / `MANUAL`) is the provenance stamped on every grant this plan produces. Per ADR-025 the timing is expressed as configuration rather than hard-coded behaviour: an `eligibilityDelayMonths` sets how long after hire the first grant applies (0 = day one, 6 = statutory Labor Standards Act Article 39, 12 = China/Canada), a `baseGrantDays` front-loads the tier-0 amount, a `tenureTiers` table gives the total entitlement per tenure year (e.g. 11 at 1y … 20 at 6y+), an `annualCapDays` bounds the yearly total (data, not a code constant), and an `expirationMonths` sets the grant's lifetime. Per ADR-026 C the generic `grantCondition` supports `MIN_WORKED_DAYS`. The model can also be extended with top-level custom fields through `defineModule({ accrualPlan: { fields } })`; create/update commands persist those values and effective-dated updates inherit omitted custom fields. Jurisdiction-specific conditions belong in those custom fields and are evaluated through the optional application `grantEligibility` policy.

The plan can front-load beyond the Labor Standards Act Article 39 minimum — a day-one hire grant (`eligibilityDelayMonths=0`) instead of the statutory 6-month start — a policy carried over from the legacy system and confirmed lawful and reasonable against KING OF TIME and Jobcan Attendance defaults (ADR-005, ADR-009), *or* express the statutory 6-month waiting period by setting the delay. Because it is a *rule* and rules change (legal amendment, policy revision), AccrualPlan is **effective-dated** (ADR-013): a rule change closes the current generation and inserts a new one, so a grant made in the past is always explainable by the plan generation that was effective on its grant date, and a future rule change can be scheduled ahead.

This model is new to the enterprise redesign: the legacy system hard-coded these numbers inside the anniversary batch resolver (ADR-011). Externalizing them as versioned configuration is what lets rules be corrected and audited without code changes (the ADR-022 externalization principle).

**Scope**: `accrualMethod` is a discriminator seam, not a promise of multiple methods — `FRONT_LOAD_TENURE` (a lump grant at the eligibility date, re-granted on each hire anniversary with tenure-tier escalation) is the single method implemented today. Making it an explicit discriminator keeps the model honest that front-load-with-tenure-tiers is *one* method rather than the only conceivable one, and lets a future method (e.g. periodic/continuous accrual) be added with its own batch handler **without a schema migration**. The `CONTINUOUS` method, the calendar-year anchor, and carryover modes remain deferred behind the ADR-018 global GO gate — added together with their consumers, not pre-built (ADR-025 §A, YAGNI). `grantType` (`STATUTORY` / `MANUAL`) makes the grant provenance configuration rather than a hard-coded `STATUTORY` in the batch. ADR-026 (JP statutory completion) materializes the deferred JP fields: `grantCondition` (the worked-days gate, decision C) is defined on the model — optional, so null/absent keeps the exact v1 unconditional behaviour and existing plans need no backfill — and its `MIN_WORKED_DAYS` evaluation is wired into the grant batch (ADR-026 B1, injected time-tracking `aggregateWorkedDays`). The current front-load company policy is `accrualMethod=FRONT_LOAD_TENURE` / `grantType=STATUTORY` / `eligibilityDelayMonths=0` / `annualCapDays=20` / `expirationMonths=24` — a data backfill with no behavioural change.

## Domain Model Definitions

### Model type

Standard

### Command Definitions

- createAccrualPlan — define a grant rule for a balance-backed leave type (initial generation)
- updateAccrualPlan — record a rule change (method, grant type, delay, base/tiers, cap, expiration, applicability) as a new effective-dated generation (closes the prior generation)
- retireAccrualPlan — close the current generation when a plan is discontinued, preserving prior generations for historical explanation

### Query Definitions

- getAccrualPlan — retrieve a single plan generation by id
- listActiveAccrualPlans — current plan generations (`effectiveEnd IS NULL`) as of today, paginated
- listAccrualPlansByLeaveType — all generations for a `leaveTypeKey` (history), paginated
### Models

- AccrualPlan

### Invariants

- Effective-dated per ADR-013: each record carries `effectiveStart`, nullable `effectiveEnd`, and `versionOf`; generations sharing a `versionOf` must not overlap in their effective ranges
- The current generation of a plan has `effectiveEnd IS NULL`; a rule change never overwrites a generation — it closes the prior one (`effectiveEnd` = day before the change) and inserts a new one
- A future-dated generation (`effectiveStart` in the future) is a scheduled rule change and must not govern grants before its start date
- References a LeaveType by `leaveTypeKey` whose `requiresBalance` is true — only balance-backed leave accrues
- `accrualMethod` is a required discriminator naming the method the grant batch dispatches on; `FRONT_LOAD_TENURE` (front-load at the eligibility date + tenure-tier escalation) is the only recognized value today, and the batch skips any plan whose method it does not implement. Additional methods can be added without a schema migration
- `grantType` is a required provenance stamped on every grant this plan produces, one of `STATUTORY` / `MANUAL`
- `eligibilityDelayMonths` is a non-negative integer; the first grant's `grantedDate` = hire date + `eligibilityDelayMonths`
- `baseGrantDays` is a non-negative decimal (half-day increments valid) — the front-loaded amount at the eligibility date
- `tenureTiers` is an ordered tenure-to-entitlement mapping (monotonic non-decreasing in both `yearsOfService` and `grantDays`); the entitlement at year N is the tier for N (or the highest defined tier at or below N)
- The granted amount is the applicable tenure tier's `grantDays` (the highest tier with `yearsOfService <= tenure`), or `baseGrantDays` when below the first tier; `annualCapDays`, when set, bounds it: `min(applicable tier total ?? baseGrantDays, annualCapDays)`. Each tier's `grantDays` must not exceed `annualCapDays`. Null cap means uncapped
- `expirationMonths` is a positive integer (24 for statutory annual leave); it determines a STATUTORY grant's `expirationDate` = grant date + `expirationMonths`. In v1 it is **required (non-null)**, so every STATUTORY grant has an expiration and `LeaveGrant.expirationDate` is always present; a null value (no-expiry, e.g. rolls-over jurisdictions like AU) is deferred to v3 together with the carryover modes (ADR-026 §E, behind the ADR-018 global GO gate)
- `grantCondition` is optional (ADR-026 C): null/absent = type NONE = unconditional grant. `MIN_WORKED_DAYS` requires a positive integer `minWorkedDays`; `referenceMonths`, when present, must be a positive integer; an unrecognized `type` is rejected — a jurisdiction-specific gate is expressed as an AccrualPlan custom field read by the application eligibility policy, not as an unknown enum value
- Consumers may add top-level custom fields through `defineModule({ accrualPlan: { fields } })`; built-in and reserved fields cannot be redefined. Create persists custom fields, and effective-dated update generations inherit omitted custom values. App-specific condition fields are interpreted by the optional typed `grantEligibility.evaluate` callback
- `appliesToEmploymentTypeId` constrains which workforce WorkerEmployment employment types the plan grants for (referenced by EmploymentType catalog id); the anniversary batch only grants to matching employments
- At most one plan generation is effective per (`leaveTypeKey`, employment type) on any given date (non-overlap consequence)
- **Backward compatibility**: `eligibilityDelayMonths=0` / `annualCapDays=20` / `expirationMonths=24` (with the renamed `baseGrantDays` / `tenureTiers`) reproduces the pre-ADR-025 front-load behaviour exactly

### Relationships

- **References LeaveType** (by `leaveTypeKey`): the balance-backed leave whose grants this plan governs
- **Drives LeaveGrant**: HIRE and ANNIVERSARY grant creation reads the plan generation effective on the grant date to determine days and expiration
- **Reads WorkerEmployment** (workforce, cross-module): eligibility (employment type) and anniversary timing derive from the employment's type and hire date on the applicable date
- **Reads time-tracking worked days** (ADR-026 B1, wired): evaluating the `MIN_WORKED_DAYS` gate reads the time-tracking `aggregateWorkedDays` query, injected into the grant batch via `DefineModuleParams`
- **Application eligibility policy** (optional): the consuming application evaluates jurisdiction- or company-specific custom fields and returns ELIGIBLE, INELIGIBLE, or NOT_EVALUABLE; an eligible result may override the proposed grant amount
