12 - Add allocation preview endpoint
This commit is contained in:
parent
4bf3aef610
commit
4a6e69d33b
3 changed files with 68 additions and 1 deletions
|
|
@ -3,15 +3,20 @@
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Requests\CalculateProjectionRequest;
|
use App\Http\Requests\CalculateProjectionRequest;
|
||||||
|
use App\Http\Requests\PreviewAllocationRequest;
|
||||||
use App\Http\Resources\ProjectionResource;
|
use App\Http\Resources\ProjectionResource;
|
||||||
|
use App\Models\Bucket;
|
||||||
use App\Models\Scenario;
|
use App\Models\Scenario;
|
||||||
|
use App\Services\Projection\PipelineAllocationService;
|
||||||
use App\Services\Projection\ProjectionGeneratorService;
|
use App\Services\Projection\ProjectionGeneratorService;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
class ProjectionController extends Controller
|
class ProjectionController extends Controller
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly ProjectionGeneratorService $projectionGeneratorService
|
private readonly ProjectionGeneratorService $projectionGeneratorService,
|
||||||
|
private readonly PipelineAllocationService $pipelineAllocationService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function calculate(CalculateProjectionRequest $request, Scenario $scenario): ProjectionResource
|
public function calculate(CalculateProjectionRequest $request, Scenario $scenario): ProjectionResource
|
||||||
|
|
@ -27,4 +32,45 @@ public function calculate(CalculateProjectionRequest $request, Scenario $scenari
|
||||||
|
|
||||||
return new ProjectionResource($projections);
|
return new ProjectionResource($projections);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function preview(PreviewAllocationRequest $request, Scenario $scenario): JsonResponse
|
||||||
|
{
|
||||||
|
$amountInCents = (int) round($request->input('amount') * 100);
|
||||||
|
|
||||||
|
$draws = $this->pipelineAllocationService->allocateInflow($scenario, $amountInCents);
|
||||||
|
|
||||||
|
/** @var array<int, Bucket> $bucketLookup */
|
||||||
|
$bucketLookup = $scenario->buckets->keyBy('id')->all();
|
||||||
|
|
||||||
|
$allocations = $draws->map(function ($draw) use ($bucketLookup) {
|
||||||
|
$bucket = $bucketLookup[$draw->bucket_id];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'bucket_id' => $bucket->uuid,
|
||||||
|
'bucket_name' => $bucket->name,
|
||||||
|
'bucket_type' => $bucket->type->value,
|
||||||
|
'allocated_amount' => $draw->amount_currency,
|
||||||
|
'remaining_capacity' => $this->remainingCapacity($bucket, $draw->amount_currency),
|
||||||
|
];
|
||||||
|
})->values();
|
||||||
|
|
||||||
|
$totalAllocatedCents = $draws->sum('amount');
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'allocations' => $allocations,
|
||||||
|
'total_allocated' => round($totalAllocatedCents / 100, 2),
|
||||||
|
'unallocated' => round(($amountInCents - $totalAllocatedCents) / 100, 2),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function remainingCapacity(Bucket $bucket, float $allocatedDollars): ?float
|
||||||
|
{
|
||||||
|
$effectiveCapacity = $bucket->getEffectiveCapacity();
|
||||||
|
|
||||||
|
if ($effectiveCapacity === PHP_FLOAT_MAX) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return round(max(0, $effectiveCapacity - $allocatedDollars), 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
20
app/Http/Requests/PreviewAllocationRequest.php
Normal file
20
app/Http/Requests/PreviewAllocationRequest.php
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class PreviewAllocationRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'amount' => ['required', 'numeric', 'min:0.01'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,6 +38,7 @@
|
||||||
|
|
||||||
// Projection routes (no auth required for MVP)
|
// Projection routes (no auth required for MVP)
|
||||||
Route::post('/scenarios/{scenario}/projections/calculate', [ProjectionController::class, 'calculate'])->name('projections.calculate');
|
Route::post('/scenarios/{scenario}/projections/calculate', [ProjectionController::class, 'calculate'])->name('projections.calculate');
|
||||||
|
Route::post('/scenarios/{scenario}/projections/preview', [ProjectionController::class, 'preview'])->name('projections.preview');
|
||||||
|
|
||||||
// Auth dashboard (hidden for single-scenario MVP, re-enable later)
|
// Auth dashboard (hidden for single-scenario MVP, re-enable later)
|
||||||
// Route::middleware(['auth', 'verified'])->group(function () {
|
// Route::middleware(['auth', 'verified'])->group(function () {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue