# TransferAssignment

## Permission Scope

`appointment`

## Overview

TransferAssignment records a transfer as a new effective-dated Assignment generation to a different Position, closing the prior generation on the transfer date rather than overwriting it.

## Business Rules

- Effective-dated per ADR-013: closes the current generation (`effectiveEnd` = transfer date − 1) and inserts a new generation sharing the same `versionOf`; generations must not overlap
- The destination Position must be effective on the transfer's effective date
- The transfer's effective date must fall within the employment's effective range
- The prior generation is preserved, not deleted or mutated — history remains queryable
- The count of current open primary Assignments on the destination Position must not exceed its headcount
- A primary Assignment transfers as primary; a concurrent (non-primary) Assignment remains non-primary at the destination

## Process Flow

```mermaid
flowchart TD
    A[Transfer Assignment to new Position on effectiveDate] --> B{Current Assignment generation open?}
    B -- No --> X[Reject: ASSIGNMENT_NOT_FOUND]
    B -- Yes --> C{Destination Position effective on date?}
    C -- No --> Y[Reject: POSITION_NOT_EFFECTIVE]
    C -- Yes --> D{Within employment's effective range?}
    D -- No --> Z[Reject: OUTSIDE_EMPLOYMENT_RANGE]
    D -- Yes --> E{Destination headcount exceeded?}
    E -- Yes --> W[Reject: POSITION_HEADCOUNT_EXCEEDED]
    E -- No --> F[Close current generation: effectiveEnd = date - 1]
    F --> G[Insert new generation: effectiveStart = date, new Position]
```

## External Dependencies

- [workforce::Assignment](../model/Assignment.md) model — the prior generation closed and new generation created
- [workforce::Position](../model/Position.md) model — the destination post
- [workforce::AppointmentHistory](../model/AppointmentHistory.md) model — the appointment event typically recording this transfer

## Error Scenarios

- **ASSIGNMENT_NOT_FOUND**: the specified Assignment does not exist
- **POSITION_NOT_FOUND**: the specified Position does not exist
- **POSITION_NOT_EFFECTIVE**: the Position is not effective on the given effective date
- **OUTSIDE_EMPLOYMENT_RANGE**: the effective date falls outside the employment's effective range
- **POSITION_HEADCOUNT_EXCEEDED**: the Position's headcount would be exceeded
- **TRANSFER_DATE_BEFORE_CURRENT_GENERATION_START**: the transfer's effective date does not come after the current generation's start

## Test Cases

- a transfer closes the prior generation and inserts a new one with no range overlap
- querying `asOf` a past date returns the Assignment effective then
- transferring to a Position not effective on the transfer date is rejected
- transferring to a Position that would exceed headcount is rejected
- transferring with an effective date outside the employment's range is rejected
- transferring with an effective date on or before the current generation's start is rejected
- transferring a non-existent Assignment is rejected
- transferring to a non-existent Position is rejected

