fix: enforce stock limits on basket quantities

No stock validation existed in the ordering flow, allowing customers
to order more items than available.

Cap quantity to ``stock_current`` in ``Baskets::getBasketData()`` when
adding to cart. Add ``min=1`` and ``max=stock`` attributes on the
basket quantity input, with JS clamping in the change handler.
Verify stock again in ``Shop\OrderController::store()`` before saving
the order as a race-condition safeguard.
This commit is contained in:
Valentin Lab
2026-02-20 12:17:22 +01:00
parent ef52addc7d
commit 63673117b3
6 changed files with 65 additions and 3 deletions

View File

@@ -12,6 +12,7 @@ use App\Repositories\Shop\Customers;
use App\Repositories\Shop\CustomerAddresses;
use App\Repositories\Shop\Deliveries;
use App\Repositories\Shop\DeliveryTypes;
use App\Repositories\Shop\Offers;
use App\Repositories\Shop\OrderMails;
use App\Repositories\Shop\Orders;
use App\Repositories\Shop\Paybox;
@@ -94,6 +95,25 @@ class OrderController extends Controller
$data['customer_id'] = Customers::getId();
$data['sale_channel_id'] = $data['sale_channel_id'] ?? SaleChannels::getDefaultID();
$data['basket'] = Baskets::getBasketSummary($data['sale_channel_id'], $data['delivery_type_id'] ?? false);
// Vérifier le stock avant de valider la commande
$insufficients = [];
foreach ($data['basket']['detail'] as $item) {
$offer = Offers::get($item['offer_id']);
if ($offer && $offer->stock_current !== null && $item['quantity'] > $offer->stock_current) {
$offer->load('article');
$insufficients[] = ($offer->article->name ?? 'Offre #'.$offer->id)
.' (stock : '.(int) $offer->stock_current.', demandé : '.$item['quantity'].')';
}
}
if (! empty($insufficients)) {
return redirect()->back()->withInput()->with(
'growl',
['Stock insuffisant pour : '.implode(' ; ', $insufficients).'. Veuillez ajuster les quantités.', 'danger']
);
}
$order = Orders::saveOrder($data);
if ($order) {

View File

@@ -43,6 +43,7 @@ class Articles
'id' => $offer->id,
'name' => $offer->variation->name,
'prices' => $offer->tariff->price_lists->first()->price_list_values->toArray(),
'stock' => $offer->stock_current !== null ? (int) $offer->stock_current : null,
];
}

View File

@@ -116,6 +116,7 @@ class Baskets
'variation' => $offer->variation->name,
'image' => Articles::getPreviewSrc(ArticleImages::getFullImageByArticle($offer->article)),
'latin' => $offer->article->product->specie->latin ?? false,
'stock' => $offer->stock_current !== null ? (int) $offer->stock_current : null,
];
}
@@ -144,6 +145,13 @@ class Baskets
{
$offer = Offers::get($id, ['article', 'variation']);
if ($offer && $offer->stock_current !== null) {
$quantity = min($quantity, max(0, (int) $offer->stock_current));
if ($quantity <= 0) {
return false;
}
}
return [
'id' => $id,
'name' => self::getArticleName($offer),