# LockTimecard

## Permission Scope

`timecard`

## Overview

LockTimecard transitions a single APPROVED Timecard to LOCKED, closing (period close) the period so that any further change must go through historical correction rather than supersede. Normal period close is the bulk `CloseTimecardPeriod`; LockTimecard is the single-card exception path — re-locking one card after a post-lock historical correction (ReopenTimecard from LOCKED → correct → re-lock), where forcing a period-level bulk op to re-close one card would be wrong. Both set `lockedAt`/`lockedBy`.

LOCKED exists as a status distinct from APPROVED because the two represent different business events with different actors and different rollback costs. Approval is a manager's per-card sign-off — "this worker's data for this period is correct" — decided one Timecard at a time. Locking is a payroll-finalization period close performed by a payroll administrator, typically in bulk across every approved card in a period, marking the point where payroll has consumed the data. Reversal semantics differ accordingly: reopening from APPROVED is an ordinary send-back (trail cleared, no permanent flag), while reopening from LOCKED is exceptional (reason required, sign-off trail preserved, `historicalCorrection` set permanently). LOCKED is also the audit boundary — once locked, any further change must be journaled as a historical correction (TimeCorrectionLog) rather than silently superseded; without a separate LOCKED status, either pre-payroll edits would be forced into the audit log unnecessarily, or payroll would have no frozen snapshot to depend on.

## Business Rules

- Only a Timecard currently in APPROVED state may be locked (APPROVED → LOCKED)
- Locking records `lockedAt` and `lockedBy` (the closing actor)
- APPROVED and LOCKED states always carry `approvedAt` and `approvedBy`
- Once LOCKED, covered ReportedTimeBlocks are no longer correctable by supersede; any change must go through ReopenTimecard (reopen → correct → re-lock), which sets `historicalCorrection` and re-derives the CalculatedTimeBlocks payroll reads
- Locking closes the period for payroll hand-off (ADR-014); category totals are frozen as of lock time unless a later historical correction occurs
- Locking is a terminal state in the forward lifecycle, reachable again only by reopening (ReopenTimecard) and re-locking

## Process Flow

```mermaid
flowchart TD
    A[Request to lock a Timecard] --> B[Look up Timecard]
    B --> C{Timecard exists and status = APPROVED?}
    C -- No, not found --> D[Reject: TIMECARD_NOT_FOUND]
    C -- No, wrong status --> E[Reject: INVALID_STATUS_TRANSITION]
    C -- Yes --> F[Set status = LOCKED]
    F --> G[Period closed: ReportedTimeBlock corrections now require ReopenTimecard first]
```

## External Dependencies

- [time-tracking::TimeCorrectionLog](../model/TimeCorrectionLog.md) - the journal any post-lock change against this Timecard must be recorded in

## Error Scenarios

- **TIMECARD_NOT_FOUND**: no Timecard exists for the given id
- **INVALID_STATUS_TRANSITION**: the Timecard's current status does not permit this transition

## Test Cases

- rejects with TIMECARD_NOT_FOUND when no Timecard exists for the given id
- rejects with INVALID_STATUS_TRANSITION when the Timecard is not APPROVED
- transitions an APPROVED Timecard to LOCKED, stamping lockedAt and lockedBy
- LOCKED preserves the approvedAt/approvedBy carried over from APPROVED
