middleware('check.permission:sales.create')->only(['create', 'store']); $this->middleware('check.permission:sales.read')->only(['index']); $this->middleware('check.permission:sales.update')->only(['edit', 'update']); $this->middleware('check.permission:sales.delete')->only(['destroy', 'deleteAll']); $this->middleware('check.permission:inventory.create')->only(['createInventory']); } public function index(Request $request) { $business_id = auth()->user()->business_id; $salesWithReturns = SaleReturn::where('business_id', $business_id) ->pluck('sale_id') ->toArray(); $salesQuery = Sale::with(['user:id,name', 'branch:id,name', 'party:id,name,email,phone,type', 'details', 'details.product:id,productName,category_id', 'details.product.category:id,categoryName', 'payment_type:id,name'])->where('business_id', $business_id); $salesQuery->when($request->branch_id, function ($q) use ($request) { $q->where('branch_id', $request->branch_id); }); // Default to today $startDate = Carbon::today()->format('Y-m-d'); $endDate = Carbon::today()->format('Y-m-d'); if ($request->custom_days === 'yesterday') { $startDate = Carbon::yesterday()->format('Y-m-d'); $endDate = Carbon::yesterday()->format('Y-m-d'); } elseif ($request->custom_days === 'last_seven_days') { $startDate = Carbon::today()->subDays(6)->format('Y-m-d'); } elseif ($request->custom_days === 'last_thirty_days') { $startDate = Carbon::today()->subDays(29)->format('Y-m-d'); } elseif ($request->custom_days === 'current_month') { $startDate = Carbon::now()->startOfMonth()->format('Y-m-d'); $endDate = Carbon::now()->endOfMonth()->format('Y-m-d'); } elseif ($request->custom_days === 'last_month') { $startDate = Carbon::now()->subMonth()->startOfMonth()->format('Y-m-d'); $endDate = Carbon::now()->subMonth()->endOfMonth()->format('Y-m-d'); } elseif ($request->custom_days === 'current_year') { $startDate = Carbon::now()->startOfYear()->format('Y-m-d'); $endDate = Carbon::now()->endOfYear()->format('Y-m-d'); } elseif ($request->custom_days === 'custom_date' && $request->from_date && $request->to_date) { $startDate = Carbon::parse($request->from_date)->format('Y-m-d'); $endDate = Carbon::parse($request->to_date)->format('Y-m-d'); } $salesQuery->whereDate('saleDate', '>=', $startDate) ->whereDate('saleDate', '<=', $endDate); // Search Filter if ($request->filled('search')) { $salesQuery->where(function ($query) use ($request) { $query->where('paymentType', 'like', '%' . $request->search . '%') ->orWhere('invoiceNumber', 'like', '%' . $request->search . '%') ->orWhereHas('party', function ($q) use ($request) { $q->where('name', 'like', '%' . $request->search . '%'); }) ->orWhereHas('payment_type', function ($q) use ($request) { $q->where('name', 'like', '%' . $request->search . '%'); }) ->orWhereHas('branch', function ($q) use ($request) { $q->where('name', 'like', '%' . $request->search . '%'); }); }); } $perPage = $request->input('per_page', 20); $sales = $salesQuery->latest()->paginate($perPage)->appends($request->query()); if ($request->ajax()) { return response()->json([ 'data' => view('business::sales.datas', compact('sales', 'salesWithReturns'))->render() ]); } $branches = Branch::withTrashed()->where('business_id', $business_id)->latest()->get(); $payment_types = PaymentType::where('business_id', $business_id)->whereStatus(1)->latest()->get(); return view('business::sales.index', compact('sales', 'salesWithReturns', 'branches', 'payment_types')); } public function create() { $business_id = auth()->user()->business_id; // Clears all cart items Cart::destroy(); $customers = Party::where('type', '!=', 'supplier') ->where('business_id', $business_id) ->latest() ->get(); $products = Product::with([ 'unit:id,unitName', 'vat:id,rate', 'brand:id,brandName', 'category:id,categoryName', 'product_model:id,name', 'stocks', 'combo_products.stock.product:id,productName,productCode', 'rack:id,name', 'shelf:id,name' ]) ->withSum('saleDetails', 'quantities') ->withSum('purchaseDetails', 'quantities') ->withSum('stocks', 'productStock') ->where('business_id', $business_id) ->where('is_displayed_in_pos', 1) ->where(function ($query) { $query->where(function ($q) { $q->where('product_type', '!=', 'combo') ->whereHas('stocks', function ($s) { $s->where('productStock', '>', 0); }); }) ->orWhere(function ($q) { $q->where('product_type', 'combo') ->whereHas('combo_products'); }); }) ->latest() ->get(); $categories = Category::where('business_id', $business_id)->latest()->get(); $brands = Brand::where('business_id', $business_id)->latest()->get(); $vats = Vat::where('business_id', $business_id)->whereStatus(1)->latest()->get(); $payment_types = PaymentType::where('business_id', $business_id)->whereStatus(1)->latest()->get(); $warehouses = Warehouse::select('id', 'name')->where('business_id', $business_id)->latest()->get(); // Generate a unique invoice number $sale_id = (Sale::max('id') ?? 0) + 1; $invoice_no = 'S-' . str_pad($sale_id, 5, '0', STR_PAD_LEFT); return view('business::sales.create', compact('customers', 'products', 'invoice_no', 'categories', 'brands', 'vats', 'payment_types', 'warehouses')); } public function store(Request $request) { $request->validate([ 'party_id' => 'nullable|exists:parties,id', 'vat_id' => 'nullable|exists:vats,id', 'invoiceNumber' => 'required|string', 'customer_phone' => 'nullable|string', 'receive_amount' => 'nullable|numeric', 'discountAmount' => 'nullable|numeric', 'discount_type' => 'nullable|in:flat,percent', 'shipping_charge' => 'nullable|numeric', 'saleDate' => 'nullable|date', ]); $business_id = auth()->user()->business_id; // Get only 'sale' type items from cart $carts = Cart::content()->filter(fn($item) => $item->options->type == 'sale'); if ($carts->count() < 1) { return response()->json(['message' => __('Cart is empty. Add items first!')], 400); } DB::beginTransaction(); try { // Calculation: subtotal, vat, discount, shipping, rounding $rawSubtotal = $carts->sum(fn($item) => (float) $item->subtotal); // Minus each cart discount from subtotal $cartWiseDiscountTotal = $carts->sum(function ($item) { $dis = (float) ($item->options->discount ?? 0); return $dis * $item->qty; }); $subtotal = $rawSubtotal - $cartWiseDiscountTotal; $vatAmount = $carts->sum(function ($item) { return ($item->price * $item->qty) * ($item->options->vat_percent ?? 0) / 100; }); // overall discount $discountAmount = $request->discountAmount ?? 0; $subtotalWithVat = $subtotal + $vatAmount; if ($request->discount_type == 'percent') { $discountAmount = ($subtotalWithVat * $discountAmount) / 100; } if ($discountAmount > $subtotalWithVat) { return response()->json(['message' => __('Discount cannot be more than subtotal with VAT!')], 400); } $shippingCharge = $request->shipping_charge ?? 0; $actualTotalAmount = $subtotalWithVat - $discountAmount + $shippingCharge; $roundingTotalAmount = sale_rounding($actualTotalAmount); $rounding_amount = $roundingTotalAmount - $actualTotalAmount; $rounding_option = sale_rounding(); $payments = $request->payments; if (isset($payments['main'])) { $mainPayment = $payments['main']; $mainPayment['amount'] = $request->receive_amount ?? 0; $payments = [$mainPayment]; } $receiveAmount = collect($payments) ->reject(fn($p) => strtolower($p['type'] ?? '') === 'cheque') ->sum(fn($p) => $p['amount'] ?? 0); $changeAmount = max($receiveAmount - $roundingTotalAmount, 0); $dueAmount = max($roundingTotalAmount - $receiveAmount, 0); $paidAmount = $receiveAmount - $changeAmount; // Update business/branch balance updateBalance($paidAmount, 'increment'); // Create sale record $sale = Sale::create([ 'user_id' => auth()->id(), 'business_id' => $business_id, 'branch_id' => auth()->user()->branch_id ?? session('branch_id'), 'type' => $request->type == 'inventory' ? 'inventory' : 'sale', 'party_id' => $request->party_id, 'invoiceNumber' => $request->invoiceNumber, 'saleDate' => $request->saleDate ? Carbon::parse($request->saleDate)->setTimeFromTimeString(now()->format('H:i:s')) : now(), 'vat_id' => $request->vat_id, 'vat_amount' => $vatAmount, 'discountAmount' => $discountAmount, 'discount_type' => $request->discount_type ?? 'flat', 'discount_percent' => $request->discount_type == 'percent' ? $request->discountAmount : 0, 'totalAmount' => $roundingTotalAmount, 'actual_total_amount' => $actualTotalAmount, 'rounding_amount' => $rounding_amount, 'rounding_option' => $rounding_option, 'paidAmount' => min($paidAmount, $roundingTotalAmount), 'change_amount' => $changeAmount, 'dueAmount' => $dueAmount, 'shipping_charge' => $shippingCharge, 'isPaid' => $dueAmount > 0 ? 0 : 1, 'meta' => [ 'customer_phone' => $request->customer_phone, 'note' => $request->note, ] ]); $avgDiscount = $discountAmount / max($carts->count(), 1); $totalPurchaseAmount = 0; $saleDetailsData = []; foreach ($carts as $cartItem) { $qty = $cartItem->qty; $purchase_price = $cartItem->options->purchase_price ?? 0; // Find the product $product = Product::find($cartItem->id); if (!$product) { return response()->json([ 'message' => __("Product not found: {$cartItem->name}") ], 400); } $itemCartDiscount = ((float) $cartItem->options->discount ?? 0) * $qty; if ($product->product_type == 'combo') { $combo_total_purchase = 0; $combo_total_sale = $cartItem->price * $qty; foreach ($product->combo_products as $comboItem) { $stock = Stock::where('id', $comboItem->stock_id)->first(); $required_qty = $comboItem->quantity * $qty; // multiply by cart quantity if ($stock->productStock < $required_qty) { $batchText = $stock->batch_no ? " ({$stock->batch_no})" : ""; return response()->json([ 'message' => __("Combo item '{$stock->product->productName}'{$batchText} - stock not available. Available: {$stock->productStock}") ], 400); } // Decrement stock $stock->decrement('productStock', $required_qty); // Add combo item purchase total $combo_total_purchase += $comboItem->purchase_price * $required_qty; // total purchase amount for sale $totalPurchaseAmount += $comboItem->purchase_price * $required_qty; } $lossProfit = $combo_total_sale - $combo_total_purchase - $avgDiscount - $itemCartDiscount; // Store sale details for combo product itself $saleDetailsData[] = [ 'sale_id' => $sale->id, 'stock_id' => null, 'product_id' => $cartItem->id, 'price' => $cartItem->price, 'discount' => $cartItem->options->discount ?? 0, 'lossProfit' => $lossProfit, 'quantities' => $qty, 'productPurchasePrice' => $combo_total_purchase, 'expire_date' => null, 'warranty_guarantee_info' => $product->warranty_guarantee_info ? json_encode($product->warranty_guarantee_info) : null, ]; } else { $stock = Stock::where('id', $cartItem->options->stock_id)->first(); if ($stock->productStock < $qty) { $batchText = $stock->batch_no ? " ($stock->batch_no)" : ""; return response()->json([ 'message' => __($cartItem->name . $batchText . ' - stock not available. Available: ' . $stock->productStock) ], 400); } $stock->decrement('productStock', $qty); $lossProfit = (($cartItem->price - $stock->productPurchasePrice) * $qty) - $avgDiscount - $itemCartDiscount; $saleDetailsData[] = [ 'sale_id' => $sale->id, 'stock_id' => $cartItem->options->stock_id, 'product_id' => $cartItem->id, 'price' => $cartItem->price, 'discount' => $cartItem->options->discount ?? 0, 'lossProfit' => $lossProfit, 'quantities' => $cartItem->qty, 'productPurchasePrice' => $purchase_price, 'expire_date' => $cartItem->options->expire_date ?? null, 'warranty_guarantee_info' => $product->warranty_guarantee_info ? json_encode($product->warranty_guarantee_info) : null, ]; $totalPurchaseAmount += $purchase_price * $qty; } } // Insert all sale details SaleDetails::insert($saleDetailsData); $sale->update([ 'lossProfit' => $subtotal - $totalPurchaseAmount - $discountAmount, ]); // Handle due tracking for customers if ($dueAmount > 0) { $allowDueForGuest = product_setting()?->modules['allow_due_sale'] ?? false; // No party if (!$request->party_id) { if (!$allowDueForGuest) { return response()->json([ 'message' => __('You have not allowed guest sales, so you cannot sell on due for a walking customer.') ], 400); } } else { // Party exist $party = Party::findOrFail($request->party_id); if ($party->credit_limit > 0 && ($party->due + $dueAmount) > $party->credit_limit) { return response()->json([ 'message' => __('Sale cannot be created. Party due will exceed credit limit!') ], 400); } $party->update(['due' => $party->due + $dueAmount]); } } // MultiPaymentProcessed Event event(new MultiPaymentProcessed( $payments ?? [], $sale->id, 'sale', $receiveAmount, $request->party_id ?? null, )); // Clear all items from cart foreach ($carts as $cartItem) { Cart::remove($cartItem->rowId); } // Notify user sendNotifyToUser($sale->id, route('business.sales.index', ['id' => $sale->id]), __('New sale created.'), $business_id); // Send SMS event(new SaleSms($sale)); DB::commit(); return response()->json([ 'message' => __('Sales created successfully.'), 'redirect' => route('business.sales.index'), 'secondary_redirect_url' => route('business.sales.invoice', $sale->id), ]); } catch (\Exception $e) { DB::rollback(); return response()->json(['message' => $e->getMessage()], 400); } } public function edit($id) { // Clears all cart items Cart::destroy(); $business_id = auth()->user()->business_id; $sale = Sale::with('user:id,name', 'party:id,name,email,phone,type', 'details', 'details.stock', 'details.product:id,productName,category_id,unit_id,productCode,productSalePrice,productPicture,vat_id', 'details.product.category:id,categoryName', 'details.product.unit:id,unitName', 'details.product.vat:id,rate', 'payment_type:id,name', 'transactions') ->where('business_id', $business_id) ->findOrFail($id); $customers = Party::where('type', '!=', 'supplier') ->where('business_id', $business_id) ->latest() ->get(); $products = Product::with('category:id,categoryName', 'unit:id,unitName', 'stocks', 'stocks.warehouse') ->where('business_id', $business_id) ->withSum('stocks as total_stock', 'productStock') ->where(function ($query) { $query->where('product_type', 'combo') ->orWhereHas('stocks', function ($q) { $q->where('productStock', '>', 0); }); }) ->latest() ->get(); $categories = Category::where('business_id', $business_id)->latest()->get(); $brands = Brand::where('business_id', $business_id)->latest()->get(); $vats = Vat::where('business_id', $business_id)->whereStatus(1)->latest()->get(); $payment_types = PaymentType::where('business_id', $business_id)->whereStatus(1)->latest()->get(); $warehouses = Warehouse::select('id', 'name')->where('business_id', $business_id)->latest()->get(); // Add sale details to the cart foreach ($sale->details as $detail) { // Add to cart Cart::add([ 'id' => $detail->product_id, 'name' => $detail->product->productName ?? '', 'qty' => $detail->quantities, 'price' => $detail->price ?? 0, 'options' => [ 'type' => 'sale', 'product_code' => $detail->product->productCode ?? '', 'product_unit_id' => $detail->product->unit_id ?? null, 'product_unit_name' => $detail->product->unit->unitName ?? '', 'product_image' => $detail->product->productPicture ?? '', 'stock_id' => $detail->stock_id ?? null, 'batch_no' => $detail->stock->batch_no ?? '', 'expire_date' => $detail->expire_date ?? '', 'purchase_price' => $detail->productPurchasePrice ?? 0, 'discount' => $detail->discount ?? 0, 'vat_percent' => $detail->product->vat->rate ?? 0, ], ]); } $cart_contents = Cart::content()->filter(fn($item) => $item->options->type == 'sale'); $view = $sale->type == 'inventory' ? 'business::sales.edit-inventory' : 'business::sales.edit'; return view($view, compact('sale', 'customers', 'products', 'cart_contents', 'categories', 'brands', 'vats', 'payment_types', 'warehouses')); } public function update(Request $request, $id) { $request->validate([ 'party_id' => 'nullable|exists:parties,id', 'vat_id' => 'nullable|exists:vats,id', 'invoiceNumber' => 'required|string', 'customer_phone' => 'nullable|string', 'receive_amount' => 'nullable|numeric', 'discountAmount' => 'nullable|numeric', 'discount_type' => 'nullable|in:flat,percent', 'saleDate' => 'nullable|date', 'shipping_charge' => 'nullable|numeric', ]); $business_id = auth()->user()->business_id; $carts = Cart::content()->filter(fn($item) => $item->options->type == 'sale'); if ($carts->count() < 1) { return response()->json(['message' => __('Cart is empty. Add items first!')], 400); } DB::beginTransaction(); try { $sale = Sale::findOrFail($id); $sale_prev_due = $sale->dueAmount; $prevDetails = $sale->details; $totalPurchaseAmount = 0; $subtotal = 0; $saleDetailsData = []; $discountAmount = $request->discountAmount ?? 0; $shippingCharge = $request->shipping_charge ?? 0; foreach ($carts as $cartItem) { $prevProduct = $prevDetails->firstWhere('product_id', $cartItem->id); $oldQty = $prevProduct ? $prevProduct->quantities : 0; $newQty = $cartItem->qty; $diffQty = $newQty - $oldQty; $product = Product::with('combo_products.product')->find($cartItem->id); if (!$product) { return response()->json(['message' => __("Product not found: {$cartItem->name}")], 400); } $itemCartDiscount = ((float) $cartItem->options->discount ?? 0) * $newQty; if ($product->product_type === 'combo') { $combo_total_purchase = 0; $combo_total_sale = $cartItem->price * $newQty; foreach ($product->combo_products as $comboItem) { $stock = Stock::where('id', $comboItem->stock_id)->first(); if (!$stock) { return response()->json([ 'message' => __("Stock not found for combo item '{$comboItem->product->productName}'") ], 400); } $requiredQty = $comboItem->quantity * $newQty; if ($stock->productStock + ($prevProduct && $prevProduct->stock_id == $stock->id ? $oldQty * $comboItem->quantity : 0) < $requiredQty) { $batchText = $stock->batch_no ? " ({$stock->batch_no})" : ""; return response()->json([ 'message' => __("Combo item '{$stock->product->productName}'{$batchText} - stock not available. Available: {$stock->productStock}") ], 400); } $stock->productStock -= $diffQty * $comboItem->quantity; $stock->save(); $combo_total_purchase += $comboItem->purchase_price * $diffQty * $comboItem->quantity; $totalPurchaseAmount += $comboItem->purchase_price * $diffQty * $comboItem->quantity; } $lossProfit = $combo_total_sale - $combo_total_purchase - ($discountAmount / max($carts->count(), 1)) - $itemCartDiscount; $saleDetailsData[] = [ 'sale_id' => $sale->id, 'stock_id' => null, 'product_id' => $cartItem->id, 'price' => $cartItem->price, 'discount' => $cartItem->options->discount ?? 0, 'lossProfit' => $lossProfit, 'quantities' => $newQty, 'productPurchasePrice' => $combo_total_purchase, 'expire_date' => null, 'warranty_guarantee_info' => $product->warranty_guarantee_info ? json_encode($product->warranty_guarantee_info) : null, ]; } else { $stock = Stock::where('id', $cartItem->options->stock_id ?? null) ->first() ?? Stock::where('product_id', $cartItem->id)->orderBy('id', 'asc')->first(); if (!$stock) { return response()->json([ 'message' => __($cartItem->name . ' - no stock found.') ], 400); } $availableStock = $stock->productStock + $oldQty; if ($availableStock < $newQty) { return response()->json([ 'message' => __($cartItem->name . ' - stock not available. Available: ' . $availableStock) ], 400); } $stock->productStock -= $diffQty; $stock->save(); $lossProfit = (($cartItem->price - $cartItem->options->purchase_price) * $newQty) - ($discountAmount / max($carts->count(), 1)) - $itemCartDiscount; $totalPurchaseAmount += $cartItem->options->purchase_price * $newQty; $saleDetailsData[] = [ 'sale_id' => $sale->id, 'stock_id' => $stock->id, 'product_id' => $cartItem->id, 'price' => $cartItem->price, 'discount' => $cartItem->options->discount ?? 0, 'lossProfit' => $lossProfit, 'quantities' => $newQty, 'productPurchasePrice' => $cartItem->options->purchase_price ?? 0, 'expire_date' => $cartItem->options->expire_date ?? null, 'warranty_guarantee_info' => $product->warranty_guarantee_info ? json_encode($product->warranty_guarantee_info) : null, ]; } $subtotal += ($cartItem->price - ($cartItem->options->discount ?? 0)) * $newQty; } // VAT & total calculations $vatAmount = $carts->sum(function ($item) { return ($item->price * $item->qty) * ($item->options->vat_percent ?? 0) / 100; }); $subtotalWithVat = $subtotal + $vatAmount; if ($request->discount_type == 'percent') { $discountAmount = ($subtotalWithVat * $discountAmount) / 100; } if ($discountAmount > $subtotalWithVat) { return response()->json(['message' => __('Discount cannot be more than subtotal with VAT!')], 400); } $actualTotalAmount = $subtotalWithVat - $discountAmount + $shippingCharge; $roundingTotalAmount = sale_rounding($actualTotalAmount, $sale->rounding_option); $rounding_amount = $roundingTotalAmount - $actualTotalAmount; // Normalize payments $payments = $request->payments ?? []; if (isset($payments['main'])) { $mainPayment = $payments['main']; $mainPayment['amount'] = $request->receive_amount ?? 0; $payments = [$mainPayment]; } // Check if any old transaction type = deposit $hasDeposit = Transaction::where('business_id', $business_id) ->where('reference_id', $sale->id) ->where('platform', 'sale') ->where('type', 'deposit') ->exists(); // Calculate total amount $receiveAmount = collect($payments) ->reject(function ($p) use ($hasDeposit) { // exclude cheque only if not deposit return !$hasDeposit && strtolower($p['type'] ?? '') === 'cheque'; }) ->sum(fn($p) => $p['amount'] ?? 0); $changeAmount = $receiveAmount > $roundingTotalAmount ? $receiveAmount - $roundingTotalAmount : 0; $dueAmount = max($roundingTotalAmount - $receiveAmount, 0); $paidAmount = $receiveAmount - $changeAmount; updateBalance($paidAmount, 'increment'); $sale->update([ 'invoiceNumber' => $request->invoiceNumber, 'saleDate' => $request->saleDate ? Carbon::parse($request->saleDate)->setTimeFromTimeString(now()->format('H:i:s')) : now(), 'vat_id' => $request->vat_id, 'party_id' => $request->party_id, 'vat_amount' => $vatAmount, 'discountAmount' => $discountAmount, 'discount_type' => $request->discount_type ?? 'flat', 'discount_percent' => $request->discount_type == 'percent' ? $request->discountAmount : 0, 'totalAmount' => $roundingTotalAmount, 'actual_total_amount' => $actualTotalAmount, 'rounding_amount' => $rounding_amount, 'lossProfit' => $subtotal - $totalPurchaseAmount - $discountAmount, 'paidAmount' => $paidAmount > $roundingTotalAmount ? $roundingTotalAmount : $paidAmount, 'change_amount' => $changeAmount, 'dueAmount' => $dueAmount, 'isPaid' => $dueAmount > 0 ? 0 : 1, 'meta' => [ 'customer_phone' => $request->customer_phone, 'note' => $request->note, ] ]); // Clear old sale details & insert new SaleDetails::where('sale_id', $sale->id)->delete(); SaleDetails::insert($saleDetailsData); if ($dueAmount > 0) { $allowDueForGuest = product_setting()?->modules['allow_due_sale'] ?? false; // If guest or no party if (!$request->party_id) { if (!$allowDueForGuest) { return response()->json([ 'message' => __('You have not allowed guest sales. So you cannot sell on due for a walking customer.') ], 400); } } else { // Regular party $party = Party::findOrFail($request->party_id); // Calculate total due $newTotalDue = $request->party_id == $sale->party_id ? $party->due + $dueAmount - $sale_prev_due : $party->due + $dueAmount; // Check credit limit if ($party->credit_limit > 0 && $newTotalDue > $party->credit_limit) { return response()->json([ 'message' => __('Cannot update sale. Party due will exceed credit limit!') ], 400); } // Update party dues if ($request->party_id == $sale->party_id) { $party->update(['due' => $party->due + $dueAmount - $sale_prev_due]); } else { $party->update(['due' => $party->due + $dueAmount]); $prevParty = Party::findOrFail($sale->party_id); $prevParty->update(['due' => $prevParty->due - $sale_prev_due]); } } } // Multiple Payment Process $oldTransactions = Transaction::where('business_id', $business_id) ->where('reference_id', $sale->id) ->where('platform', 'sale') ->get(); // Revert old transactions foreach ($oldTransactions as $old) { switch ($old->transaction_type) { case 'bank_payment': $paymentType = PaymentType::find($old->payment_type_id); if ($paymentType) { $paymentType->decrement('balance', $old->amount); } break; case 'wallet_payment': if ($request->party_id) { $party = Party::find($request->party_id); if ($party) { $party->increment('wallet', $old->amount); } } break; case 'cash_payment': case 'cheque_payment': // nothing to revert for cash/cheque break; } } // Delete old transactions Transaction::where('business_id', $business_id) ->where('reference_id', $sale->id) ->where('platform', 'sale') ->delete(); // Process new payments event(new MultiPaymentProcessed( $payments ?? [], $sale->id, 'sale', $receiveAmount, $request->party_id ?? null, )); foreach ($carts as $cartItem) { Cart::remove($cartItem->rowId); } sendNotifyToUser($sale->id, route('business.sales.index', ['id' => $sale->id]), __('Sale has been updated.'), $business_id); DB::commit(); return response()->json([ 'message' => __('Sales updated successfully.'), 'redirect' => route('business.sales.index'), 'secondary_redirect_url' => route('business.sales.invoice', $sale->id), ]); } catch (\Exception $e) { DB::rollback(); return response()->json(['message' => __('Something went wrong!')], 404); } } public function destroy($id) { DB::beginTransaction(); try { $sale = Sale::findOrFail($id); foreach ($sale->details as $detail) { $stock = Stock::find($detail->stock_id); if (!$stock) { $stock = Stock::where('product_id', $detail->product_id)->orderBy('id', 'asc')->first(); } if ($stock) { $stock->increment('productStock', $detail->quantities); } } if ($sale->party_id) { $party = Party::findOrFail($sale->party_id); $party->update(['due' => $party->due - $sale->dueAmount]); } updateBalance($sale->paidAmount, 'decrement'); sendNotifyToUser($sale->id, route('business.sales.index', ['id' => $sale->id]), __('Sale has been deleted.'), $sale->business_id); $sale->delete(); // Clears all cart items Cart::destroy(); DB::commit(); return response()->json([ 'message' => __('Sale deleted successfully.'), 'redirect' => route('business.sales.index') ]); } catch (\Exception $e) { DB::rollback(); return response()->json(['message' => __('Something went wrong!')], 404); } } public function deleteAll(Request $request) { DB::beginTransaction(); try { $sales = Sale::whereIn('id', $request->ids)->get(); foreach ($sales as $sale) { // Restore stock foreach ($sale->details as $detail) { $stock = Stock::find($detail->stock_id); if (!$stock) { $stock = Stock::where('product_id', $detail->product_id)->orderBy('id', 'asc')->first(); } if ($stock) { $stock->increment('productStock', $detail->quantities); } } // Adjust party due if ($sale->party_id) { $party = Party::findOrFail($sale->party_id); $party->update(['due' => $party->due - $sale->dueAmount]); } // Adjust business balance updateBalance($sale->paidAmount, 'decrement'); sendNotifyToUser($sale->id, route('business.sales.index', ['id' => $sale->id]), __('Sale has been deleted.'), $sale->business_id); $sale->delete(); } Cart::destroy(); DB::commit(); return response()->json([ 'message' => __('Selected sales deleted successfully.'), 'redirect' => route('business.sales.index') ]); } catch (\Exception $e) { DB::rollback(); return response()->json(['message' => __('Something went wrong!')], 404); } } public function acnooFilter(Request $request) { $salesWithReturns = SaleReturn::where('business_id', auth()->user()->business_id) ->pluck('sale_id') ->toArray(); $salesQuery = Sale::with('user:id,name', 'branch:id,name', 'party:id,name,email,phone,type', 'details', 'details.product:id,productName,category_id', 'details.product.category:id,categoryName', 'payment_type:id,name') ->where('business_id', auth()->user()->business_id); $salesQuery->when($request->branch_id, function ($q) use ($request) { $q->where('branch_id', $request->branch_id); }); // Default to today $startDate = Carbon::today()->format('Y-m-d'); $endDate = Carbon::today()->format('Y-m-d'); if ($request->custom_days == 'yesterday') { $startDate = Carbon::yesterday()->format('Y-m-d'); $endDate = Carbon::yesterday()->format('Y-m-d'); } elseif ($request->custom_days == 'last_seven_days') { $startDate = Carbon::today()->subDays(6)->format('Y-m-d'); } elseif ($request->custom_days == 'last_thirty_days') { $startDate = Carbon::today()->subDays(29)->format('Y-m-d'); } elseif ($request->custom_days == 'current_month') { $startDate = Carbon::now()->startOfMonth()->format('Y-m-d'); $endDate = Carbon::now()->endOfMonth()->format('Y-m-d'); } elseif ($request->custom_days == 'last_month') { $startDate = Carbon::now()->subMonth()->startOfMonth()->format('Y-m-d'); $endDate = Carbon::now()->subMonth()->endOfMonth()->format('Y-m-d'); } elseif ($request->custom_days == 'current_year') { $startDate = Carbon::now()->startOfYear()->format('Y-m-d'); $endDate = Carbon::now()->endOfYear()->format('Y-m-d'); } elseif ($request->custom_days == 'custom_date' && $request->from_date && $request->to_date) { $startDate = Carbon::parse($request->from_date)->format('Y-m-d'); $endDate = Carbon::parse($request->to_date)->format('Y-m-d'); } $salesQuery->whereDate('saleDate', '>=', $startDate) ->whereDate('saleDate', '<=', $endDate); // Search Filter if ($request->filled('search')) { $salesQuery->where(function ($query) use ($request) { $query->where('paymentType', 'like', '%' . $request->search . '%') ->orWhere('invoiceNumber', 'like', '%' . $request->search . '%') ->orWhereHas('party', function ($q) use ($request) { $q->where('name', 'like', '%' . $request->search . '%'); }) ->orWhereHas('payment_type', function ($q) use ($request) { $q->where('name', 'like', '%' . $request->search . '%'); }) ->orWhereHas('branch', function ($q) use ($request) { $q->where('name', 'like', '%' . $request->search . '%'); }); }); } $perPage = $request->input('per_page', 10); $sales = $salesQuery->latest()->paginate($perPage); if ($request->ajax()) { return response()->json([ 'data' => view('business::sales.datas', compact('sales', 'salesWithReturns'))->render() ]); } return redirect(url()->previous()); } public function productFilter(Request $request) { $total_products_count = Product::where('business_id', auth()->user()->business_id) ->whereHas('stocks', function ($s) { $s->where('productStock', '>', 0); }) ->count(); $products = Product::where('business_id', auth()->user()->business_id) ->where('is_displayed_in_pos', 1) ->whereHas('stocks', function ($s) use ($request) { $s->when($request->warehouse_id, function ($sw) use ($request) { $sw->where('warehouse_id', $request->warehouse_id); }) ->where('productStock', '>', 0); }) ->with([ 'vat:id,rate', 'stocks' => function ($s) use ($request) { $s->when($request->warehouse_id, function ($sw) use ($request) { $sw->where('warehouse_id', $request->warehouse_id); }) ->where('productStock', '>', 0); } ]) ->when($request->search, function ($query) use ($request) { $query->where(function ($q) use ($request) { $q->where('productName', 'like', '%' . $request->search . '%') ->orWhere('productCode', 'like', '%' . $request->search . '%'); }); }) ->when($request->category_id, function ($query) use ($request) { $query->where('category_id', $request->category_id); }) ->when($request->brand_id, function ($query) use ($request) { $query->where('brand_id', $request->brand_id); }) ->latest() ->get(); // Query categories for search options $categories = Category::where('business_id', auth()->user()->business_id) ->when($request->search, function ($query) use ($request) { $query->where('categoryName', 'like', '%' . $request->search . '%'); }) ->get(); // Query brands for search options $brands = Brand::where('business_id', auth()->user()->business_id) ->when($request->search, function ($query) use ($request) { $query->where('brandName', 'like', '%' . $request->search . '%'); }) ->get(); $total_products = $products->count(); if ($request->ajax()) { return response()->json([ 'total_products' => $total_products, 'total_products_count' => $total_products_count, 'product_id' => $total_products == 1 ? $products->first()->id : null, 'data' => view('business::sales.product-list', compact('products'))->render(), 'categories' => view('business::sales.category-list', compact('categories'))->render(), 'brands' => view('business::sales.brand-list', compact('brands'))->render(), ]); } return redirect(url()->previous()); } // Category search Filter public function categoryFilter(Request $request) { $search = $request->search; $categories = Category::where('business_id', auth()->user()->business_id) ->when($search, function ($query) use ($search) { $query->where('categoryName', 'like', '%' . $search . '%'); }) ->get(); return response()->json([ 'categories' => view('business::sales.category-list', compact('categories'))->render(), ]); } // Brand search Filter public function brandFilter(Request $request) { $search = $request->search; $brands = Brand::where('business_id', auth()->user()->business_id) ->when($search, function ($query) use ($search) { $query->where('brandName', 'like', '%' . $search . '%'); }) ->get(); return response()->json([ 'brands' => view('business::sales.brand-list', compact('brands'))->render(), ]); } /** Get customer wise prices */ public function getProductPrices(Request $request) { $type = $request->type; $products = Product::where('business_id', auth()->user()->business_id)->get(); $prices = []; foreach ($products as $product) { if ($type == 'Dealer') { $prices[$product->id] = currency_format($product->productDealerPrice, currency: business_currency()); } elseif ($type == 'Wholesaler') { $prices[$product->id] = currency_format($product->productWholeSalePrice, currency: business_currency()); } else { // For Retailer or any other type $prices[$product->id] = currency_format($product->productSalePrice, currency: business_currency()); } } return response()->json($prices); } /** Get batch wise prices */ public function getStockPrices(Request $request) { $businessId = auth()->user()->business_id; $customerType = $request->input('type'); $cartStocks = $request->input('stocks', []); // optional, only cart rows $currency = business_currency(); // Fetch all stocks for product list prices $allStocks = Stock::where('business_id', $businessId) ->where('productStock', '>', 0) ->get(); $productPrices = []; // Product-single prices foreach ($allStocks as $stock) { // Determine price based on customer type if ($customerType == 'Dealer') { $price = $stock->productDealerPrice; } elseif ($customerType == 'Wholesaler') { $price = $stock->productWholeSalePrice; } else { $price = $stock->productSalePrice; } $productPrices[$stock->product_id] = currency_format($price, currency: $currency); } // Fetch stocks for cart list prices $stockPrices = []; if (!empty($cartStocks)) { $cartStockIds = collect($cartStocks)->pluck('stock_id')->toArray(); $cartStockQuery = Stock::where('business_id', $businessId) ->whereIn('id', $cartStockIds) ->where('productStock', '>', 0) ->get(); foreach ($cartStockQuery as $stock) { $batchNo = $stock->batch_no ?? 'default'; if ($customerType == 'Dealer') { $price = $stock->productDealerPrice; } elseif ($customerType == 'Wholesaler') { $price = $stock->productWholeSalePrice; } else { $price = $stock->productSalePrice; } $stockPrices[$stock->id][$batchNo] = currency_format($price, currency: $currency); } } // Return both product and cart prices return response()->json([ 'products' => $productPrices, 'stocks' => $stockPrices, ]); } /** Get cart info */ public function getCartData() { $cart_contents = Cart::content()->filter(fn($item) => $item->options->type == 'sale'); $data['sub_total'] = 0; foreach ($cart_contents as $cart) { $data['sub_total'] += $cart->price; } $data['sub_total'] = currency_format($data['sub_total'], currency: business_currency()); return response()->json($data); } public function getInvoice($sale_id) { $sale = Sale::where('business_id', auth()->user()->business_id)->with('user:id,name,role', 'party:id,name,phone,address', 'business:id,phoneNumber,companyName,vat_name,vat_no,address,email,meta', 'details:id,price,discount,quantities,product_id,sale_id,stock_id,warranty_guarantee_info', 'details.stock:id,batch_no', 'details.product:id,productName,vat_id', 'details.product.vat:id,rate', 'payment_type:id,name', 'transactions:id,reference_id,transaction_type,payment_type_id', 'transactions.paymentType:id,name')->findOrFail($sale_id); $transactionTypes = $sale->transactions ->map(function ($transaction) { if ($transaction->transaction_type === 'bank_payment' && !empty($transaction->paymentType?->name)) { return $transaction->paymentType->name; } return $transaction->transaction_type ? ucfirst(explode('_', $transaction->transaction_type)[0]) : ''; }) ->unique() ->implode(', '); $sale_returns = SaleReturn::with('sale:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber', 'sale.party:id,name', 'details', 'details.saleDetail.product:id,productName') ->where('business_id', auth()->user()->business_id) ->where('sale_id', $sale_id) ->latest() ->get(); $returnTransaction = $sale_returns->first()?->transactions->first(); $returnTransactionType = ''; if ($returnTransaction) { $returnTransactionType = $returnTransaction->transaction_type === 'bank_payment' ? ($returnTransaction->paymentType?->name) : ucfirst(explode('_', $returnTransaction->transaction_type)[0]); } // sum of return_qty $sale->details = $sale->details->map(function ($detail) use ($sale_returns) { $return_qty_sum = $sale_returns->flatMap(function ($return) use ($detail) { return $return->details->where('saleDetail.id', $detail->id)->pluck('return_qty'); })->sum(); $detail->quantities = $detail->quantities + $return_qty_sum; return $detail; }); // Calculate the initial discount for each product during sale returns $total_discount = 0; $product_discounts = []; foreach ($sale_returns as $return) { foreach ($return->details as $detail) { // Add the return quantities and return amounts for each sale_detail_id if (!isset($product_discounts[$detail->sale_detail_id])) { // Initialize the first occurrence $product_discounts[$detail->sale_detail_id] = [ 'return_qty' => 0, 'return_amount' => 0, 'price' => $detail->saleDetail->price, ]; } // Accumulate quantities and return amounts for the same sale_detail_id $product_discounts[$detail->sale_detail_id]['return_qty'] += $detail->return_qty; $product_discounts[$detail->sale_detail_id]['return_amount'] += $detail->return_amount; } } // Calculate the total discount based on accumulated quantities and return amounts foreach ($product_discounts as $data) { $product_price = $data['price'] * $data['return_qty']; $discount = $product_price - $data['return_amount']; $total_discount += $discount; } $hasWarranty = false; $hasGuarantee = false; foreach ($sale->details as $detail) { $info = $detail->warranty_guarantee_info ?? []; if (!empty($info['warranty_duration'])) { $hasWarranty = true; } if (!empty($info['guarantee_duration'])) { $hasGuarantee = true; } if ($hasWarranty && $hasGuarantee) break; } ; $bank_detail = PaymentType::where('business_id', auth()->user()->business_id)->where('show_in_invoice', 1)->latest()->first(); return view('business::sales.invoice', compact('sale', 'sale_returns', 'total_discount', 'hasWarranty', 'hasGuarantee', 'transactionTypes', 'bank_detail', 'returnTransactionType')); } public function generatePDF(Request $request, $sale_id) { $sale = Sale::where('business_id', auth()->user()->business_id) ->with( 'user:id,name,role', 'party:id,name,phone,address', 'business:id,phoneNumber,companyName,vat_name,vat_no,address,email,meta', 'details:id,price,discount,quantities,product_id,sale_id,stock_id,warranty_guarantee_info', 'details.stock:id,batch_no', 'details.product:id,productName,vat_id', 'details.product.vat:id,rate', 'payment_type:id,name', 'transactions:id,reference_id,transaction_type,payment_type_id', 'transactions.paymentType:id,name' ) ->findOrFail($sale_id); // Transaction types (helper) $transactionTypes = transaction_types($sale->transactions); // Sale returns $sale_returns = SaleReturn::with( 'sale:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber', 'sale.party:id,name', 'details', 'details.saleDetail.product:id,productName' ) ->where('business_id', auth()->user()->business_id) ->where('sale_id', $sale_id) ->latest() ->get(); // Return transaction type (unchanged) $returnTransaction = $sale_returns->first()?->transactions->first(); $returnTransactionType = ''; if ($returnTransaction) { $returnTransactionType = $returnTransaction->transaction_type === 'bank_payment' ? ($returnTransaction->paymentType?->name) : ucfirst(explode('_', $returnTransaction->transaction_type)[0]); } // Return qty merge (unchanged) $sale->details = $sale->details->map(function ($detail) use ($sale_returns) { $return_qty_sum = $sale_returns->flatMap(function ($return) use ($detail) { return $return->details ->where('saleDetail.id', $detail->id) ->pluck('return_qty'); })->sum(); $detail->quantities += $return_qty_sum; return $detail; }); // Discount calculation (unchanged) $total_discount = 0; $product_discounts = []; foreach ($sale_returns as $return) { foreach ($return->details as $detail) { if (!isset($product_discounts[$detail->sale_detail_id])) { $product_discounts[$detail->sale_detail_id] = [ 'return_qty' => 0, 'return_amount' => 0, 'price' => $detail->saleDetail->price, ]; } $product_discounts[$detail->sale_detail_id]['return_qty'] += $detail->return_qty; $product_discounts[$detail->sale_detail_id]['return_amount'] += $detail->return_amount; } } foreach ($product_discounts as $data) { $product_price = $data['price'] * $data['return_qty']; $total_discount += ($product_price - $data['return_amount']); } // Warranty / Guarantee detection $hasWarranty = false; $hasGuarantee = false; foreach ($sale->details as $detail) { $info = $detail->warranty_guarantee_info ?? []; if (!empty($info['warranty_duration'])) $hasWarranty = true; if (!empty($info['guarantee_duration'])) $hasGuarantee = true; if ($hasWarranty && $hasGuarantee) break; } $bank_detail = PaymentType::where('business_id', auth()->user()->business_id)->where('show_in_invoice', 1)->latest()->first(); // ONLY PDF rendering is centralized return PdfService::render( 'business::sales.pdf', compact( 'sale', 'sale_returns', 'total_discount', 'transactionTypes', 'returnTransactionType', 'bank_detail', 'hasWarranty', 'hasGuarantee' ), 'sales-invoice.pdf' ); } public function sendMail(Request $request, $sale_id) { $sale = Sale::where('business_id', auth()->user()->business_id)->with('user:id,name,role', 'party:id,name,phone,address', 'business:id,phoneNumber,companyName,vat_name,vat_no,address,email,meta', 'details:id,price,discount,quantities,product_id,sale_id,stock_id,warranty_guarantee_info', 'details.stock:id,batch_no', 'details.product:id,productName', 'payment_type:id,name', 'transactions:id,reference_id,transaction_type,payment_type_id', 'transactions.paymentType:id,name')->findOrFail($sale_id); $transactionTypes = $sale->transactions ->map(function ($transaction) { if ($transaction->transaction_type === 'bank_payment' && !empty($transaction->paymentType?->name)) { return $transaction->paymentType->name; } return $transaction->transaction_type ? ucfirst(explode('_', $transaction->transaction_type)[0]) : ''; }) ->unique() ->implode(', '); $sale_returns = SaleReturn::with('sale:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber', 'sale.party:id,name', 'details', 'details.saleDetail.product:id,productName') ->where('business_id', auth()->user()->business_id) ->where('sale_id', $sale_id) ->latest() ->get(); $returnTransaction = $sale_returns->first()?->transactions->first(); $returnTransactionType = ''; if ($returnTransaction) { $returnTransactionType = $returnTransaction->transaction_type === 'bank_payment' ? ($returnTransaction->paymentType?->name) : ucfirst(explode('_', $returnTransaction->transaction_type)[0]); } // sum of return_qty $sale->details = $sale->details->map(function ($detail) use ($sale_returns) { $return_qty_sum = $sale_returns->flatMap(function ($return) use ($detail) { return $return->details->where('saleDetail.id', $detail->id)->pluck('return_qty'); })->sum(); $detail->quantities = $detail->quantities + $return_qty_sum; return $detail; }); // Calculate the initial discount for each product during sale returns $total_discount = 0; $product_discounts = []; foreach ($sale_returns as $return) { foreach ($return->details as $detail) { // Add the return quantities and return amounts for each sale_detail_id if (!isset($product_discounts[$detail->sale_detail_id])) { // Initialize the first occurrence $product_discounts[$detail->sale_detail_id] = [ 'return_qty' => 0, 'return_amount' => 0, 'price' => $detail->saleDetail->price, ]; } // Accumulate quantities and return amounts for the same sale_detail_id $product_discounts[$detail->sale_detail_id]['return_qty'] += $detail->return_qty; $product_discounts[$detail->sale_detail_id]['return_amount'] += $detail->return_amount; } } // Calculate the total discount based on accumulated quantities and return amounts foreach ($product_discounts as $data) { $product_price = $data['price'] * $data['return_qty']; $discount = $product_price - $data['return_amount']; $total_discount += $discount; } $hasWarranty = false; $hasGuarantee = false; foreach ($sale->details as $detail) { $info = $detail->warranty_guarantee_info ?? []; if (!empty($info['warranty_duration'])) { $hasWarranty = true; } if (!empty($info['guarantee_duration'])) { $hasGuarantee = true; } if ($hasWarranty && $hasGuarantee) break; } ; $bank_detail = PaymentType::where('business_id', auth()->user()->business_id)->where('show_in_invoice', 1)->latest()->first(); $pdf = Pdf::loadView('business::sales.pdf', compact('sale', 'sale_returns', 'total_discount', 'transactionTypes', 'returnTransactionType', 'bank_detail', 'hasWarranty', 'hasGuarantee')); // Send email with PDF attachment Mail::raw('Please find attached your sales invoice.', function ($message) use ($pdf) { $message->to(auth()->user()->email) ->subject('Sales Invoice') ->attachData($pdf->output(), 'sales-invoice.pdf', [ 'mime' => 'application/pdf', ]); }); return response()->json([ 'message' => __('Email Sent Successfully.'), 'redirect' => route('business.sales.index'), ]); } public function createCustomer(Request $request) { $request->validate([ 'phone' => 'nullable|max:20|' . Rule::unique('parties')->where('business_id', auth()->user()->business_id), 'name' => 'required|string|max:255', 'type' => 'required|string|in:Retailer,Dealer,Wholesaler,Supplier', 'email' => 'nullable|email', 'image' => 'nullable|image', 'address' => 'nullable|string|max:255', 'due' => 'nullable|numeric|min:0', ]); Party::create($request->except('image', 'due') + [ 'due' => $request->due ?? 0, 'image' => $request->image ? $this->upload($request, 'image') : NULL, 'business_id' => auth()->user()->business_id ]); return response()->json([ 'message' => __('Customer created successfully'), 'redirect' => route('business.sales.create') ]); } public function createInventory() { // Clears all cart items Cart::destroy(); $customers = Party::where('type', '!=', 'supplier') ->where('business_id', auth()->user()->business_id) ->latest() ->get(); $products = Product::with([ 'stocks' => function ($query) { $query->where('productStock', '>', 0); }, 'category:id,categoryName', 'unit:id,unitName', 'stocks.warehouse' ]) ->where('business_id', auth()->user()->business_id) ->withSum('stocks as total_stock', 'productStock') ->latest() ->get() ->where('total_stock', '>', 0) ->values(); $categories = Category::where('business_id', auth()->user()->business_id)->latest()->get(); $vats = Vat::where('business_id', auth()->user()->business_id)->whereStatus(1)->latest()->get(); $payment_types = PaymentType::where('business_id', auth()->user()->business_id)->whereStatus(1)->latest()->get(); // Generate a unique invoice number $sale_id = (Sale::max('id') ?? 0) + 1; $invoice_no = 'S-' . str_pad($sale_id, 5, '0', STR_PAD_LEFT); return view('business::sales.inventory', compact('customers', 'products', 'invoice_no', 'categories', 'vats', 'payment_types')); } public function viewPayment(string $type, string $id) { $modelMap = [ 'sale' => Sale::class, 'purchase' => Purchase::class, 'income' => Income::class, 'expense' => Expense::class, 'due' => DueCollect::class, ]; if (!isset($modelMap[$type])) { return response()->json(['message' => 'Invalid type'], 400); } $modelClass = $modelMap[$type]; $record = $modelClass::with(['transactions.paymentType:id,name']) ->where('business_id', auth()->user()->business_id) ->findOrFail($id); $transactions = $record->transactions->map(function ($transaction) { return [ 'id' => $transaction->id, 'date' => formatted_date($transaction->date), 'raw_date' => $transaction->date, 'receipt_no' => $transaction->invoice_no ?? '-', 'amount' => currency_format($transaction->amount, currency: business_currency()), 'raw_amount' => $transaction->amount, 'payment_type' => $transaction->transaction_type === 'bank_payment' ? ($transaction->paymentType->name ?? '') : ucfirst(explode('_', $transaction->transaction_type)[0] ?? ''), 'note' => $transaction->note ?? '', 'cheque_number' => $transaction->meta['cheque_number'] ?? '', ]; }); // Return additional data including total amount return response()->json([ 'data' => $transactions, 'total_amount' => $record->actual_total_amount ?? 0, ]); } public function deletePayment(string $type, string $id) { $modelMap = [ 'sale' => Sale::class, 'purchase' => Purchase::class, 'income' => Income::class, 'expense' => Expense::class, 'due' => DueCollect::class, ]; if (!isset($modelMap[$type])) { return response()->json(['message' => 'Invalid type'], 400); } Transaction::where('id', $id)->delete(); return response()->json([ 'message' => __('Transaction deleted successfully.'), 'redirect' => url()->previous(), ]); } }