# WorkRule

## Description

WorkRule is the **complete time-calculation rule** (work rules) — the single configurable object that defines how raw reported time is turned into calculated time for a given population. It is what a worker is *on*: from a single WorkRule the calculation engine derives every parameter for their time. Prior to ADR-022, the mechanical parameters lived in WorkRule while the pay-period framing and bundling lived in a separate PayRule entity; ADR-022 absorbed PayRule into WorkRule, making WorkRule the sole assignable rule. The two pay-period fields absorbed from PayRule — `payPeriod` (and its `MONTHLY` / `SEMI_MONTHLY` / `WEEKLY` enum) and `dayBreakerMinute` — were dropped in #39 as unused: nothing in the calculation consumed them.

A WorkRule bundles the mechanical parameters of a Japanese working-time regime: rounding, break deduction, daily overtime threshold, the night-work window, and the premium-category pins (`premiumRatePercent`) that bind time-entry categories to their TimeEntryCode `key`.

Rounding is configured independently for clock-in, clock-out, and break, each as a `unitMinutes` plus a `direction` (`UP` / `DOWN` / `NEAREST`). Break deduction is expressed as minute-based rules. The daily overtime threshold (`dailyOvertimeThresholdMinutes`, e.g. `480` for an 8-hour day) marks where statutory-excess overtime begins. The night window is stored as minutes-from-midnight bounds (`nightWindowStart` / `nightWindowEnd`, e.g. 22:00 = `1320` to 05:00 = `300`). `premiumRatePercent` is a list of pins, each binding a time-entry `category` to a TimeEntryCode `key` (e.g. pinning the statutory-excess overtime / late-night or statutory holiday category to a specific code) — bound by key, never by display name (ADR-015, issue #14). It carries no percent multiplier: it only pins which TimeEntryCode a category resolves to; no rate multiplication is performed anywhere in the codebase.

A WorkRule also carries an optional `holidayCalendarId` referencing a [HolidayCalendar](HolidayCalendar.md): it selects which calendar the calculation uses to classify a worker's dates as holidays. When `holidayCalendarId` is null the rule applies no calendar — no date is treated as a holiday for a worker on that rule. Holiday classification is therefore scoped: the engine marks a workday as `HOLIDAY` only via a `(calendarId, holidayDate)` lookup against the calendar resolved from the effective WorkRule, never via a global holiday list.

WorkRule is effective-dated (ADR-013): a legal change — such as a premium-rate revision or an overtime-cap reform — is absorbed by inserting a **new generation** effective on the change date, with no code change and no redeploy (issue #13, absorbing legal reforms under a flat-fee maintenance model).

WorkRule is *what* applies; `EligibilityRule` governs *who may be assigned* which WorkRule. The time-tracking engine, when converting Reported → Calculated for a person on a date, resolves that person's WorkRule generation effective on that date (via the workforce employment/assignment) and applies it (ADR-014, ADR-015).

## Domain Model Definitions

### Model type

Standard

### Command Definitions

- createWorkRule — define a new work rule with calculation mechanics (initial generation)
- updateWorkRule — record a parameter change (e.g. a revised premium-category pin) as a new effective-dated generation, closing the prior one
- assignWorkRule — bind a WorkRule to a WorkerEmployment/Worker on an effective date (subject to EligibilityRule)
- deleteWorkRule — remove a WorkRule only when no current assignment references it

### Query Definitions

- getWorkRule — retrieve a single work-rule generation by id
- listWorkRules — paginated list of work-rule generations (history included)
- listActiveWorkRules — current generations (`effectiveEnd IS NULL`) as of today, paginated
### Models

- WorkRule

### 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 has `effectiveEnd IS NULL`; a parameter 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 change and must not be applied before its start date
- All time quantities are minutes-based integers: `unitMinutes`, `breakDeductionMinutes`, `dailyOvertimeThresholdMinutes`, and the night-window bounds are minutes (bounds are minutes-from-midnight, 0–1439)
- Rounding `direction` is a normalized enum (`UP` / `DOWN` / `NEAREST`) configured separately for clock-in, clock-out, and break
- `premiumRatePercent` entries are pins keyed by TimeEntryCode `category`/`key`, never by display name; they carry no percent value (ADR-015)
- For any worker and date, at most one WorkRule generation is in force (the assignment effective on that date); calculation reads that generation, never a display label
- `holidayCalendarId` is an optional reference to a [HolidayCalendar](HolidayCalendar.md); a null value means the rule applies no calendar and classifies no date as a holiday
- Holiday classification is scoped to the WorkRule's `holidayCalendarId`: a date is treated as a holiday only when a `CompanyHoliday` exists for `(holidayCalendarId, holidayDate)` in the effective generation's calendar
- `displayName` is a UI-only label (same pattern as `TimeEntryCode.displayName`) carrying no calculation semantics; renaming it never alters a calculation
- Assignability of a WorkRule to a target is governed by EligibilityRule; WorkRule does not itself encode who is eligible

### Relationships

- **References TimeEntryCode**: premium-category pins and break/overtime treatment are keyed by TimeEntryCode `category`/`key`
- **References HolidayCalendar (optional)**: `holidayCalendarId` selects the [HolidayCalendar](HolidayCalendar.md) used to classify a worker's dates as holidays; null means no calendar applies
- **Assigned via WorkRuleAssignment**: the effective-dated binding ([WorkRuleAssignment](WorkRuleAssignment.md)) references the workforce target by id/enum; employment type / work type are read on the calculated date
- **Governed by EligibilityRule**: which targets may be assigned this WorkRule is expressed as eligibility, not embedded here
- **Consumed by time-tracking**: the Reported → Calculated conversion (ADR-014) resolves the WorkRule generation effective on the calculated date and applies it, stamping the result with a CalculationTag
