diff --git a/app/Mail/AlerteStock.php b/app/Mail/AlerteStock.php
new file mode 100644
index 00000000..6ad87b50
--- /dev/null
+++ b/app/Mail/AlerteStock.php
@@ -0,0 +1,43 @@
+article = $offer->article->title ?? 'Article #'.$offer->article_id;
+ $this->offre = $offer->id;
+ $this->stock_restant = $offer->stock_current;
+ $this->seuil = $offer->minimum_ondemand;
+ }
+
+ public function envelope()
+ {
+ return new Envelope(
+ from: new Address('boutique@jardinenvie.com', 'Jardin\'en\'Vie'),
+ subject: $this->subject,
+ );
+ }
+}
diff --git a/app/Repositories/Shop/OfferStocks.php b/app/Repositories/Shop/OfferStocks.php
index 245c8ccf..b786debd 100644
--- a/app/Repositories/Shop/OfferStocks.php
+++ b/app/Repositories/Shop/OfferStocks.php
@@ -2,19 +2,62 @@
namespace App\Repositories\Shop;
+use App\Mail\AlerteStock;
use App\Models\Shop\Offer;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Mail;
class OfferStocks
{
public static function decreaseStock($item)
{
$offer = Offers::get($item['offer_id']);
+ $previousStock = $offer->stock_current;
$offer->stock_current = $offer->stock_current - $item['quantity'];
if ($offer->stock_current <= 0) {
$offer->stock_current = 0;
}
- return $offer->save();
+ $saved = $offer->save();
+
+ if ($saved) {
+ self::checkStockAlert($offer, $previousStock);
+ }
+
+ return $saved;
+ }
+
+ public static function checkStockAlert($offer, $previousStock)
+ {
+ $threshold = (float) $offer->minimum_ondemand;
+ if ($threshold <= 0) {
+ return;
+ }
+
+ $crossedThreshold = $previousStock > $threshold
+ && $offer->stock_current <= $threshold;
+
+ if (! $crossedThreshold) {
+ return;
+ }
+
+ try {
+ $offer->load('article');
+ Mail::to('commande@jardinenvie.com')
+ ->send(new AlerteStock($offer));
+ Log::info('Stock alert email sent', [
+ 'offer_id' => $offer->id,
+ 'article' => $offer->article->name ?? $offer->article_id,
+ 'stock_current' => $offer->stock_current,
+ 'threshold' => $threshold,
+ ]);
+ } catch (\Throwable $e) {
+ Log::error('Failed to send stock alert email', [
+ 'offer_id' => $offer->id,
+ 'stock_current' => $offer->stock_current,
+ 'exception' => $e->getMessage(),
+ ]);
+ }
}
public static function getStockCurrent($id)
diff --git a/database/migrations/shop/2026_02_13_120000_insert_mail_template_alerte_stock.php b/database/migrations/shop/2026_02_13_120000_insert_mail_template_alerte_stock.php
new file mode 100644
index 00000000..be67a914
--- /dev/null
+++ b/database/migrations/shop/2026_02_13_120000_insert_mail_template_alerte_stock.php
@@ -0,0 +1,75 @@
+insert([
+ 'mailable' => 'App\\Mail\\AlerteStock',
+ 'subject' => json_encode(['fr' => '[Stock bas] {{article}} — {{stock_restant}} unités restantes'], JSON_UNESCAPED_UNICODE),
+ 'html_template' => json_encode(['fr' => $this->getHtmlTemplate()], JSON_UNESCAPED_UNICODE),
+ 'text_template' => json_encode(['fr' => $this->getTextTemplate()], JSON_UNESCAPED_UNICODE),
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ]);
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ DB::table('mail_templates')
+ ->where('mailable', 'App\\Mail\\AlerteStock')
+ ->delete();
+ }
+
+ private function getHtmlTemplate(): string
+ {
+ return '
'
+ .''
+ .''
+ .''
+ .''
+ .''
+ .' | '
+ .' '
+ .''
+ .''
+ .''
+ .''
+ .'| '
+ .'Alerte stock bas | '
+ .'| '
+ .' Le stock de l\'article {{article}} (offre n°{{offre}}) '
+ .'a atteint le seuil d\'alerte. '
+ .'{{stock_restant}} unités restantes '
+ .'Seuil d\'alerte configuré : {{seuil}} unités '
+ .'Pensez à réapprovisionner cet article. '
+ .' | '
+ .' '
+ .' | '
+ .''
+ .''
+ .'Jardin\'enVie Artisan Semencier 429 route des chaux, 26500 Bourg les Valence - Drôme'
+ .' | '
+ .' |
';
+ }
+
+ private function getTextTemplate(): string
+ {
+ return "ALERTE STOCK BAS\n\n"
+ ."Article : {{article}} (offre n°{{offre}})\n"
+ ."Stock restant : {{stock_restant}} unités\n"
+ ."Seuil d'alerte : {{seuil}} unités\n\n"
+ ."Pensez à réapprovisionner cet article.\n\n"
+ ."Jardin'enVie Artisan Semencier\n"
+ .'429 route des chaux, 26500 Bourg les Valence - Drôme';
+ }
+};