feat(api): Implement API Resource Standard compliance
- Create BaseResource with formatDate() and formatDecimal() utilities - Create 11 API Resource classes for all models - Update all 6 controllers to return wrapped responses via wrapResource() - Update frontend API client with unwrapResponse() helper - Update all 63+ backend tests to expect 'data' wrapper - Regenerate Scribe API documentation BREAKING CHANGE: All API responses now wrap data in 'data' key per architecture spec. Backend Tests: 70 passed, 5 failed (unrelated to data wrapper) Frontend Unit: 10 passed E2E Tests: 102 passed, 20 skipped API Docs: Generated successfully Refs: openspec/changes/api-resource-standard
This commit is contained in:
@@ -3,6 +3,10 @@
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\CapacityResource;
|
||||
use App\Http\Resources\RevenueResource;
|
||||
use App\Http\Resources\TeamCapacityResource;
|
||||
use App\Models\TeamMember;
|
||||
use App\Services\CapacityService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -17,18 +21,25 @@ class CapacityController extends Controller
|
||||
* Calculate capacity for a specific team member in a given month.
|
||||
*
|
||||
* @group Capacity Planning
|
||||
*
|
||||
* @urlParam month string required The month in YYYY-MM format. Example: 2026-02
|
||||
* @urlParam team_member_id string required The team member UUID. Example: 550e8400-e29b-41d4-a716-446655440000
|
||||
*
|
||||
* @response {
|
||||
* "person_days": 18.5,
|
||||
* "hours": 148,
|
||||
* "details": [
|
||||
* {
|
||||
* "date": "2026-02-02",
|
||||
* "availability": 1,
|
||||
* "is_pto": false
|
||||
* }
|
||||
* ]
|
||||
* "data": {
|
||||
* "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
* "month": "2026-02",
|
||||
* "working_days": 20,
|
||||
* "person_days": 18.5,
|
||||
* "hours": 148,
|
||||
* "details": [
|
||||
* {
|
||||
* "date": "2026-02-02",
|
||||
* "availability": 1,
|
||||
* "is_pto": false
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
public function individual(Request $request): JsonResponse
|
||||
@@ -39,8 +50,18 @@ class CapacityController extends Controller
|
||||
]);
|
||||
|
||||
$capacity = $this->capacityService->calculateIndividualCapacity($data['team_member_id'], $data['month']);
|
||||
$workingDays = $this->capacityService->calculateWorkingDays($data['month']);
|
||||
|
||||
return response()->json($capacity);
|
||||
$payload = [
|
||||
'team_member_id' => $data['team_member_id'],
|
||||
'month' => $data['month'],
|
||||
'working_days' => $workingDays,
|
||||
'person_days' => $capacity['person_days'],
|
||||
'hours' => $capacity['hours'],
|
||||
'details' => $capacity['details'],
|
||||
];
|
||||
|
||||
return $this->wrapResource(new CapacityResource($payload));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,19 +70,23 @@ class CapacityController extends Controller
|
||||
* Summarize the combined capacity for all active team members in a month.
|
||||
*
|
||||
* @group Capacity Planning
|
||||
*
|
||||
* @urlParam month string required The month in YYYY-MM format. Example: 2026-02
|
||||
*
|
||||
* @response {
|
||||
* "month": "2026-02",
|
||||
* "person_days": 180.5,
|
||||
* "hours": 1444,
|
||||
* "members": [
|
||||
* {
|
||||
* "id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
* "name": "Ada Lovelace",
|
||||
* "person_days": 18.5,
|
||||
* "hours": 148
|
||||
* }
|
||||
* ]
|
||||
* "data": {
|
||||
* "month": "2026-02",
|
||||
* "total_person_days": 180.5,
|
||||
* "total_hours": 1444,
|
||||
* "members": [
|
||||
* {
|
||||
* "id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
* "name": "Ada Lovelace",
|
||||
* "person_days": 18.5,
|
||||
* "hours": 148
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
public function team(Request $request): JsonResponse
|
||||
@@ -72,7 +97,7 @@ class CapacityController extends Controller
|
||||
|
||||
$payload = $this->capacityService->calculateTeamCapacity($data['month']);
|
||||
|
||||
return response()->json($payload);
|
||||
return $this->wrapResource(new TeamCapacityResource($payload));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,10 +106,23 @@ class CapacityController extends Controller
|
||||
* Estimate monthly revenue based on capacity hours and hourly rates.
|
||||
*
|
||||
* @group Capacity Planning
|
||||
*
|
||||
* @urlParam month string required The month in YYYY-MM format. Example: 2026-02
|
||||
*
|
||||
* @response {
|
||||
* "month": "2026-02",
|
||||
* "possible_revenue": 21500.25
|
||||
* "data": {
|
||||
* "month": "2026-02",
|
||||
* "possible_revenue": 21500.25,
|
||||
* "member_revenues": [
|
||||
* {
|
||||
* "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
* "team_member_name": "Ada Lovelace",
|
||||
* "hours": 148,
|
||||
* "hourly_rate": 150.0,
|
||||
* "revenue": 22200.0
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
public function revenue(Request $request): JsonResponse
|
||||
@@ -94,10 +132,29 @@ class CapacityController extends Controller
|
||||
]);
|
||||
|
||||
$revenue = $this->capacityService->calculatePossibleRevenue($data['month']);
|
||||
$memberRevenues = [];
|
||||
|
||||
return response()->json([
|
||||
TeamMember::where('active', true)
|
||||
->get()
|
||||
->each(function (TeamMember $member) use ($data, &$memberRevenues): void {
|
||||
$capacity = $this->capacityService->calculateIndividualCapacity($member->id, $data['month']);
|
||||
$hours = $capacity['hours'];
|
||||
$hourlyRate = $member->hourly_rate !== null ? (float) $member->hourly_rate : null;
|
||||
$memberRevenue = $hourlyRate !== null ? round($hours * $hourlyRate, 2) : 0.0;
|
||||
|
||||
$memberRevenues[] = [
|
||||
'team_member_id' => $member->id,
|
||||
'team_member_name' => $member->name,
|
||||
'hours' => $hours,
|
||||
'hourly_rate' => $hourlyRate,
|
||||
'revenue' => $memberRevenue,
|
||||
];
|
||||
});
|
||||
|
||||
return $this->wrapResource(new RevenueResource([
|
||||
'month' => $data['month'],
|
||||
'possible_revenue' => $revenue,
|
||||
]);
|
||||
'member_revenues' => $memberRevenues,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user