get(); // Transform products with availability info $produks = $produks->map(function ($produk) { return [ 'id' => $produk->id, 'nama_produk' => $produk->nama_produk, 'harga_produk' => $produk->harga_produk, 'stok_produk' => $produk->stok_produk, 'foto' => $produk->foto, 'deskripsi_produk' => $produk->deskripsi_produk, 'recipes' => $produk->recipes, 'can_sell' => $this->canSellProduct($produk), 'max_sellable' => $this->getMaxSellableQuantity($produk), 'missing_materials' => $this->getMissingMaterialsForSale($produk), 'has_recipe' => $produk->hasCompleteRecipe(), ]; }); return view('pages.admin.pos.index', compact('produks')); } private function generateTransactionCode() { $today = now()->format('Ymd'); $lastTransaction = Transaksi::whereDate('created_at', today())->orderBy('kode_transaksi', 'desc')->first(); if ($lastTransaction) { // Extract the numeric part and increment $lastNumber = (int) substr($lastTransaction->kode_transaksi, -4); $newNumber = str_pad($lastNumber + 1, 4, '0', STR_PAD_LEFT); } else { $newNumber = '0001'; } return 'TRX-' . $today . '-' . $newNumber; } public function store(Request $request) { $request->validate([ 'customer_name' => 'required|string', 'customer_phone' => 'required|string', 'product_id' => 'required|array', 'quantity' => 'required|array', 'price' => 'required|array', 'total_harga' => 'required|numeric|min:0', ]); try { DB::beginTransaction(); // Generate kode transaksi $kodeTransaksi = $this->generateTransactionCode(); // Buat transaksi $transaksi = Transaksi::create([ 'kode_transaksi' => $kodeTransaksi, 'tanggal_transaksi' => now(), 'total_harga' => $request->total_harga, 'customer_name' => $request->customer_name, 'customer_phone' => $request->customer_phone, 'user_id' => auth()->id(), // Tambahkan user_id dari user yang login 'status' => 'completed', ]); // Proses setiap item foreach ($request->product_id as $index => $productId) { $produk = Produk::findOrFail($productId); $quantity = $request->quantity[$index]; $price = $request->price[$index]; // Validasi stok produk if ($produk->stok_produk < $quantity) { throw new \Exception("Stok tidak mencukupi untuk produk {$produk->nama_produk}"); } // Hanya kurangi stok produk $produk->stok_produk -= $quantity; $produk->save(); // Buat detail transaksi TransaksiDetail::create([ 'transaksi_id' => $transaksi->id, 'produk_id' => $productId, 'quantity' => $quantity, 'price' => $price, 'subtotal' => $price * $quantity, ]); } DB::commit(); return response()->json([ 'success' => true, 'message' => 'Transaksi berhasil', 'transaction_id' => $transaksi->id, ]); } catch (\Exception $e) { DB::rollback(); Log::error('Transaction error: ' . $e->getMessage()); return response()->json( [ 'success' => false, 'message' => $e->getMessage(), ], 400, ); } } public function printReceipt($id) { try { $transaction = Transaksi::with('details.produk')->findOrFail($id); return view('pages.admin.pos.receipt', compact('transaction')); } catch (\Exception $e) { Log::error('Print receipt error: ' . $e->getMessage()); return response()->json( [ 'success' => false, 'message' => 'Gagal memuat struk: ' . $e->getMessage(), ], 500, ); } } public function printThermalReceipt($id) { try { $transaction = Transaksi::with('details.produk')->findOrFail($id); $pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView('pages.admin.pos.thermal-receipt', compact('transaction')); // Set paper size for thermal receipt (58mm width) $pdf->setPaper([0, 0, 164.41, 'auto'], 'portrait'); // 58mm = 164.41 points return $pdf->stream('struk-' . $transaction->id . '.pdf'); } catch (\Exception $e) { Log::error('Print thermal receipt error: ' . $e->getMessage()); return response()->json( [ 'success' => false, 'message' => 'Gagal membuat PDF struk: ' . $e->getMessage(), ], 500, ); } } /** * Check if product can be sold (considering both product stock and raw materials) */ private function canSellProduct($produk, $quantity = 1) { // Check product stock if ($produk->stok_produk < $quantity) { return false; } // If product has recipe, check raw materials if ($produk->hasCompleteRecipe()) { return $produk->canProduce($quantity); } return true; } /** * Get maximum sellable quantity considering both product stock and raw materials */ private function getMaxSellableQuantity($produk) { $maxFromStock = $produk->stok_produk; // If no recipe, return product stock if (!$produk->hasCompleteRecipe()) { return $maxFromStock; } // Get max producible from raw materials $maxFromMaterials = $produk->getMaxProducibleQuantity(); // Return the minimum of both return min($maxFromStock, $maxFromMaterials); } /** * Get missing materials for selling a product */ private function getMissingMaterialsForSale($produk, $quantity = 1) { if (!$produk->hasCompleteRecipe()) { return []; } return $produk->getMissingMaterials($quantity); } /** * Check product availability via AJAX */ public function checkAvailability(Request $request) { $request->validate([ 'product_id' => 'required|exists:produks,id', 'quantity' => 'required|integer|min:1', ]); $produk = Produk::with(['recipes.rawMaterial'])->findOrFail($request->product_id); $quantity = $request->quantity; $canSell = $this->canSellProduct($produk, $quantity); $maxSellable = $this->getMaxSellableQuantity($produk); $missingMaterials = $this->getMissingMaterialsForSale($produk, $quantity); return response()->json([ 'can_sell' => $canSell, 'max_sellable' => $maxSellable, 'product_stock' => $produk->stok_produk, 'has_recipe' => $produk->hasCompleteRecipe(), 'missing_materials' => $missingMaterials, 'message' => $canSell ? 'Produk tersedia' : 'Stok tidak mencukupi', ]); } }