16 - Migrate allocation_value to integer and update model
This commit is contained in:
parent
d603fc6401
commit
4554f4e417
7 changed files with 37 additions and 49 deletions
|
|
@ -116,8 +116,8 @@ private function validateAllocationValue(BucketAllocationTypeEnum $allocationTyp
|
||||||
if ($allocationValue === null) {
|
if ($allocationValue === null) {
|
||||||
throw new InvalidArgumentException('Percentage buckets require an allocation value');
|
throw new InvalidArgumentException('Percentage buckets require an allocation value');
|
||||||
}
|
}
|
||||||
if ($allocationValue < 0.01 || $allocationValue > 100) {
|
if ($allocationValue < 1 || $allocationValue > 10000) {
|
||||||
throw new InvalidArgumentException('Percentage allocation value must be between 0.01 and 100');
|
throw new InvalidArgumentException('Percentage allocation value must be between 1 and 10000');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,15 +65,12 @@ public function preview(PreviewAllocationRequest $request, Scenario $scenario):
|
||||||
|
|
||||||
private function remainingCapacity(Bucket $bucket, int $allocatedCents): ?float
|
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;
|
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);
|
$remainingCents = max(0, $capacityCents - $allocatedCents);
|
||||||
|
|
||||||
return round($remainingCents / 100, 2);
|
return round($remainingCents / 100, 2);
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@
|
||||||
* @property string $name
|
* @property string $name
|
||||||
* @property int $priority
|
* @property int $priority
|
||||||
* @property BucketAllocationTypeEnum $allocation_type
|
* @property BucketAllocationTypeEnum $allocation_type
|
||||||
* @property float $starting_amount
|
* @property int $starting_amount
|
||||||
* @property float $allocation_value
|
* @property int|null $allocation_value
|
||||||
* @property float $buffer_multiplier
|
* @property float $buffer_multiplier
|
||||||
*
|
*
|
||||||
* @method static BucketFactory factory()
|
* @method static BucketFactory factory()
|
||||||
|
|
@ -47,7 +47,7 @@ class Bucket extends Model
|
||||||
'type' => BucketTypeEnum::class,
|
'type' => BucketTypeEnum::class,
|
||||||
'priority' => 'integer',
|
'priority' => 'integer',
|
||||||
'sort_order' => 'integer',
|
'sort_order' => 'integer',
|
||||||
'allocation_value' => 'decimal:2',
|
'allocation_value' => 'integer',
|
||||||
'buffer_multiplier' => 'decimal:2',
|
'buffer_multiplier' => 'decimal:2',
|
||||||
'starting_amount' => 'integer',
|
'starting_amount' => 'integer',
|
||||||
'allocation_type' => BucketAllocationTypeEnum::class,
|
'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)
|
* Formula: allocation_value * (1 + buffer_multiplier)
|
||||||
*/
|
*/
|
||||||
public function getEffectiveCapacity(): float
|
public function getEffectiveCapacity(): int
|
||||||
{
|
{
|
||||||
if ($this->allocation_type !== BucketAllocationTypeEnum::FIXED_LIMIT) {
|
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) {
|
if ($this->allocation_type !== BucketAllocationTypeEnum::FIXED_LIMIT) {
|
||||||
return PHP_FLOAT_MAX;
|
return PHP_INT_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
return max(0, $this->getEffectiveCapacity() - $this->getCurrentBalance());
|
return max(0, $this->getEffectiveCapacity() - $this->getCurrentBalance());
|
||||||
|
|
|
||||||
|
|
@ -81,20 +81,19 @@ private function calculateBucketAllocation(Bucket $bucket, int $remainingAmount)
|
||||||
*/
|
*/
|
||||||
private function calculateFixedAllocation(Bucket $bucket, int $remainingAmount): int
|
private function calculateFixedAllocation(Bucket $bucket, int $remainingAmount): int
|
||||||
{
|
{
|
||||||
$bucketCapacity = (int) round($bucket->getEffectiveCapacity());
|
$availableSpace = $bucket->getAvailableSpace();
|
||||||
$currentBalance = $bucket->getCurrentBalance();
|
|
||||||
$availableSpace = max(0, $bucketCapacity - $currentBalance);
|
|
||||||
|
|
||||||
return min($availableSpace, $remainingAmount);
|
return min($availableSpace, $remainingAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate allocation for percentage buckets.
|
* Calculate allocation for percentage buckets.
|
||||||
|
* allocation_value is stored in basis points (2500 = 25%).
|
||||||
*/
|
*/
|
||||||
private function calculatePercentageAllocation(Bucket $bucket, int $remainingAmount): int
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,27 +53,31 @@ public function definition(): array
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a fixed limit bucket.
|
* 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([
|
return $this->state([
|
||||||
'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT,
|
'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT,
|
||||||
'allocation_value' => $amount,
|
'allocation_value' => $amountInCents,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a percentage bucket.
|
* 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([
|
return $this->state([
|
||||||
'allocation_type' => BucketAllocationTypeEnum::PERCENTAGE,
|
'allocation_type' => BucketAllocationTypeEnum::PERCENTAGE,
|
||||||
'allocation_value' => $percentage,
|
'allocation_value' => $basisPoints,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,11 +173,11 @@ public function defaultSet(): array
|
||||||
/**
|
/**
|
||||||
* Get allocation value based on type.
|
* Get allocation value based on type.
|
||||||
*/
|
*/
|
||||||
private function getAllocationValueForType(BucketAllocationTypeEnum $type): ?float
|
private function getAllocationValueForType(BucketAllocationTypeEnum $type): ?int
|
||||||
{
|
{
|
||||||
return match ($type) {
|
return match ($type) {
|
||||||
BucketAllocationTypeEnum::FIXED_LIMIT => $this->faker->numberBetween(100, 10000),
|
BucketAllocationTypeEnum::FIXED_LIMIT => $this->faker->numberBetween(10000, 1000000),
|
||||||
BucketAllocationTypeEnum::PERCENTAGE => $this->faker->numberBetween(5, 50),
|
BucketAllocationTypeEnum::PERCENTAGE => $this->faker->numberBetween(500, 5000),
|
||||||
BucketAllocationTypeEnum::UNLIMITED => null,
|
BucketAllocationTypeEnum::UNLIMITED => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,17 +11,15 @@ public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('buckets', function (Blueprint $table) {
|
Schema::create('buckets', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->uuid('uuid')->unique();
|
$table->uuid()->unique();
|
||||||
$table->foreignId('scenario_id')->constrained()->onDelete('cascade');
|
$table->foreignId('scenario_id')->constrained()->onDelete('cascade');
|
||||||
$table->enum('type', BucketTypeEnum::values())->default(BucketTypeEnum::NEED->value);
|
$table->enum('type', BucketTypeEnum::values())->default(BucketTypeEnum::NEED->value);
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
$table->integer('priority')->comment('Lower number = higher priority, 1 = first');
|
$table->integer('priority');
|
||||||
$table->integer('sort_order')->default(0)->comment('For UI display ordering');
|
$table->integer('sort_order')->default(0);
|
||||||
$table->enum('allocation_type', ['fixed_limit', 'percentage', 'unlimited']);
|
$table->enum('allocation_type', ['fixed_limit', 'percentage', 'unlimited']);
|
||||||
$table->decimal('allocation_value', 10, 2)->nullable()
|
$table->unsignedBigInteger('allocation_value')->nullable();
|
||||||
->comment('Limit amount for fixed_limit, percentage for percentage type, NULL for unlimited');
|
$table->unsignedBigInteger('starting_amount')->default(0);
|
||||||
$table->unsignedBigInteger('starting_amount')->default(0)
|
|
||||||
->comment('Initial amount in bucket in cents before any draws or outflows');
|
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
|
||||||
// Indexes for performance
|
// Indexes for performance
|
||||||
|
|
|
||||||
|
|
@ -438,11 +438,6 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: app/Http/Resources/StreamResource.php
|
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\.$#'
|
message: '#^Property App\\Models\\Draw\:\:\$casts \(array\<string, string\>\) on left side of \?\? is not nullable\.$#'
|
||||||
|
|
@ -624,8 +619,3 @@ parameters:
|
||||||
count: 3
|
count: 3
|
||||||
path: tests/Unit/Actions/CreateBucketActionTest.php
|
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
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue