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 '' + .'
' + .'' + .'' + .'' + .'
' + .'' + .'Jardin\'enVie
' + .'' + .'
' + .'' + .'' + .'' + .'' + .'
' + .'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'; + } +};