*/ use HasFactory; protected $fillable = [ 'user_id', 'wine_id', 'amount', 'price', 'status', 'purchased_at', 'note', ]; /** * Casts for attributes * * @var array */ protected $casts = [ 'amount' => 'integer', 'price' => 'decimal:2', 'purchased_at' => 'datetime', 'status' => 'string', 'note' => 'string', ]; public function user() { return $this->belongsTo(User::class, 'user_id'); } public function wine() { return $this->belongsTo(Wine::class, 'wine_id'); } /** * Purchase has many items (weak entity) */ public function items(): HasMany { return $this->hasMany(PurchaseItem::class, 'purchase_id'); } /** * Create a purchase with items and decrement wine stock atomically. * * $items = [ ['wine_id'=>1,'quantity'=>2,'unit_price'=>12.5], ... ] */ public static function createWithItems(array $data, array $items): self { return DB::transaction(function () use ($data, $items) { $purchase = self::create($data); foreach ($items as $it) { $wine = Wine::findOrFail($it['wine_id']); $qty = (int) ($it['quantity'] ?? 1); if ($wine->bottles_in_stock !== null && $wine->bottles_in_stock < $qty) { throw new \Exception("Insufficient stock for wine id {$wine->id}"); } $unit = $it['unit_price'] ?? $wine->price_per_bottle ?? 0; $lineTotal = round($qty * $unit, 2); $purchase->items()->create([ 'wine_id' => $wine->getKey(), 'quantity' => $qty, 'unit_price' => $unit, 'line_total' => $lineTotal, ]); // decrement stock if tracked if ($wine->bottles_in_stock !== null) { $wine->decrement('bottles_in_stock', $qty); } } return $purchase; }); } }