# ImportReportedBlocks

## Permission Scope

`reportedBlock`

## Overview

ImportReportedBlocks bulk-creates declared ReportedTimeBlocks from an external source, with `sourceKind = IMPORT`, without disturbing any existing block. After the batch is written it re-derives the CalculatedTimeBlocks for every workday it touched, in the same transaction, so bulk-imported time is calculated like any other Reported write rather than being left as uncalculated raw declarations.

## Business Rules

- Imported blocks are declared blocks like manual declarations: they are the source of truth for their interval and are correctable by supersede only while their covering Timecard is OPEN
- `blockType` is one of WORK, BREAK, STEP_OUT, stored with normalized enum naming; `sourceKind = IMPORT` for every row created by this command
- Each imported block must resolve to exactly one workforce Assignment effective on its `workDate`
- Durations are whole minutes (no float hours); intervals must not produce overlapping current WORK blocks for the same Assignment and workday
- Each row's interval must be well-formed by the same rule manual declaration and correction enforce: a positive span (`endAt` after `startAt`) on whole-minute boundaries; a row failing this is rejected individually as `INVALID_ROW`
- An imported block is an original declaration and has no `correctionReason`
- Partial success is allowed: rows that fail validation are rejected individually and do not block valid rows in the same batch
- Import into a period whose covering Timecard is not OPEN (SUBMITTED, APPROVED, or LOCKED) is rejected for that row
- Every workday the batch writes to has its CalculatedTimeBlocks re-derived in the same transaction (the "Reported change → Calculated re-derivation" invariant shared with declare/correct/form). Because import is best-effort per-row, a workday whose WorkRule does not resolve defers its calculation to a later `recalculateRange` sweep instead of rolling back the other successfully-imported rows

## Process Flow

```mermaid
flowchart TD
    A[External file/system with declared block rows] --> B[Parse row: Assignment, workDate, blockType, interval]
    B --> C{Assignment resolvable and Timecard OPEN?}
    C -- No --> D[Reject row: ASSIGNMENT_NOT_FOUND / TIMECARD_NOT_OPEN]
    C -- Yes --> E{Overlaps existing current WORK block?}
    E -- Yes --> F[Reject row: OVERLAPPING_BLOCK]
    E -- No --> G[Create ReportedTimeBlock with sourceKind = IMPORT]
    G --> H[Collect rejected rows in import result]
    D --> H
    F --> H
    H --> I[Re-derive CalculatedTimeBlocks for each written workday]
    I --> J{WorkRule resolves for the workday?}
    J -- No --> K[Defer calculation to a later recalculateRange sweep]
    J -- Yes --> L[Replace that workday's CalculatedTimeBlocks and refresh OPEN Timecard totals]
```

## External Dependencies

- [time-tracking::Timecard](../model/Timecard.md) - governs whether the target workday's period still permits new declared blocks (OPEN only)

## Error Scenarios

- **ASSIGNMENT_NOT_FOUND**: no workforce Assignment is effective for the relevant worker/date
- **TIMECARD_NOT_OPEN**: the Timecard covering the relevant workDate is not OPEN (SUBMITTED/APPROVED/LOCKED require `reopenTimecard` first)
- **OVERLAPPING_BLOCK**: the interval overlaps an existing current WORK block for the same Assignment and workday
- **INVALID_ROW**: a row in the batch fails schema/enum validation

## Test Cases

- declared blocks are created in bulk from an external source and are correctable by supersede until lock
- `blockType` and `sourceKind` are stored with normalized enum naming
- current (non-superseded) WORK blocks for an Assignment and workday do not overlap
- block durations are whole minutes (no float hours)
- a row with a malformed interval (empty/inverted or non-whole-minute) is rejected as INVALID_ROW without blocking valid rows
- re-derives CalculatedTimeBlocks for each imported workday (issue #37 regression: import must not leave blocks uncalculated)
