paginate(15); return view('purchase.grns.index', compact('grns')); } public function create() { $purchaseOrders = PurchaseOrder::whereIn('status', ['sent', 'partial'])->with('supplier')->get(); $warehouses = Warehouse::all(); return view('purchase.grns.create', compact('purchaseOrders', 'warehouses')); } public function store(Request $request) { $request->validate([ 'purchase_order_id' => 'required|exists:purchase_orders,id', 'warehouse_id' => 'required|exists:warehouses,id', 'received_date' => 'required|date', 'items' => 'required|array|min:1', 'items.*.item_id' => 'required|exists:items,id', 'items.*.quantity' => 'required|numeric|min:1', ]); $grnNumber = 'GRN-' . str_pad(GoodsReceiptNote::max('id') + 1, 5, '0', STR_PAD_LEFT); $po = PurchaseOrder::findOrFail($request->purchase_order_id); $grn = GoodsReceiptNote::create([ 'grn_number' => $grnNumber, 'purchase_order_id' => $request->purchase_order_id, 'supplier_id' => $po->supplier_id, 'warehouse_id' => $request->warehouse_id, 'received_date' => $request->received_date, 'status' => 'draft', 'received_by' => auth()->id(), ]); foreach ($request->items as $item) { GrnItem::create([ 'goods_receipt_note_id' => $grn->id, 'purchase_order_item_id' => $item['po_item_id'] ?? $po->items()->where('item_id', $item['item_id'])->first()?->id ?? 0, 'item_id' => $item['item_id'], 'quantity_received' => $item['quantity'], 'unit_cost' => $item['unit_cost'] ?? 0, 'type' => in_array($item['type'] ?? '', ['inventory', 'consumable']) ? $item['type'] : 'inventory', ]); } return redirect()->route('purchase.grns.show', $grn)->with('success', 'GRN created successfully.'); } public function show(GoodsReceiptNote $grn) { $grn->load(['purchaseOrder.supplier', 'warehouse', 'items.item']); return view('purchase.grns.show', compact('grn')); } public function edit(GoodsReceiptNote $grn) { $warehouses = Warehouse::all(); return view('purchase.grns.edit', compact('grn', 'warehouses')); } public function update(Request $request, GoodsReceiptNote $grn) { $request->validate([ 'received_date' => 'required|date', 'warehouse_id' => 'required|exists:warehouses,id', ]); $grn->update($request->only('received_date', 'warehouse_id')); return redirect()->route('purchase.grns.show', $grn)->with('success', 'GRN updated successfully.'); } public function destroy(GoodsReceiptNote $grn) { $grn->delete(); return redirect()->route('purchase.grns.index')->with('success', 'GRN deleted successfully.'); } public function confirm(GoodsReceiptNote $grn) { if ($grn->status === 'confirmed') { return redirect()->back()->with('error', 'GRN is already confirmed.'); } DB::transaction(function () use ($grn) { $grn->load('items'); foreach ($grn->items as $grnItem) { $stockLevel = StockLevel::firstOrCreate( ['item_id' => $grnItem->item_id, 'warehouse_id' => $grn->warehouse_id], ['quantity' => 0] ); $stockLevel->increment('quantity', $grnItem->quantity_received); StockMovement::create([ 'item_id' => $grnItem->item_id, 'warehouse_id' => $grn->warehouse_id, 'type' => 'in', 'quantity' => $grnItem->quantity_received, 'reference_type' => 'GoodsReceiptNote', 'reference_id' => $grn->id, 'created_by' => auth()->id(), ]); $grn->purchaseOrder->items() ->where('item_id', $grnItem->item_id) ->increment('quantity_received', $grnItem->quantity_received); } $grn->update(['status' => 'confirmed']); // Check if all PO items have been fully received $po = $grn->purchaseOrder->fresh(['items']); $allFullyReceived = $po->items->every( fn($poItem) => $poItem->quantity_received >= $poItem->quantity ); if ($allFullyReceived) { $po->update(['status' => 'received']); } }); return redirect()->back()->with('success', 'GRN confirmed and stock updated.'); } }