Warenwirtschaft: AP-33a Bestell-Abwicklung — Nav „Bestellungen" + Fulfillment-Board

Neuer Nav-Punkt „Bestellungen" (Abwicklung + API-Bestellungen als Unterpunkte).
Board für Versand-Mitarbeiter (copyreader): 4 Filter-Kacheln (heute eingegangen /
zu konfektionieren / versandfertig / heute versendet), Packliste je Bestellung
(Sets mit Komponenten), Dokumente (Rechnung/Lieferschein/Kombi-Download über
eigene copyreader-Route, Erstellen ohne Mailversand; API-Bestellungen verlinken
die externe Rechnung), Aktionen Konfektioniert/Versendet (nur vorwärts, Storno
bleibt Verkäufe/admin). Versandfertig-Regel per Kundenentscheid: Rechnung sofort,
Vorkasse nach Zahlungseingang, API immer (ShoppingOrder::isReadyToShip()).
Statuswechsel-Logik in OrderStatusService extrahiert (Verkäufe-Modal + Board
teilen Transaktion/Lock/Margin-Folgen/AP-13-Abzug). Kundenentscheid Punkt 3:
Board-Statuswechsel setzt shipped_status_locked — die Bestell-Detailseiten
ändern den Status dann nicht mehr automatisch (offen → in Bearbeitung).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-16 11:30:57 +00:00
parent f1eb5fbc7f
commit a367d851f6
9 changed files with 818 additions and 71 deletions

View file

@ -2,16 +2,14 @@
namespace App\Http\Controllers;
use App\Models\Setting;
use App\Models\ShoppingOrder;
use App\Models\ShoppingUser;
use App\Repositories\InvoiceRepository;
use App\Services\CustomerPriority;
use App\Services\Invoice;
use App\Services\OrderStatusService;
use App\Services\Payment;
use App\Services\PaymentService;
use App\Services\ProductStockService;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Request;
@ -46,7 +44,8 @@ class SalesController extends Controller
{
$ShoppingOrder = ShoppingOrder::find($id);
if ($ShoppingOrder->shipped == 0) {
// AP-33: kein Auto-Wechsel mehr, wenn der Status über die WaWi gesetzt wurde
if ($ShoppingOrder->shipped == 0 && ! $ShoppingOrder->shipped_status_locked) {
$ShoppingOrder->shipped = 1;
$ShoppingOrder->save();
}
@ -209,7 +208,7 @@ class SalesController extends Controller
public function usersDetail($id)
{
$ShoppingOrder = ShoppingOrder::find($id);
if($ShoppingOrder->shipped === 0){
if($ShoppingOrder->shipped === 0 && ! $ShoppingOrder->shipped_status_locked){
$ShoppingOrder->shipped = 1;
$ShoppingOrder->save();
}
@ -291,7 +290,7 @@ class SalesController extends Controller
public function customersDetail($id)
{
$ShoppingOrder = ShoppingOrder::find($id);
if($ShoppingOrder->shipped === 0){
if($ShoppingOrder->shipped === 0 && ! $ShoppingOrder->shipped_status_locked){
$ShoppingOrder->shipped = 1;
$ShoppingOrder->save();
}
@ -429,67 +428,7 @@ class SalesController extends Controller
]);
if ($data['action'] === 'store_shipped') {
// Statuswechsel + Margin-/Provisionsfolgen atomar; Lock gegen Doppelklick/zweiten Tab
DB::transaction(function () use ($data) {
$shopping_order = ShoppingOrder::query()->lockForUpdate()->findOrFail($data['id']);
$shopping_order->shipped = (int) $data['shipped'];
$shopping_order->save();
// handel Promotion Product and credit by storno
Payment::handelUserPromotionOrder($shopping_order);
Payment::handelUserShopOrder($shopping_order);
if ($shopping_order->getAPIShippedType() === 'sent' || $shopping_order->getAPIShippedType() === 'close') {
if (! $shopping_order->shipped_at) {
$shopping_order->shipped_at = now();
$shopping_order->save();
// is shipped set pending_to
if ($shopping_order->shopping_order_margin) {
if ($shopping_order->shopping_order_margin->hasPartnerCommission()) {
$days = Setting::getContentBySlug('pending_partner_commissions_in_days');
$days = $days ? $days : 20;
$partner_commission_pending_to = $shopping_order->shipped_at;
$partner_commission_pending_to->addDays($days);
$shopping_order->shopping_order_margin->partner_commission_pending_to = $partner_commission_pending_to;
$shopping_order->shopping_order_margin->save();
} else {
$days = Setting::getContentBySlug('pending_order_margins_in_days');
$days = $days ? $days : 20;
$margin_pending_to = $shopping_order->shipped_at;
$margin_pending_to->addDays($days);
$shopping_order->shopping_order_margin->margin_pending_to = $margin_pending_to;
$shopping_order->shopping_order_margin->save();
}
}
}
} else {
$shopping_order->shipped_at = null;
$shopping_order->save();
if ($shopping_order->shopping_order_margin) {
// zurücksetzen der pending_to
$shopping_order->shopping_order_margin->partner_commission_pending_to = null;
$shopping_order->shopping_order_margin->margin_pending_to = null;
$shopping_order->shopping_order_margin->save();
}
}
if ($shopping_order->getAPIShippedType() === 'cancel') {
if ($shopping_order->shopping_order_margin) {
$shopping_order->shopping_order_margin->cancellation = true;
$shopping_order->shopping_order_margin->partner_commission_pending_to = null;
$shopping_order->shopping_order_margin->margin_pending_to = null;
$shopping_order->shopping_order_margin->save();
}
} else {
if ($shopping_order->shopping_order_margin && $shopping_order->shopping_order_margin->cancellation) {
$shopping_order->shopping_order_margin->cancellation = false;
$shopping_order->shopping_order_margin->partner_commission_pending_to = null;
$shopping_order->shopping_order_margin->margin_pending_to = null;
$shopping_order->shopping_order_margin->save();
}
}
// AP-13: Verkaufs-Abgänge (Produktbestand) mit dem neuen Versandstatus abgleichen
app(ProductStockService::class)->syncSaleStock($shopping_order, auth()->id());
});
app(OrderStatusService::class)->setShipped((int) $data['id'], (int) $data['shipped'], auth()->id());
}
/* txaction ändern