# UpdateShiftPattern

## Permission Scope

shiftPattern

## Overview

updateShiftPattern adjusts a pattern's `kind`, display `name`, or its embedded `segments` array in place. ShiftPattern is reference/master data, not effective-dated (ADR-013) — the edit applies immediately to the template. Segment-level mutations (formerly the separate `addShiftPatternSegment` / `updateShiftPatternSegment` / `removeShiftPatternSegment` commands) are now absorbed into this command (ADR-022).

## Business Rules

- `code` is immutable once set; renaming or changing `code` is not supported by this command (selection binds to `code`, ADR-015)
- `kind` must remain one of `DAY` / `EARLY` / `LATE` / `NIGHT` / `ON_CALL` / `OFF` — the same set creation accepts
- The **resulting** kind and segment set must agree, whether or not both are supplied: an `OFF` pattern has zero segments, every other kind has at least one. Patching only `kind` therefore fails when the segments already on the record contradict the new kind
- Turning a pattern into `OFF` requires replacing its segments with an empty set in the same call; turning an `OFF` pattern into any other kind requires supplying at least one segment
- If segments are replaced as part of this update, the resulting set must still satisfy: single-part pattern = one segment; 中抜け pattern = two or more segments with a positive gap; sequences 1-based and contiguous
- Existing Shifts that already instantiated this pattern are not retroactively changed — only future instantiations see the updated template
- `name` changes do not affect any logic bound to `code` (ADR-015; #14 反面教師)

## Process Flow

```mermaid
flowchart TD
    A[Receive shiftPatternId,<br/>optional name/kind/segments] --> B{Pattern exists?}
    B -->|No| BX[Return error: SHIFT_PATTERN_NOT_FOUND]
    B -->|Yes| C{kind provided?}
    C -->|Yes| D{Valid kind enum?}
    D -->|No| DX[Return error: INVALID_KIND]
    D -->|Yes| E
    C -->|No| E{segments provided?}
    E -->|Yes| F[Validate segment set:<br/>gaps positive,<br/>sequence contiguous]
    F -->|Invalid| FX[Return error: SEGMENT_GAP_INVALID]
    F -->|Valid| G{Resulting kind and<br/>segment count agree?}
    E -->|No| G
    G -->|OFF with segments| GX[Return error: SEGMENT_KIND_MISMATCH]
    G -->|non-OFF with none| GY[Return error: SEGMENT_REQUIRED]
    G -->|Yes| H2[Apply updates to pattern<br/>and segment set]
    H2 --> H[Return updated pattern]
```

## External Dependencies

- [shiftSchedule::Shift](../model/Shift.md) model - existing shifts that instantiated this pattern are unaffected by the update

## Error Scenarios

- **SHIFT_PATTERN_NOT_FOUND**: no ShiftPattern exists for the given id
- **INVALID_KIND**: kind is not one of the normalized enum values
- **SEGMENT_REQUIRED**: no segments were provided, or the change would leave zero segments
- **SEGMENT_KIND_MISMATCH**: one or more segments were provided when kind = OFF (an OFF pattern must have zero segments)
- **SEGMENT_GAP_INVALID**: two consecutive segments do not leave a positive gap
- **SEGMENT_TIME_INVALID**: a segment's startTime/endTime is outside [0, 1440) or inconsistent with spansMidnight
- **CODE_IMMUTABLE**: An attempt was made to change the pattern's code

## Test Cases

- updates a pattern's name without affecting code-bound logic
- updates a pattern's kind from DAY to NIGHT
- turns a pattern into OFF when kind and an empty segment set are supplied together
- turns an OFF pattern into DAY when kind and at least one segment are supplied together
- replaces a single-segment pattern's segments with a 中抜け two-segment set
- throws SHIFT_PATTERN_NOT_FOUND when the pattern does not exist
- throws INVALID_KIND when kind is not one of the normalized enum values
- throws SEGMENT_REQUIRED when the update would remove all segments
- throws SEGMENT_REQUIRED when only kind is patched away from OFF, leaving zero segments
- throws SEGMENT_KIND_MISMATCH when only kind is patched to OFF, leaving the existing segments
- throws SEGMENT_GAP_INVALID when the updated segments leave a zero or negative gap
- throws SEGMENT_TIME_INVALID when a segment's startTime/endTime is outside [0, 1440)
- throws SEGMENT_BREAK_INVALID when a segment's breakMinutes is not strictly less than its gross span
- existing Shifts already instantiated from the pattern retain their previously copied segments after the pattern update
