# CreateShift

## Permission Scope

shift

## Overview

createShift adds a planned work slot (`Shift`) to a ShiftSchedule period on a concrete date, carrying one or more ordered embedded `segments`. It may instantiate a `ShiftPattern` — copying its segments onto the date — or be entered ad-hoc. Every slot starts unstaffed: it is an **open shift** (未割当) until a `ShiftPlacement` places someone on it, and the Shift itself never names anyone.

## Business Rules

- New shifts are created with no status of its own
- A shift must be created with **at least one** segment in its `segments` array; segments carry the planned working time, not the Shift itself
- When `shiftPatternId` is provided, segments are copied from the pattern's `segments` onto `date` (minutes-from-midnight resolved against the date, honoring `spansMidnight`); the caller may override the copied times
- When no `shiftPatternId` is provided, segments must be entered ad-hoc and satisfy the same segment invariants
- `shiftType` must be consistent with segment count: `SPLIT` (中抜け) requires two or more segments with a positive gap; `NORMAL` / `THROUGH` / `ON_CALL` require exactly one segment; `SUPPORT` may have one or more
- `plannedStartAt`/`plannedEndAt` (the envelope) are derived as the earliest segment start and latest segment end, not independently supplied
- `date` must be consistent with the first segment's start and is used with the time-tracking `CompanyHoliday` calendar to determine 所定日 / 休日
- `shiftScheduleId` is required: every slot belongs to exactly one ShiftSchedule period, and `date` must fall within that period's `[startDate, endDate]`
- The slot is created unstaffed and stays open until an ACTIVE `ShiftPlacement` references it; nothing here names a person
- `siteId` is optional (organization); a multi-site or 応援 slot carries the Site it is staffed at
- Consecutive segments must leave a positive gap; a zero-gap pair is rejected (should be one segment instead)
- Each segment's `breakMinutes` must be less than that segment's gross span

## Process Flow

```mermaid
flowchart TD
    A[Receive shiftScheduleId, date, shiftType,<br/>optional shiftPatternId, siteId, segments] --> B{shiftPatternId provided?}
    B -->|Yes| C{Pattern exists?}
    C -->|No| CX[Return error: SHIFT_PATTERN_NOT_FOUND]
    C -->|Yes| D[Copy pattern segments onto date,<br/>honoring spansMidnight]
    B -->|No| E[Use ad-hoc segments provided]
    D --> F
    E --> F[Validate segment count vs shiftType,<br/>positive gaps, breakMinutes < gross span]
    F -->|Invalid| FX[Return error: SEGMENT_GAP_INVALID / SHIFT_TYPE_SEGMENT_MISMATCH]
    F -->|Valid| G{shiftScheduleId provided?}
    G -->|Yes| H{Assignment effective on date?}
    H -->|No| HX[Return error: ASSIGNMENT_NOT_EFFECTIVE]
    H -->|Yes| I
    G -->|No| I[Open shift]
    I --> J[Read time-tracking CompanyHoliday<br/>for date to note 所定日/休日]
    J --> K[Compute plannedStartAt/plannedEndAt envelope]
    K --> L[Insert Shift under its ShiftSchedule<br/>with its embedded segments]
    L --> M[Return created shift]
```

## External Dependencies

- [shiftSchedule::ShiftPattern](../model/ShiftPattern.md) model - optionally instantiated, copying its segments onto the concrete date
- [shiftSchedule::ShiftSchedule](../model/ShiftSchedule.md) model - the period the slot belongs to; the shift's date must fall inside it
- organization::Site (bundled erp-kit module, package import per ADR-001) - optional siteId places the slot at a facility
- time-tracking::CompanyHoliday (erp-kit bundled module) - read to determine 所定日 / 休日 for the shift's date

## Error Scenarios

- **MISSING_REQUIRED_FIELD**: a required field is missing
- **SHIFT_SCHEDULE_NOT_FOUND**: no ShiftSchedule exists for the given id
- **SHIFT_DATE_OUT_OF_PERIOD**: the shift's date falls outside its ShiftSchedule's period
- **SHIFT_PATTERN_NOT_FOUND**: no ShiftPattern exists for the given id
- **SEGMENT_REQUIRED**: no segments were provided, or the change would leave zero segments
- **SEGMENT_GAP_INVALID**: two consecutive segments do not leave a positive gap
- **SEGMENT_BREAK_INVALID**: breakMinutes is negative or not strictly less than the segment's gross span
- **SHIFT_TYPE_SEGMENT_MISMATCH**: segment count is inconsistent with shiftType

## Test Cases

- creating a shift from a single-segment pattern copies its segment (start, end, break) onto the date
- creating a shift from a 中抜け pattern (two segments) yields two segments on the Shift with the gap preserved
- a shift with two or more segments is classified SPLIT; a single-segment shift is NORMAL / THROUGH / ON_CALL
- consecutive segments must leave a positive gap; a zero-gap pair is rejected
- the Shift envelope plannedStartAt / plannedEndAt equals the earliest segment start and latest segment end
- a spansMidnight pattern segment yields a Shift segment whose plannedEndAt is on the following day
- creates a pure demand slot: the insert writes no assignment field
- a segment's breakMinutes must be less than that segment's gross span
- throws SHIFT_TYPE_SEGMENT_MISMATCH when segment count is inconsistent with the declared shiftType
- throws SHIFT_PATTERN_NOT_FOUND when the referenced pattern does not exist
- throws SEGMENT_REQUIRED when no segments and no shiftPatternId are provided
- throws MISSING_REQUIRED_FIELD when date is missing
- writes a consumer's custom field onto the Shift row alongside the builtin columns
