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:
parent
2269ce031f
commit
8288ea59ac
16 changed files with 356 additions and 46 deletions
|
|
@ -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 () {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ uses(Tests\TestCase::class, RefreshDatabase::class);
|
|||
describe('Abo Einmal-Produkte Views', function () {
|
||||
beforeEach(fn () => makeShopEnv());
|
||||
|
||||
it('rendert die Einmal-Produktliste mit Position und Zwischensumme', function () {
|
||||
it('rendert die Einmal-Produktliste mit Position und offener Zwischensumme', function () {
|
||||
$abo = makeMeAbo();
|
||||
$product = makeProduct(['2'], 119, 19);
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ describe('Abo Einmal-Produkte Views', function () {
|
|||
'price_net' => 100.0,
|
||||
'tax_rate' => 19.0,
|
||||
'tax' => 19.0,
|
||||
'status' => 1,
|
||||
'status' => 0,
|
||||
]);
|
||||
|
||||
$summary = ['one_time' => ['gross' => 238.0, 'net' => 200.0, 'tax' => 38.0]];
|
||||
|
|
@ -32,10 +32,50 @@ describe('Abo Einmal-Produkte Views', function () {
|
|||
])->render();
|
||||
|
||||
expect($html)->toContain($product->getLang('name'))
|
||||
->and($html)->toContain(__('abo.onetime_subtotal'))
|
||||
->and($html)->toContain(__('abo.onetime_pending_subtotal'))
|
||||
->and($html)->toContain(__('abo.onetime_status_pending'))
|
||||
->and($html)->toContain('238,00');
|
||||
});
|
||||
|
||||
it('zeigt bestätigte und offene Artikel mit getrennten Zwischensummen', 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,
|
||||
]);
|
||||
|
||||
$html = view('admin.abo._order_onetime_show', [
|
||||
'user_abo' => $abo->fresh(),
|
||||
'summary' => ['one_time' => ['gross' => 119.0], 'total_with_shipping' => 219.0],
|
||||
])->render();
|
||||
|
||||
expect($html)->toContain(__('abo.onetime_confirmed_subtotal'))
|
||||
->and($html)->toContain(__('abo.onetime_pending_subtotal'))
|
||||
->and($html)->toContain(__('abo.onetime_new_total'))
|
||||
->and($html)->toContain(__('abo.onetime_status_confirmed'))
|
||||
->and($html)->toContain(__('abo.onetime_status_pending'))
|
||||
->and($html)->toContain(__('abo.onetime_pending_hint'))
|
||||
->and($html)->toContain('50,00')
|
||||
->and($html)->toContain('169,00');
|
||||
});
|
||||
|
||||
it('zeigt bei unbestätigten Einmal-Artikeln die Bestätigungsbuttons', function () {
|
||||
$abo = makeMeAbo();
|
||||
$product = makeProduct(['2'], 119, 19);
|
||||
|
|
@ -56,7 +96,7 @@ describe('Abo Einmal-Produkte Views', function () {
|
|||
'summary' => ['one_time' => ['gross' => 119.0], 'total_with_shipping' => 219.0],
|
||||
])->render();
|
||||
|
||||
expect($html)->toContain(__('abo.onetime_next_delivery_total'))
|
||||
expect($html)->toContain(__('abo.onetime_pending_subtotal'))
|
||||
->and($html)->toContain(__('abo.onetime_legal_notice', [
|
||||
'nextBillingDate' => $abo->next_date,
|
||||
'agb' => '<a href="'.url('/agb').'" target="_blank">'.__('abo.confirm_terms_link').'</a>',
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use App\Models\ShoppingOrderItem;
|
|||
use App\Models\UserAbo;
|
||||
use App\Models\UserAboOneTimeItem;
|
||||
use App\Services\AboHelper;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
|
|
@ -82,6 +83,48 @@ describe('isOneTimeWindowOpen', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('isOneTimeFeatureVisible', function () {
|
||||
it('ist sichtbar, wenn das Fenster offen ist und ein VIP-User (Admin) eingeloggt ist', function () {
|
||||
$vip = new User;
|
||||
$vip->admin = 1;
|
||||
$this->actingAs($vip, 'user');
|
||||
|
||||
$abo = new UserAbo;
|
||||
$abo->next_date = now()->addDays(2); // innerhalb des 4-Tage-Fensters
|
||||
|
||||
expect(AboHelper::isOneTimeFeatureVisible($abo))->toBeTrue();
|
||||
});
|
||||
|
||||
it('ist nicht sichtbar für eingeloggte Nicht-VIP-User', function () {
|
||||
$user = new User;
|
||||
$user->admin = 0;
|
||||
$this->actingAs($user, 'user');
|
||||
|
||||
$abo = new UserAbo;
|
||||
$abo->next_date = now()->addDays(2);
|
||||
|
||||
expect(AboHelper::isOneTimeFeatureVisible($abo))->toBeFalse();
|
||||
});
|
||||
|
||||
it('ist nicht sichtbar ohne User auf dem user-Guard (Portal/Endkunde)', function () {
|
||||
$abo = new UserAbo;
|
||||
$abo->next_date = now()->addDays(2);
|
||||
|
||||
expect(AboHelper::isOneTimeFeatureVisible($abo))->toBeFalse();
|
||||
});
|
||||
|
||||
it('ist nicht sichtbar bei geschlossenem Fenster, auch für VIP-User', function () {
|
||||
$vip = new User;
|
||||
$vip->admin = 1;
|
||||
$this->actingAs($vip, 'user');
|
||||
|
||||
$abo = new UserAbo;
|
||||
$abo->next_date = now()->addDays(30); // außerhalb des Fensters
|
||||
|
||||
expect(AboHelper::isOneTimeFeatureVisible($abo))->toBeFalse();
|
||||
});
|
||||
});
|
||||
|
||||
describe('UserAboOneTimeItem Model', function () {
|
||||
it('definiert die Beziehung one_time_items auf UserAbo', function () {
|
||||
$relation = (new UserAbo)->one_time_items();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue