Abo Einmalprodukte: Review-Gate (VIP), Verbindlichkeit & Summen-Layout

- Live-Review-Gate: Einmalprodukte nur fuer VIP im Sales Center sichtbar,
  Portal ausgeblendet (AboHelper::isOneTimeFeatureVisible + Gates in Controllern)
- Nur verbindlich bestaetigte Einmal-Artikel fliessen in die Lieferung;
  Service-Helfer confirmedItems/pendingItems/pendingGross
- Footer-Layout der Einmalprodukt-Liste: bestaetigte Summe + Gesamtbetrag,
  Trennstrich, offener Betrag und neue Gesamtsumme (dunkelgruen)
- Uebersetzungen DE/EN/ES/FR (onetime_new_total u.a.), Tests angepasst/ergaenzt

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin 2026-06-08 14:59:22 +00:00
parent 2269ce031f
commit 8288ea59ac
16 changed files with 356 additions and 46 deletions

View file

@ -129,6 +129,71 @@ describe('AboOneTimeService', function () {
expect($this->service->handleAction($abo, ['action' => 'foo']))->toBe(__('abo.onetime_action_invalid'));
});
it('trennt bestätigte und offene Einmal-Artikel inkl. offener Zwischensumme', function () {
$abo = makeMeAbo();
$confirmed = makeProduct(['2'], 119, 19);
$pending = makeProduct(['2'], 50, 19);
UserAboOneTimeItem::create([
'user_abo_id' => $abo->id, 'product_id' => $confirmed->id,
'qty' => 1, 'confirmed_qty' => 1, 'confirmed_at' => now(),
'price' => 119.0, 'tax_rate' => 19.0, 'status' => 1,
]);
UserAboOneTimeItem::create([
'user_abo_id' => $abo->id, 'product_id' => $pending->id,
'qty' => 2, 'price' => 50.0, 'tax_rate' => 19.0, 'status' => 0,
]);
expect(AboOneTimeService::confirmedItems($abo)->count())->toBe(1)
->and(AboOneTimeService::pendingItems($abo)->count())->toBe(1)
->and(AboOneTimeService::pendingGross($abo))->toBe(100.0);
});
it('behandelt einen geänderten bestätigten Artikel als offen', function () {
$abo = makeMeAbo();
$product = makeProduct(['2'], 119, 19);
UserAboOneTimeItem::create([
'user_abo_id' => $abo->id, 'product_id' => $product->id,
'qty' => 3, 'confirmed_qty' => 1, 'confirmed_at' => now(),
'price' => 119.0, 'tax_rate' => 19.0, 'status' => 0,
]);
expect(AboOneTimeService::confirmedItems($abo)->count())->toBe(0)
->and(AboOneTimeService::pendingItems($abo)->count())->toBe(1)
->and(AboOneTimeService::pendingGross($abo))->toBe(357.0);
});
});
describe('AboOrderCart::addOneTimeItemsToYard', function () {
beforeEach(fn () => makeShopEnv());
it('lädt nur verbindlich bestätigte Einmal-Artikel in den Warenkorb', function () {
$abo = makeMeAbo();
$confirmed = makeProduct(['2'], 119, 19);
$pending = makeProduct(['2'], 50, 19);
UserAboOneTimeItem::create([
'user_abo_id' => $abo->id, 'product_id' => $confirmed->id,
'qty' => 1, 'confirmed_qty' => 1, 'confirmed_at' => now(),
'price' => 119.0, 'tax_rate' => 19.0, 'status' => 1,
]);
UserAboOneTimeItem::create([
'user_abo_id' => $abo->id, 'product_id' => $pending->id,
'qty' => 1, 'price' => 50.0, 'tax_rate' => 19.0, 'status' => 0,
]);
$yard = Yard::instance(AboOrderCart::INSTANCE);
$yard->destroy();
AboOrderCart::addOneTimeItemsToYard($abo->fresh());
expect($yard->content()->count())->toBe(1)
->and((int) $yard->content()->first()->id)->toBe($confirmed->id);
$yard->destroy();
});
});
describe('AboOrderCart::buildOneTimeSnapshot', function () {