# Position

## Description

Position is a **post** in the organization — one seat that exists independently of whoever occupies it. A Position belongs to an `organization` Department (and optionally a Site) and references a `JobProfile` for its role definition. It can be vacant or to-be-hired (TBH) with no incumbent, which is the whole point of the Worker / Position / Job separation: headcount and org structure are managed on the seat, not on the person (issue #7, ADR-016).

Position is effective-dated (ADR-013) so that structural changes — a seat's department placement, its headcount — are versioned and the organization is reconstructable as of any date. Occupancy (vacant/TBH vs filled) is **derived** from the current open Assignments against `headcount`, not stored on the Position (see `listVacantPositions`). `Assignment` links a `WorkerEmployment` to a Position; `AppointmentHistory` records the appointments that move people between Positions.

## Domain Model Definitions

### Model type

Standard

### Command Definitions

Command docs are out of scope for this design phase (ADR-011). Anticipated commands:

- createPosition — define a new post under a Department/Site with a JobProfile
- updatePosition — record a structural change (department, site, job profile, headcount) as a new effective-dated generation
- abolishPosition — close the current generation when a post is eliminated

### Query Definitions

- getPosition — retrieve a single position generation by id
- listVacantPositions — posts with no current open Assignment (vacant/TBH), paginated
- listPositionsByDepartment — posts under a Department, paginated
### Models

- Position

### Invariants

- Effective-dated per ADR-013: `effectiveStart`, nullable `effectiveEnd`, `versionOf`; generations sharing a `versionOf` must not overlap
- Every Position references exactly one organization Department; Site reference is optional (some posts are not site-bound)
- Every Position references exactly one JobProfile — a post without a role definition cannot exist (mirrors "no Position without a Job Profile"; issue #7)
- Occupancy is **derived, not stored**: a Position is vacant (vacant/TBH) when it has fewer current open primary Assignments than `headcount`, and filled otherwise (`listVacantPositions`). The former stored `status` enum was dropped as a drift-prone denormalization not connected to Assignment linkage (issue #39)
- `headcount` is the number of seats the Position represents (default 1); the count of current open primary Assignments must not exceed headcount
- Abolishing a Position closes its generation but preserves it for historical org reconstruction; it is not hard-deleted while Assignments reference it

### Relationships

- **References Department** (organization, cross-module): `departmentId` places the post in the functional structure
- **References Site** (organization, cross-module, optional): `siteId` places the post at a physical facility
- **References JobProfile**: `jobProfileId` supplies the role definition (job family, grade, requirements)
- **Referenced by Assignment**: employments are assigned to this Position over time
- **Referenced by AppointmentHistory**: appointment events reference the from/to Position of a move
