Modules\Core\Order\Services\TransactionRecorder). * * Only decrements for `purchasable === 'in_stock'` variants — 'always' and * 'backorder' variants are deliberately allowed to sell past (or without * regard to) their stock count already (see ProductVariant:: * canBeFulfilledAtQuantity()), so decrementing their stock would just make * that column an inaccurate, decreasingly-negative number with no purchasing * consequence. Only `OrderLine::type === 'physical'` lines are considered — * a digital line has no stock to decrement (ProductVariant::getType()). * * A single UPDATE per variant (`DB::table(...)->update()` with a raw * expression), not a read-then-write on the Eloquent model — avoids a * lost-update race between two orders decrementing the same variant * concurrently, and skips Modules\Core\Catalog\Services\ProductIndexer:: * stock's staleness gap for the DB value itself even though the search * index still only refreshes on the next reindex event/nightly job (see * that class's own docblock). * * Never lets stock go negative (`GREATEST(stock - qty, 0)` via a raw * expression) — an order can still be placed against a variant whose stock * was already fully consumed by another concurrent order (Lunar has no * stock-reservation step at cart/checkout time), so this is a best-effort * count, not a hard inventory guarantee. */ class StockService { public function decrementForOrder(Order $order): void { $lines = $order->lines() ->where('type', 'physical') ->where('purchasable_type', ProductVariant::morphName()) ->get(['purchasable_id', 'quantity']); if ($lines->isEmpty()) { return; } foreach ($lines as $line) { DB::table((new ProductVariant())->getTable()) ->where('id', $line->purchasable_id) ->where('purchasable', 'in_stock') ->update([ 'stock' => DB::raw('GREATEST(stock - '.(int) $line->quantity.', 0)'), ]); } $productIds = ProductVariant::whereIn('id', $lines->pluck('purchasable_id')) ->pluck('product_id') ->unique(); Product::whereIn('id', $productIds)->get()->each->searchable(); } }