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:
109
backend/tests/Unit/Services/UtilizationFormatterTest.php
Normal file
109
backend/tests/Unit/Services/UtilizationFormatterTest.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
use App\Services\UtilizationFormatter;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @mixin \Tests\TestCase
|
||||
*/
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
test('7.3.2a UtilizationFormatter getIndicator returns correct values', function () {
|
||||
$formatter = app(UtilizationFormatter::class);
|
||||
|
||||
// Under-utilized (< 70%)
|
||||
expect($formatter->getIndicator(0))->toBe('gray')
|
||||
->and($formatter->getIndicator(50))->toBe('gray')
|
||||
->and($formatter->getIndicator(69.9))->toBe('gray');
|
||||
|
||||
// Low utilization (70-80%)
|
||||
expect($formatter->getIndicator(70))->toBe('blue')
|
||||
->and($formatter->getIndicator(75))->toBe('blue')
|
||||
->and($formatter->getIndicator(79.9))->toBe('blue');
|
||||
|
||||
// Optimal (80-100%)
|
||||
expect($formatter->getIndicator(80))->toBe('green')
|
||||
->and($formatter->getIndicator(90))->toBe('green')
|
||||
->and($formatter->getIndicator(100))->toBe('green');
|
||||
|
||||
// Caution (100-110%)
|
||||
expect($formatter->getIndicator(100.1))->toBe('yellow')
|
||||
->and($formatter->getIndicator(105))->toBe('yellow')
|
||||
->and($formatter->getIndicator(110))->toBe('yellow');
|
||||
|
||||
// Over-allocated (> 110%)
|
||||
expect($formatter->getIndicator(110.1))->toBe('red')
|
||||
->and($formatter->getIndicator(120))->toBe('red')
|
||||
->and($formatter->getIndicator(200))->toBe('red');
|
||||
});
|
||||
|
||||
test('7.3.2b UtilizationFormatter getDisplayColor maps yellow to amber', function () {
|
||||
$formatter = app(UtilizationFormatter::class);
|
||||
|
||||
expect($formatter->getDisplayColor(50))->toBe('gray')
|
||||
->and($formatter->getDisplayColor(75))->toBe('blue')
|
||||
->and($formatter->getDisplayColor(90))->toBe('green')
|
||||
->and($formatter->getDisplayColor(105))->toBe('amber')
|
||||
->and($formatter->getDisplayColor(120))->toBe('red');
|
||||
});
|
||||
|
||||
test('7.3.2c UtilizationFormatter getStatusDescription returns correct descriptions', function () {
|
||||
$formatter = app(UtilizationFormatter::class);
|
||||
|
||||
expect($formatter->getStatusDescription(50))->toBe('Under-utilized')
|
||||
->and($formatter->getStatusDescription(75))->toBe('Low utilization')
|
||||
->and($formatter->getStatusDescription(90))->toBe('Optimal')
|
||||
->and($formatter->getStatusDescription(105))->toBe('High utilization')
|
||||
->and($formatter->getStatusDescription(120))->toBe('Over-allocated');
|
||||
});
|
||||
|
||||
test('7.3.2d UtilizationFormatter formatPercentage formats correctly', function () {
|
||||
$formatter = app(UtilizationFormatter::class);
|
||||
|
||||
expect($formatter->formatPercentage(87.54))->toBe('87.5%')
|
||||
->and($formatter->formatPercentage(87.54, 2))->toBe('87.54%')
|
||||
->and($formatter->formatPercentage(100))->toBe('100.0%');
|
||||
});
|
||||
|
||||
test('7.3.2e UtilizationFormatter formatHours formats correctly', function () {
|
||||
$formatter = app(UtilizationFormatter::class);
|
||||
|
||||
expect($formatter->formatHours(160))->toBe('160.0h')
|
||||
->and($formatter->formatHours(160.5, 2))->toBe('160.50h');
|
||||
});
|
||||
|
||||
test('7.3.2f UtilizationFormatter getTailwindClasses returns correct classes', function () {
|
||||
$formatter = app(UtilizationFormatter::class);
|
||||
|
||||
$classes = $formatter->getTailwindClasses(90);
|
||||
expect($classes['bg'])->toBe('bg-green-100')
|
||||
->and($classes['text'])->toBe('text-green-700')
|
||||
->and($classes['border'])->toBe('border-green-300');
|
||||
});
|
||||
|
||||
test('7.3.2g UtilizationFormatter getDaisyuiBadgeClass returns correct classes', function () {
|
||||
$formatter = app(UtilizationFormatter::class);
|
||||
|
||||
expect($formatter->getDaisyuiBadgeClass(50))->toBe('badge-neutral')
|
||||
->and($formatter->getDaisyuiBadgeClass(75))->toBe('badge-info')
|
||||
->and($formatter->getDaisyuiBadgeClass(90))->toBe('badge-success')
|
||||
->and($formatter->getDaisyuiBadgeClass(105))->toBe('badge-warning')
|
||||
->and($formatter->getDaisyuiBadgeClass(120))->toBe('badge-error');
|
||||
});
|
||||
|
||||
test('7.3.2h UtilizationFormatter formatUtilizationResponse returns complete structure', function () {
|
||||
$formatter = app(UtilizationFormatter::class);
|
||||
|
||||
$response = $formatter->formatUtilizationResponse(87.5, 160, 140);
|
||||
|
||||
expect($response)->toHaveKeys(['capacity', 'allocated', 'utilization', 'indicator', 'display'])
|
||||
->and($response['capacity'])->toBe(160.0)
|
||||
->and($response['allocated'])->toBe(140.0)
|
||||
->and($response['utilization'])->toBe(87.5)
|
||||
->and($response['indicator'])->toBe('green')
|
||||
->and($response['display']['percentage'])->toBe('87.5%')
|
||||
->and($response['display']['color'])->toBe('green')
|
||||
->and($response['display']['status'])->toBe('Optimal')
|
||||
->and($response['display']['badge_class'])->toBe('badge-success');
|
||||
});
|
||||
Reference in New Issue
Block a user