# ImportPunches

## Permission Scope

`punch`

## Overview

ImportPunches bulk-appends raw punch events from an external time-clock device or file, typically with `source = IMPORT` or `IC_CARD`, without altering any existing event.

## Business Rules

- Imported events are append-only, identical in nature to individually recorded punches — no existing event is modified or deleted (ADR-014)
- Each imported record must resolve to exactly one workforce Assignment effective on its `occurredAt`
- `eventType` and `source` must be normalized to the standard enum values before persisting
- `occurredAt` is required per record; no workday is assigned at import time (the day-breaker assigns it later)
- Partial success is allowed: rows that fail validation are rejected individually and do not block valid rows in the same batch

## Process Flow

```mermaid
flowchart TD
    A[External file / time-clock device feed with punch rows] --> B[Parse and normalize eventType/source per row]
    B --> C{Row valid and Assignment resolvable?}
    C -- No --> D[Reject row: INVALID_ROW / ASSIGNMENT_NOT_FOUND]
    C -- Yes --> E[Append immutable TimeClockEvent with source = IMPORT/IC_CARD]
    E --> F[Events available for block formation]
    D --> G[Collect rejected rows in import result]
    F --> G
```

## External Dependencies

- [workforce::Assignment](../model/TimeClockEvent.md) - resolves the Assignment effective on each imported punch's `occurredAt`

## Error Scenarios

- **INVALID_ROW**: a row in the batch fails schema/enum validation
- **ASSIGNMENT_NOT_FOUND**: no workforce Assignment is effective for the relevant worker/date
- **IMPORT_SOURCE_INVALID**: `source` is not a valid bulk-import source (IMPORT / IC_CARD)

## Test Cases

- punches from an external time-clock device or file are appended with source = IMPORT / IC_CARD
- `eventType` and `source` are stored with normalized enum naming
- imported events are never modified or deleted after being appended
- rejects individual rows with INVALID_ROW without blocking valid rows in the same batch
- rejects a row with IMPORT_SOURCE_INVALID when source is not IMPORT or IC_CARD
- rejects a row with ASSIGNMENT_NOT_FOUND when its Assignment is not in force on occurredAt
- rejects all rows without writing when every row in the batch fails validation
