# OpenTimecard

## Permission Scope

`timecard`

## Overview

OpenTimecard creates a new Timecard for an Assignment and period, aggregating the current CalculatedTimeBlocks in that period into category totals, entering the lifecycle at OPEN.

## Business Rules

- `status` is set to OPEN on creation, the entry point of the OPEN → SUBMITTED → APPROVED → LOCKED lifecycle
- `periodStart` must be on or before `periodEnd`; the period defines the set of workdays whose CalculatedTimeBlocks it aggregates
- At most one Timecard exists per Assignment and non-overlapping period
- The per-category minute totals (`categoryTotals`, keyed by the strategy-defined category key — not fixed JP columns) are non-negative integers and reconcile to the sum of the covered CalculatedTimeBlocks per category for the period
- The Timecard must reference exactly one workforce Assignment
- While OPEN, covered ReportedTimeBlocks remain correctable via supersede (CorrectReportedBlock)

## Process Flow

```mermaid
flowchart TD
    A[Request to open a Timecard: Assignment, periodStart, periodEnd] --> B{Existing Timecard overlaps this period for the Assignment?}
    B -- Yes --> C[Reject: TIMECARD_PERIOD_OVERLAP]
    B -- No --> D{periodStart on or before periodEnd?}
    D -- No --> E[Reject: INVALID_PERIOD]
    D -- Yes --> F[Aggregate CalculatedTimeBlocks in period into category totals]
    F --> G[Create Timecard with status = OPEN]
```

## External Dependencies

- [time-tracking::CalculatedTimeBlock](../model/CalculatedTimeBlock.md) - the calculated blocks whose category minutes are aggregated into the Timecard's totals

## Error Scenarios

- **TIMECARD_PERIOD_OVERLAP**: An existing Timecard already covers an overlapping period for the same Assignment
- **INVALID_PERIOD**: `periodStart` is after `periodEnd`
- **ASSIGNMENT_NOT_FOUND**: no workforce Assignment is effective for the relevant worker/date

## Test Cases

- creates a new Timecard entering the lifecycle at OPEN with empty category totals when the period has no calculated time
- seeds the opening category totals from CalculatedTimeBlocks already covering the period
- rejects with INVALID_PERIOD when periodStart is after periodEnd
- rejects with ASSIGNMENT_NOT_FOUND when the Assignment is not in force at the period start
- rejects with TIMECARD_PERIOD_OVERLAP when a Timecard already covers an overlapping period
