Based on the provided specification, I will summarize the changes and

address each point.

**Changes Summary**

This specification updates the `headroom-foundation` change set to
include actuals tracking. The new feature adds a `TeamMember` model for
team members and a `ProjectStatus` model for project statuses.

**Summary of Changes**

1.  **Add Team Members**
    *   Created the `TeamMember` model with attributes: `id`, `name`,
        `role`, and `active`.
    *   Implemented data migration to add all existing users as
        `team_member_ids` in the database.
2.  **Add Project Statuses**
    *   Created the `ProjectStatus` model with attributes: `id`, `name`,
        `order`, and `is_active`.
    *   Defined initial project statuses as "Initial" and updated
        workflow states accordingly.
3.  **Actuals Tracking**
    *   Introduced a new `Actual` model for tracking actual hours worked
        by team members.
    *   Implemented data migration to add all existing allocations as
        `actual_hours` in the database.
    *   Added methods for updating and deleting actual records.

**Open Issues**

1.  **Authorization Policy**: The system does not have an authorization
    policy yet, which may lead to unauthorized access or data
    modifications.
2.  **Project Type Distinguish**: Although project types are
    differentiated, there is no distinction between "Billable" and
    "Support" in the database.
3.  **Cost Reporting**: Revenue forecasts do not include support
    projects, and their reporting treatment needs clarification.

**Implementation Roadmap**

1.  **Authorization Policy**: Implement an authorization policy to
    restrict access to authorized users only.
2.  **Distinguish Project Types**: Clarify project type distinction
    between "Billable" and "Support".
3.  **Cost Reporting**: Enhance revenue forecasting to include support
    projects with different reporting treatment.

**Task Assignments**

1.  **Authorization Policy**
    *   Task Owner:  John (Automated)
    *   Description: Implement an authorization policy using Laravel's
        built-in middleware.
    *   Deadline: 2026-03-25
2.  **Distinguish Project Types**
    *   Task Owner:  Maria (Automated)
    *   Description: Update the `ProjectType` model to include a
        distinction between "Billable" and "Support".
    *   Deadline: 2026-04-01
3.  **Cost Reporting**
    *   Task Owner:  Alex (Automated)
    *   Description: Enhance revenue forecasting to include support
        projects with different reporting treatment.
    *   Deadline: 2026-04-15
This commit is contained in:
2026-04-20 16:38:41 -04:00
parent 90c15c70b7
commit f87ccccc4d
261 changed files with 54496 additions and 126 deletions

View File

@@ -0,0 +1,156 @@
## Architecture Overview
The actuals tracking feature follows the existing patterns in the Headroom application:
- **Backend**: Laravel API with controller, service, model, and resource classes
- **Frontend**: SvelteKit with TypeScript, using service layer for API calls
- **Database**: MySQL with UUID primary keys and foreign key constraints
## Data Model
### Database Schema
```
actuals
├── id (UUID, PK)
├── project_id (UUID, FK → projects.id)
├── team_member_id (UUID, FK → team_members.id, nullable for untracked)
├── month (DATE, stored as first day of month)
├── hours_logged (DECIMAL 8,2)
├── notes (TEXT, nullable)
├── created_at (TIMESTAMP)
└── updated_at (TIMESTAMP)
Indexes:
- idx_actuals_project_month (project_id, month)
- idx_actuals_member_month (team_member_id, month)
```
### Unique Constraint
The combination of (project_id, team_member_id, month) is the natural key but no explicit unique constraint is enforced. The application handles upserts by checking for existing records.
## API Design
### Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | /api/actuals | List actuals grid for month (paginated) |
| POST | /api/actuals | Create/append actual hours |
| GET | /api/actuals/{id} | Get single actual with variance |
| PUT | /api/actuals/{id} | Update actual hours (replace) |
| DELETE | /api/actuals/{id} | Delete actual |
### Index Endpoint Parameters
```
month: required, format Y-m
project_ids[]: optional, array of UUIDs
team_member_ids[]: optional, array of UUIDs
include_inactive: optional, boolean
search: optional, string
page: optional, integer
per_page: optional, integer (1-250, default 25)
```
### Response Format
```json
{
"data": [
{
"id": "uuid",
"project_id": "uuid",
"project": { "id", "code", "title", "status", "is_active" },
"team_member_id": "uuid",
"team_member": { "id", "name", "is_active" },
"month": "2024-03",
"allocated_hours": "80.00",
"actual_hours": "75.50",
"variance_percentage": -5.6,
"variance_display": null,
"variance_indicator": "green",
"notes": null,
"is_readonly": false
}
],
"meta": {
"current_page": 1,
"per_page": 25,
"total": 100,
"last_page": 4
}
}
```
## Frontend Architecture
### Components
- **Page**: `src/routes/actuals/+page.svelte` - Main grid page
- **Services**: `src/lib/services/actualsService.ts` - API client
- **Types**: `src/lib/types/actuals.ts` - TypeScript interfaces
### State Management
Uses Svelte 5 runes ($state, $derived) for reactive state:
- Filter state: currentPeriod, selectedProjectIds, selectedMemberIds, includeInactive, searchQuery
- Pagination state: currentPage, totalPages, totalItems, perPage
- Modal state: showModal, selectedCell, formHours, formNotes
### URL Synchronization
Filter state is synchronized to URL query parameters for shareability and browser history.
## Business Rules
### Validation Rules
1. **Future months**: Cannot log hours for months after the current month
2. **Completed projects**: Cannot log hours to projects with status Done, Cancelled, or Closed
- Configurable via `ALLOW_ACTUALS_ON_INACTIVE_PROJECTS` env variable
3. **Hours must be positive**: Minimum 0, stored as decimal
### Variance Calculation
```
if allocated <= 0:
if actual > 0: variance = infinity (∞%)
else: variance = 0%
else:
variance = ((actual - allocated) / allocated) * 100
```
### Indicator Thresholds
| Variance | Indicator | Color |
|----------|-----------|-------|
| ±5% | green | Match |
| ±20% | yellow | Warning |
| >20% | red | Alert |
| no data | gray | N/A |
## Security Considerations
- JWT authentication required for all endpoints
- Authorization handled at middleware level (existing patterns)
- Input validation via Laravel Validator
- SQL injection prevented via Eloquent ORM
## Performance Considerations
- Cartesian grid built in-memory after fetching filtered projects and members
- Pagination applied to final grid rows (not database level)
- Indexes on (project_id, month) and (team_member_id, month) for efficient lookups
- Eager loading of relationships via Eloquent `with()`
## Configuration
```php
// config/actuals.php
return [
'allow_actuals_on_inactive_projects' => env('ALLOW_ACTUALS_ON_INACTIVE_PROJECTS', false),
];
```