Ein Block bringt seinen eigenen Kopfraum mit

Ein Block gibt 20 GB und belegt 22. Ohne das haette ein Start mit drei Bloecken
90 GB auf 100 GB Platte gehabt — zehn Gigabyte Kopfraum, wo die Regel bei dieser
Plattengroesse zwoelf verlangt, und der gestapelte Tarif waere genau der Fall
geworden, den die Regel verhindern soll.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat/neue-pakete
nexxo 2026-08-01 14:31:52 +02:00
parent af05ed6694
commit 3ed80a05e0
2 changed files with 78 additions and 1 deletions

View File

@ -111,8 +111,19 @@ class ResizeVirtualMachine extends CustomerStep
$allowance = StorageAllowance::for($instance);
// Kontingent + Blöcke + Kopfraum des Pakets + Kopfraum der Blöcke.
//
// Der letzte Summand ist der neue: ein Block gibt dem Kunden 20 GB und
// belegt 22, damit die Regel max(10 GB, 12 %) auch für ein gestapeltes
// Paket gilt. Ohne ihn hätte ein Start mit drei Blöcken 90 GB auf 100 GB
// Platte — zehn Gigabyte Kopfraum, wo die Regel zwölf verlangt.
//
// Beide Kopfräume werden gelesen, nicht abgeleitet: der des Pakets aus
// dem eingefrorenen Vertrag, der der Blöcke aus den Buchungen.
$target = $allowance->totalGb() > 0
? $allowance->totalGb() + max(0, $planDisk - $planQuota)
? $allowance->totalGb()
+ max(0, $planDisk - $planQuota)
+ max(0, $allowance->packDiskGb() - $allowance->packGb())
: $planDisk;
$actual = (int) $instance->disk_gb;

View File

@ -0,0 +1,66 @@
<?php
use App\Models\Order;
use App\Models\SubscriptionAddon;
use App\Services\Billing\AddonCatalogue;
use App\Services\Billing\StorageAllowance;
use App\Provisioning\Steps\Customer\ResizeVirtualMachine;
/**
* Was ein Block auf der Platte bedeutet.
*
* Der Kopfraum wächst mit der Instanz (max(10 GB, 12 %)), sonst gilt die Regel
* nur für frisch gekaufte Pakete und nicht für gestapelte. Eine volle Platte
* legt nicht den Upload still, sondern die Instanz.
*/
it('rechnet Kopfraum des Pakets und der Blöcke zusammen', function () {
// Start: 30 GB Kontingent auf 40 GB Platte. Drei Blöcke zu 20 GB nutzbar
// und 22 GB belegt.
$allowance = new StorageAllowance(planGb: 30, packs: 3, bookedGb: 60, bookedDiskGb: 66, packSizeGb: 20);
$planDisk = 40;
$planQuota = 30;
$target = $allowance->totalGb()
+ max(0, $planDisk - $planQuota)
+ ($allowance->packDiskGb() - $allowance->packGb());
expect($target)->toBe(106);
});
// Den Schritt selbst fahren: reservedRun() (aus CustomerStepsTest.php) baut ein
// 'start'-Paket (100 GB Kontingent, 120 GB Platte — 20 GB Kopfraum des Pakets).
// Drei Blöcke zu 20 GB nutzbar / 22 GB belegt kommen direkt als
// SubscriptionAddon-Zeile dazu, statt über BookAddon zu buchen — das würde
// einen Lieferlauf anstoßen und Stripe befragen, was hier nichts zur Sache tut;
// gefragt ist nur, was ResizeVirtualMachine aus einer bereits gebuchten Zeile
// macht.
//
// Ziel: 100 (Kontingent) + 60 (drei Blöcke à 20) + 20 (Kopfraum des Pakets,
// 120-100) + 6 (Kopfraum der Blöcke, 66-60 belegt) = 186 GB.
it('vergrößert die Platte um Paket- und Block-Kopfraum, und nur einmal', function () {
$s = fakeServices();
['run' => $run, 'instance' => $instance, 'order' => $order] = reservedRun();
SubscriptionAddon::create([
'subscription_id' => $order->subscription->id,
'addon_key' => AddonCatalogue::STORAGE,
'price_cents' => 1000,
'currency' => 'EUR',
'quantity' => 3,
'booked_at' => now(),
'pack_gb' => 20,
'pack_disk_gb' => 22,
]);
expect(app(ResizeVirtualMachine::class)->execute($run)->type)->toBe('advance')
->and($s['pve']->resizeCalls)->toContain('101:scsi0:186G')
->and($instance->fresh()->disk_gb)->toBe(186);
// Wiederholungslauf: das Ziel ist absolut, nicht '+…', also darf ein
// zweiter Durchlauf nicht noch einmal vergrößern.
$s['pve']->resizeCalls = [];
expect(app(ResizeVirtualMachine::class)->execute($run->fresh())->type)->toBe('advance')
->and($s['pve']->resizeCalls)->toBe([])
->and($instance->fresh()->disk_gb)->toBe(186);
});