- Öffentliches Widerrufsformular /widerruf mit Bestell-Lookup (throttled) - Bestätigungsmail an Kunde + Weiterleitung an bestellungen@ (config app.withdrawal_mail) - Admin-Übersicht/-Detail unter /admin/withdrawals inkl. Sidenav-Eintrag - Widerruf-Button in Portal-Bestellansicht und Footer-Links - Model OrderWithdrawal + Migration + Factory + Tests Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
192 lines
6 KiB
PHP
192 lines
6 KiB
PHP
<?php
|
|
|
|
use App\Mail\MailWithdrawalConfirmation;
|
|
use App\Models\Country;
|
|
use App\Models\OrderWithdrawal;
|
|
use App\Models\ShoppingOrder;
|
|
use App\Models\ShoppingOrderItem;
|
|
use App\Models\ShoppingUser;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Tests\TestCase;
|
|
|
|
uses(TestCase::class, RefreshDatabase::class);
|
|
|
|
/**
|
|
* @return array{order: ShoppingOrder, item: ShoppingOrderItem}
|
|
*/
|
|
function makeShopOrder(string $email = 'kunde@example.com'): array
|
|
{
|
|
makeShopEnv();
|
|
$country = Country::first();
|
|
$product = makeProduct(['1']);
|
|
|
|
$shoppingUser = ShoppingUser::create([
|
|
'auth_user_id' => 0,
|
|
'billing_firstname' => 'Max',
|
|
'billing_lastname' => 'Mustermann',
|
|
'billing_email' => $email,
|
|
'billing_country_id' => $country->id,
|
|
'shipping_country_id' => $country->id,
|
|
'is_from' => 'shopping',
|
|
]);
|
|
|
|
$order = ShoppingOrder::create([
|
|
'shopping_user_id' => $shoppingUser->id,
|
|
'auth_user_id' => 0,
|
|
'user_shop_id' => 0,
|
|
'payment_for' => 6,
|
|
'country_id' => $country->id,
|
|
'language' => 'de',
|
|
'total' => 119,
|
|
'txaction' => 'paid',
|
|
]);
|
|
|
|
$item = ShoppingOrderItem::create([
|
|
'shopping_order_id' => $order->id,
|
|
'product_id' => $product->id,
|
|
'qty' => 2,
|
|
'price' => 59.5,
|
|
]);
|
|
|
|
return ['order' => $order, 'item' => $item];
|
|
}
|
|
|
|
it('zeigt die Widerrufsseite an', function () {
|
|
$this->get(route('withdrawal.create'))->assertOk();
|
|
});
|
|
|
|
it('zeigt nach erfolgreicher Identifikation die Bestellung mit Absende-Button', function () {
|
|
['order' => $order] = makeShopOrder('kunde@example.com');
|
|
|
|
$response = $this->post(route('withdrawal.lookup'), [
|
|
'order_number' => (string) $order->id,
|
|
'email' => 'kunde@example.com',
|
|
]);
|
|
|
|
$response->assertOk()
|
|
->assertSee('#'.$order->id)
|
|
->assertSee(__('withdrawal.submit'));
|
|
});
|
|
|
|
it('legt bei vollständigem Widerruf einen Datensatz an und versendet die Eingangsbestätigung', function () {
|
|
Mail::fake();
|
|
['order' => $order] = makeShopOrder('kunde@example.com');
|
|
|
|
$response = $this->post(route('withdrawal.store'), [
|
|
'order_number' => (string) $order->id,
|
|
'email' => 'kunde@example.com',
|
|
'scope' => 'full',
|
|
]);
|
|
|
|
$response->assertOk();
|
|
|
|
$withdrawal = OrderWithdrawal::first();
|
|
expect($withdrawal)->not->toBeNull()
|
|
->and($withdrawal->shopping_order_id)->toBe($order->id)
|
|
->and($withdrawal->type)->toBe(OrderWithdrawal::TYPE_FULL)
|
|
->and($withdrawal->status)->toBe(OrderWithdrawal::STATUS_RECEIVED)
|
|
->and($withdrawal->customer_email)->toBe('kunde@example.com')
|
|
->and($withdrawal->declared_at)->not->toBeNull();
|
|
|
|
Mail::assertSent(MailWithdrawalConfirmation::class, function (MailWithdrawalConfirmation $mail) {
|
|
return $mail->hasTo('kunde@example.com')
|
|
&& $mail->hasBcc(config('app.withdrawal_mail'));
|
|
});
|
|
});
|
|
|
|
it('findet die Bestellung auch über Groß-/Kleinschreibung der E-Mail', function () {
|
|
Mail::fake();
|
|
['order' => $order] = makeShopOrder('kunde@example.com');
|
|
|
|
$this->post(route('withdrawal.store'), [
|
|
'order_number' => (string) $order->id,
|
|
'email' => 'KUNDE@EXAMPLE.COM',
|
|
'scope' => 'full',
|
|
])->assertOk();
|
|
|
|
expect(OrderWithdrawal::count())->toBe(1);
|
|
});
|
|
|
|
it('speichert nur die ausgewählten Positionen beim teilweisen Widerruf', function () {
|
|
Mail::fake();
|
|
['order' => $order, 'item' => $item] = makeShopOrder();
|
|
|
|
$this->post(route('withdrawal.store'), [
|
|
'order_number' => (string) $order->id,
|
|
'email' => 'kunde@example.com',
|
|
'scope' => 'partial',
|
|
'items' => [$item->id => 1],
|
|
])->assertOk();
|
|
|
|
$withdrawal = OrderWithdrawal::first();
|
|
expect($withdrawal->type)->toBe(OrderWithdrawal::TYPE_PARTIAL)
|
|
->and($withdrawal->items)->toHaveCount(1)
|
|
->and($withdrawal->items[0]['qty'])->toBe(1)
|
|
->and($withdrawal->items[0]['shopping_order_item_id'])->toBe($item->id);
|
|
});
|
|
|
|
it('lehnt einen teilweisen Widerruf ohne ausgewählte Artikel ab', function () {
|
|
Mail::fake();
|
|
['order' => $order] = makeShopOrder();
|
|
|
|
$this->post(route('withdrawal.store'), [
|
|
'order_number' => (string) $order->id,
|
|
'email' => 'kunde@example.com',
|
|
'scope' => 'partial',
|
|
'items' => [],
|
|
])->assertRedirect();
|
|
|
|
expect(OrderWithdrawal::count())->toBe(0);
|
|
Mail::assertNothingSent();
|
|
});
|
|
|
|
it('findet keine Bestellung bei falscher E-Mail', function () {
|
|
['order' => $order] = makeShopOrder('kunde@example.com');
|
|
|
|
$this->post(route('withdrawal.lookup'), [
|
|
'order_number' => (string) $order->id,
|
|
'email' => 'falsch@example.com',
|
|
])->assertRedirect();
|
|
|
|
expect(OrderWithdrawal::count())->toBe(0);
|
|
});
|
|
|
|
it('widerruft keine Nicht-Shop-Bestellung (payment_for != 6)', function () {
|
|
makeShopEnv();
|
|
$country = Country::first();
|
|
$shoppingUser = ShoppingUser::create([
|
|
'auth_user_id' => 0,
|
|
'billing_firstname' => 'Max',
|
|
'billing_lastname' => 'Mustermann',
|
|
'billing_email' => 'kunde@example.com',
|
|
'billing_country_id' => $country->id,
|
|
'shipping_country_id' => $country->id,
|
|
'is_from' => 'user_order',
|
|
]);
|
|
$order = ShoppingOrder::create([
|
|
'shopping_user_id' => $shoppingUser->id,
|
|
'auth_user_id' => 0,
|
|
'user_shop_id' => 0,
|
|
'payment_for' => 3,
|
|
'country_id' => $country->id,
|
|
'language' => 'de',
|
|
'total' => 119,
|
|
]);
|
|
|
|
$this->post(route('withdrawal.store'), [
|
|
'order_number' => (string) $order->id,
|
|
'email' => 'kunde@example.com',
|
|
'scope' => 'full',
|
|
])->assertRedirect();
|
|
|
|
expect(OrderWithdrawal::count())->toBe(0);
|
|
});
|
|
|
|
it('validiert Pflichtfelder', function () {
|
|
$this->post(route('withdrawal.store'), [
|
|
'order_number' => '',
|
|
'email' => 'keine-mail',
|
|
'scope' => 'full',
|
|
])->assertSessionHasErrors(['order_number', 'email']);
|
|
});
|