# README

## Overview

The time-tracking module owns the **time-data domain** (punches, raw/calculated time blocks, timecards) of the enterprise attendance platform. It is the concrete implementation of the Reported → Calculated separation that enterprise WFM/HCM products treat as non-negotiable (ADR-014): raw punch data is preserved immutably, and everything payroll, costing, and Article-36 agreement monitoring consume is a **re-derivable calculation** on top of it, never the raw itself.

The domain is organized as **three data layers plus a sign-off unit**. `TimeClockEvent` is the immutable raw punch captured from any source — web, mobile, kiosk, IC card, biometric, PC logon, Slack, import — reflecting Japan's IC/biometric punch culture (issues #10, #11); corrections are void records in a separate `TimeClockEventVoid` table, never mutations (ADR-023). `ReportedTimeBlock` is the declared work/break interval formed from those events or entered manually; pre-lock corrections create a new block and supersede the prior one so history is never destroyed — the structural antidote to "manual entry erasing the original data" (issue #6). `CalculatedTimeBlock` is the derived layer produced from reported blocks plus the effective work-rules (ADR-015); it carries calculation tags recording which rules fired, so any single block's result is traceable in the manner of UKG's Rule Analysis and Workday's Calculation Debugger (issue #7). `Timecard` aggregates a period and is the approval/period-close unit; changes after lock become explicit historical corrections journaled in `TimeCorrectionLog`.

The module resolves **"whose"** through a workforce `Assignment`, and it **owns its calculation-rule sub-domain** (formerly work-rules; ADR-020): `WorkRule` / `TimeEntryCode` / `EligibilityRule` / `CompanyHoliday` live here as effective-dated configuration and are bound by stable key, never display name (ADR-015). Externalizing the rule engine remains an architectural principle, now realized as an internal sub-domain rather than a separate module. It is a hard architectural rule that downstream consumers (payroll, costing, Article-36 agreement) read only the calculated layer — the raw and reported layers are inputs to re-derivation, so they are never destroyed and never fed directly to pay.

## Key Features

- [Time Clock Punching](docs/feature/time-clock-punching.md) — record raw multi-source punch events immutably; correct via void records in a separate table, never mutation; handle overnight shifts through a workday day-breaker so the calculated date can differ from the occurred date
- [Reported Time Blocks](docs/feature/reported-time-blocks.md) — form declared work/break/step-out blocks from events or manual entry; correct pre-lock via supersede so the prior block is preserved as history, not overwritten
- [Time Calculation](docs/feature/time-calculation.md) — derive CalculatedTimeBlocks from reported blocks driven by the effective work-rules; assign time-entry categories and calculation tags; re-derive on rule change or effective-dated retroactive correction
- [Timecard Approval](docs/feature/timecard-approval.md) — aggregate a period into a Timecard; submit / approve / lock via the bundled approval wrapper (direct mode, one step, timecard-approver role, quorum ANY, per ADR-003; implementation in progress); treat post-lock changes as explicit historical corrections
- [Work And Pay Rules](docs/feature/work-and-pay-rules.md) — (rule sub-domain, formerly work-rules / ADR-020) `WorkRule` — the complete, assignable rule (rounding, break deduction, daily overtime threshold, night window, premium-category pins), effective-dated
- [Time Entry Codes](docs/feature/time-entry-codes.md) — key-bound classification of calculated time (replacing name-bound RecordType), carrying the `payMapKey` seam to payroll
- [Eligibility Rules](docs/feature/eligibility-rules.md) — effective-dated grants controlling which target may be assigned which WorkRule
- [Company Holiday Calendar](docs/feature/company-holiday-calendar.md) — `CompanyHoliday` dated facts (STATUTORY / PRESCRIBED) driving holiday classification and premium

## Module Scope

### In Scope

- Raw punch events (`TimeClockEvent`) from all punch sources (WEB / MOBILE / KIOSK / IC_CARD / BIOMETRIC / PC_LOGON / SLACK / IMPORT), stored append-only with corrections recorded in a separate TimeClockEventVoid table
- Reported time blocks (`ReportedTimeBlock`) — the declared work/break/step-out intervals, from punch-derived, manual, or imported sources, with supersede-based pre-lock correction preserving history
- Calculated time blocks (`CalculatedTimeBlock`) — the derived, re-derivable layer categorized by TimeEntryCode key and tagged with the rules that fired; the single layer payroll / costing / Article-36 agreement read
- Timecards (`Timecard`) — period aggregation and the submit/approve/lock sign-off unit, with per-category minute totals and a historical-correction flag
- Historical-correction journal (`TimeCorrectionLog`) — append-only record of post-lock changes, part of the audit trail that replaces the deprecated audit module (ADR-013)
- Overnight / cross-midnight handling via a workday day-breaker, so an event's calculated workday can differ from its occurred date (issue #7)
- Recomputability: CalculatedTimeBlocks are always re-derivable from ReportedTimeBlocks plus the work-rules effective on the target date, and are recomputed on retroactive correction or rule change (ADR-013, ADR-014)
- Work / pay rules and the rule engine (`WorkRule` / `TimeEntryCode` / `EligibilityRule` / `CompanyHoliday`) — the calculation-rule sub-domain merged from the former work-rules module (ADR-020), applied by key during derivation (ADR-015)

### Out of Scope

- Who a person is, their employment, and their organizational post — owned by workforce; a punch resolves "whose" via an `Assignment` referenced by id
- Company / Department / Site master data and time zones — owned by the bundled organization module, reached transitively through workforce
- Work schedules, shifts, rosters, planned-vs-actual comparison — owned by scheduling; time-tracking supplies the actual (calculated) side it reads
- Leave grants, balances, and consumption — owned by leave-management; leave affects workday classification via events/commands, not by time-tracking owning leave data
- Payroll earnings, costing postings, and Article-36 agreement threshold evaluation themselves — future/other domains that read CalculatedTimeBlock; time-tracking produces the input, not the payroll result
- Approval routing internals (steps, assignees, decision log) — owned by the bundled approval module; the Timecard submit/approve/reopen lifecycle is wrapped over it (direct mode, one step, timecard-approver role, quorum ANY) per ADR-003
- Multi-dimensional labor allocation (multi-worktag cost/project distribution) and global payroll / multi-currency — deferred with structural room only (ADR-018); the single-dimension `costObjectRef` seam once threaded on CalculatedTimeBlock was removed in #39 (a nullable allocation column can be re-added later)

### Scope Decision Rationale

time-tracking is scoped to **the raw-to-calculated pipeline of time data plus the period sign-off unit**, and nothing else. The layered separation of `TimeClockEvent` (immutable raw) → `ReportedTimeBlock` (declared, correctable by supersede) → `CalculatedTimeBlock` (re-derivable, rule-driven) is the core that issues #7, #9, and #14 identify as impossible to retrofit: the legacy `attendance-management` `Attendance` row co-located raw punches and computed minutes on one record, which weakens audit and makes re-calculation untraceable (ADR-014). Committing to the split in the first schema is what lets retroactive payroll re-calculation, rule-change replay, and single-block "why did it calculate this way" tracing work without a data migration.

The module deliberately excludes the rules themselves (work-rules) and the identity of the worker (workforce) because each is a separate domain with its own change cadence and consistency boundary; time-tracking depends on them but does not own them, referencing rules by stable key (ADR-015) and workers by `assignmentId`. It also stops at producing the calculated layer: payroll, costing, and Article-36 agreement evaluation are downstream readers, which keeps the hard invariant — "pay reads only calculated, raw is never destroyed" — enforceable at the module boundary. Multi-dimensional allocation and global payroll are cut (ADR-018); the single-dimension allocation seam on CalculatedTimeBlock was removed in #39, and re-adding a nullable allocation column later keeps the future extension an added dimension, not a schema migration.

## Module Dependencies

- workforce — `Assignment` referenced by `assignmentId` on TimeClockEvent / ReportedTimeBlock / CalculatedTimeBlock / Timecard to resolve "whose" time this is (which employment, post, legal entity), effective on the work date
- work-rules — `TimeEntryCode`, `WorkRule`, and `CompanyHoliday` referenced **by stable key** to drive Reported → Calculated derivation; display names never participate in calculation (ADR-015)
- approval (bundled, via `@tailor-platform/erp-kit/module`) — drives the Timecard submit/approve/reopen wrapper in direct mode (one step, timecard-approver role, quorum ANY); wiring is in progress (ADR-003)
- notification (bundled) — reserved for future events (e.g. on-leave clock-in alert, timecard-due reminders); not wired in this design phase (ADR-004)
- user-management (bundled) — `User` identity referenced by `approvedBy` / `correctedBy`; `Role`/RBAC enforce command permissions (ADR-007)
