# UpdatePosition

## Permission Scope

`position`

## Overview

UpdatePosition records a structural change to a Position — Department, Site, JobProfile, or headcount — as a new effective-dated generation, closing the prior one.

## Business Rules

- Effective-dated per ADR-013: closes the current generation (`effectiveEnd` = change date − 1) and inserts a new generation sharing the same `versionOf`; generations must not overlap
- The count of current open primary Assignments cannot exceed the new `headcount`
- Any new Department, Site, or JobProfile referenced by the new generation must exist
- When the change takes effect now or retroactively (`effectiveStart` on or before today), the position's open Assignments are moved onto the new generation so occupancy, headcount, and vacancy keep resolving against the position in force; a future-dated change leaves them on the current generation until that date (that generation is still in force today, so its occupancy must not be emptied ahead of time)

## Process Flow

```mermaid
flowchart TD
    A[Update Position: Department/Site/JobProfile/headcount] --> B{Position exists?}
    B -- No --> X[Reject: POSITION_NOT_FOUND]
    B -- Yes --> C{New references valid: Department/Site/JobProfile?}
    C -- No --> Y[Reject: DEPARTMENT_NOT_FOUND / SITE_NOT_FOUND / JOB_PROFILE_NOT_FOUND]
    C -- Yes --> D{New headcount below current open primaries?}
    D -- Yes --> Z[Reject: HEADCOUNT_BELOW_CURRENT_OCCUPANCY]
    D -- No --> E[Close current generation: effectiveEnd = date - 1]
    E --> F[Insert new generation with changed attributes]
```

## External Dependencies

- organization::Department (cross-module) — new Department reference if changed
- organization::Site (cross-module) — new Site reference if changed
- [workforce::JobProfile](../model/JobProfile.md) model — new JobProfile reference if changed
- [workforce::Assignment](../model/Assignment.md) model — current open Assignments checked against the new headcount

## Error Scenarios

- **POSITION_NOT_FOUND**: the specified Position does not exist
- **DEPARTMENT_NOT_FOUND**: the specified Department does not exist
- **SITE_NOT_FOUND**: the specified Site does not exist
- **JOB_PROFILE_NOT_FOUND**: the specified JobProfile does not exist
- **HEADCOUNT_BELOW_CURRENT_OCCUPANCY**: the new `headcount` is below the number of current open primary Assignments

## Test Cases

- a structural change closes the prior Position generation and inserts a new one
- a change effective now or retroactively moves the position's open Assignments onto the new generation
- a future-dated change leaves the open Assignments on the current generation
- reducing headcount below the current number of open primary Assignments is rejected
- updating a Position to a non-existent Department is rejected
- updating a Position to a non-existent Site is rejected
- updating a Position to a non-existent JobProfile is rejected
- updating a non-existent Position is rejected

