buckets/app/Services/Projection/PipelineAllocationService.php

100 lines
3 KiB
PHP

<?php
namespace App\Services\Projection;
use App\Enums\BucketAllocationTypeEnum;
use App\Models\Bucket;
use App\Models\Draw;
use App\Models\Scenario;
use Carbon\Carbon;
use Illuminate\Support\Collection;
readonly class PipelineAllocationService
{
/**
* Allocate an inflow amount across scenario buckets according to priority rules.
*
* @return Collection<Draw> Collection of Draw models
*/
public function allocateInflow(Scenario $scenario, int $amount, ?Carbon $date = null, ?string $description = null): Collection
{
$draws = collect();
// Guard clauses
if ($amount <= 0) {
return $draws;
}
// Get buckets ordered by priority
$buckets = $scenario->buckets()
->orderBy('priority')
->get();
if ($buckets->isEmpty()) {
return $draws;
}
$priorityOrder = 1;
$remainingAmount = $amount;
$allocationDate = $date ?? now();
foreach ($buckets as $bucket) {
if ($remainingAmount <= 0) {
break;
}
$allocation = $this->calculateBucketAllocation($bucket, $remainingAmount);
if ($allocation > 0) {
$draw = new Draw([
'bucket_id' => $bucket->id,
'amount' => $allocation,
'date' => $allocationDate,
'description' => $description ?? 'Allocation from inflow',
'is_projected' => true,
]);
$draws->push($draw);
$remainingAmount -= $allocation;
$priorityOrder++;
}
}
return $draws;
}
/**
* Calculate how much should be allocated to a specific bucket.
*/
private function calculateBucketAllocation(Bucket $bucket, int $remainingAmount): int
{
return match ($bucket->allocation_type) {
BucketAllocationTypeEnum::FIXED_LIMIT => $this->calculateFixedAllocation($bucket, $remainingAmount),
BucketAllocationTypeEnum::PERCENTAGE => $this->calculatePercentageAllocation($bucket, $remainingAmount),
BucketAllocationTypeEnum::UNLIMITED => $remainingAmount, // Takes all remaining
default => 0,
};
}
/**
* Calculate allocation for fixed limit buckets.
*/
private function calculateFixedAllocation(Bucket $bucket, int $remainingAmount): int
{
$bucketCapacity = (int) ($bucket->allocation_value ?? 0);
$currentBalance = $bucket->getCurrentBalance();
$availableSpace = max(0, $bucketCapacity - $currentBalance);
return min($availableSpace, $remainingAmount);
}
/**
* Calculate allocation for percentage buckets.
*/
private function calculatePercentageAllocation(Bucket $bucket, int $remainingAmount): int
{
$percentage = $bucket->allocation_value ?? 0;
return (int) round($remainingAmount * ($percentage / 100));
}
}