99 lines
2.9 KiB
PHP
99 lines
2.9 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
|
|
{
|
|
$availableSpace = $bucket->getAvailableSpace();
|
|
|
|
return min($availableSpace, $remainingAmount);
|
|
}
|
|
|
|
/**
|
|
* Calculate allocation for percentage buckets.
|
|
* allocation_value is stored in basis points (2500 = 25%).
|
|
*/
|
|
private function calculatePercentageAllocation(Bucket $bucket, int $remainingAmount): int
|
|
{
|
|
$basisPoints = $bucket->allocation_value ?? 0;
|
|
|
|
return (int) round($remainingAmount * ($basisPoints / 10000));
|
|
}
|
|
}
|