16 - Migrate allocation_value to integer and update model

This commit is contained in:
myrmidex 2026-03-21 16:54:11 +01:00
parent d603fc6401
commit 4554f4e417
7 changed files with 37 additions and 49 deletions

View file

@ -116,8 +116,8 @@ private function validateAllocationValue(BucketAllocationTypeEnum $allocationTyp
if ($allocationValue === null) {
throw new InvalidArgumentException('Percentage buckets require an allocation value');
}
if ($allocationValue < 0.01 || $allocationValue > 100) {
throw new InvalidArgumentException('Percentage allocation value must be between 0.01 and 100');
if ($allocationValue < 1 || $allocationValue > 10000) {
throw new InvalidArgumentException('Percentage allocation value must be between 1 and 10000');
}
break;

View file

@ -65,15 +65,12 @@ public function preview(PreviewAllocationRequest $request, Scenario $scenario):
private function remainingCapacity(Bucket $bucket, int $allocatedCents): ?float
{
$effectiveCapacity = $bucket->getEffectiveCapacity();
$capacityCents = $bucket->getEffectiveCapacity();
if ($effectiveCapacity === PHP_FLOAT_MAX) {
if ($capacityCents === PHP_INT_MAX) {
return null;
}
// PipelineAllocationService treats getEffectiveCapacity() as cents,
// so we compute remaining in the same unit, then convert to dollars.
$capacityCents = (int) round($effectiveCapacity);
$remainingCents = max(0, $capacityCents - $allocatedCents);
return round($remainingCents / 100, 2);

View file

@ -20,8 +20,8 @@
* @property string $name
* @property int $priority
* @property BucketAllocationTypeEnum $allocation_type
* @property float $starting_amount
* @property float $allocation_value
* @property int $starting_amount
* @property int|null $allocation_value
* @property float $buffer_multiplier
*
* @method static BucketFactory factory()
@ -47,7 +47,7 @@ class Bucket extends Model
'type' => BucketTypeEnum::class,
'priority' => 'integer',
'sort_order' => 'integer',
'allocation_value' => 'decimal:2',
'allocation_value' => 'integer',
'buffer_multiplier' => 'decimal:2',
'starting_amount' => 'integer',
'allocation_type' => BucketAllocationTypeEnum::class,
@ -103,18 +103,18 @@ public function getCurrentBalance(): int
}
/**
* Get the effective capacity including buffer.
* Get the effective capacity including buffer, in cents.
* Formula: allocation_value * (1 + buffer_multiplier)
*/
public function getEffectiveCapacity(): float
public function getEffectiveCapacity(): int
{
if ($this->allocation_type !== BucketAllocationTypeEnum::FIXED_LIMIT) {
return PHP_FLOAT_MAX;
return PHP_INT_MAX;
}
$base = (float) ($this->allocation_value ?? 0);
$base = $this->allocation_value ?? 0;
return round($base * (1 + (float) $this->buffer_multiplier), 2);
return (int) round($base * (1 + (float) $this->buffer_multiplier));
}
/**
@ -130,12 +130,12 @@ public function hasAvailableSpace(): bool
}
/**
* Get available space for fixed_limit buckets.
* Get available space in cents for fixed_limit buckets.
*/
public function getAvailableSpace(): float
public function getAvailableSpace(): int
{
if ($this->allocation_type !== BucketAllocationTypeEnum::FIXED_LIMIT) {
return PHP_FLOAT_MAX;
return PHP_INT_MAX;
}
return max(0, $this->getEffectiveCapacity() - $this->getCurrentBalance());

View file

@ -81,20 +81,19 @@ private function calculateBucketAllocation(Bucket $bucket, int $remainingAmount)
*/
private function calculateFixedAllocation(Bucket $bucket, int $remainingAmount): int
{
$bucketCapacity = (int) round($bucket->getEffectiveCapacity());
$currentBalance = $bucket->getCurrentBalance();
$availableSpace = max(0, $bucketCapacity - $currentBalance);
$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
{
$percentage = $bucket->allocation_value ?? 0;
$basisPoints = $bucket->allocation_value ?? 0;
return (int) round($remainingAmount * ($percentage / 100));
return (int) round($remainingAmount * ($basisPoints / 10000));
}
}

View file

@ -53,27 +53,31 @@ public function definition(): array
/**
* Create a fixed limit bucket.
*
* @param int|null $amountInCents Capacity in cents (e.g., 50000 = $500)
*/
public function fixedLimit($amount = null): Factory
public function fixedLimit(?int $amountInCents = null): Factory
{
$amount = $amount ?? $this->faker->numberBetween(500, 5000);
$amountInCents = $amountInCents ?? $this->faker->numberBetween(50000, 500000);
return $this->state([
'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT,
'allocation_value' => $amount,
'allocation_value' => $amountInCents,
]);
}
/**
* Create a percentage bucket.
*
* @param int|null $basisPoints Percentage in basis points (e.g., 2500 = 25%)
*/
public function percentage($percentage = null): Factory
public function percentage(?int $basisPoints = null): Factory
{
$percentage = $percentage ?? $this->faker->numberBetween(10, 50);
$basisPoints = $basisPoints ?? $this->faker->numberBetween(1000, 5000);
return $this->state([
'allocation_type' => BucketAllocationTypeEnum::PERCENTAGE,
'allocation_value' => $percentage,
'allocation_value' => $basisPoints,
]);
}
@ -169,11 +173,11 @@ public function defaultSet(): array
/**
* Get allocation value based on type.
*/
private function getAllocationValueForType(BucketAllocationTypeEnum $type): ?float
private function getAllocationValueForType(BucketAllocationTypeEnum $type): ?int
{
return match ($type) {
BucketAllocationTypeEnum::FIXED_LIMIT => $this->faker->numberBetween(100, 10000),
BucketAllocationTypeEnum::PERCENTAGE => $this->faker->numberBetween(5, 50),
BucketAllocationTypeEnum::FIXED_LIMIT => $this->faker->numberBetween(10000, 1000000),
BucketAllocationTypeEnum::PERCENTAGE => $this->faker->numberBetween(500, 5000),
BucketAllocationTypeEnum::UNLIMITED => null,
};
}

View file

@ -11,17 +11,15 @@ public function up(): void
{
Schema::create('buckets', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->uuid()->unique();
$table->foreignId('scenario_id')->constrained()->onDelete('cascade');
$table->enum('type', BucketTypeEnum::values())->default(BucketTypeEnum::NEED->value);
$table->string('name');
$table->integer('priority')->comment('Lower number = higher priority, 1 = first');
$table->integer('sort_order')->default(0)->comment('For UI display ordering');
$table->integer('priority');
$table->integer('sort_order')->default(0);
$table->enum('allocation_type', ['fixed_limit', 'percentage', 'unlimited']);
$table->decimal('allocation_value', 10, 2)->nullable()
->comment('Limit amount for fixed_limit, percentage for percentage type, NULL for unlimited');
$table->unsignedBigInteger('starting_amount')->default(0)
->comment('Initial amount in bucket in cents before any draws or outflows');
$table->unsignedBigInteger('allocation_value')->nullable();
$table->unsignedBigInteger('starting_amount')->default(0);
$table->timestamps();
// Indexes for performance

View file

@ -438,11 +438,6 @@ parameters:
count: 1
path: app/Http/Resources/StreamResource.php
-
message: '#^Method App\\Models\\Bucket\:\:getCurrentBalance\(\) should return int but returns float\.$#'
identifier: return.type
count: 1
path: app/Models/Bucket.php
-
message: '#^Property App\\Models\\Draw\:\:\$casts \(array\<string, string\>\) on left side of \?\? is not nullable\.$#'
@ -624,8 +619,3 @@ parameters:
count: 3
path: tests/Unit/Actions/CreateBucketActionTest.php
-
message: '#^Call to method PHPUnit\\Framework\\Assert\:\:assertNull\(\) with float will always evaluate to false\.$#'
identifier: method.impossibleType
count: 2
path: tests/Unit/Actions/CreateBucketActionTest.php