From f93b16c8f7b99e542aebbb638e5820fbed545ede Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Tue, 8 Sep 2026 11:04:07 +0000 Subject: [PATCH] =?UTF-8?q?Warenwirtschaft:=20F-10=20=E2=80=94=20Versandab?= =?UTF-8?q?schluss=20nur=20mit=20Zahlungsfreigabe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Board-Statuswechsel auf „versendet“ prüft serverseitig isReadyToShip() (Rechnung/API weiterhin erlaubt); Button im Board ohne Freigabe deaktiviert. Konfektionieren bleibt vor Zahlungseingang möglich. Co-Authored-By: Claude Fable 5.1 --- .../Inventory/OrderFulfillmentController.php | 9 ++++++ .../admin/inventory/orders/index.blade.php | 7 +++- tests/Feature/OrderFulfillmentTest.php | 32 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Admin/Inventory/OrderFulfillmentController.php b/app/Http/Controllers/Admin/Inventory/OrderFulfillmentController.php index a9077ed..451669b 100644 --- a/app/Http/Controllers/Admin/Inventory/OrderFulfillmentController.php +++ b/app/Http/Controllers/Admin/Inventory/OrderFulfillmentController.php @@ -92,6 +92,15 @@ class OrderFulfillmentController extends Controller return redirect()->route('admin.inventory.orders.index'); } + // Prüfung 08.09.2026 F-10: „versendet“ (Bestandsabzug) nur für versandfertige + // Bestellungen — Zahlung eingegangen bzw. Rechnung/API (Kundenentscheid 16.07.2026, + // isReadyToShip). Konfektionieren bleibt auch vor Zahlungseingang möglich. + if ((int) $request->input('shipped') === 2 && ! $order->isReadyToShip()) { + session()->flash('alert-error', __('Bestellung #:id wartet auf Zahlung und kann noch nicht als versendet gebucht werden.', ['id' => $id])); + + return redirect()->route('admin.inventory.orders.index', array_filter(['filter' => $request->input('back_filter')])); + } + app(OrderStatusService::class)->setShipped($id, (int) $request->input('shipped'), auth()->id(), lockStatus: true); session()->flash('alert-success', (int) $request->input('shipped') === 2 diff --git a/resources/views/admin/inventory/orders/index.blade.php b/resources/views/admin/inventory/orders/index.blade.php index b9d5401..3165c50 100644 --- a/resources/views/admin/inventory/orders/index.blade.php +++ b/resources/views/admin/inventory/orders/index.blade.php @@ -227,7 +227,12 @@ @endif - @if (in_array((int) $order->shipped, [0, 1], true)) + @if (in_array((int) $order->shipped, [0, 1], true) && ! $readyToShip) + {{-- F-10: ohne Zahlungsfreigabe kein Versandabschluss (Server prüft ebenfalls) --}} + + @elseif (in_array((int) $order->shipped, [0, 1], true))
@csrf diff --git a/tests/Feature/OrderFulfillmentTest.php b/tests/Feature/OrderFulfillmentTest.php index 17d4bde..52a9bb7 100644 --- a/tests/Feature/OrderFulfillmentTest.php +++ b/tests/Feature/OrderFulfillmentTest.php @@ -423,3 +423,35 @@ test('api-bestellung bekommt keine rechnung im system', function () { expect($order->fresh()->invoice)->toBeNull() ->and(session('alert-error'))->toContain('API-Bestellung'); }); + +test('F-10: ohne zahlungsfreigabe kein versandabschluss ueber das board, konfektionieren bleibt moeglich', function () { + $product = ofMakeProduct('OF F10'); + $order = ofMakeOrder([['product' => $product, 'qty' => 1]], ['txaction' => 'failed', 'paid' => false], 'vor'); + expect($order->isReadyToShip())->toBeFalse(); + + $staff = ofMakeUser(1); + $this->actingAs($staff) + ->post(route('admin.inventory.orders.status', $order->id), ['shipped' => 2]) + ->assertRedirect(route('admin.inventory.orders.index')); + + expect((int) $order->fresh()->shipped)->toBe(0) + ->and(session('alert-error'))->toContain('wartet auf Zahlung') + ->and(ProductStockMovement::query()->where('product_id', $product->id)->count())->toBe(0); + + // Board zeigt den Versand-Button nur deaktiviert. + $this->actingAs($staff)->get(route('admin.inventory.orders.index')) + ->assertOk()->assertSee('Wartet auf Zahlung — Versand erst nach Zahlungseingang'); + + $this->actingAs($staff) + ->post(route('admin.inventory.orders.status', $order->id), ['shipped' => 1]) + ->assertRedirect(); + expect((int) $order->fresh()->shipped)->toBe(1); + + // Nach Zahlungseingang geht der Versand. + $order->forceFill(['txaction' => 'paid', 'paid' => true])->save(); + $this->actingAs($staff) + ->post(route('admin.inventory.orders.status', $order->id), ['shipped' => 2]) + ->assertRedirect(); + expect((int) $order->fresh()->shipped)->toBe(2) + ->and(ProductStockMovement::query()->where('product_id', $product->id)->count())->toBe(1); +});