filter(fn($item) => $item->options->type == 'sale'); $stockIds = $cart_contents->pluck('options.stock_id')->filter()->unique(); $batchNos = Stock::whereIn('id', $stockIds)->pluck('batch_no', 'id'); foreach ($cart_contents as $cartItem) { $stockId = $cartItem->options->stock_id ?? null; if ($stockId && isset($batchNos[$stockId])) { $newOptions = $cartItem->options->merge([ 'batch_no' => $batchNos[$stockId], ]); Cart::update($cartItem->rowId, [ 'qty' => $cartItem->qty, 'options' => $newOptions, ]); } } $modules = product_setting()->modules ?? []; return view('business::sales.cart-list', compact('cart_contents', 'modules')); } public function store(Request $request) { $this->validate($request, [ 'stock_id' => 'nullable|exists:stocks,id', 'warehouse_id' => 'nullable|exists:warehouses,id', 'type' => 'nullable|string|in:sale,purchase', 'id' => 'required|integer', 'name' => 'nullable|string', 'quantity' => 'required|numeric', 'price' => 'required|numeric', 'product_code' => 'nullable|string', 'product_unit_id' => 'nullable|integer', 'product_unit_name' => 'nullable|string', 'product_image' => 'nullable|string', 'profit_percent' => 'nullable|numeric', 'sales_price' => 'nullable|numeric', 'whole_sale_price' => 'nullable|numeric', 'dealer_price' => 'nullable|numeric', 'expire_date' => 'nullable|date', 'product_type' => 'nullable|in:single,variant,combo', 'variant_name' => 'nullable|string', 'serial_numbers' => 'nullable|array', 'serial_numbers.*' => 'string', 'has_serial' => 'nullable|boolean', 'price_without_tax' => 'required_without:serial_numbers|numeric|nullable', 'customer_type' => 'nullable|string' ]); $incomingSerials = $request->serial_numbers ?? []; // Serial Mode if (!empty($incomingSerials)) { // Sale if ($request->type === 'sale') { $stocks = Stock::where('business_id', auth()->user()->business_id) ->where('product_id', $request->id) ->where(function ($query) use ($incomingSerials) { foreach ($incomingSerials as $serial) { $query->orWhereJsonContains('serial_numbers', $serial); } })->get(); // Map serial → stock $serialStockMap = []; foreach ($stocks as $stock) { foreach ($incomingSerials as $serial) { if (in_array($serial, $stock->serial_numbers ?? [])) { $serialStockMap[$stock->id][] = $serial; } } } // Add or merge cart per stock foreach ($serialStockMap as $stockId => $serialsForStock) { $resolvedStock = $stocks->firstWhere('id', $stockId); $qty = count($serialsForStock); $customerType = $request->customer_type ?? 'Retailer'; // Determine price based on customer type $price = round($resolvedStock->productSalePrice ?? 0, 2); if ($customerType === 'Dealer') { $price = round($resolvedStock->productDealerPrice ?? 0, 2); } elseif ($customerType === 'Wholesaler') { $price = round($resolvedStock->productWholeSalePrice ?? 0, 2); } $existingCartItem = Cart::search(function ($item) use ($request, $stockId) { return $item->id == $request->id && $item->options->type === 'sale' && $item->options->stock_id == $stockId; })->first(); if ($existingCartItem) { $existingSerials = $existingCartItem->options->serial_numbers ?? []; // Remove already existing serials from incoming $newSerials = array_diff($serialsForStock, $existingSerials); $duplicates = array_intersect($existingSerials, $newSerials); if (!empty($duplicates)) { return response()->json([ 'success' => false, 'message' => 'Serial already exists in cart', 'duplicates' => array_values($duplicates), ], 422); } $mergedSerials = array_values(array_unique( array_merge($existingSerials, $serialsForStock) )); Cart::update($existingCartItem->rowId, [ 'qty' => count($mergedSerials), 'options' => $existingCartItem->options->merge([ 'serial_numbers' => $mergedSerials, ]), ]); } else { Cart::add([ 'id' => $request->id, 'name' => $request->name, 'qty' => $qty, 'price' => $price, 'options' => [ 'type' => 'sale', 'product_code' => $request->product_code, 'product_unit_id' => $request->product_unit_id, 'product_unit_name' => $request->product_unit_name, 'product_image' => $request->product_image, 'product_type' => $request->product_type, 'variant_name' => $request->variant_name, 'stock_id' => $resolvedStock->id, 'warehouse_id' => $resolvedStock->warehouse_id, 'sales_price' => $resolvedStock->productSalePrice, 'whole_sale_price' => $resolvedStock->productWholeSalePrice, 'dealer_price' => $resolvedStock->productDealerPrice, 'serial_numbers' => $serialsForStock, 'has_serial' => $request->has_serial ?? 1, 'price_without_tax' => $resolvedStock->price_without_tax, ] ]); } } } // Purchase else { Cart::add([ 'id' => $request->id, 'name' => $request->name, 'qty' => count($incomingSerials), 'price' => round($request->price, 2), 'options' => [ 'type' => 'purchase', 'product_code' => $request->product_code, 'product_unit_id' => $request->product_unit_id, 'product_unit_name' => $request->product_unit_name, 'product_image' => $request->product_image, 'product_type' => $request->product_type, 'variant_name' => $request->variant_name, 'stock_id' => null, 'warehouse_id' => $request->warehouse_id, 'serial_numbers' => $incomingSerials, 'batch_no' => $request->batch_no, 'expire_date' => $request->expire_date, 'purchase_price' => $request->purchase_price, 'profit_percent' => $request->profit_percent, 'sales_price' => $request->sales_price, 'whole_sale_price' => $request->whole_sale_price, 'dealer_price' => $request->dealer_price, 'has_serial' => $request->has_serial ?? 1, 'price_without_tax' => $request->price_without_tax, ] ]); } return response()->json([ 'success' => true, 'message' => 'Serial product added successfully.' ]); } // Normal product ( without serial) $existingCartItem = Cart::search(function ($item) use ($request) { return $item->id == $request->id && $item->options->type == $request->type; })->first(); if ($existingCartItem) { Cart::update($existingCartItem->rowId, [ 'qty' => $existingCartItem->qty + $request->quantity ]); } else { Cart::add([ 'id' => $request->id, 'name' => $request->name, 'qty' => $request->quantity, 'price' => round($request->price, 2), 'options' => [ 'type' => $request->type, 'product_code' => $request->product_code, 'product_unit_id' => $request->product_unit_id, 'product_unit_name' => $request->product_unit_name, 'product_image' => $request->product_image, 'product_type' => $request->product_type, 'variant_name' => $request->variant_name, 'stock_id' => $request->stock_id, 'batch_no' => $request->batch_no, 'expire_date' => $request->expire_date, 'purchase_price' => $request->purchase_price, 'profit_percent' => $request->profit_percent, 'sales_price' => $request->sales_price, 'whole_sale_price' => $request->whole_sale_price, 'dealer_price' => $request->dealer_price, 'warehouse_id' => $request->warehouse_id, 'has_serial' => $request->has_serial ?? 0, 'price_without_tax' => $request->price_without_tax, ] ]); } return response()->json([ 'success' => true, 'message' => 'Item added to cart successfully.' ]); } public function update(Request $request, $id) { $cart = Cart::get($id); if (!$cart) { return response()->json([ 'success' => false, 'message' => __('Item not found in cart') ]); } $qty = $request->qty ?? $cart->qty; if ($qty < 0) { return response()->json([ 'success' => false, 'message' => __('Enter a valid quantity') ]); } // incoming serial $incomingSerials = $request->serial_numbers ?? null; if (is_array($incomingSerials)) { $existingSerials = $incomingSerials; } else { $existingSerials = $cart->options->serial_numbers ?? []; } // normalize to array if (!is_array($existingSerials)) { $existingSerials = !empty($existingSerials) ? [$existingSerials] : []; } $hasSerial = $cart->options->has_serial ?? 0; if ($hasSerial) { $qty = count($existingSerials); } Cart::update($id, [ 'qty' => $qty, 'price' => round($request->price ?? $cart->price, 2), 'options' => [ 'type' => $cart->options->type, 'expire_date' => $request->expire_date ?? $cart->options->expire_date, 'stock_id' => $request->stock_id ?? $cart->options->stock_id, 'batch_no' => $request->batch_no ?? $cart->options->batch_no, 'product_code' => $cart->options->product_code, 'product_unit_id' => $cart->options->product_unit_id, 'product_unit_name' => $cart->options->product_unit_name, 'product_image' => $cart->options->product_image, 'profit_percent' => $cart->options->profit_percent, 'sales_price' => $cart->options->sales_price, 'discount' => $request->discount ?? $cart->options->discount, 'whole_sale_price' => $cart->options->whole_sale_price, 'dealer_price' => $cart->options->dealer_price, 'purchase_price' => $cart->options->purchase_price, 'product_type' => $cart->options->product_type, 'warehouse_id' => $cart->options->warehouse_id, 'variant_name' => $cart->options->variant_name, 'price_without_tax' => $cart->options->price_without_tax, 'has_serial' => $hasSerial, 'serial_numbers' => $existingSerials, ] ]); return response()->json([ 'success' => true, 'message' => __('Cart updated successfully') ]); } public function destroy($id) { try { Cart::remove($id); return response()->json(['success' => true, 'message' => __('Item removed from cart')]); } catch (InvalidRowIDException $e) { return response()->json(['success' => false, 'message' => __('The cart does not contain this item')]); } } public function removeAllCart(Request $request) { $carts = Cart::content(); if ($carts->count() < 1) { return response()->json(['message' => __('Cart is empty. Add items first!')]); } Cart::destroy(); $response = [ 'success' => true, 'message' => __('All cart removed successfully!'), ]; return response()->json($response); } }