docs(openspec): archive completed changes and sync main specs
Archive three completed changes to archive/: - api-resource-standard (70 tasks, 14 resource classes) - capacity-expert-mode (68 tasks, expert mode planning grid) - enhanced-allocation (62 tasks, planning fidelity + reporting) Sync all delta specs to main specs/: - api-resource-standard: API response standardization - capacity-expert-mode: Expert mode toggle, grid, KPIs, batch API - resource-allocation: Month execution comparison, bulk, untracked - untracked-allocation: Null team member support - allocation-indicators: Variance indicators (red/amber/neutral) - monthly-budget: Explicit project-month planning All changes verified and tested (157 tests passing).
This commit is contained in:
65
openspec/specs/allocation-indicators/spec.md
Normal file
65
openspec/specs/allocation-indicators/spec.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Purpose
|
||||
|
||||
TBD
|
||||
|
||||
# Requirements
|
||||
|
||||
## Requirement: Use minimal status palette
|
||||
|
||||
The system SHALL use a minimal indicator palette:
|
||||
- `OVER` -> red
|
||||
- `UNDER` -> amber
|
||||
- `MATCH/SETTLED` -> neutral
|
||||
|
||||
### Scenario: Match is neutral
|
||||
- **GIVEN** row variance equals 0
|
||||
- **WHEN** rendering status
|
||||
- **THEN** status uses neutral styling
|
||||
- **AND** no additional success color emphasis is required
|
||||
|
||||
## Requirement: Place indicators at summary edges
|
||||
|
||||
The system SHALL prioritize indicator display on row/column summary edges.
|
||||
|
||||
### Scenario: Row-level over-allocation indicator
|
||||
- **GIVEN** project row total exceeds selected month plan
|
||||
- **WHEN** allocation grid renders
|
||||
- **THEN** project row summary status shows `OVER` in red
|
||||
|
||||
### Scenario: Column-level over-capacity indicator
|
||||
- **GIVEN** member column total exceeds member month capacity
|
||||
- **WHEN** allocation grid renders
|
||||
- **THEN** member column summary status shows `OVER` in red
|
||||
|
||||
### Scenario: Under-allocation indicator
|
||||
- **GIVEN** row or column total is below comparison target
|
||||
- **WHEN** grid renders
|
||||
- **THEN** summary status shows `UNDER` in amber
|
||||
|
||||
## Requirement: Keep indicators explainable
|
||||
|
||||
The system SHALL provide text status labels with numeric deltas for accessibility and clarity.
|
||||
|
||||
### Scenario: Color is not sole signal
|
||||
- **WHEN** status is rendered
|
||||
- **THEN** UI includes text label (`OVER`/`UNDER`) and delta value
|
||||
|
||||
## Requirement: Distinguish project and resource variance semantics
|
||||
|
||||
Project variance and resource variance SHALL remain separate.
|
||||
|
||||
### Scenario: Project over, resource under
|
||||
- **GIVEN** a project row is `OVER`
|
||||
- **AND** a member column is `UNDER`
|
||||
- **WHEN** indicators render
|
||||
- **THEN** each axis displays its own status independently
|
||||
|
||||
## Requirement: Allocation indicator source
|
||||
|
||||
Indicator semantics in execution surface SHALL compare:
|
||||
- project row totals vs **selected month planned hours**
|
||||
- member column totals vs **selected month capacity**
|
||||
|
||||
## Requirement: Color usage policy
|
||||
|
||||
The system SHALL use minimal red/amber/neutral policy with status emphasis on summary edges (not broad RED/YELLOW/GREEN/GRAY usage in many cells).
|
||||
205
openspec/specs/api-resource-standard/spec.md
Normal file
205
openspec/specs/api-resource-standard/spec.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# Purpose
|
||||
|
||||
Define the API resource standardization requirements for Headroom. All API responses MUST follow Laravel API Resource format with a consistent `"data"` wrapper to ensure predictable response structures across the entire API surface.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: API Response Standardization
|
||||
All API responses MUST follow Laravel API Resource format with consistent `"data"` wrapper.
|
||||
|
||||
#### Scenario: Single resource response
|
||||
- **WHEN** an API endpoint returns a single model
|
||||
- **THEN** the response MUST have a `"data"` key containing the resource
|
||||
- **AND** the resource MUST include all required fields defined in the resource class
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "John Doe",
|
||||
"role": {
|
||||
"id": 1,
|
||||
"name": "Backend Developer"
|
||||
},
|
||||
"hourly_rate": 150.00,
|
||||
"active": true,
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Scenario: Collection response
|
||||
- **WHEN** an API endpoint returns multiple models
|
||||
- **THEN** the response MUST have a `"data"` key containing an array
|
||||
- **AND** each item in the array MUST follow the single resource format
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "John Doe",
|
||||
"role": { "id": 1, "name": "Backend Developer" },
|
||||
"hourly_rate": 150.00,
|
||||
"active": true,
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "660e8400-e29b-41d4-a716-446655440001",
|
||||
"name": "Jane Smith",
|
||||
"role": { "id": 2, "name": "Frontend Developer" },
|
||||
"hourly_rate": 140.00,
|
||||
"active": true,
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Scenario: Nested relationships
|
||||
- **WHEN** a resource has relationships (e.g., TeamMember has Role)
|
||||
- **THEN** the relationship MUST be nested within the resource
|
||||
- **AND** the nested resource SHOULD also follow the standard format
|
||||
|
||||
#### Scenario: Error responses
|
||||
- **WHEN** an error occurs (validation, not found, etc.)
|
||||
- **THEN** the response format remains unchanged (no `"data"` wrapper for errors)
|
||||
- **AND** errors continue to use standard Laravel error format:
|
||||
```json
|
||||
{
|
||||
"message": "The given data was invalid.",
|
||||
"errors": {
|
||||
"field": ["Error message"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Requirement: Resource Classes
|
||||
|
||||
The following resource classes MUST be implemented:
|
||||
|
||||
**UserResource**
|
||||
- **Model:** User
|
||||
- **Purpose:** Transform user data for API responses
|
||||
- **Fields:** id (UUID), name (string), email (string), role (RoleResource when loaded)
|
||||
- **Excluded:** password, remember_token
|
||||
|
||||
**RoleResource**
|
||||
- **Model:** Role
|
||||
- **Purpose:** Transform role data
|
||||
- **Fields:** id (integer), name (string), description (string|null)
|
||||
|
||||
**TeamMemberResource**
|
||||
- **Model:** TeamMember
|
||||
- **Purpose:** Transform team member data
|
||||
- **Fields:** id (UUID), name (string), role (RoleResource), hourly_rate (number), active (boolean), created_at, updated_at (ISO 8601)
|
||||
|
||||
**ProjectStatusResource**
|
||||
- **Model:** ProjectStatus
|
||||
- **Purpose:** Transform project status data
|
||||
- **Fields:** id (integer), name (string), order (integer), is_active (boolean), is_billable (boolean)
|
||||
|
||||
**ProjectTypeResource**
|
||||
- **Model:** ProjectType
|
||||
- **Purpose:** Transform project type data
|
||||
- **Fields:** id (integer), name (string), description (string|null)
|
||||
|
||||
**ProjectResource**
|
||||
- **Model:** Project
|
||||
- **Purpose:** Transform project data
|
||||
- **Fields:** id (UUID), code (string), title (string), status (ProjectStatusResource), type (ProjectTypeResource), approved_estimate (number|null), forecasted_effort (object), start_date, end_date (date|null), created_at, updated_at (ISO 8601)
|
||||
|
||||
**HolidayResource**
|
||||
- **Model:** Holiday
|
||||
- **Purpose:** Transform holiday data
|
||||
- **Fields:** id (UUID), date (date), name (string), description (string|null)
|
||||
|
||||
**PtoResource**
|
||||
- **Model:** Pto
|
||||
- **Purpose:** Transform PTO request data
|
||||
- **Fields:** id (UUID), team_member (TeamMemberResource when loaded), team_member_id (UUID), start_date, end_date (date), reason (string|null), status (string: pending|approved|rejected), created_at (ISO 8601)
|
||||
|
||||
**CapacityResource**
|
||||
- **Model:** N/A (calculated data)
|
||||
- **Purpose:** Transform individual capacity calculation
|
||||
- **Fields:** team_member_id (UUID), month (YYYY-MM), working_days (integer), person_days (number), hours (integer), details (array of day-by-day breakdown)
|
||||
|
||||
**TeamCapacityResource**
|
||||
- **Model:** N/A (calculated data)
|
||||
- **Purpose:** Transform team capacity aggregation
|
||||
- **Fields:** month (YYYY-MM), total_person_days (number), total_hours (integer), members (array of member capacities)
|
||||
|
||||
**RevenueResource**
|
||||
- **Model:** N/A (calculated data)
|
||||
- **Purpose:** Transform revenue calculation
|
||||
- **Fields:** month (YYYY-MM), possible_revenue (number), member_revenues (array of individual revenues)
|
||||
|
||||
### Requirement: API Endpoints Updated Format
|
||||
|
||||
All existing endpoints remain functionally identical, only response format changes to use Resource classes with `"data"` wrapper:
|
||||
|
||||
**Auth Endpoints:**
|
||||
- POST /api/auth/login → UserResource with tokens
|
||||
- POST /api/auth/refresh → UserResource with new token
|
||||
|
||||
**Team Member Endpoints:**
|
||||
- GET /api/team-members → TeamMemberResource[] (collection)
|
||||
- POST /api/team-members → TeamMemberResource (single)
|
||||
- GET /api/team-members/{id} → TeamMemberResource (single)
|
||||
- PUT /api/team-members/{id} → TeamMemberResource (single)
|
||||
- DELETE /api/team-members/{id} → { message: "..." }
|
||||
|
||||
**Project Endpoints:**
|
||||
- GET /api/projects → ProjectResource[] (collection)
|
||||
- POST /api/projects → ProjectResource (single)
|
||||
- GET /api/projects/{id} → ProjectResource (single)
|
||||
- PUT /api/projects/{id} → ProjectResource (single)
|
||||
- PUT /api/projects/{id}/status → ProjectResource (single)
|
||||
- PUT /api/projects/{id}/estimate → ProjectResource (single)
|
||||
- PUT /api/projects/{id}/forecast → ProjectResource (single)
|
||||
- DELETE /api/projects/{id} → { message: "..." }
|
||||
|
||||
**Capacity Endpoints:**
|
||||
- GET /api/capacity → CapacityResource (single)
|
||||
- GET /api/capacity/team → TeamCapacityResource (single)
|
||||
- GET /api/capacity/revenue → RevenueResource (single)
|
||||
|
||||
**Holiday Endpoints:**
|
||||
- GET /api/holidays → HolidayResource[] (collection)
|
||||
- POST /api/holidays → HolidayResource (single)
|
||||
- DELETE /api/holidays/{id} → { message: "..." }
|
||||
|
||||
**PTO Endpoints:**
|
||||
- GET /api/ptos → PtoResource[] (collection)
|
||||
- POST /api/ptos → PtoResource (single)
|
||||
- PUT /api/ptos/{id}/approve → PtoResource (single)
|
||||
|
||||
### Requirement: Test Coverage
|
||||
|
||||
**Resource Unit Tests**
|
||||
Each resource MUST have unit tests verifying:
|
||||
1. Single resource returns `"data"` wrapper
|
||||
2. Collection returns `"data"` array wrapper
|
||||
3. All expected fields are present
|
||||
4. Sensitive fields are excluded (e.g., password)
|
||||
5. Relationships are properly nested
|
||||
6. Date formatting follows ISO 8601
|
||||
|
||||
**Feature Test Updates**
|
||||
All 63 existing feature tests MUST be updated to:
|
||||
1. Assert response has `"data"` key
|
||||
2. Access nested data via `response.json()['data']`
|
||||
3. Verify collection responses have `"data"` array
|
||||
|
||||
**E2E Test Verification**
|
||||
All 134 E2E tests MUST pass after frontend API client is updated.
|
||||
|
||||
### Requirement: Deprecated Patterns
|
||||
|
||||
The following patterns are DEPRECATED and MUST NOT be used:
|
||||
- Direct `response()->json($model)` calls in controllers
|
||||
- Raw array/object responses without `"data"` wrapper
|
||||
170
openspec/specs/capacity-expert-mode/spec.md
Normal file
170
openspec/specs/capacity-expert-mode/spec.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# Purpose
|
||||
|
||||
TBD
|
||||
|
||||
# Requirements
|
||||
|
||||
## Requirement: Toggle Expert Mode
|
||||
|
||||
The system SHALL provide a toggle switch on the Capacity Planning page that enables or disables Expert Mode. The toggle SHALL be persisted in `localStorage` under the key `headroom.capacity.expertMode` so the user's preference survives page reloads.
|
||||
|
||||
### Scenario: Toggle defaults to off
|
||||
- **WHEN** a user visits the Capacity Planning page for the first time
|
||||
- **THEN** Expert Mode is off and the standard calendar view is shown
|
||||
|
||||
### Scenario: Toggle persists across reloads
|
||||
- **WHEN** a user enables Expert Mode and reloads the page
|
||||
- **THEN** Expert Mode is still enabled and the grid view is shown
|
||||
|
||||
### Scenario: Toggle is right-aligned on the tabs row
|
||||
- **WHEN** the Capacity Planning page is rendered
|
||||
- **THEN** the Expert Mode toggle appears right-aligned on the same row as the Calendar, Summary, Holidays, and PTO tabs
|
||||
|
||||
### Scenario: Switching mode with unsaved changes warns user
|
||||
- **WHEN** a user has dirty (unsaved) cells in the Expert Mode grid
|
||||
- **AND** the user toggles Expert Mode off
|
||||
- **THEN** the system shows a confirmation dialog: "You have unsaved changes. Discard and switch?"
|
||||
- **AND** if confirmed, changes are discarded and the calendar view is shown
|
||||
|
||||
---
|
||||
|
||||
## Requirement: Display Expert Mode planning grid
|
||||
|
||||
The system SHALL render a dense planning grid when Expert Mode is enabled. The grid SHALL show all active team members as rows and all days of the selected month as columns.
|
||||
|
||||
### Scenario: Grid shows all active team members
|
||||
- **WHEN** Expert Mode is enabled for a given month
|
||||
- **THEN** each active team member appears as a row in the grid
|
||||
- **AND** inactive team members are excluded
|
||||
|
||||
### Scenario: Grid shows all days of the month as columns
|
||||
- **WHEN** Expert Mode is enabled for February 2026
|
||||
- **THEN** the grid has 28 columns (one per calendar day)
|
||||
- **AND** each column header shows the day number
|
||||
|
||||
### Scenario: Weekend columns are visually distinct
|
||||
- **WHEN** the grid is rendered
|
||||
- **THEN** weekend columns (Saturday, Sunday) are visually distinguished (e.g. muted background)
|
||||
|
||||
### Scenario: Holiday columns are visually distinct
|
||||
- **WHEN** a day in the month is a company holiday
|
||||
- **THEN** that column header is visually marked as a holiday
|
||||
|
||||
### Scenario: Grid loads existing availability data
|
||||
- **WHEN** Expert Mode grid is opened for a month where availability overrides exist
|
||||
- **THEN** each cell pre-populates with the stored token matching the saved availability value
|
||||
|
||||
---
|
||||
|
||||
## Requirement: Cell token input and validation
|
||||
|
||||
The system SHALL accept exactly the following tokens in each grid cell: `H`, `O`, `0`, `.5`, `0.5`, `1`. Any other value SHALL be treated as invalid.
|
||||
|
||||
### Scenario: Valid token accepted on blur
|
||||
- **WHEN** a user types `1` into a cell and moves focus away
|
||||
- **THEN** the cell displays `1` and is marked valid
|
||||
|
||||
### Scenario: Valid token `.5` normalized on blur
|
||||
- **WHEN** a user types `.5` into a cell and moves focus away
|
||||
- **THEN** the cell displays `0.5` and is marked valid with numeric value `0.5`
|
||||
|
||||
### Scenario: `H` and `O` accepted on any date
|
||||
- **WHEN** a user types `H` or `O` into any cell (weekend, holiday, or working day)
|
||||
- **THEN** the cell is marked valid with numeric value `0`
|
||||
- **AND** the display shows the typed token (`H` or `O`)
|
||||
|
||||
### Scenario: Invalid token marked red on blur
|
||||
- **WHEN** a user types `2` or `abc` or any value not in the allowed set into a cell and moves focus away
|
||||
- **THEN** the cell border turns red
|
||||
- **AND** the raw text is preserved so the user can correct it
|
||||
|
||||
### Scenario: Submit disabled while invalid cell exists
|
||||
- **WHEN** any cell in the grid has an invalid token
|
||||
- **THEN** the Submit button is disabled
|
||||
|
||||
### Scenario: `0` auto-renders as `O` on weekend column
|
||||
- **WHEN** a user types `0` into a cell whose column is a weekend day and moves focus away
|
||||
- **THEN** the cell displays `O` (not `0`)
|
||||
- **AND** the numeric value is `0`
|
||||
|
||||
### Scenario: `0` auto-renders as `H` on holiday column
|
||||
- **WHEN** a user types `0` into a cell whose column is a company holiday and moves focus away
|
||||
- **THEN** the cell displays `H` (not `0`)
|
||||
- **AND** the numeric value is `0`
|
||||
|
||||
---
|
||||
|
||||
## Requirement: Live KPI bar in Expert Mode
|
||||
|
||||
The system SHALL display a live KPI bar above the grid showing team-level Capacity (person-days) and Projected Revenue, updating in real time as cell values change.
|
||||
|
||||
### Scenario: KPI bar shows correct capacity on load
|
||||
- **WHEN** Expert Mode grid loads for a month
|
||||
- **THEN** the KPI bar shows total team capacity in person-days matching the sum of all members' numeric cell values
|
||||
|
||||
### Scenario: KPI bar updates when a cell changes
|
||||
- **WHEN** a user changes a valid cell from `1` to `0.5`
|
||||
- **THEN** the KPI bar immediately reflects the reduced capacity and revenue without a page reload
|
||||
|
||||
### Scenario: Invalid cells excluded from KPI totals
|
||||
- **WHEN** a cell contains an invalid token
|
||||
- **THEN** that cell contributes `0` to the KPI totals (not `null` or an error)
|
||||
|
||||
### Scenario: Projected Revenue uses hourly rate and hours per day
|
||||
- **WHEN** the KPI bar calculates projected revenue
|
||||
- **THEN** revenue = SUM(member capacity in person-days × hourly_rate × 8 hours/day) for all active members
|
||||
|
||||
---
|
||||
|
||||
## Requirement: Batch save availability from Expert Mode
|
||||
|
||||
The system SHALL allow saving all pending cell changes in a single batch operation via a Submit button.
|
||||
|
||||
### Scenario: Submit saves all dirty valid cells
|
||||
- **WHEN** a user has changed multiple cells and clicks Submit
|
||||
- **THEN** the system sends a single batch request with all dirty cell values
|
||||
- **AND** on success, all dirty flags are cleared and a success toast is shown
|
||||
|
||||
### Scenario: Submit is disabled when no dirty cells exist
|
||||
- **WHEN** no cells have been changed since the last save (or since load)
|
||||
- **THEN** the Submit button is disabled
|
||||
|
||||
### Scenario: Submit is disabled when any invalid cell exists
|
||||
- **WHEN** at least one cell contains an invalid token
|
||||
- **THEN** the Submit button is disabled regardless of other valid dirty cells
|
||||
|
||||
### Scenario: Submit failure shows error
|
||||
- **WHEN** the batch save API call fails
|
||||
- **THEN** the system shows an error alert
|
||||
- **AND** dirty flags are preserved so the user can retry
|
||||
|
||||
### Scenario: Batch endpoint validates each availability value
|
||||
- **WHEN** the batch endpoint receives an availability value not in `[0, 0.5, 1]`
|
||||
- **THEN** the system returns HTTP 422 with a validation error message
|
||||
|
||||
---
|
||||
|
||||
## Requirement: Batch availability API endpoint
|
||||
|
||||
The system SHALL expose `POST /api/capacity/availability/batch` accepting an array of availability updates for a given month.
|
||||
|
||||
### Scenario: Batch endpoint saves multiple updates
|
||||
- **WHEN** a POST request is made to `/api/capacity/availability/batch` with a valid month and array of updates
|
||||
- **THEN** the system upserts each `{team_member_id, date, availability}` entry
|
||||
- **AND** returns HTTP 200 with `{ "data": { "saved": <count>, "month": "<YYYY-MM>" } }`
|
||||
|
||||
### Scenario: Batch endpoint invalidates cache once
|
||||
- **WHEN** a batch save completes for a given month
|
||||
- **THEN** the month-level capacity cache is flushed exactly once (not once per row)
|
||||
|
||||
### Scenario: Batch endpoint rejects invalid team_member_id
|
||||
- **WHEN** a batch request contains a `team_member_id` that does not exist
|
||||
- **THEN** the system returns HTTP 422 with a validation error
|
||||
|
||||
### Scenario: Batch endpoint rejects invalid availability value
|
||||
- **WHEN** a batch request contains an `availability` value not in `[0, 0.5, 1]`
|
||||
- **THEN** the system returns HTTP 422 with a validation error
|
||||
|
||||
### Scenario: Empty batch is a no-op
|
||||
- **WHEN** a POST request is made with an empty `updates` array
|
||||
- **THEN** the system returns HTTP 200 with `{ "data": { "saved": 0, "month": "<YYYY-MM>" } }`
|
||||
81
openspec/specs/monthly-budget/spec.md
Normal file
81
openspec/specs/monthly-budget/spec.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Purpose
|
||||
|
||||
TBD
|
||||
|
||||
# Requirements
|
||||
|
||||
## Requirement: Manager enters explicit monthly plan per project
|
||||
|
||||
The system SHALL allow managers to set planned hours for each project-month cell.
|
||||
|
||||
### Scenario: Set monthly plan across multiple months
|
||||
- **GIVEN** project `PROJ-001` has `approved_estimate = 3000`
|
||||
- **WHEN** manager sets Jan=1200, Feb=1400, Mar=400
|
||||
- **THEN** the system stores those exact values
|
||||
- **AND** no derived monthly average is applied
|
||||
|
||||
### Scenario: Edit monthly plan cell inline
|
||||
- **GIVEN** a month-plan grid cell contains 1200
|
||||
- **WHEN** manager edits the cell to 1100 and commits
|
||||
- **THEN** the system persists 1100
|
||||
- **AND** reconciliation status recalculates immediately
|
||||
|
||||
## Requirement: Reconcile month-plan sum against lifecycle approved estimate
|
||||
|
||||
The system SHALL compute reconciliation status per project based on:
|
||||
`sum(non-null monthly planned hours)` vs `approved_estimate`.
|
||||
|
||||
### Scenario: OVER reconciliation
|
||||
- **GIVEN** `approved_estimate = 3000`
|
||||
- **AND** month-plan sum is 3200
|
||||
- **THEN** status is `OVER`
|
||||
|
||||
### Scenario: UNDER reconciliation
|
||||
- **GIVEN** `approved_estimate = 3000`
|
||||
- **AND** month-plan sum is 2800
|
||||
- **THEN** status is `UNDER`
|
||||
|
||||
### Scenario: MATCH reconciliation
|
||||
- **GIVEN** `approved_estimate = 3000`
|
||||
- **AND** month-plan sum is 3000
|
||||
- **THEN** status is `MATCH`
|
||||
|
||||
## Requirement: Preserve blank month semantics
|
||||
|
||||
The planning grid SHALL keep unset months visually blank and semantically distinct from explicit zero.
|
||||
|
||||
### Scenario: Blank remains blank
|
||||
- **GIVEN** no plan exists for April
|
||||
- **WHEN** manager views planning grid
|
||||
- **THEN** April cell is blank (no `0` shown)
|
||||
|
||||
### Scenario: Clear cell sets blank semantics
|
||||
- **GIVEN** a month cell has planned value
|
||||
- **WHEN** manager clears the cell and commits
|
||||
- **THEN** the month is stored as blank/unset semantics
|
||||
- **AND** planning UI displays blank
|
||||
|
||||
## Requirement: Allocation variance uses blank plan as zero
|
||||
|
||||
For allocation variance computation only, missing month plan SHALL be treated as planned `0`.
|
||||
|
||||
### Scenario: Allocate against blank plan month
|
||||
- **GIVEN** no plan is set for selected month
|
||||
- **AND** project allocations total 40h
|
||||
- **WHEN** variance is computed
|
||||
- **THEN** planned value used is 0
|
||||
- **AND** row variance is +40
|
||||
- **AND** allocation operation remains allowed
|
||||
|
||||
## Requirement: Grid-first planning interaction
|
||||
|
||||
Project-month planning SHALL be managed in a grid-first interface.
|
||||
|
||||
### Scenario: Keyboard-first editing
|
||||
- **WHEN** manager navigates month-plan grid with keyboard
|
||||
- **THEN** inline cell editing and commit are supported
|
||||
- **AND** modal interaction is not required for normal edits
|
||||
|
||||
## Requirement: Monthly budget derivation
|
||||
|
||||
Monthly plan values SHALL be explicit manager-entered project-month values; no derivation formula is used for planning behavior (rejected: `approved_estimate / 12`).
|
||||
41
openspec/specs/resource-allocation/spec.md
Normal file
41
openspec/specs/resource-allocation/spec.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Purpose
|
||||
|
||||
TBD
|
||||
|
||||
# Requirements
|
||||
|
||||
## Requirement: Month execution comparison target
|
||||
|
||||
The system SHALL compare selected month project allocation against explicit project-month planned hours (not derived or lifecycle assumptions).
|
||||
|
||||
### Scenario: Compare row total to month plan
|
||||
- **GIVEN** selected month plan for project is 1200h
|
||||
- **AND** project allocations total 1300h
|
||||
- **THEN** project row variance is +100h
|
||||
- **AND** row status is `OVER`
|
||||
|
||||
### Scenario: Blank month plan comparison
|
||||
- **GIVEN** selected month has no plan value set
|
||||
- **AND** project allocations total 50h
|
||||
- **THEN** comparison target is 0h
|
||||
- **AND** row status is `OVER`
|
||||
- **AND** allocation remains allowed
|
||||
|
||||
## Requirement: Bulk allocation behavior
|
||||
|
||||
The system SHALL save valid items even if some items fail (partial bulk success).
|
||||
|
||||
### Scenario: Partial bulk success
|
||||
- **WHEN** 10 allocation items are submitted and 2 fail validation
|
||||
- **THEN** 8 valid items are persisted
|
||||
- **AND** failed items return per-index validation errors
|
||||
- **AND** response includes summary created/failed counts
|
||||
|
||||
## Requirement: Untracked execution semantics
|
||||
|
||||
The system SHALL treat `team_member_id = null` as untracked effort.
|
||||
|
||||
### Scenario: Untracked counted in project, excluded from capacity
|
||||
- **WHEN** untracked allocation exists for selected month
|
||||
- **THEN** project totals include it
|
||||
- **AND** member capacity/utilization computations exclude it
|
||||
49
openspec/specs/untracked-allocation/spec.md
Normal file
49
openspec/specs/untracked-allocation/spec.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Purpose
|
||||
|
||||
TBD
|
||||
|
||||
# Requirements
|
||||
|
||||
## Requirement: Support null team member in allocation APIs
|
||||
|
||||
The system SHALL allow allocation records with `team_member_id = null`.
|
||||
|
||||
### Scenario: Create untracked allocation
|
||||
- **GIVEN** user has allocation create permission
|
||||
- **WHEN** POST /api/allocations with `team_member_id = null`
|
||||
- **THEN** allocation is created successfully
|
||||
|
||||
### Scenario: Bulk create with mixed tracked/untracked
|
||||
- **GIVEN** a bulk payload contains tracked and untracked entries
|
||||
- **WHEN** POST /api/allocations/bulk is executed
|
||||
- **THEN** untracked entries with valid data are processed successfully
|
||||
|
||||
## Requirement: Include untracked in project totals
|
||||
|
||||
Untracked hours SHALL contribute to project-level and grand totals.
|
||||
|
||||
### Scenario: Project total includes untracked
|
||||
- **GIVEN** project has tracked 80h and untracked 20h in selected month
|
||||
- **WHEN** project row total is computed
|
||||
- **THEN** row total is 100h
|
||||
|
||||
## Requirement: Exclude untracked from member capacity metrics
|
||||
|
||||
Untracked allocations SHALL NOT contribute to any team member utilization/capacity variance.
|
||||
|
||||
### Scenario: Member utilization ignores untracked
|
||||
- **GIVEN** selected month has untracked allocations
|
||||
- **WHEN** member column totals and capacity variance are computed
|
||||
- **THEN** untracked rows are excluded from member computations
|
||||
|
||||
## Requirement: Present untracked in execution grid
|
||||
|
||||
The allocation grid SHALL expose untracked as a first-class execution bucket.
|
||||
|
||||
### Scenario: Untracked column visible
|
||||
- **WHEN** manager opens allocation execution grid
|
||||
- **THEN** untracked column/bucket is visible and editable
|
||||
|
||||
## Requirement: Capacity validation
|
||||
|
||||
Capacity validation SHALL be skipped for untracked allocations (`team_member_id = null`).
|
||||
Reference in New Issue
Block a user