- Statusservice: Vorbedingung (Ausgangsstatus, Zahlungsfreigabe) wird nach lockForUpdate erneut geprüft (Board übergibt sie als Closure). - Wareneingang: Transaktion + Sperre, „offen“ wird innerhalb der Sperre geprüft. - Ausschuss: betroffene Wareneingänge sperren und Restbestand in der Transaktion erneut prüfen. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
101 lines
5.5 KiB
PHP
101 lines
5.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Setting;
|
|
use App\Models\ShoppingOrder;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
/**
|
|
* AP-33a: Zentraler Versand-Statuswechsel einer Bestellung — extrahiert aus
|
|
* SalesController::store(), damit Verkäufe-Modal und WaWi-Abwicklungs-Board
|
|
* dieselbe Logik nutzen (Margin-/Provisionsfolgen, Storno-Flag, AP-13-Bestandsabzug).
|
|
* Läuft transaktional mit lockForUpdate gegen Doppelklick/parallele Tabs.
|
|
*/
|
|
class OrderStatusService
|
|
{
|
|
/**
|
|
* @param (\Closure(ShoppingOrder): ?string)|null $precondition Prüfung 08.09.2026 F-16: fachliche
|
|
* Vorbedingung, die innerhalb der Sperre
|
|
* erneut geprüft wird (liefert Fehlertext
|
|
* oder null). Sonst können zwei Requests
|
|
* denselben Ausgangsstatus lesen und sich
|
|
* gegenseitig überschreiben.
|
|
*
|
|
* @throws ValidationException
|
|
*/
|
|
public function setShipped(int $orderId, int $shipped, ?int $userId = null, bool $lockStatus = false, ?\Closure $precondition = null): ShoppingOrder
|
|
{
|
|
return DB::transaction(function () use ($orderId, $shipped, $userId, $lockStatus, $precondition) {
|
|
$shopping_order = ShoppingOrder::query()->lockForUpdate()->findOrFail($orderId);
|
|
if ($precondition !== null && ($error = $precondition($shopping_order)) !== null) {
|
|
throw ValidationException::withMessages(['shipped' => $error]);
|
|
}
|
|
$shopping_order->shipped = $shipped;
|
|
if ($lockStatus) {
|
|
// Kundenentscheid 16.07.2026 (AP-33): Status aus der WaWi gesetzt ⇒
|
|
// die Bestell-Detailseite darf ihn nicht mehr automatisch verändern.
|
|
$shopping_order->shipped_status_locked = true;
|
|
}
|
|
$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, $userId);
|
|
|
|
return $shopping_order;
|
|
});
|
|
}
|
|
}
|