diff --git a/Modules/Business/App/Exports/ExportBalanceSheet.php b/Modules/Business/App/Exports/ExportBalanceSheet.php index b9a9cd9f..ac573ef3 100644 --- a/Modules/Business/App/Exports/ExportBalanceSheet.php +++ b/Modules/Business/App/Exports/ExportBalanceSheet.php @@ -4,36 +4,42 @@ use App\Models\PaymentType; use App\Models\Product; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportBalanceSheet implements FromView { + use DateFilterTrait; + public function view(): View { $businessId = auth()->user()->business_id; + $duration = request('custom_days') ? : 'today'; + $fromDate = request('from_date'); + $toDate = request('to_date'); + $perPage = request('per_page') ?? 20; - // PRODUCT $productQuery = Product::select('id', 'business_id', 'productName', 'product_type', 'created_at') ->with(['stocks:id,business_id,product_id,productStock,productPurchasePrice', 'combo_products.stock']) ->where('business_id', $businessId); + $this->applyDateFilter($productQuery, $duration, 'created_at', $fromDate, $toDate); $products = $productQuery->get() ->map(function ($item) { $item->source = 'product'; return $item; }); - // BANK $bankQuery = PaymentType::where('business_id', $businessId); + $this->applyDateFilter($bankQuery, $duration, 'opening_date', $fromDate, $toDate); $banks = $bankQuery->get() ->map(function ($item) { $item->source = 'bank'; return $item; }); - $asset_datas = $products->merge($banks); + $asset_datas = $products->merge($banks)->take($perPage); - // TOTAL STOCK VALUE $total_stock_value = 0; foreach ($products as $product) { if (in_array($product->product_type, ['single', 'variant'])) { diff --git a/Modules/Business/App/Exports/ExportBillWisePofit.php b/Modules/Business/App/Exports/ExportBillWisePofit.php index d0dab195..e6684f43 100644 --- a/Modules/Business/App/Exports/ExportBillWisePofit.php +++ b/Modules/Business/App/Exports/ExportBillWisePofit.php @@ -3,17 +3,42 @@ namespace Modules\Business\App\Exports; use App\Models\Sale; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportBillWisePofit implements FromView { + use DateFilterTrait; + public function view(): View { - $profits = Sale::with('party:id,name') - ->where('business_id', auth()->user()->business_id) - ->latest() - ->get(); + $salesQuery = Sale::with('party:id,name') + ->where('business_id', auth()->user()->business_id); + + $salesQuery->when(request('search'), function ($query) { + $query->where(function ($q) { + $q->where('lossProfit', 'like', '%' . request('search') . '%') + ->orWhere('totalAmount', 'like', '%' . request('search') . '%') + ->orWhere('invoiceNumber', 'like', '%' . request('search') . '%') + ->orWhereHas('party', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }); + }); + }); + + $salesQuery->when(request('party_id'), function ($query, $partyId) { + if ($partyId === 'Guest') { + $query->whereNull('party_id'); + } else { + $query->where('party_id', $partyId); + } + }); + + $duration = request('custom_days') ?: 'today'; + $this->applyDateFilter($salesQuery, $duration, 'saleDate', request('from_date'), request('to_date')); + + $profits = $salesQuery->latest()->limit(request('per_page') ?? 20)->get(); return view('business::bill-wise-profits.excel-csv', compact('profits')); } diff --git a/Modules/Business/App/Exports/ExportCashBalance.php b/Modules/Business/App/Exports/ExportCashBalance.php new file mode 100644 index 00000000..f1a80af7 --- /dev/null +++ b/Modules/Business/App/Exports/ExportCashBalance.php @@ -0,0 +1,42 @@ +where('business_id', auth()->user()->business_id) + ->whereIn('transaction_type', ['cash_payment', 'bank_to_cash', 'cash_to_bank', 'adjust_cash', 'cheque_to_cash']); + + // Date Filter + $duration = request('custom_days') ?: 'today'; + $this->applyDateFilter($cash, $duration, 'date', request('from_date'), request('to_date')); + + $cashes = $cash->when(request('search'), function ($q) { + $q->where(function ($q) { + $q->where('date', 'like', '%' . request('search') . '%') + ->orWhere('platform', 'like', '%' . request('search'). '%') + ->orWhere('transaction_type', 'like', '%' . request('search') . '%') + ->orWhere('amount', 'like', '%' . request('search') . '%') + ->orWhereHas('user', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }); + }); + }) + ->latest() + ->limit(request('per_page') ?? 20)->get(); + + return view('business::cashes.excel-csv', compact('cashes')); + } +} diff --git a/Modules/Business/App/Exports/ExportCashFlowReport.php b/Modules/Business/App/Exports/ExportCashFlowReport.php index 3371000e..d8c44dd1 100644 --- a/Modules/Business/App/Exports/ExportCashFlowReport.php +++ b/Modules/Business/App/Exports/ExportCashFlowReport.php @@ -3,14 +3,17 @@ namespace Modules\Business\App\Exports; use App\Models\Transaction; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportCashFlowReport implements FromView { + use DateFilterTrait; + public function view(): View { - $cash_flows = Transaction::with([ + $query = Transaction::with([ 'paymentType:id,name', 'sale:id,party_id', 'sale.party:id,name', @@ -22,11 +25,44 @@ public function view(): View 'dueCollect.party:id,name', ]) ->where('business_id', auth()->user()->business_id) - ->whereIn('type', ['debit', 'credit']) - ->latest() - ->get(); + ->whereIn('type', ['debit', 'credit']); - $opening_balance = 0; + // Date Filter + $duration = request('custom_days') ?: 'today'; + $this->applyDateFilter($query, $duration, 'date', request('from_date'), request('to_date')); + + // Search filter + if (request('search')) { + $query->where(function ($query) { + $query->where('date', 'like', '%' . request('search') . '%') + ->orWhere('invoice_no', 'like', '%' . request('search') . '%') + ->orWhere('platform', 'like', '%' . request('search') . '%') + ->orWhere('amount', 'like', '%' . request('search') . '%') + ->orWhere('transaction_type', 'like', '%' . request('search') . '%') + ->orWhereHas('paymentType', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }); + }); + } + + // Platform filter + if (request('platform')) { + $query->where('platform', request('platform')); + } + + // Paginate data + $cash_flows = $query->limit(request('per_page') ?? 20)->get(); + + $firstDate = $cash_flows->first()?->date; + + if ($firstDate) { + $opening_balance = (clone $query) + ->whereDate('date', '<', $firstDate) + ->selectRaw("SUM(CASE WHEN type='credit' THEN amount ELSE 0 END) - SUM(CASE WHEN type='debit' THEN amount ELSE 0 END) as balance") + ->value('balance') ?? 0; + } else { + $opening_balance = 0; + } return view('business::cash-flow.excel-csv', compact('cash_flows', 'opening_balance')); } diff --git a/Modules/Business/App/Exports/ExportComboProduct.php b/Modules/Business/App/Exports/ExportComboProduct.php index ebe33830..e81e64d8 100644 --- a/Modules/Business/App/Exports/ExportComboProduct.php +++ b/Modules/Business/App/Exports/ExportComboProduct.php @@ -11,12 +11,39 @@ class ExportComboProduct implements FromView public function view(): View { - $combo_products = Product::with(['combo_products', 'unit:id,unitName', 'category:id,categoryName']) + $user = auth()->user(); + + $branchId = null; + if (moduleCheck('MultiBranchAddon')) { + $branchId = $user->branch_id ?? $user->active_branch_id; + } + + $search = request('search'); + + $combo_products = Product::with(['combo_products.stock.product', 'unit:id,unitName', 'category:id,categoryName']) ->where('business_id', auth()->user()->business_id) ->where('product_type', 'combo') ->withCount('combo_products as total_combo_products') + ->when($search, function ($q) use ($search) { + $q->where(function ($q) use ($search) { + $q->where('productName', 'like', '%' . $search . '%') + ->orWhere('productCode', 'like', '%' . $search . '%') + ->orWhereHas('category', function ($q) use ($search) { + $q->where('categoryName', 'like', '%' . $search . '%'); + }) + ->orWhereHas('unit', function ($q) use ($search) { + $q->where('unitName', 'like', '%' . $search . '%'); + }); + }); + }) + ->when($branchId, function ($q) use ($branchId) { + $q->whereHas('combo_products.stock', function ($q) use ($branchId) { + $q->where('branch_id', $branchId); + }); + }) + ->latest() - ->get(); + ->limit(request('per_page') ?? 20)->get(); $combo_products->transform(function ($product) { $product->total_stock = $product->combo_products->sum(function ($combo) { @@ -29,7 +56,7 @@ public function view(): View return $product; }); - + return view('business::products.combo-products.excel-csv', [ 'combo_products' => $combo_products ]); diff --git a/Modules/Business/App/Exports/ExportComboProductReport.php b/Modules/Business/App/Exports/ExportComboProductReport.php index 819e5a32..bcc5e788 100644 --- a/Modules/Business/App/Exports/ExportComboProductReport.php +++ b/Modules/Business/App/Exports/ExportComboProductReport.php @@ -11,12 +11,39 @@ class ExportComboProductReport implements FromView public function view(): View { - $combo_products = Product::with(['combo_products', 'unit:id,unitName', 'category:id,categoryName']) + $user = auth()->user(); + + $branchId = null; + if (moduleCheck('MultiBranchAddon')) { + $branchId = $user->branch_id ?? $user->active_branch_id; + } + + $search = request('search'); + + $combo_products = Product::with(['combo_products.stock.product', 'unit:id,unitName', 'category:id,categoryName']) ->where('business_id', auth()->user()->business_id) ->where('product_type', 'combo') ->withCount('combo_products as total_combo_products') + ->when($search, function ($q) use ($search) { + $q->where(function ($q) use ($search) { + $q->where('productName', 'like', '%' . $search . '%') + ->orWhere('productCode', 'like', '%' . $search . '%') + ->orWhereHas('category', function ($q) use ($search) { + $q->where('categoryName', 'like', '%' . $search . '%'); + }) + ->orWhereHas('unit', function ($q) use ($search) { + $q->where('unitName', 'like', '%' . $search . '%'); + }); + }); + }) + ->when($branchId, function ($q) use ($branchId) { + $q->whereHas('combo_products.stock', function ($q) use ($branchId) { + $q->where('branch_id', $branchId); + }); + }) + ->latest() - ->get(); + ->limit(request('per_page') ?? 20)->get(); $combo_products->transform(function ($product) { $product->total_stock = $product->combo_products->sum(function ($combo) { diff --git a/Modules/Business/App/Exports/ExportCurrentStock.php b/Modules/Business/App/Exports/ExportCurrentStock.php index d1b1d9f8..c22b1728 100644 --- a/Modules/Business/App/Exports/ExportCurrentStock.php +++ b/Modules/Business/App/Exports/ExportCurrentStock.php @@ -10,21 +10,58 @@ class ExportCurrentStock implements FromView { public function view(): View { - $query = Product::with('stocks') - ->where('product_type', '!=', 'combo') - ->where('business_id', auth()->user()->business_id); + $businessId = auth()->user()->business_id; + $alert_qty_filter = request('alert_qty'); + $search_term = request('search'); + $per_page = request('per_page', 20); - if (request('alert_qty')) { - $products = $query->get()->filter(function ($product) { - $totalStock = $product->stocks->sum('productStock'); - return $totalStock <= $product->alert_qty; + // Base query + $query = Product::with('stocks', 'category:id,categoryName') + ->where('product_type', '!=', 'combo') + ->where('business_id', $businessId); + + if ($search_term) { + $query->where(function ($q) use ($search_term) { + $q->where('productName', 'like', '%' . $search_term . '%') + ->orWhere('productCode', 'like', '%' . $search_term . '%') + ->orWhere('hsn_code', 'like', '%' . $search_term . '%') + ->orWhereHas('category', function ($q) use ($search_term) { + $q->where('categoryName', 'like', '%' . $search_term . '%'); + }); }); - } else { - $products = $query->latest()->get(); } - return view('business::stocks.excel-csv', [ - 'products' => $products - ]); + if ($alert_qty_filter) { + $products = $query + ->withSum('stocks as total_stock', 'productStock') + ->havingRaw('total_stock <= alert_qty') + ->latest() + ->limit($per_page) + ->get(); + + $total_stock_qty = $products->sum('total_stock'); + + $total_stock_value = $products->sum(function ($product) { + return $product->stocks->sum(function ($stock) { + return $stock->productStock * $stock->productPurchasePrice; + }); + }); + } else { + $products = $query + ->withSum('stocks as total_stock', 'productStock') + ->latest() + ->limit($per_page) + ->get(); + + $total_stock_qty = $products->sum('total_stock'); + + $total_stock_value = $products->sum(function ($product) { + return $product->stocks->sum(function ($stock) { + return $stock->productStock * $stock->productPurchasePrice; + }); + }); + } + + return view('business::stocks.excel-csv', compact('products', 'total_stock_qty', 'total_stock_value')); } } diff --git a/Modules/Business/App/Exports/ExportCurrentStockReport.php b/Modules/Business/App/Exports/ExportCurrentStockReport.php index 9f0e2664..873656ad 100644 --- a/Modules/Business/App/Exports/ExportCurrentStockReport.php +++ b/Modules/Business/App/Exports/ExportCurrentStockReport.php @@ -10,13 +10,27 @@ class ExportCurrentStockReport implements FromView { public function view(): View { - return view('business::reports.stocks.excel-csv', [ - 'stock_reports' => Product::with('stocks') - ->where('business_id', auth()->user()->business_id) - ->where('product_type', '!=', 'combo') - ->withSum('stocks', 'productStock') - ->latest() - ->get() - ]); + $productsQuery = Product::with(['stocks' => function ($q) { + $q->select('id', 'product_id', 'productStock', 'productPurchasePrice', 'productSalePrice'); + }]) + ->where('business_id', auth()->user()->business_id) + ->where('product_type', '!=', 'combo'); + + if (request('search')) { + $productsQuery->where(function ($q) { + $q->where('productName', 'like', '%' . request('search') . '%') + ->orWhereHas('stocks', function ($stock) { + $stock->where('productSalePrice', 'like', '%' . request('search') . '%') + ->orWhere('productPurchasePrice', 'like', '%' . request('search') . '%'); + }); + }); + } + + $stock_reports = $productsQuery + ->latest() + ->limit($request->per_page ?? 20) + ->get(); + + return view('business::reports.stocks.excel-csv', compact('stock_reports')); } } diff --git a/Modules/Business/App/Exports/ExportCustomerLedger.php b/Modules/Business/App/Exports/ExportCustomerLedger.php index 731a64f9..24c0983b 100644 --- a/Modules/Business/App/Exports/ExportCustomerLedger.php +++ b/Modules/Business/App/Exports/ExportCustomerLedger.php @@ -13,8 +13,20 @@ public function view(): View $customers = Party::with('sales') ->where('business_id', auth()->user()->business_id) ->where('type', '!=', 'Supplier') + ->when(request('search'), function ($q) { + $q->where(function ($q) { + $q->where('name', 'like', '%' . request('search') . '%') + ->orWhere('phone', 'like', '%' . request('search') . '%') + ->orWhere('type', 'like', '%' . request('search') . '%'); + }); + }) + ->when(request('type'), function ($q) { + $q->where(function ($q) { + $q->where('type', request('type')); + }); + }) ->latest() - ->get(); + ->limit(request('per_page') ?? 20)->get(); $totalAmount = $customers->sum(function ($customer) { return $customer->sales?->sum('totalAmount') ?? 0; diff --git a/Modules/Business/App/Exports/ExportDayBookReport.php b/Modules/Business/App/Exports/ExportDayBookReport.php index 1f888166..54f61e86 100644 --- a/Modules/Business/App/Exports/ExportDayBookReport.php +++ b/Modules/Business/App/Exports/ExportDayBookReport.php @@ -3,14 +3,17 @@ namespace Modules\Business\App\Exports; use App\Models\Transaction; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportDayBookReport implements FromView { + use DateFilterTrait; + public function view(): View { - $day_books = Transaction::with([ + $query = Transaction::with([ 'paymentType:id,name', 'sale:id,user_id,party_id,totalAmount', 'sale.party:id,name', @@ -25,9 +28,34 @@ public function view(): View 'dueCollect.user:id,name', ]) ->where('business_id', auth()->user()->business_id) - ->whereIn('type', ['debit', 'credit']) - ->latest() - ->get(); + ->whereIn('type', ['debit', 'credit']); + + // Apply date filter + $duration = request('custom_days') ?: 'today'; + $this->applyDateFilter($query, $duration, 'date', request('from_date'), request('to_date')); + + // Search filter + if (request('search')) { + $query->where(function ($query) { + $query->where('date', 'like', '%' . request('search') . '%') + ->orWhere('invoice_no', 'like', '%' . request('search') . '%') + ->orWhere('platform', 'like', '%' . request('search') . '%') + ->orWhere('amount', 'like', '%' . request('search') . '%') + ->orWhere('type', 'like', '%' . request('search') . '%') + ->orWhere('transaction_type', 'like', '%' . request('search') . '%') + ->orWhereHas('paymentType', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }); + }); + } + + // Platform filter + if (request('platform')) { + $query->where('platform', request('platform')); + } + + // Paginate data + $day_books = $query->latest()->limit(request('per_page') ?? 20)->get(); return view('business::day-book.excel-csv', compact('day_books')); } diff --git a/Modules/Business/App/Exports/ExportDiscountProduct.php b/Modules/Business/App/Exports/ExportDiscountProduct.php index c9840b23..6bcaa03d 100644 --- a/Modules/Business/App/Exports/ExportDiscountProduct.php +++ b/Modules/Business/App/Exports/ExportDiscountProduct.php @@ -10,11 +10,33 @@ class ExportDiscountProduct implements FromView { public function view(): View { - $discount_products = SaleDetails::with('product:id,productName,productCode') + $user = auth()->user(); + + $branchId = null; + if (moduleCheck('MultiBranchAddon')) { + $branchId = $user->branch_id ?? $user->active_branch_id; + } + + $discount_products = SaleDetails::with('product:id,productName,productCode,hsn_code') ->whereHas('product', function ($q) { $q->where('business_id', auth()->user()->business_id); }) + ->when(request('search'), function ($q) { + $q->whereHas('product', function ($q) { + $q->where('productName', 'like', '%' . request('search') . '%') + ->orWhere('productCode', 'like', '%' . request('search') . '%') + ->orWhere('productPurchasePrice', 'like', '%' . request('search') . '%') + ->orWhere('price', 'like', '%' . request('search') . '%') + ->orWhere('discount', 'like', '%' . request('search') . '%'); + }); + }) + ->when($branchId, function ($q) use ($branchId) { + $q->whereHas('sale', function ($q) use ($branchId) { + $q->where('branch_id', $branchId); + }); + }) ->where('discount', '>', 0) + ->limit(request('per_page') ?? 20) ->get(); return view('business::reports.discount-products.excel-csv', compact('discount_products')); diff --git a/Modules/Business/App/Exports/ExportDue.php b/Modules/Business/App/Exports/ExportDue.php index 45f8b930..6b2135fa 100644 --- a/Modules/Business/App/Exports/ExportDue.php +++ b/Modules/Business/App/Exports/ExportDue.php @@ -11,37 +11,56 @@ class ExportDue implements FromView public function view(): View { $user = auth()->user(); - $businessId = $user->business_id; $activeBranch = $user->active_branch; + $business_id = $user->business_id; - $query = Party::where('business_id', $businessId) + $query = Party::where('business_id', $business_id) ->where('type', '!=', 'Supplier') + ->when(request('search'), function ($q) { + $q->where(function ($q2) { + $q2->where('type', 'like', '%' . request('search') . '%') + ->orWhere('name', 'like', '%' . request('search') . '%') + ->orWhere('phone', 'like', '%' . request('search') . '%') + ->orWhere('credit_limit', 'like', '%' . request('search') . '%'); + }); + }) + ->when(request('type'), function ($q) { + $q->where('type', request('type')); + }) ->with('sales_dues') ->latest(); - if ($activeBranch) { - $query->whereHas('sales_dues', function ($q) use ($activeBranch) { - $q->where('branch_id', $activeBranch->id) - ->where('dueAmount', '>', 0); - }); - } else { - $query->where('due', '>', 0); - } - $parties = $query->get(); if ($activeBranch) { - $parties->transform(function ($supplier) use ($activeBranch) { - $supplier->due = $supplier->sales_dues - ->where('branch_id', $activeBranch->id) - ->sum('dueAmount'); - return $supplier; - })->filter(fn($supplier) => $supplier->due > 0); + $parties = $parties + ->transform(function ($customer) use ($activeBranch) { + $party_due = $customer->sales_dues + ->where('branch_id', $activeBranch->id) + ->sum('dueAmount'); + + if ($customer->branch_id === $activeBranch->id) { + $openingBalanceDue = $customer->opening_balance_type === 'due' + ? ($customer->opening_balance ?? 0) + : 0; + + $customer->due = $openingBalanceDue + $party_due; + } else { + + $customer->due = $party_due; + } + return $customer; + }) + ->filter(fn($customer) => $customer->due > 0) + ->take(request('per_page') ?? 20) + ->values(); + } else { + $parties = $parties + ->filter(fn($customer) => ($customer->due ?? 0) > 0) + ->take(request('per_page') ?? 20) + ->values(); } - return view('business::reports.due.excel-csv', [ - 'parties' => $parties - ]); + return view('business::reports.due.excel-csv', compact('parties')); } - } diff --git a/Modules/Business/App/Exports/ExportDueList.php b/Modules/Business/App/Exports/ExportDueList.php new file mode 100644 index 00000000..793fa7ac --- /dev/null +++ b/Modules/Business/App/Exports/ExportDueList.php @@ -0,0 +1,65 @@ +user()->business_id; + $activeBranch = auth()->user()->active_branch; + + $query = Party::where('business_id', $businessId) + ->with(['purchases_dues', 'sales_dues']); + + if (request('type')) { + $query->where('type', request('type')); + } + + if (request('search')) { + $search = request('search'); + + $query->where(function ($q) use ($search, $activeBranch) { + $q->where('type', 'like', "%$search%") + ->orWhere('name', 'like', "%$search%") + ->orWhere('phone', 'like', "%$search%") + ->orWhere('email', 'like', "%$search%"); + + if (!$activeBranch) { + $q->orWhere('due', 'like', "%$search%"); + } + }); + } + + $parties = $query->latest()->limit(request('per_page') ?? 20)->get(); + + if ($activeBranch) { + $parties = $parties->map(function ($party) use ($activeBranch) { + $sale_dues = $party->type === 'Supplier' + ? $party->purchases_dues->where('branch_id', $activeBranch->id) + : $party->sales_dues->where('branch_id', $activeBranch->id); + + $party_due = $sale_dues->sum('dueAmount'); + + if ($party->branch_id === $activeBranch->id) { + $openingBalanceDue = $party->opening_balance_type === 'due' ? ($party->opening_balance ?? 0) : 0; + $party->due = $openingBalanceDue + $party_due; + } else { + $party->due = $party_due; + } + + return $party; + })->filter(fn($p) => $p->due > 0)->values(); + } else { + $parties = $parties->filter(fn($p) => $p->due > 0)->values(); + } + + return view('business::dues.excel-csv', [ + 'parties' => $parties + ]); + } +} diff --git a/Modules/Business/App/Exports/ExportExpense.php b/Modules/Business/App/Exports/ExportExpense.php index ed7a5799..06f22889 100644 --- a/Modules/Business/App/Exports/ExportExpense.php +++ b/Modules/Business/App/Exports/ExportExpense.php @@ -3,15 +3,48 @@ namespace Modules\Business\App\Exports; use App\Models\Expense; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportExpense implements FromView { + use DateFilterTrait; + public function view(): View { - return view('business::reports.expense.excel-csv', [ - 'expense_reports' => Expense::with('category:id,categoryName', 'payment_type:id,name', 'branch:id,name', 'transactions')->where('business_id', auth()->user()->business_id)->latest()->get() - ]); + $expenseQuery = Expense::with('category:id,categoryName', 'payment_type:id,name', 'branch:id,name', 'transactions') + ->where('business_id', auth()->user()->business_id); + + $expenseQuery->when(request('branch_id'), function ($q) { + $q->where('branch_id', request('branch_id')); + }); + + // Date Filter + $duration = request('custom_days') ?: 'today'; + $this->applyDateFilter($expenseQuery, $duration, 'expenseDate', request('from_date'), request('to_date')); + + // Search Filter + if (request('search')) { + $expenseQuery->where(function ($query) { + $query->where('expanseFor', 'like', '%' . request('search') . '%') + ->orWhere('paymentType', 'like', '%' . request('search') . '%') + ->orWhere('referenceNo', 'like', '%' . request('search') . '%') + ->orWhere('amount', 'like', '%' . request('search') . '%') + ->orWhereHas('category', function ($q) { + $q->where('categoryName', 'like', '%' . request('search') . '%'); + }) + ->orWhereHas('payment_type', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }) + ->orWhereHas('branch', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }); + }); + } + + $expense_reports = $expenseQuery->latest()->limit(request('per_page' ?? 20))->get(); + + return view('business::reports.expense.excel-csv', compact('expense_reports')); } } diff --git a/Modules/Business/App/Exports/ExportExpiredProduct.php b/Modules/Business/App/Exports/ExportExpiredProduct.php index e7aded71..e1117475 100644 --- a/Modules/Business/App/Exports/ExportExpiredProduct.php +++ b/Modules/Business/App/Exports/ExportExpiredProduct.php @@ -10,16 +10,35 @@ class ExportExpiredProduct implements FromView { public function view(): View { - $expired_products = Product::with('stocks', 'unit:id,unitName', 'brand:id,brandName', 'category:id,categoryName') + $expired_products = Product::with('unit:id,unitName', 'brand:id,brandName', 'category:id,categoryName', 'stocks') ->where('business_id', auth()->user()->business_id) ->withSum('stocks as total_stock', 'productStock') ->whereHas('stocks', function ($query) { - $query->whereDate('expire_date', '<', today())->where('productStock', '>', 0); - }) - ->latest()->get(); + $query->whereDate('expire_date', '<', today()) + ->where('productStock', '>', 0); + }) + ->when(request('search'), function ($q) { + $q->where(function ($q) { + $q->where('type', 'like', '%' . request('search') . '%') + ->orWhere('productName', 'like', '%' . request('search') . '%') + ->orWhere('productCode', 'like', '%' . request('search') . '%') + ->orWhere('hsn_code', 'like', '%' . request('search') . '%') + ->orWhere('productSalePrice', 'like', '%' . request('search') . '%') + ->orWhere('productPurchasePrice', 'like', '%' . request('search') . '%') + ->orWhereHas('unit', function ($q) { + $q->where('unitName', 'like', '%' . request('search') . '%'); + }) + ->orWhereHas('brand', function ($q) { + $q->where('brandName', 'like', '%' . request('search') . '%'); + }) + ->orWhereHas('category', function ($q) { + $q->where('categoryName', 'like', '%' . request('search') . '%'); + }); + }); + }) + ->latest() + ->limit(request('per_page') ?? 20)->get(); - return view('business::expired-products.excel-csv', [ - 'expired_products' => $expired_products - ]); + return view('business::expired-products.excel-csv', compact('expired_products')); } } diff --git a/Modules/Business/App/Exports/ExportExpiredProductReport.php b/Modules/Business/App/Exports/ExportExpiredProductReport.php index 7b3b8b6a..1bc57cd8 100644 --- a/Modules/Business/App/Exports/ExportExpiredProductReport.php +++ b/Modules/Business/App/Exports/ExportExpiredProductReport.php @@ -3,22 +3,52 @@ namespace Modules\Business\App\Exports; use App\Models\Product; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportExpiredProductReport implements FromView { + use DateFilterTrait; + public function view(): View { - return view('business::reports.expired-products.excel-csv', [ - 'expired_products' => Product::with('unit:id,unitName', 'brand:id,brandName', 'category:id,categoryName', 'stocks') - ->withSum('stocks', 'productStock') - ->where('business_id', auth()->user()->business_id) - ->whereHas('stocks', function ($query) { - $query->whereDate('expire_date', '<', today())->where('productStock', '>', 0); - }) - ->latest() - ->get() - ]); + $businessId = auth()->user()->business_id; + $expiredProductsQuery = Product::with(['unit:id,unitName', 'brand:id,brandName', 'category:id,categoryName', 'stocks']) + ->withSum('stocks', 'productStock') + ->where('business_id', $businessId) + ->whereHas('stocks', function ($query) { + $query->whereDate('expire_date', '<=', today())->where('productStock', '>', 0); + }); + + // Date Filter + $duration = request('custom_days') ?: 'today'; + $expiredProductsQuery->whereHas('stocks', function ($q) use ($duration) { + $this->applyDateFilter($q, $duration, 'expire_date', request('from_date'), request('to_date')); + }); + + // Search Filter + if (request('search')) { + $search = request('search'); + $expiredProductsQuery->where(function ($query) use ($search) { + $query->where('productName', 'like', '%' . $search . '%') + ->orWhere('productCode', 'like', '%' . $search . '%') + ->orWhere('productPurchasePrice', 'like', '%' . $search . '%') + ->orWhere('productSalePrice', 'like', '%' . $search . '%') + ->orWhereHas('category', function ($q) use ($search) { + $q->where('categoryName', 'like', '%' . $search . '%'); + }) + ->orWhereHas('brand', function ($q) use ($search) { + $q->where('brandName', 'like', '%' . $search . '%'); + }) + ->orWhereHas('unit', function ($q) use ($search) { + $q->where('unitName', 'like', '%' . $search . '%'); + }); + }); + } + + $expired_products = $expiredProductsQuery->latest()->limit(request('per_page') ?? 20)->get(); + + return view('business::reports.expired-products.excel-csv', compact('expired_products')); } } diff --git a/Modules/Business/App/Exports/ExportGuestDue.php b/Modules/Business/App/Exports/ExportGuestDue.php new file mode 100644 index 00000000..479357e6 --- /dev/null +++ b/Modules/Business/App/Exports/ExportGuestDue.php @@ -0,0 +1,28 @@ +user()->business_id) + ->with('dueCollect') + ->whereNull('party_id') + ->where('dueAmount', '>', 0) + ->when(request('search'), function ($query) { + $query->where(function ($q) { + $q->where('invoiceNumber', 'like', '%' . request('search') . '%') + ->orwhere('dueAmount', 'like', '%' . request('search') . '%'); + }); + }) + ->latest() + ->limit(request('per_page') ?? 20)->get(); + + return view('business::walk-dues.excel-csv', compact('walk_in_customers')); + } +} diff --git a/Modules/Business/App/Exports/ExportIncome.php b/Modules/Business/App/Exports/ExportIncome.php index 1bd4a211..0ef03cfb 100644 --- a/Modules/Business/App/Exports/ExportIncome.php +++ b/Modules/Business/App/Exports/ExportIncome.php @@ -3,15 +3,44 @@ namespace Modules\Business\App\Exports; use App\Models\Income; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportIncome implements FromView { + use DateFilterTrait; + public function view(): View { - return view('business::reports.income.excel-csv', [ - 'income_reports' => Income::with('category:id,categoryName', 'payment_type:id,name', 'branch:id,name', 'transactions')->where('business_id', auth()->user()->business_id)->latest()->get() - ]); + $incomeQuery = Income::with('category:id,categoryName', 'payment_type:id,name', 'branch:id,name', 'transactions') + ->where('business_id', auth()->user()->business_id); + + // Branch filter + if (request('branch_id')) { + $incomeQuery->where('branch_id', request('branch_id')); + } + + // Date Filter + $duration = request('custom_days') ?: 'today'; + $this->applyDateFilter($incomeQuery, $duration, 'incomeDate', request('from_date'), request('to_date')); + + // Search filter + if (request('search')) { + $search = request('search'); + $incomeQuery->where(function ($query) use ($search) { + $query->where('incomeFor', 'like', '%' . $search . '%') + ->orWhere('paymentType', 'like', '%' . $search . '%') + ->orWhere('amount', 'like', '%' . $search . '%') + ->orWhere('referenceNo', 'like', '%' . $search . '%') + ->orWhereHas('category', fn($q) => $q->where('categoryName', 'like', '%' . $search . '%')) + ->orWhereHas('payment_type', fn($q) => $q->where('name', 'like', '%' . $search . '%')) + ->orWhereHas('branch', fn($q) => $q->where('name', 'like', '%' . $search . '%')); + }); + } + + $income_reports = $incomeQuery->latest()->limit(request('per_page') ?? 20)->get(); + + return view('business::reports.income.excel-csv', compact('income_reports')); } } diff --git a/Modules/Business/App/Exports/ExportLossProfitHistory.php b/Modules/Business/App/Exports/ExportLossProfitHistory.php index b7f008ef..b46b3769 100644 --- a/Modules/Business/App/Exports/ExportLossProfitHistory.php +++ b/Modules/Business/App/Exports/ExportLossProfitHistory.php @@ -2,117 +2,166 @@ namespace Modules\Business\App\Exports; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Illuminate\Support\Facades\DB; use Maatwebsite\Excel\Concerns\FromView; class ExportLossProfitHistory implements FromView { + use DateFilterTrait; + public function view(): View { $user = auth()->user(); $businessId = $user->business_id; + $perPage = request('per_page') ?? 20; $branchId = null; if (moduleCheck('MultiBranchAddon')) { $branchId = $user->branch_id ?? $user->active_branch_id; } - // SALES - $dailySales = DB::table('sales') + $duration = request('custom_days') ?: 'today'; + + $salesQuery = DB::table('sales') ->select( DB::raw('"saleDate"::date as date'), + DB::raw(" + CASE + WHEN \"lossProfit\" >= 0 THEN 'positive' + ELSE 'negative' + END as profit_type + "), DB::raw('SUM(actual_total_amount) as total_sales'), DB::raw('SUM("lossProfit") as total_sale_income') ) ->where('business_id', $businessId) - ->when($branchId, fn($q) => $q->where('branch_id', $branchId)) - ->groupBy(DB::raw('"saleDate"::date')) - ->get(); + ->when($branchId, fn ($q) => + $q->where('branch_id', $branchId) + ) + ->groupBy( + DB::raw('"saleDate"::date'), + DB::raw(" + CASE + WHEN \"lossProfit\" >= 0 THEN 'positive' + ELSE 'negative' + END + ") + ); - $sale_datas = $dailySales->map(fn($sale) => (object)[ - 'type' => 'Sale', - 'date' => $sale->date, - 'total_sales' => $sale->total_sales, - 'total_incomes' => $sale->total_sale_income, + $this->applyDateFilter($salesQuery, $duration, 'saleDate', request('from_date'), request('to_date')); + + $dailySales = $salesQuery->limit($perPage)->get(); + + $sale_datas = $dailySales->map(fn ($sale) => (object)[ + 'type' => 'Sale', + 'date' => $sale->date, + 'profit_type' => $sale->profit_type, + 'total_sales' => $sale->total_sales, + 'total_incomes' => $sale->total_sale_income, ]); - // INCOME - $dailyIncomes = DB::table('incomes') + $incomeQuery = DB::table('incomes') ->select( DB::raw('"incomeDate"::date as date'), DB::raw('SUM(amount) as total_incomes') ) ->where('business_id', $businessId) - ->when($branchId, fn($q) => $q->where('branch_id', $branchId)) - ->groupBy(DB::raw('"incomeDate"::date')) - ->get(); + ->when($branchId, fn ($q) => + $q->where('branch_id', $branchId) + ) + ->groupBy(DB::raw('"incomeDate"::date')); - $income_datas = $dailyIncomes->map(fn($income) => (object)[ - 'type' => 'Income', - 'date' => $income->date, + $this->applyDateFilter($incomeQuery, $duration, 'incomeDate', request('from_date'), request('to_date')); + $dailyIncomes = $incomeQuery->limit($perPage)->get(); + + $income_datas = $dailyIncomes->map(fn ($income) => (object)[ + 'type' => 'Income', + 'date' => $income->date, 'total_incomes' => $income->total_incomes, ]); - // MERGE SALE + INCOME $mergedIncomeSaleData = collect(); - $allDates = $dailySales->pluck('date')->merge($dailyIncomes->pluck('date'))->unique()->sort(); + $allDates = $dailySales->pluck('date') + ->merge($dailyIncomes->pluck('date')) + ->unique() + ->sort(); + foreach ($allDates as $date) { + if ($income = $income_datas->firstWhere('date', $date)) { $mergedIncomeSaleData->push($income); } - if ($sale = $sale_datas->firstWhere('date', $date)) { + + // multiple sale rows handle + $salesOfDate = $sale_datas->where('date', $date); + + foreach ($salesOfDate as $sale) { $mergedIncomeSaleData->push($sale); } } - // PAYROLL $dailyPayrolls = collect(); + if (moduleCheck('HrmAddon')) { - $dailyPayrolls = DB::table('payrolls') + $payrollQuery = DB::table('payrolls') ->select( DB::raw('"date"::date as date'), DB::raw('SUM(amount) as total_payrolls') ) ->where('business_id', $businessId) - ->when($branchId, fn($q) => $q->where('branch_id', $branchId)) - ->groupBy(DB::raw('"date"::date')) - ->get(); + ->when($branchId, fn ($q) => + $q->where('branch_id', $branchId) + ) + ->groupBy(DB::raw('"date"::date')); + + $this->applyDateFilter($payrollQuery, $duration, 'date', request('from_date'), request('to_date')); + $dailyPayrolls = $payrollQuery->limit($perPage)->get(); } - // EXPENSES - $dailyExpenses = DB::table('expenses') + $expenseQuery = DB::table('expenses') ->select( DB::raw('"expenseDate"::date as date'), DB::raw('SUM(amount) as total_expenses_only') ) ->where('business_id', $businessId) - ->when($branchId, fn($q) => $q->where('branch_id', $branchId)) - ->groupBy(DB::raw('"expenseDate"::date')) - ->get(); + ->when($branchId, fn ($q) => + $q->where('branch_id', $branchId) + ) + ->groupBy(DB::raw('"expenseDate"::date')); + + $this->applyDateFilter($expenseQuery, $duration, 'expenseDate', request('from_date'), request('to_date')); + $dailyExpenses = $expenseQuery->limit($perPage)->get(); $mergedExpenseData = collect(); - $allExpenseDates = $dailyExpenses->pluck('date')->merge($dailyPayrolls->pluck('date'))->unique()->sort(); + $allExpenseDates = $dailyExpenses->pluck('date') + ->merge($dailyPayrolls->pluck('date')) + ->unique() + ->sort(); + foreach ($allExpenseDates as $date) { + if ($expense = $dailyExpenses->firstWhere('date', $date)) { $mergedExpenseData->push((object)[ - 'type' => 'Expense', - 'date' => $date, + 'type' => 'Expense', + 'date' => $date, 'total_expenses' => $expense->total_expenses_only, ]); } + if ($payroll = $dailyPayrolls->firstWhere('date', $date)) { $mergedExpenseData->push((object)[ - 'type' => 'Payroll', - 'date' => $date, + 'type' => 'Payroll', + 'date' => $date, 'total_expenses' => $payroll->total_payrolls, ]); } } - // SUMMARY $grossSaleProfit = $sale_datas->sum('total_sales'); $grossIncomeProfit = $income_datas->sum('total_incomes') + $sale_datas->sum('total_incomes'); + $totalExpenses = $mergedExpenseData->sum('total_expenses'); $netProfit = $grossIncomeProfit - $totalExpenses; diff --git a/Modules/Business/App/Exports/ExportPartyLossProfit.php b/Modules/Business/App/Exports/ExportPartyLossProfit.php index 63c15cba..b8b6803f 100644 --- a/Modules/Business/App/Exports/ExportPartyLossProfit.php +++ b/Modules/Business/App/Exports/ExportPartyLossProfit.php @@ -13,7 +13,13 @@ public function view(): View $parties = Party::with('sales') ->where('business_id', auth()->user()->business_id) ->where('type', '!=', 'Supplier') + ->when(request('search'), function ($query) { + $query->where(function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }); + }) ->latest() + ->limit(request('per_page') ?? 20) ->get(); return view('business::party-reports.loss-profit.excel-csv', compact('parties')); diff --git a/Modules/Business/App/Exports/ExportProduct.php b/Modules/Business/App/Exports/ExportProduct.php index 194469ad..f0088dcc 100644 --- a/Modules/Business/App/Exports/ExportProduct.php +++ b/Modules/Business/App/Exports/ExportProduct.php @@ -8,10 +8,61 @@ class ExportProduct implements FromView { - public function view(): View { - $products = Product::with('unit:id,unitName', 'brand:id,brandName', 'category:id,categoryName')->where('business_id', auth()->user()->business_id)->withSum('stocks as total_stock', 'productStock')->latest()->get(); + $user = auth()->user(); + $search = request('search'); + + $products = Product::query() + ->where('business_id', $user->business_id) + ->with([ + 'stocks', + 'unit:id,unitName', + 'brand:id,brandName', + 'category:id,categoryName', + 'warehouse:id,name', + 'rack:id,name', + 'shelf:id,name', + 'combo_products.stock.product:id,productName' + ]) + ->withSum('stocks as total_stock', 'productStock') + ->where(function ($query) { + $query->where('product_type', '!=', 'combo') + ->orWhere(function ($q) { + $q->where('product_type', 'combo') + ->whereHas('combo_products'); + }); + }) + ->when($search, function ($q) use ($search) { + $q->where(function ($q) use ($search) { + $q->where('productName', 'like', "%{$search}%") + ->orWhere('productCode', 'like', "%{$search}%") + ->orWhere('hsn_code', 'like', "%{$search}%") + ->orWhere('productPurchasePrice', 'like', "%{$search}%") + ->orWhere('productSalePrice', 'like', "%{$search}%") + ->orWhere('product_type', 'like', "%{$search}%") + ->orWhereHas('category', fn ($q) => $q->where('categoryName', 'like', "%{$search}%")) + ->orWhereHas('brand', fn ($q) => $q->where('brandName', 'like', "%{$search}%")) + ->orWhereHas('unit', fn ($q) => $q->where('unitName', 'like', "%{$search}%")); + }); + }) + ->latest() + ->limit(request('per_page') ?? 20) + ->get(); + + $products = $products->transform(function ($product) { + if ($product->product_type === 'combo') { + $product->total_stock = $product->combo_products->sum( + fn ($combo) => $combo->stock?->productStock ?? 0 + ); + + $product->total_cost = $product->combo_products->sum( + fn ($combo) => ($combo->quantity ?? 0) * ($combo->purchase_price ?? 0) + ); + } + + return $product; + }); return view('business::products.excel-csv', [ 'products' => $products diff --git a/Modules/Business/App/Exports/ExportProductLossProfit.php b/Modules/Business/App/Exports/ExportProductLossProfit.php index acfb7a55..fcc33972 100644 --- a/Modules/Business/App/Exports/ExportProductLossProfit.php +++ b/Modules/Business/App/Exports/ExportProductLossProfit.php @@ -11,23 +11,35 @@ class ExportProductLossProfit implements FromView { public function view(): View { - $branchId = moduleCheck('MultiBranchAddon') ? auth()->user()->branch_id ?? auth()->user()->active_branch_id : null; + $user = auth()->user(); + $branchId = $user->branch_id ?? $user->active_branch_id; - $product_lossProfits = SaleDetails::with('product:id,productName,productCode') - ->whereHas('product', function ($q) { - $q->where('business_id', auth()->user()->business_id); + $baseQuery = SaleDetails::query() + ->whereHas( + 'product', + fn($q) => + $q->where('business_id', $user->business_id) + ) + ->when(moduleCheck('MultiBranchAddon') && $branchId, function ($q) use ($branchId) { + $q->whereHas('sale', fn($q) => $q->where('branch_id', $branchId)); }) - ->when($branchId, function ($q) use ($branchId) { - $q->whereHas('sale', function ($sale) use ($branchId) { - $sale->where('branch_id', $branchId); + ->when(request('search'), function ($q) { + $q->whereHas('product', function ($q) { + $q->where('productName', 'like', '%' . request('search') . '%') + ->orWhere('productCode', 'like', '%' . request('search') . '%') + ->orWhere('hsn_code', 'like', '%' . request('search') . '%'); }); - }) + }); + + $product_lossProfits = $baseQuery + ->with('product:id,productName,productCode,hsn_code') ->select( 'product_id', - DB::raw('SUM(CASE WHEN "lossProfit" > 0 THEN "lossProfit" ELSE 0 END) as profit'), - DB::raw('SUM(CASE WHEN "lossProfit" < 0 THEN "lossProfit" ELSE 0 END) as loss') + DB::raw('SUM(CASE WHEN "lossProfit" > 0 THEN "lossProfit" ELSE 0 END) AS profit'), + DB::raw('SUM(CASE WHEN "lossProfit" < 0 THEN "lossProfit" ELSE 0 END) AS loss') ) ->groupBy('product_id') + ->limit(request('per_page') ?? 20) ->get(); return view('business::reports.product-loss-profit.excel-csv', compact('product_lossProfits')); diff --git a/Modules/Business/App/Exports/ExportProductPurchaseHistoryDetailReport.php b/Modules/Business/App/Exports/ExportProductPurchaseHistoryDetailReport.php index 5562ad7c..1f038f6b 100644 --- a/Modules/Business/App/Exports/ExportProductPurchaseHistoryDetailReport.php +++ b/Modules/Business/App/Exports/ExportProductPurchaseHistoryDetailReport.php @@ -3,29 +3,54 @@ namespace Modules\Business\App\Exports; use App\Models\Product; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportProductPurchaseHistoryDetailReport implements FromView { - protected $id; + use DateFilterTrait; - public function __construct($id) + protected string $id; + + public function __construct(string $id) { $this->id = $id; } public function view(): View { + $businessId = auth()->user()->business_id; + $duration = request('custom_days') ?: 'today'; + $product = Product::select('id', 'business_id', 'productName') - ->where('business_id', auth()->user()->business_id) + ->where('business_id', $businessId) ->findOrFail($this->id); $purchaseDetailsQuery = $product->purchaseDetails() - ->with('purchase:id,invoiceNumber,purchaseDate') + ->whereHas('purchase', function ($purchase) use ($duration) { + $this->applyDateFilter($purchase, $duration, 'purchaseDate', request('from_date'), request('to_date')); + }) + ->with('purchase:id,party_id,invoiceNumber,purchaseDate', 'purchase.party:id,name') ->select('id', 'purchase_id', 'product_id', 'quantities', 'productPurchasePrice'); - $purchaseDetails = $purchaseDetailsQuery->get(); + $purchaseDetailsQuery->when(request('search'), function ($q) { + $search = request('search'); + $q->where(function ($q) use ($search) { + $q->where('productPurchasePrice', 'like', "%{$search}%") + ->orWhere('quantities', 'like', "%{$search}%") + ->orWhereHas('purchase', function ($q) use ($search) { + $q->where('invoiceNumber', 'like', "%{$search}%") + ->orWhere('purchaseDate', 'like', "%{$search}%"); + }) + ->orWhereHas('purchase.party', function ($q) use ($search) { + $q->where('name', 'like', "%{$search}%"); + }); + }); + } + ); + + $purchaseDetails = $purchaseDetailsQuery->limit(request('per_page') ?? 20)->get(); return view('business::product-purchase-history-report.excel-csv-detail', compact('product', 'purchaseDetails')); } diff --git a/Modules/Business/App/Exports/ExportProductPurchaseHistoryReport.php b/Modules/Business/App/Exports/ExportProductPurchaseHistoryReport.php index 9187877f..06fafafe 100644 --- a/Modules/Business/App/Exports/ExportProductPurchaseHistoryReport.php +++ b/Modules/Business/App/Exports/ExportProductPurchaseHistoryReport.php @@ -3,17 +3,30 @@ namespace Modules\Business\App\Exports; use App\Models\Product; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportProductPurchaseHistoryReport implements FromView { + use DateFilterTrait; + public function view(): View { $businessId = auth()->user()->business_id; + $duration = request('custom_days') ?: 'today'; - $productQuery = Product::with('saleDetails', 'purchaseDetails', 'stocks', 'combo_products')->where('business_id', $businessId); - $products = $productQuery->get(); + $productQuery = Product::with(['saleDetails', 'purchaseDetails', 'purchaseDetails.purchase', 'stocks', 'combo_products']) + ->where('business_id', $businessId) + ->whereHas('purchaseDetails.purchase', function ($purchase) use ($duration) { + $this->applyDateFilter($purchase, $duration, 'purchaseDate', request('from_date'), request('to_date')); + }); + + $productQuery->when(request('search'), function ($q) { + $q->where('productName', 'like', '%' . $request->search . '%'); + }); + + $products = $productQuery->limit(request('per_page') ?? 20)->get(); $total_purchase_qty = $products->sum(function ($product) { return $product->purchaseDetails->sum('quantities'); diff --git a/Modules/Business/App/Exports/ExportProductPurchaseReport.php b/Modules/Business/App/Exports/ExportProductPurchaseReport.php index 6ca7d444..5be28cbc 100644 --- a/Modules/Business/App/Exports/ExportProductPurchaseReport.php +++ b/Modules/Business/App/Exports/ExportProductPurchaseReport.php @@ -2,21 +2,44 @@ namespace Modules\Business\App\Exports; -use App\Models\Product; use App\Models\PurchaseDetails; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportProductPurchaseReport implements FromView { + use DateFilterTrait; public function view(): View { - $product_purchases = PurchaseDetails::with('product:id,productName', 'purchase:id,party_id,invoiceNumber,purchaseDate', 'purchase.party:id,name') + $query = PurchaseDetails::with('product:id,productName', 'purchase:id,party_id,invoiceNumber,purchaseDate', 'purchase.party:id,name') ->whereHas('purchase', function ($q) { $q->where('business_id', auth()->user()->business_id); - }) - ->get(); + }); + + // Date Filter + $duration = request('custom_days') ?: 'today'; + $query->whereHas('purchase', function ($q) use ($duration) { + $this->applyDateFilter($q, $duration, 'purchaseDate', request('from_date'), request('to_date')); + }); + + $query->when(request('search'), function ($q) { + $q->where(function ($q) { + $q->whereHas('product', function ($q) { + $q->where('productName', 'like', '%' . request('search') . '%'); + }) + ->orWhereHas('purchase', function ($q) { + $q->where('invoiceNumber', 'like', '%' . request('search') . '%') + ->orWhere('purchaseDate', 'like', '%' . request('search') . '%'); + }) + ->orWhereHas('purchase.party', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }); + }); + }); + + $product_purchases = $query->limit(request('per_page') ?? 20)->get(); return view('business::reports.product-purchase.excel-csv', compact('product_purchases')); } diff --git a/Modules/Business/App/Exports/ExportProductSaleHistoryDetailReport.php b/Modules/Business/App/Exports/ExportProductSaleHistoryDetailReport.php index 4e30139e..90167cfd 100644 --- a/Modules/Business/App/Exports/ExportProductSaleHistoryDetailReport.php +++ b/Modules/Business/App/Exports/ExportProductSaleHistoryDetailReport.php @@ -3,29 +3,53 @@ namespace Modules\Business\App\Exports; use App\Models\Product; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportProductSaleHistoryDetailReport implements FromView { - protected $id; + use DateFilterTrait; - public function __construct($id) + protected string $id; + + public function __construct(string $id) { $this->id = $id; } public function view(): View { + $businessId = auth()->user()->business_id; + $duration = request('custom_days') ?: 'today'; $product = Product::select('id', 'business_id', 'productName') - ->where('business_id', auth()->user()->business_id) + ->where('business_id', $businessId) ->findOrFail($this->id); $saleDetailsQuery = $product->saleDetails() + ->whereHas('sale', function ($sale) use ($duration) { + $this->applyDateFilter($sale, $duration, 'saleDate', request('from_date'), request('to_date')); + }) ->with('sale:id,party_id,invoiceNumber,saleDate', 'sale.party:id,name') ->select('id', 'sale_id', 'product_id', 'quantities', 'lossprofit', 'price', 'productPurchasePrice'); - $saleDetails = $saleDetailsQuery->get(); + $saleDetailsQuery->when(request('search'), function ($q) { + $search = request('search'); + $q->where(function ($q) use ($search) { + $q->where('price', 'like', "%{$search}%") + ->orWhere('quantities', 'like', "%{$search}%") + ->orWhereHas('sale', function ($q) use ($search) { + $q->where('invoiceNumber', 'like', "%{$search}%") + ->orWhere('saleDate', 'like', "%{$search}%"); + }) + ->orWhereHas('sale.party', function ($q) use ($search) { + $q->where('name', 'like', "%{$search}%"); + }); + }); + } + ); + + $saleDetails = $saleDetailsQuery->limit(request('per_page') ?? 20)->get(); return view('business::product-sale-history-report.excel-csv-detail', compact('product', 'saleDetails')); } diff --git a/Modules/Business/App/Exports/ExportProductSaleHistoryReport.php b/Modules/Business/App/Exports/ExportProductSaleHistoryReport.php index 6b5018c4..996399e3 100644 --- a/Modules/Business/App/Exports/ExportProductSaleHistoryReport.php +++ b/Modules/Business/App/Exports/ExportProductSaleHistoryReport.php @@ -3,17 +3,30 @@ namespace Modules\Business\App\Exports; use App\Models\Product; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportProductSaleHistoryReport implements FromView { + use DateFilterTrait; + public function view(): View { $businessId = auth()->user()->business_id; + $duration = request('custom_days') ?: 'today'; - $productQuery = Product::with('saleDetails', 'purchaseDetails', 'stocks', 'combo_products')->where('business_id', $businessId); - $products = $productQuery->get(); + $productQuery = Product::with(['saleDetails', 'purchaseDetails', 'saleDetails.sale', 'stocks', 'combo_products']) + ->where('business_id', $businessId) + ->whereHas('saleDetails.sale', function ($sale) use ($duration) { + $this->applyDateFilter($sale, $duration, 'saleDate', request('from_date'), request('to_date')); + }); + + $productQuery->when(request('search'), function ($q) { + $q->where('productName', 'like', '%' . request('search') . '%'); + }); + + $products = $productQuery->limit(request('per_page') ?? 20)->get(); $total_single_sale_price = $products->sum(function ($product) { return $product->saleDetails->sum('price'); diff --git a/Modules/Business/App/Exports/ExportProductSaleReport.php b/Modules/Business/App/Exports/ExportProductSaleReport.php index 15dd2007..51c50d44 100644 --- a/Modules/Business/App/Exports/ExportProductSaleReport.php +++ b/Modules/Business/App/Exports/ExportProductSaleReport.php @@ -3,18 +3,45 @@ namespace Modules\Business\App\Exports; use App\Models\Sale; +use App\Models\SaleDetails; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportProductSaleReport implements FromView { + use DateFilterTrait; public function view(): View { - $product_sales = Sale::with('details:id,sale_id,product_id,quantities,price', 'details.product:id,productName', 'party:id,name') - ->where('business_id', auth()->user()->business_id) - ->latest() - ->get(); + $query = SaleDetails::with('product:id,productName', 'sale:id,party_id,invoiceNumber,saleDate', 'sale.party:id,name') + ->whereHas('sale', function ($q) { + $q->where('business_id', auth()->user()->business_id); + }); + + // Date Filter + $duration = request('custom_days') ?: 'today'; + $query->whereHas('sale', function ($q) use ($duration) { + $this->applyDateFilter($q, $duration, 'saleDate', request('from_date'), request('to_date')); + }); + + // Search Filter + $query->when(request('search'), function ($q) { + $q->where(function ($q) { + $q->whereHas('product', function ($q) { + $q->where('productName', 'like', '%' . request('search') . '%'); + }) + ->orWhereHas('sale', function ($q) { + $q->where('invoiceNumber', 'like', '%' . request('search') . '%') + ->orWhere('saleDate', 'like', '%' . request('search') . '%'); + }) + ->orWhereHas('sale.party', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }); + }); + }); + + $product_sales = $query->limit(request('per_page') ?? 20)->get(); return view('business::reports.product-sale.excel-csv', compact('product_sales')); } diff --git a/Modules/Business/App/Exports/ExportPurchaseReturn.php b/Modules/Business/App/Exports/ExportPurchaseReturn.php index d35a713b..55a5d50b 100644 --- a/Modules/Business/App/Exports/ExportPurchaseReturn.php +++ b/Modules/Business/App/Exports/ExportPurchaseReturn.php @@ -3,14 +3,17 @@ namespace Modules\Business\App\Exports; use App\Models\Purchase; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportPurchaseReturn implements FromView { + use DateFilterTrait; + public function view(): View { - $purchases = Purchase::with([ + $purchasesQuery = Purchase::with([ 'user:id,name', 'branch:id,name', 'party:id,name,email,phone,type', @@ -22,9 +25,32 @@ public function view(): View } ]) ->where('business_id', auth()->user()->business_id) - ->whereHas('purchaseReturns') - ->latest() - ->get(); + ->whereHas('purchaseReturns'); + + $purchasesQuery->when(request('branch_id'), function ($q) { + $q->where('branch_id', request('branch_id')); + }); + + // Date Filter + $duration = request('custom_days') ?: 'today'; + $purchasesQuery->whereHas('purchaseReturns', function ($query) use ($duration) { + $this->applyDateFilter($query, $duration, 'return_date', request('from_date'), request('to_date')); + }); + + // Search Filter + if (request('search')) { + $purchasesQuery->where(function ($query) { + $query->where('invoiceNumber', 'like', '%' . request('search') . '%') + ->orWhereHas('party', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }) + ->orWhereHas('branch', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }); + }); + } + + $purchases = $purchasesQuery->latest()->limit(request('per_page') ?? 20)->get(); return view('business::reports.purchase-return.excel-csv', compact('purchases')); } diff --git a/Modules/Business/App/Exports/ExportSaleReport.php b/Modules/Business/App/Exports/ExportSaleReport.php index b3770ba0..d7f689c6 100644 --- a/Modules/Business/App/Exports/ExportSaleReport.php +++ b/Modules/Business/App/Exports/ExportSaleReport.php @@ -3,16 +3,46 @@ namespace Modules\Business\App\Exports; use App\Models\Sale; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportSaleReport implements FromView { + use DateFilterTrait; + public function view(): View { - $sales = Sale::with('user:id,name', 'party:id,name,email,phone,type', 'payment_type:id,name', 'branch:id,name', 'transactions')->where('business_id', auth()->user()->business_id) - ->latest() - ->get(); + $salesQuery = Sale::with('user:id,name', 'party:id,name,email,phone,type', 'payment_type:id,name', 'branch:id,name', 'transactions') + ->where('business_id', auth()->user()->business_id); + + $salesQuery->when(request('branch_id'), function ($q) { + $q->where('branch_id', request('branch_id')); + }); + + // Date Filter + $duration = request('custom_days') ?: 'today'; + $this->applyDateFilter($salesQuery, $duration, 'saleDate', request('from_date'), request('to_date')); + + // Search Filter + if (request('search')) { + $salesQuery->where(function ($query) { + $query->where('paymentType', 'like', '%' . request('search') . '%') + ->orWhere('invoiceNumber', 'like', '%' . request('search') . '%') + ->orWhereHas('party', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }) + ->orWhereHas('payment_type', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }) + ->orWhereHas('branch', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }); + }); + } + + $sales = $salesQuery->latest()->limit(request('per_page') ?? 20)->get(); + return view('business::reports.sales.excel', compact('sales')); } diff --git a/Modules/Business/App/Exports/ExportSalesReturn.php b/Modules/Business/App/Exports/ExportSalesReturn.php index 9d2dbacf..992d8e0e 100644 --- a/Modules/Business/App/Exports/ExportSalesReturn.php +++ b/Modules/Business/App/Exports/ExportSalesReturn.php @@ -3,14 +3,17 @@ namespace Modules\Business\App\Exports; use App\Models\Sale; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportSalesReturn implements FromView { + use DateFilterTrait; + public function view(): View { - $sales = Sale::with([ + $salesQuery = Sale::with([ 'user:id,name', 'party:id,name', 'branch:id,name', @@ -23,9 +26,30 @@ public function view(): View } ]) ->where('business_id', auth()->user()->business_id) - ->whereHas('saleReturns') - ->latest() - ->get(); + ->when(request('branch_id'), function ($q) { + $q->where('branch_id', request('branch_id')); + })->whereHas('saleReturns'); + + // Date Filter + $duration = request('custom_days') ?: 'today'; + $salesQuery->whereHas('saleReturns', function ($query) use ($duration) { + $this->applyDateFilter($query, $duration, 'return_date', request('from_date'), request('to_date')); + }); + + // Search Filter + if (request('search')) { + $salesQuery->where(function ($query) { + $query->where('invoiceNumber', 'like', '%' . request('search') . '%') + ->orWhereHas('party', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }) + ->orWhereHas('branch', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }); + }); + } + + $sales = $salesQuery->latest()->limit(request('per_page') ?? 20)->get(); return view('business::reports.sales-return.excel-csv', compact('sales')); } diff --git a/Modules/Business/App/Exports/ExportSingleCustomerLedger.php b/Modules/Business/App/Exports/ExportSingleCustomerLedger.php index 0b663860..18a4aeaf 100644 --- a/Modules/Business/App/Exports/ExportSingleCustomerLedger.php +++ b/Modules/Business/App/Exports/ExportSingleCustomerLedger.php @@ -2,15 +2,17 @@ namespace Modules\Business\App\Exports; +use App\Models\Party; use Maatwebsite\Excel\Concerns\FromView; use Illuminate\Contracts\View\View; +use Illuminate\Support\Collection; class ExportSingleCustomerLedger implements FromView { - public $ledger; - public $party; + public Collection $ledger; + public Party $party; - public function __construct($ledger, $party) + public function __construct(Collection $ledger, Party $party) { $this->ledger = $ledger; $this->party = $party; diff --git a/Modules/Business/App/Exports/ExportSingleSupplierLedger.php b/Modules/Business/App/Exports/ExportSingleSupplierLedger.php index 8a4a8a0c..f1747667 100644 --- a/Modules/Business/App/Exports/ExportSingleSupplierLedger.php +++ b/Modules/Business/App/Exports/ExportSingleSupplierLedger.php @@ -2,15 +2,17 @@ namespace Modules\Business\App\Exports; +use App\Models\Party; use Maatwebsite\Excel\Concerns\FromView; use Illuminate\Contracts\View\View; +use Illuminate\Support\Collection; class ExportSingleSupplierLedger implements FromView { - public $ledger; - public $party; + public Collection $ledger; + public Party $party; - public function __construct($ledger, $party) + public function __construct(Collection $ledger, Party $party) { $this->ledger = $ledger; $this->party = $party; diff --git a/Modules/Business/App/Exports/ExportSubscription.php b/Modules/Business/App/Exports/ExportSubscription.php index 0fadbf6b..f5a3b0d0 100644 --- a/Modules/Business/App/Exports/ExportSubscription.php +++ b/Modules/Business/App/Exports/ExportSubscription.php @@ -3,15 +3,44 @@ namespace Modules\Business\App\Exports; use App\Models\PlanSubscribe; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportSubscription implements FromView { + use DateFilterTrait; + public function view(): View { - return view('business::reports.subscription-reports.excel-csv', [ - 'subscribers' => PlanSubscribe::with(['plan:id,subscriptionName','business:id,companyName,business_category_id,pictureUrl','business.category:id,name','gateway:id,name'])->where('business_id', auth()->user()->business_id)->latest()->get() - ]); + $subscriberQuery = PlanSubscribe::with(['plan:id,subscriptionName','business:id,companyName,business_category_id,pictureUrl','business.category:id,name','gateway:id,name'])->where('business_id', auth()->user()->business_id); + + // Date Filter + $duration = request('custom_days') ?: 'today'; + $this->applyDateFilter($subscriberQuery, $duration, 'created_at', request('from_date'), request('to_date')); + + // Search Filter + if (request('search')) { + $search = request('search'); + $subscriberQuery->where(function ($query) use ($search) { + $query->where('duration', 'like', '%' . $search . '%') + ->orWhereHas('plan', function ($q) use ($search) { + $q->where('subscriptionName', 'like', '%' . $search . '%'); + }) + ->orWhereHas('gateway', function ($q) use ($search) { + $q->where('name', 'like', '%' . $search . '%'); + }) + ->orWhereHas('business', function ($q) use ($search) { + $q->where('companyName', 'like', '%' . $search . '%') + ->orWhereHas('category', function ($q) use ($search) { + $q->where('name', 'like', '%' . $search . '%'); + }); + }); + }); + } + + $subscribers = $subscriberQuery->latest()->limit(request('per_page') ?? 20)->get(); + + return view('business::reports.subscription-reports.excel-csv', compact('subscribers')); } } diff --git a/Modules/Business/App/Exports/ExportSupplierDue.php b/Modules/Business/App/Exports/ExportSupplierDue.php index d52ab56e..085b638d 100644 --- a/Modules/Business/App/Exports/ExportSupplierDue.php +++ b/Modules/Business/App/Exports/ExportSupplierDue.php @@ -16,31 +16,48 @@ public function view(): View $query = Party::where('business_id', $businessId) ->where('type', 'Supplier') + ->when(request('search'), function ($q) { + $q->where(function ($q2) { + $q2->where('type', 'like', '%' . request('search') . '%') + ->orWhere('name', 'like', '%' . request('search') . '%') + ->orWhere('phone', 'like', '%' . request('search') . '%') + ->orWhere('credit_limit', 'like', '%' . request('search') . '%'); + }); + }) ->with('purchases_dues') ->latest(); - if ($activeBranch) { - $query->whereHas('purchases_dues', function ($q) use ($activeBranch) { - $q->where('branch_id', $activeBranch->id) - ->where('dueAmount', '>', 0); - }); - } else { - $query->where('due', '>', 0); - } - $parties = $query->get(); if ($activeBranch) { - $parties->transform(function ($supplier) use ($activeBranch) { - $supplier->due = $supplier->purchases_dues - ->where('branch_id', $activeBranch->id) - ->sum('dueAmount'); - return $supplier; - })->filter(fn($supplier) => $supplier->due > 0); + $parties = $parties + ->transform(function ($supplier) use ($activeBranch) { + $party_due = $supplier->purchases_dues + ->where('branch_id', $activeBranch->id) + ->sum('dueAmount'); + + if ($supplier->branch_id === $activeBranch->id) { + $openingBalanceDue = $supplier->opening_balance_type === 'due' + ? ($supplier->opening_balance ?? 0) + : 0; + + $supplier->due = $openingBalanceDue + $party_due; + } else { + $supplier->due = $party_due; + } + + return $supplier; + }) + ->filter(fn($supplier) => $supplier->due > 0) + ->take(request('per_page') ?? 20) + ->values(); + } else { + $parties = $parties + ->filter(fn($supplier) => ($supplier->due ?? 0) > 0) + ->take(request('per_page') ?? 20) + ->values(); } - return view('business::reports.supplier-due.excel-csv', [ - 'parties' => $parties - ]); + return view('business::reports.supplier-due.excel-csv', compact('parties')); } } diff --git a/Modules/Business/App/Exports/ExportSupplierLedger.php b/Modules/Business/App/Exports/ExportSupplierLedger.php index 59e8b9ed..8a455c46 100644 --- a/Modules/Business/App/Exports/ExportSupplierLedger.php +++ b/Modules/Business/App/Exports/ExportSupplierLedger.php @@ -13,20 +13,27 @@ public function view(): View $suppliers = Party::with('purchases') ->where('business_id', auth()->user()->business_id) ->where('type', '=', 'Supplier') + ->when(request('search'), function ($q) { + $q->where(function ($q) { + $q->where('name', 'like', '%' . request('search') . '%') + ->orWhere('phone', 'like', '%' . request('search') . '%') + ->orWhere('type', 'like', '%' . request('search') . '%'); + }); + }) ->latest() - ->get(); + ->limit(request('per_page') ?? 20)->get(); - $totalAmount = $suppliers->sum(function ($customer) { + $totalAmount = $suppliers->sum(function ($customer) { return $customer->purchases?->sum('totalAmount') ?? 0; - }); + }); - $totalPaid = $suppliers->sum(function ($customer) { - return $customer->purchases?->sum('paidAmount') ?? 0; - }); + $totalPaid = $suppliers->sum(function ($customer) { + return $customer->purchases?->sum('paidAmount') ?? 0; + }); - $totalDue = $suppliers->sum(function ($customer) { - return $customer->purchases?->sum('dueAmount') ?? 0; - }); + $totalDue = $suppliers->sum(function ($customer) { + return $customer->purchases?->sum('dueAmount') ?? 0; + }); return view('business::party-reports.supplier-ledger.excel-csv', compact('suppliers', 'totalAmount', 'totalPaid', 'totalDue')); } diff --git a/Modules/Business/App/Exports/ExportTaxReport.php b/Modules/Business/App/Exports/ExportTaxReport.php new file mode 100644 index 00000000..735d6ec5 --- /dev/null +++ b/Modules/Business/App/Exports/ExportTaxReport.php @@ -0,0 +1,211 @@ +user()->business_id; + $type = request('type') ?? 'sales'; + + $duration = request('custom_days') ?: 'today'; + + // SALES QUERY + $salesQuery = Sale::with('party', 'payment_type', 'transactions') + ->where('business_id', $businessId); + + $this->applyDateFilter($salesQuery, $duration, 'saleDate', request('from_date'), request('to_date')); + + if (request('search')) { + $search = request('search'); + $salesQuery->where(function ($query) use ($search) { + $query->where('invoiceNumber', 'like', '%' . $search . '%') + ->orWhere('totalAmount', 'like', '%' . $search . '%') + ->orWhere('discountAmount', 'like', '%' . $search . '%') + ->orWhereHas('party', fn($q) => $q->where('name', 'like', '%' . $search . '%')->orWhere('tax_no', 'like', '%' . $search . '%')); + }); + } + + $salesVatTotals = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', SaleDetails::class) + ->join('sale_details', 'sale_details.id', '=', 'vat_transactions.vatable_id') + ->join('sales', 'sales.id', '=', 'sale_details.sale_id') + ->where('sales.business_id', $businessId) + ->selectRaw('vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('vat_transactions.vat_id') + ->pluck('total','vat_transactions.vat_id') + ->toArray(); + + $salesReturnVatTotals = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', SaleReturnDetails::class) + ->join('sale_return_details', 'sale_return_details.id', '=', 'vat_transactions.vatable_id') + ->join('sale_details', 'sale_details.id', '=', 'sale_return_details.sale_detail_id') + ->join('sales', 'sales.id', '=', 'sale_details.sale_id') + ->where('sales.business_id', $businessId) + ->selectRaw('vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('vat_transactions.vat_id') + ->pluck('total', 'vat_transactions.vat_id') + ->toArray(); + + $saleVatRowMap = VatTransaction::where('business_id', $businessId) + ->where('vatable_type', \App\Models\SaleDetails::class) + ->whereIn('vatable_id', function ($query) use ($businessId, $duration) { + $query->select('sale_details.id') + ->from('sale_details') + ->join('sales', 'sales.id', '=', 'sale_details.sale_id') + ->where('sales.business_id', $businessId); + + if ($duration === 'custom_days' && request('from_date') && request('to_date')) { + $query->whereBetween('sales.saleDate', [request('from_date'), request('to_date')]); + } + }) + ->join('sale_details', 'sale_details.id', '=', 'vat_transactions.vatable_id') + ->selectRaw('sale_details.sale_id, vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('sale_details.sale_id', 'vat_transactions.vat_id') + ->get() + ->groupBy('sale_id') + ->map(function ($items) { + return $items->pluck('total', 'vat_id')->toArray(); + }) + ->toArray(); + + $saleReturnVatRows = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', SaleReturnDetails::class) + + ->join('sale_return_details','sale_return_details.id','=','vat_transactions.vatable_id') + ->join('sale_details','sale_details.id','=','sale_return_details.sale_detail_id') + ->join('sales','sales.id','=','sale_details.sale_id') + + ->selectRaw('sales.id as sale_id, vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as vat_amount') + ->groupBy('sales.id','vat_transactions.vat_id') + ->get(); + + $saleReturnVatRowMap = $saleReturnVatRows + ->groupBy('sale_id') + ->map(fn($rows)=>$rows->pluck('vat_amount','vat_id')) + ->toArray(); + + // PURCHASE QUERY + $purchasesQuery = Purchase::with('party', 'payment_type', 'transactions') + ->where('business_id', $businessId); + + $this->applyDateFilter($purchasesQuery, $duration, 'purchaseDate', request('from_date'), request('to_date')); + + if (request('search')) { + $search = request('search'); + $purchasesQuery->where(function ($query) use ($search) { + $query->where('invoiceNumber', 'like', '%' . $search . '%') + ->orWhere('totalAmount', 'like', '%' . $search . '%') + ->orWhere('discountAmount', 'like', '%' . $search . '%') + ->orWhereHas('party', fn($q) => $q->where('name', 'like', '%' . $search . '%')->orWhere('tax_no', 'like', '%' . $search . '%')); + }); + } + + $purchasesVatTotals = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', \App\Models\PurchaseDetails::class) + ->join('purchase_details', 'purchase_details.id', '=', 'vat_transactions.vatable_id') + ->join('purchases', 'purchases.id', '=', 'purchase_details.purchase_id') + ->where('purchases.business_id', $businessId) + ->selectRaw('vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('vat_transactions.vat_id') + ->pluck('total','vat_transactions.vat_id') + ->toArray(); + + + $purchaseReturnVatTotals = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', PurchaseReturnDetail::class) + ->join('purchase_return_details', 'purchase_return_details.id', '=', 'vat_transactions.vatable_id') + ->join('purchase_details', 'purchase_details.id', '=', 'purchase_return_details.purchase_detail_id') + ->join('purchases', 'purchases.id', '=', 'purchase_details.purchase_id') + ->where('purchases.business_id', $businessId) + ->selectRaw('vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('vat_transactions.vat_id') + ->pluck('total', 'vat_transactions.vat_id') + ->toArray(); + + $purchaseVatRowMap = VatTransaction::where('business_id', $businessId) + ->where('vatable_type', \App\Models\PurchaseDetails::class) + ->whereIn('vatable_id', function ($query) use ($businessId, $duration) { + $query->select('purchase_details.id') + ->from('purchase_details') + ->join('purchases', 'purchases.id', '=', 'purchase_details.purchase_id') + ->where('purchases.business_id', $businessId); + + if ($duration === 'custom_date' && request('from_date') && request('to_date')) { + $query->whereBetween('purchases.purchaseDate', [request('from_date'), request('to_date')]); + } + }) + ->join('purchase_details', 'purchase_details.id', '=', 'vat_transactions.vatable_id') + ->selectRaw('purchase_details.purchase_id, vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('purchase_details.purchase_id', 'vat_transactions.vat_id') + ->get() + ->groupBy('purchase_id') + ->map(function ($items) { + return $items->pluck('total', 'vat_id')->toArray(); + }) + ->toArray(); + + $purchaseReturnVatRows = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', PurchaseReturnDetail::class) + + ->join('purchase_return_details','purchase_return_details.id','=','vat_transactions.vatable_id') + ->join('purchase_details','purchase_details.id','=','purchase_return_details.purchase_detail_id') + ->join('purchases','purchases.id','=','purchase_details.purchase_id') + + ->selectRaw('purchases.id as purchase_id, vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as vat_amount') + ->groupBy('purchases.id','vat_transactions.vat_id') + ->get(); + + $purchaseReturnVatRowMap = $purchaseReturnVatRows + ->groupBy('purchase_id') + ->map(fn($rows) => $rows->pluck('vat_amount','vat_id')) + ->toArray(); + + // TAB CONTROL + $sales = collect(); + $purchases = collect(); + + $limit = request('per_page') ?? 20; + + if ($type === 'sales') { + $sales = $salesQuery->latest()->limit($limit)->get(); + } + + if ($type === 'purchases') { + $purchases = $purchasesQuery->latest()->limit($limit)->get(); + } + + $vats = Vat::where('business_id', $businessId)->get(); + + return view('business::reports.vats.excel-csv', + compact( + 'sales', + 'salesVatTotals', + 'salesReturnVatTotals', + 'saleVatRowMap', + 'saleReturnVatRowMap', + 'purchases', + 'purchasesVatTotals', + 'purchaseReturnVatTotals', + 'purchaseVatRowMap', + 'purchaseReturnVatRowMap', + 'vats', + 'type' + ) + ); + } +} diff --git a/Modules/Business/App/Exports/ExportTopCustomer.php b/Modules/Business/App/Exports/ExportTopCustomer.php index 1038c5c5..d85eba72 100644 --- a/Modules/Business/App/Exports/ExportTopCustomer.php +++ b/Modules/Business/App/Exports/ExportTopCustomer.php @@ -14,10 +14,23 @@ public function view(): View ->where('business_id', auth()->user()->business_id) ->where('type', '!=', 'Supplier') ->whereHas('sales') + ->when(request('search'), function ($q) { + $q->where(function ($q) { + $q->where('name', 'like', '%' . request('search') . '%') + ->orWhere('phone', 'like', '%' . request('search') . '%') + ->orWhere('email', 'like', '%' . request('search') . '%') + ->orWhere('type', 'like', '%' . request('search') . '%'); + }); + }) ->withCount('sales') ->withSum('sales', 'totalAmount') ->orderByDesc('sales_count') ->orderByDesc('sales_sum_total_amount') + ->when(request('type'), function ($q) { + $q->where(function ($q) { + $q->where('type', request('type')); + }); + }) ->take(5) ->get(); diff --git a/Modules/Business/App/Exports/ExportTopProduct.php b/Modules/Business/App/Exports/ExportTopProduct.php index 65934fb2..9a4131b4 100644 --- a/Modules/Business/App/Exports/ExportTopProduct.php +++ b/Modules/Business/App/Exports/ExportTopProduct.php @@ -11,26 +11,39 @@ class ExportTopProduct implements FromView { public function view(): View { - $branchId = moduleCheck('MultiBranchAddon') ? auth()->user()->branch_id ?? auth()->user()->active_branch_id : null; + $user = auth()->user(); + $businessId = $user->business_id; - $top_products = SaleDetails::with('product:id,productName,productCode') - ->whereHas('product', function ($q) { - $q->where('business_id', auth()->user()->business_id); - }) - ->when($branchId, function ($q) use ($branchId) { - $q->whereHas('sale', function ($sale) use ($branchId) { - $sale->where('branch_id', $branchId); - }); - }) - ->select( - 'product_id', - DB::raw('SUM(quantities) as total_sold_qty'), - DB::raw('SUM(price * quantities) as total_sale_amount') - ) - ->groupBy('product_id') - ->orderByDesc('total_sold_qty') - ->take(5) - ->get(); + $branchId = null; + if (moduleCheck('MultiBranchAddon')) { + $branchId = $user->branch_id ?? $user->active_branch_id; + } + + $top_products = SaleDetails::query() + ->with('product:id,productName,productCode,hsn_code') + ->whereHas('product', function ($q) use ($businessId) { + $q->where('business_id', $businessId); + }) + ->when(request('search'), function ($q) { + $q->whereHas('product', function ($q) { + $q->where('productName', 'like', '%' . request('search') . '%') + ->orWhere('productCode', 'like', '%' . request('search') . '%'); + }); + }) + ->when($branchId, function ($q) use ($branchId) { + $q->whereHas('sale', function ($q) use ($branchId) { + $q->where('branch_id', $branchId); + }); + }) + ->select( + 'product_id', + DB::raw('SUM(quantities) as total_sold_qty'), + DB::raw('SUM((price - discount) * quantities) as total_sale_amount') + ) + ->groupBy('product_id') + ->orderByDesc('total_sold_qty') + ->limit(5) + ->get(); return view('business::reports.top-products.excel-csv', compact('top_products')); } diff --git a/Modules/Business/App/Exports/ExportTopSupplier.php b/Modules/Business/App/Exports/ExportTopSupplier.php index 90046f92..d41ef81c 100644 --- a/Modules/Business/App/Exports/ExportTopSupplier.php +++ b/Modules/Business/App/Exports/ExportTopSupplier.php @@ -14,6 +14,14 @@ public function view(): View ->where('business_id', auth()->user()->business_id) ->where('type', '=', 'Supplier') ->whereHas('purchases') + ->when(request('search'), function ($q) { + $q->where(function ($q) { + $q->where('name', 'like', '%' . request('search') . '%') + ->orWhere('phone', 'like', '%' . request('search') . '%') + ->orWhere('email', 'like', '%' . request('search') . '%') + ->orWhere('type', 'like', '%' . request('search') . '%'); + }); + }) ->withCount('purchases') ->withSum('purchases', 'totalAmount') ->orderByDesc('purchases_count') diff --git a/Modules/Business/App/Exports/ExportTransaction.php b/Modules/Business/App/Exports/ExportTransaction.php index 8463057f..c1b0ec6a 100644 --- a/Modules/Business/App/Exports/ExportTransaction.php +++ b/Modules/Business/App/Exports/ExportTransaction.php @@ -3,15 +3,49 @@ namespace Modules\Business\App\Exports; use App\Models\DueCollect; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportTransaction implements FromView { + use DateFilterTrait; + public function view(): View { - return view('business::reports.transaction-history.excel-csv', [ - 'transactions' => DueCollect::where('business_id', auth()->user()->business_id)->with('party:id,name,type', 'payment_type:id,name', 'transactions')->latest()->get() - ]); + $businessId = auth()->user()->business_id; + $transactionsQuery = DueCollect::with('party:id,name,type', 'payment_type:id,name', 'transactions')->where('business_id', $businessId); + + // Date Filter + $duration = request('custom_days') ?: 'today'; + $this->applyDateFilter($transactionsQuery, $duration, 'paymentDate', request('from_date'), request('to_date')); + + // Search Filter + if (request('search')) { + $transactionsQuery->where(function ($query) { + $query->where('paymentType', 'like', '%' . request('search') . '%') + ->orWhere('totalDue', 'like', '%' . request('search') . '%') + ->orWhere('invoiceNumber', 'like', '%' . request('search') . '%') + ->orWhere('payDueAmount', 'like', '%' . request('search') . '%') + ->orWhereHas('party', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }) + ->orWhereHas('payment_type', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }); + }); + } + + if (request('type')) { + $transactionsQuery->whereHas('party', function ($q) { + $q->where(function ($q) { + $q->where('type', request('type')); + }); + }); + } + + $transactions = $transactionsQuery->latest()->limit(request('per_page'))->get(); + + return view('business::reports.transaction-history.excel-csv', compact('transactions')); } } diff --git a/Modules/Business/App/Exports/ExportTransactionReport.php b/Modules/Business/App/Exports/ExportTransactionReport.php index a3c60e4b..27b90750 100644 --- a/Modules/Business/App/Exports/ExportTransactionReport.php +++ b/Modules/Business/App/Exports/ExportTransactionReport.php @@ -3,17 +3,82 @@ namespace Modules\Business\App\Exports; use App\Models\Transaction; +use App\Traits\DateFilterTrait; use Illuminate\Contracts\View\View; use Maatwebsite\Excel\Concerns\FromView; class ExportTransactionReport implements FromView { + use DateFilterTrait; + public function view(): View { - $transactions = Transaction::with('paymentType') - ->where('business_id', auth()->user()->business_id) - ->latest() - ->get(); + $businessId = auth()->user()->business_id; + $transactionsQuery = Transaction::with('paymentType') + ->where('business_id', $businessId); + + $duration = request('custom_days') ?: 'today'; + $this->applyDateFilter($transactionsQuery, $duration, 'date', request('from_date'), request('to_date')); + + // Search filter + if (request('search')) { + $transactionsQuery->where(function ($query) { + $query->where('amount', 'like', '%' . request('search') . '%') + ->orWhere('invoice_no', 'like', '%' . request('search') . '%') + ->orWhere('platform', 'like', '%' . request('search') . '%') + ->orWhere('type', 'like', '%' . request('search') . '%'); + }); + } + + // Platform filter + if (request('platform')) { + $transactionsQuery->where('platform', request('platform')); + } + + // Party Filter + if (request('party_id')) { + + $transactionsQuery->where(function ($q) { + + $q->where(function ($saleQ) { + $saleQ->where('platform', 'sale') + ->whereHas('sale', function ($s) { + $s->where('party_id', request('party_id')); + }); + }); + + $q->where(function ($saleRtnQ) { + $saleRtnQ->where('platform', 'sale_return') + ->whereHas('saleReturn.sale', function ($s) { + $s->where('party_id', request('party_id')); + }); + }); + + $q->orWhere(function ($purQ) { + $purQ->where('platform', 'purchase') + ->whereHas('purchase', function ($p) { + $p->where('party_id', request('party_id')); + }); + }); + + $q->orWhere(function ($purRtnQ) { + $purRtnQ->where('platform', 'purchase_return') + ->whereHas('purchaseReturn.purchase', function ($p) { + $p->where('party_id', request('party_id')); + }); + }); + + $q->orWhere(function ($dueQ) { + $dueQ->where('platform', 'due_collect') + ->whereHas('dueCollect', function ($d) { + $d->where('party_id', request('party_id')); + }); + }); + + }); + } + + $transactions = $transactionsQuery->latest()->limit(request('per_page') ?? 20)->get(); return view('business::transactions.excel-csv', compact('transactions')); } diff --git a/Modules/Business/App/Exports/ExportTransfer.php b/Modules/Business/App/Exports/ExportTransfer.php index a0816c71..c4b0a8df 100644 --- a/Modules/Business/App/Exports/ExportTransfer.php +++ b/Modules/Business/App/Exports/ExportTransfer.php @@ -10,8 +10,45 @@ class ExportTransfer implements FromView { public function view(): View { - return view('business::transfers.excel-csv', [ - 'transfers' => Transfer::with(['fromWarehouse:id,name', 'toWarehouse:id,name', 'toBranch:id,name', 'fromBranch:id,name', 'transferProducts'])->where('business_id', auth()->user()->business_id)->latest()->get() - ]); + $user = auth()->user(); + $activeBranchId = $user->active_branch_id; + + $transfers = Transfer::with(['fromWarehouse:id,name', 'toWarehouse:id,name', 'toBranch:id,name', 'fromBranch:id,name', 'transferProducts']) + ->where('business_id', $user->business_id) + ->when($activeBranchId, function ($q) use ($activeBranchId) { + $q->where(function ($query) use ($activeBranchId) { + $query->where('from_branch_id', $activeBranchId) + ->orWhere('to_branch_id', $activeBranchId); + }); + }) + ->when(request('branch_id') && !$activeBranchId, function ($q) { + $q->where(function ($query) { + $query->where('from_branch_id', request('branch_id'))->orWhere('to_branch_id', request('branch_id')); + }); + }) + ->when(request('search'), function ($q) { + $search = request('search'); + $q->where(function ($q) use ($search) { + $q->where('note', 'like', '%' . $search . '%') + ->orWhere('status', 'like', '%' . $search . '%') + ->orWhere('invoice_no', 'like', '%' . $search . '%') + ->orWhereHas('fromWarehouse', function ($q) use ($search) { + $q->where('name', 'like', '%' . $search . '%'); + }) + ->orWhereHas('toWarehouse', function ($q) use ($search) { + $q->where('name', 'like', '%' . $search . '%'); + }) + ->orWhereHas('toBranch', function ($q) use ($search) { + $q->where('name', 'like', '%' . $search . '%'); + }) + ->orWhereHas('fromBranch', function ($q) use ($search) { + $q->where('name', 'like', '%' . $search . '%'); + }); + }); + }) + ->latest() + ->limit(request('per_page') ?? 20)->get(); + + return view('business::transfers.excel-csv', compact('transfers')); } } diff --git a/Modules/Business/App/Exports/PurchaseExport.php b/Modules/Business/App/Exports/PurchaseExport.php new file mode 100644 index 00000000..28462ef5 --- /dev/null +++ b/Modules/Business/App/Exports/PurchaseExport.php @@ -0,0 +1,50 @@ +where('business_id', auth()->user()->business_id); + + $purchasesQuery->when(request('branch_id'), function ($q) { + $q->where('branch_id', request('branch_id')); + }); + + // Date Filter + $duration = request('custom_days') ?: 'today'; + $this->applyDateFilter($purchasesQuery, $duration, 'purchaseDate', request('from_date'), request('to_date')); + + // Search Filter + if (request('search')) { + $purchasesQuery->where(function ($query) { + $query->where('paymentType', 'like', '%' . request('search') . '%') + ->orWhere('invoiceNumber', 'like', '%' . request('search') . '%') + ->orWhereHas('party', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }) + ->orWhereHas('payment_type', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }) + ->orWhereHas('branch', function ($q) { + $q->where('name', 'like', '%' . request('search') . '%'); + }); + }); + } + + $purchases = $purchasesQuery->latest()->limit($request->per_page ?? 20)->get(); + + + return view('business::reports.purchase.excel-csv', compact('purchases')); + } + +} diff --git a/Modules/Business/App/Http/Controllers/.gitkeep b/Modules/Business/App/Http/Controllers/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/Modules/Business/App/Http/Controllers/AcnooBalanceSheetController.php b/Modules/Business/App/Http/Controllers/AcnooBalanceSheetController.php index 0a3980f7..de72b1db 100644 --- a/Modules/Business/App/Http/Controllers/AcnooBalanceSheetController.php +++ b/Modules/Business/App/Http/Controllers/AcnooBalanceSheetController.php @@ -100,28 +100,33 @@ public function exportCsv() public function exportPdf(Request $request) { $businessId = auth()->user()->business_id; + $duration = $request->custom_days ? : 'today'; + $fromDate = $request->from_date; + $toDate = $request->to_date; + $perPage = $request->per_page ?? 20; + + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); - // PRODUCT $productQuery = Product::select('id', 'business_id', 'productName', 'product_type', 'created_at') ->with(['stocks:id,business_id,product_id,productStock,productPurchasePrice', 'combo_products.stock']) ->where('business_id', $businessId); + $this->applyDateFilter($productQuery, $duration, 'created_at', $fromDate, $toDate); $products = $productQuery->get() ->map(function ($item) { $item->source = 'product'; return $item; }); - // BANK $bankQuery = PaymentType::where('business_id', $businessId); + $this->applyDateFilter($bankQuery, $duration, 'opening_date', $fromDate, $toDate); $banks = $bankQuery->get() ->map(function ($item) { $item->source = 'bank'; return $item; }); - $asset_datas = $products->merge($banks); + $asset_datas = $products->merge($banks)->take($perPage); - // TOTAL STOCK VALUE $total_stock_value = 0; foreach ($products as $product) { if (in_array($product->product_type, ['single', 'variant'])) { @@ -143,6 +148,6 @@ public function exportPdf(Request $request) $totalBankBalance = $banks->sum('balance'); $total_asset = $total_stock_value + $totalBankBalance; - return PdfService::render('business::balance-sheets.pdf', compact('asset_datas', 'total_asset'),'balance-sheet-report.pdf'); + return PdfService::render('business::balance-sheets.pdf', compact('asset_datas', 'total_asset', 'duration', 'filter_from_date', 'filter_to_date'),'balance-sheet-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooBillWiseProfitController.php b/Modules/Business/App/Http/Controllers/AcnooBillWiseProfitController.php index 173cdffe..1d23f58a 100644 --- a/Modules/Business/App/Http/Controllers/AcnooBillWiseProfitController.php +++ b/Modules/Business/App/Http/Controllers/AcnooBillWiseProfitController.php @@ -90,13 +90,37 @@ public function exportCsv() return Excel::download(new ExportBillWisePofit, 'bill-wise-profit.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { - $profits = Sale::with('party:id,name') - ->where('business_id', auth()->user()->business_id) - ->latest() - ->get(); + $salesQuery = Sale::with('party:id,name') + ->where('business_id', auth()->user()->business_id); - return PdfService::render('business::bill-wise-profits.pdf', compact('profits'),'bill-wise-profit-report.pdf'); + $salesQuery->when($request->search, function ($query) use ($request) { + $query->where(function ($q) use ($request) { + $q->where('lossProfit', 'like', '%' . $request->search . '%') + ->orWhere('totalAmount', 'like', '%' . $request->search . '%') + ->orWhere('invoiceNumber', 'like', '%' . $request->search . '%') + ->orWhereHas('party', function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%'); + }); + }); + }); + + $salesQuery->when($request->party_id, function ($query, $partyId) { + if ($partyId === 'Guest') { + $query->whereNull('party_id'); + } else { + $query->where('party_id', $partyId); + } + }); + + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + $this->applyDateFilter($salesQuery, $duration, 'saleDate', $request->from_date, $request->to_date); + + $profits = $salesQuery->latest()->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::bill-wise-profits.pdf', compact('profits', 'duration', 'filter_from_date', 'filter_to_date'),'bill-wise-profit-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooCashController.php b/Modules/Business/App/Http/Controllers/AcnooCashController.php index 10b14c99..88c59e15 100644 --- a/Modules/Business/App/Http/Controllers/AcnooCashController.php +++ b/Modules/Business/App/Http/Controllers/AcnooCashController.php @@ -6,15 +6,21 @@ use App\Models\PaymentType; use App\Models\Transaction; use App\Http\Controllers\Controller; -use Carbon\Carbon; +use App\Services\PdfService; +use App\Traits\DateFilterTrait; +use App\Traits\DateRangeTrait; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Storage; +use Maatwebsite\Excel\Facades\Excel; +use Modules\Business\App\Exports\ExportCashBalance; class AcnooCashController extends Controller { use HasUploader; + use DateFilterTrait, DateRangeTrait; + public function index(Request $request) { $business_id = auth()->user()->business_id; @@ -24,37 +30,16 @@ public function index(Request $request) ->where('business_id', auth()->user()->business_id) ->whereIn('transaction_type', ['cash_payment', 'bank_to_cash', 'cash_to_bank', 'adjust_cash', 'cheque_to_cash']); - // Default to today - $startDate = Carbon::today()->format('Y-m-d'); - $endDate = Carbon::today()->format('Y-m-d'); + // Date Filter + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); - 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'); - } - - $cash->whereDate('date', '>=', $startDate) - ->whereDate('date', '<=', $endDate); + $this->applyDateFilter($cash, $duration, 'date', $request->from_date, $request->to_date); $cashes = $cash->when(request('search'), function ($q) use ($request) { $q->where(function ($q) use ($request) { $q->where('date', 'like', '%' . $request->search . '%') + ->orWhere('platform', 'like', '%' . $request->search . '%') ->orWhere('transaction_type', 'like', '%' . $request->search . '%') ->orWhere('amount', 'like', '%' . $request->search . '%') ->orWhereHas('user', function ($q) use ($request) { @@ -65,13 +50,30 @@ public function index(Request $request) ->latest() ->paginate($request->per_page ?? 20)->appends($request->query()); + // Sum of all credits and bank_to_cash + $totalCredit = (clone $cash)->where(function ($q) { + $q->where('type', 'credit') + ->orWhere('transaction_type', 'bank_to_cash'); + }) + ->sum('amount'); + + // Sum of all debits and cash_to_bank + $totalDebit = (clone $cash)->where(function ($q) { + $q->where('type', 'debit') + ->orWhere('transaction_type', 'cash_to_bank'); + }) + ->sum('amount'); + + $cash_balance = $totalCredit - $totalDebit; + if ($request->ajax()) { return response()->json([ - 'data' => view('business::cashes.datas', compact('cashes'))->render() + 'data' => view('business::cashes.datas', compact('cashes', 'cash_balance', 'duration', 'filter_from_date', 'filter_to_date'))->render(), + 'cash_balance' => currency_format($cash_balance, 'icon', 2, business_currency()) ]); } - return view('business::cashes.index', compact('cashes', 'banks')); + return view('business::cashes.index', compact('cashes', 'banks', 'cash_balance', 'duration', 'filter_from_date', 'filter_to_date')); } public function store(Request $request) @@ -282,5 +284,109 @@ public function destroy(string $id) } } + public function deleteAll(Request $request) + { + $ids = $request->ids; + DB::beginTransaction(); + + try { + + $transactions = Transaction::whereIn('id', $ids)->get(); + + foreach ($transactions as $transaction) { + + // Allow only "cash" platform transactions + if ($transaction->platform !== 'cash') { + return response()->json([ + 'message' => 'Cannot delete here, please delete from ' . ucfirst($transaction->platform) . ' section.', + ], 400); + } + + $amount = $transaction->amount; + $toBank = $transaction->to_bank ? PaymentType::find($transaction->to_bank) : null; + + switch ($transaction->transaction_type) { + + case 'cash_to_bank': + + if ($toBank) { + if ($toBank->balance < $amount) { + return response()->json([ + 'message' => 'Cannot delete: bank balance would go negative.', + ], 400); + } + + $toBank->decrement('balance', $amount); + } + + break; + + case 'adjust_cash': + // Cash is static, so no bank adjustments needed + break; + } + + if ($transaction->image && file_exists($transaction->image)) { + Storage::delete($transaction->image); + } + + $transaction->delete(); + } + + DB::commit(); + + return response()->json([ + 'message' => __('Cash transactions reversed and deleted successfully.'), + 'redirect' => route('business.cashes.index'), + ]); + + } catch (\Exception $e) { + + DB::rollBack(); + + return response()->json([ + 'message' => 'Error: ' . $e->getMessage(), + ], 500); + } + } + + public function exportExcel() + { + return Excel::download(new ExportCashBalance, 'cash-balances.xlsx'); + } + + public function exportCsv() + { + return Excel::download(new ExportCashBalance, 'cash-balances.csv'); + } + + public function exportPdf(Request $request) + { + $cash = Transaction::with('user:id,name') + ->where('business_id', auth()->user()->business_id) + ->whereIn('transaction_type', ['cash_payment', 'bank_to_cash', 'cash_to_bank', 'adjust_cash', 'cheque_to_cash']); + + // Date Filter + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + $this->applyDateFilter($cash, $duration, 'date', $request->from_date, $request->to_date); + + $cashes = $cash->when(request('search'), function ($q) use ($request) { + $q->where(function ($q) use ($request) { + $q->where('date', 'like', '%' . $request->search . '%') + ->orWhere('platform', 'like', '%' . $request->search . '%') + ->orWhere('transaction_type', 'like', '%' . $request->search . '%') + ->orWhere('amount', 'like', '%' . $request->search . '%') + ->orWhereHas('user', function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%'); + }); + }); + }) + ->latest() + ->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::cashes.pdf', compact('cashes', 'duration', 'filter_from_date', 'filter_to_date'), 'cash-balances.pdf'); + } } diff --git a/Modules/Business/App/Http/Controllers/AcnooCashFlowReportController.php b/Modules/Business/App/Http/Controllers/AcnooCashFlowReportController.php index 3bf928f2..5c52df80 100644 --- a/Modules/Business/App/Http/Controllers/AcnooCashFlowReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooCashFlowReportController.php @@ -31,16 +31,6 @@ public function index(Request $request) ->where('business_id', auth()->user()->business_id) ->whereIn('type', ['debit', 'credit']); - $total_cash_in = (clone $query) - ->where('type', 'credit') - ->sum('amount'); - - $total_cash_out = (clone $query) - ->where('type', 'debit') - ->sum('amount'); - - $total_running_cash = $total_cash_in - $total_cash_out; - // Date Filter $duration = $request->custom_days ?: 'today'; [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); @@ -70,6 +60,16 @@ public function index(Request $request) $perPage = $request->input('per_page', 20); $cash_flows = $query->paginate($perPage)->appends($request->query()); + $cashflow_cash_in = (clone $query) + ->where('type', 'credit') + ->sum('amount'); + + $cashflow_cash_out = (clone $query) + ->where('type', 'debit') + ->sum('amount'); + + $total_running_cash = $cashflow_cash_in - $cashflow_cash_out; + $firstDate = $cash_flows->first()?->date; if ($firstDate) { @@ -83,11 +83,14 @@ public function index(Request $request) if ($request->ajax()) { return response()->json([ - 'data' => view('business::cash-flow.datas', compact('cash_flows', 'total_cash_in', 'total_cash_out', 'opening_balance', 'total_running_cash', 'filter_from_date', 'filter_to_date', 'duration'))->render() + 'data' => view('business::cash-flow.datas', compact('cash_flows', 'cashflow_cash_in', 'cashflow_cash_out', 'opening_balance', 'total_running_cash', 'filter_from_date', 'filter_to_date', 'duration'))->render(), + 'cashflow_cash_in' => currency_format($cashflow_cash_in, 'icon', 2, business_currency()), + 'cashflow_cash_out' => currency_format($cashflow_cash_out, 'icon', 2, business_currency()), + 'total_running_cash' => currency_format($total_running_cash, 'icon', 2, business_currency()), ]); } - return view('business::cash-flow.index', compact('cash_flows', 'total_cash_in', 'total_cash_out', 'opening_balance', 'total_running_cash', 'filter_from_date', 'filter_to_date', 'duration')); + return view('business::cash-flow.index', compact('cash_flows', 'cashflow_cash_in', 'cashflow_cash_out', 'opening_balance', 'total_running_cash', 'filter_from_date', 'filter_to_date', 'duration')); } public function exportExcel() @@ -100,9 +103,9 @@ public function exportCsv() return Excel::download(new ExportCashFlowReport, 'cash-flow.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { - $cash_flows = Transaction::with([ + $query = Transaction::with([ 'paymentType:id,name', 'sale:id,party_id', 'sale.party:id,name', @@ -114,12 +117,47 @@ public function exportPdf() 'dueCollect.party:id,name', ]) ->where('business_id', auth()->user()->business_id) - ->whereIn('type', ['debit', 'credit']) - ->latest() - ->get(); + ->whereIn('type', ['debit', 'credit']); - $opening_balance = 0; + // Date Filter + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); - return PdfService::render('business::cash-flow.pdf', compact('cash_flows', 'opening_balance'),'cash-flow-report.pdf'); + $this->applyDateFilter($query, $duration, 'date', $request->from_date, $request->to_date); + + // Search filter + if ($request->filled('search')) { + $query->where(function ($query) use ($request) { + $query->where('date', 'like', '%' . $request->search . '%') + ->orWhere('invoice_no', 'like', '%' . $request->search . '%') + ->orWhere('platform', 'like', '%' . $request->search . '%') + ->orWhere('amount', 'like', '%' . $request->search . '%') + ->orWhere('transaction_type', 'like', '%' . $request->search . '%') + ->orWhereHas('paymentType', function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%'); + }); + }); + } + + // Platform filter + if ($request->filled('platform')) { + $query->where('platform', $request->platform); + } + + // Paginate data + $cash_flows = $query->limit($request->per_page ?? 20)->get(); + + $firstDate = $cash_flows->first()?->date; + + if ($firstDate) { + $opening_balance = (clone $query) + ->whereDate('date', '<', $firstDate) + ->selectRaw("SUM(CASE WHEN type='credit' THEN amount ELSE 0 END) - SUM(CASE WHEN type='debit' THEN amount ELSE 0 END) as balance") + ->value('balance') ?? 0; + } else { + $opening_balance = 0; + } + + return PdfService::render('business::cash-flow.pdf', compact('cash_flows', 'opening_balance', 'duration', 'filter_from_date', 'filter_to_date'),'cash-flow-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooComboProductController.php b/Modules/Business/App/Http/Controllers/AcnooComboProductController.php index 0ecbb6d0..de92b852 100644 --- a/Modules/Business/App/Http/Controllers/AcnooComboProductController.php +++ b/Modules/Business/App/Http/Controllers/AcnooComboProductController.php @@ -5,6 +5,7 @@ use App\Models\Product; use Illuminate\Http\Request; use App\Http\Controllers\Controller; +use App\Services\PdfService; use Maatwebsite\Excel\Facades\Excel; use Modules\Business\App\Exports\ExportComboProduct; @@ -13,9 +14,14 @@ class AcnooComboProductController extends Controller public function index(Request $request) { $user = auth()->user(); + + $branchId = null; + if (moduleCheck('MultiBranchAddon')) { + $branchId = $user->branch_id ?? $user->active_branch_id; + } $search = $request->input('search'); - $combo_products = Product::with(['combo_products', 'unit:id,unitName', 'category:id,categoryName']) + $combo_products = Product::with(['combo_products.stock.product', 'unit:id,unitName', 'category:id,categoryName']) ->where('business_id', auth()->user()->business_id) ->where('product_type', 'combo') ->withCount('combo_products as total_combo_products') @@ -31,6 +37,12 @@ public function index(Request $request) }); }); }) + ->when($branchId, function ($q) use ($branchId) { + $q->whereHas('combo_products.stock', function ($q) use ($branchId) { + $q->where('branch_id', $branchId); + }); + }) + ->latest() ->paginate($request->per_page ?? 20)->appends($request->query()); @@ -73,4 +85,55 @@ public function exportCsv() { return Excel::download(new ExportComboProduct, 'combo-products.csv'); } + + public function exportPdf(Request $request) + { + $user = auth()->user(); + + $branchId = null; + if (moduleCheck('MultiBranchAddon')) { + $branchId = $user->branch_id ?? $user->active_branch_id; + } + + $search = $request->input('search'); + + $combo_products = Product::with(['combo_products.stock.product', 'unit:id,unitName', 'category:id,categoryName']) + ->where('business_id', auth()->user()->business_id) + ->where('product_type', 'combo') + ->withCount('combo_products as total_combo_products') + ->when($search, function ($q) use ($search) { + $q->where(function ($q) use ($search) { + $q->where('productName', 'like', '%' . $search . '%') + ->orWhere('productCode', 'like', '%' . $search . '%') + ->orWhereHas('category', function ($q) use ($search) { + $q->where('categoryName', 'like', '%' . $search . '%'); + }) + ->orWhereHas('unit', function ($q) use ($search) { + $q->where('unitName', 'like', '%' . $search . '%'); + }); + }); + }) + ->when($branchId, function ($q) use ($branchId) { + $q->whereHas('combo_products.stock', function ($q) use ($branchId) { + $q->where('branch_id', $branchId); + }); + }) + + ->latest() + ->limit($request->per_page ?? 20)->get(); + + $combo_products->transform(function ($product) { + $product->total_stock = $product->combo_products->sum(function ($combo) { + return optional($combo->stock)->productStock ?? 0; + }); + + $product->total_cost = $product->combo_products->sum(function ($combo) { + return ($combo->quantity ?? 0) * ($combo->purchase_price ?? 0); + }); + + return $product; + }); + + return PdfService::render('business::products.combo-products.pdf', compact('combo_products'),'combo-products.pdf'); + } } diff --git a/Modules/Business/App/Http/Controllers/AcnooComboProductReportController.php b/Modules/Business/App/Http/Controllers/AcnooComboProductReportController.php index a618c2e5..969c120c 100644 --- a/Modules/Business/App/Http/Controllers/AcnooComboProductReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooComboProductReportController.php @@ -86,27 +86,54 @@ public function exportCsv() return Excel::download(new ExportComboProductReport, 'combo-product-report.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { - $combo_products = Product::with(['combo_products', 'unit:id,unitName', 'category:id,categoryName']) + $user = auth()->user(); + + $branchId = null; + if (moduleCheck('MultiBranchAddon')) { + $branchId = $user->branch_id ?? $user->active_branch_id; + } + + $search = $request->input('search'); + + $combo_products = Product::with(['combo_products.stock.product', 'unit:id,unitName', 'category:id,categoryName']) ->where('business_id', auth()->user()->business_id) ->where('product_type', 'combo') ->withCount('combo_products as total_combo_products') + ->when($search, function ($q) use ($search) { + $q->where(function ($q) use ($search) { + $q->where('productName', 'like', '%' . $search . '%') + ->orWhere('productCode', 'like', '%' . $search . '%') + ->orWhereHas('category', function ($q) use ($search) { + $q->where('categoryName', 'like', '%' . $search . '%'); + }) + ->orWhereHas('unit', function ($q) use ($search) { + $q->where('unitName', 'like', '%' . $search . '%'); + }); + }); + }) + ->when($branchId, function ($q) use ($branchId) { + $q->whereHas('combo_products.stock', function ($q) use ($branchId) { + $q->where('branch_id', $branchId); + }); + }) + ->latest() - ->get(); + ->limit($request->per_page ?? 20)->get(); - $combo_products->transform(function ($product) { - $product->total_stock = $product->combo_products->sum(function ($combo) { - return optional($combo->stock)->productStock ?? 0; + $combo_products->transform(function ($product) { + $product->total_stock = $product->combo_products->sum(function ($combo) { + return optional($combo->stock)->productStock ?? 0; + }); + + $product->total_cost = $product->combo_products->sum(function ($combo) { + return ($combo->quantity ?? 0) * ($combo->purchase_price ?? 0); + }); + + return $product; }); - $product->total_cost = $product->combo_products->sum(function ($combo) { - return ($combo->quantity ?? 0) * ($combo->purchase_price ?? 0); - }); - - return $product; - }); - return PdfService::render('business::reports.combo-products.pdf', compact('combo_products'),'combo-product-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooCustomerLedgerController.php b/Modules/Business/App/Http/Controllers/AcnooCustomerLedgerController.php index 3d8be955..94226780 100644 --- a/Modules/Business/App/Http/Controllers/AcnooCustomerLedgerController.php +++ b/Modules/Business/App/Http/Controllers/AcnooCustomerLedgerController.php @@ -54,7 +54,7 @@ public function index(Request $request) return view('business::party-reports.customer-ledger.index', compact('customers', 'totalAmount', 'totalPaid', 'totalDue')); } - public function show(Request $request, $partyId, PartyLedgerService $service) + public function show(Request $request, string $partyId, PartyLedgerService $service) { $party = Party::find($partyId); $ledger = $service->list($request, $partyId); @@ -78,18 +78,30 @@ public function exportCsv() return Excel::download(new ExportCustomerLedger, 'customer-ledger.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { $customers = Party::with('sales') ->where('business_id', auth()->user()->business_id) ->where('type', '!=', 'Supplier') + ->when(request('search'), function ($q) use ($request) { + $q->where(function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%') + ->orWhere('phone', 'like', '%' . $request->search . '%') + ->orWhere('type', 'like', '%' . $request->search . '%'); + }); + }) + ->when(request('type'), function ($q) use ($request) { + $q->where(function ($q) use ($request) { + $q->where('type', $request->type); + }); + }) ->latest() - ->get(); + ->limit($request->per_page ?? 20)->get(); return PdfService::render('business::party-reports.customer-ledger.pdf', compact('customers'),'customer-ledger-report.pdf'); } - public function exportLedgerExcel(Request $request, $partyId, PartyLedgerService $service) + public function exportLedgerExcel(Request $request, string $partyId, PartyLedgerService $service) { $party = Party::findOrFail($partyId); @@ -98,7 +110,7 @@ public function exportLedgerExcel(Request $request, $partyId, PartyLedgerService return Excel::download(new ExportSingleCustomerLedger($ledger, $party), 'single-customer-ledger.xlsx'); } - public function exportLedgerCsv(Request $request, $partyId, PartyLedgerService $service) + public function exportLedgerCsv(Request $request, string $partyId, PartyLedgerService $service) { $party = Party::findOrFail($partyId); @@ -107,15 +119,18 @@ public function exportLedgerCsv(Request $request, $partyId, PartyLedgerService $ return Excel::download(new ExportSingleCustomerLedger($ledger, $party), 'single-customer-ledger.csv'); } - public function exportLedgerPdf(Request $request, $partyId, PartyLedgerService $service) + public function exportLedgerPdf(Request $request, string $partyId, PartyLedgerService $service) { $party = Party::findOrFail($partyId); $ledger = $service->list($request, $partyId); - return PdfService::render( - 'business::party-reports.customer-ledger.show-details.pdf', - compact('ledger', 'party'), + $duration = $request->custom_days ?: $request->duration ?: 'today'; + + [$fromDate, $toDate] = app(PartyLedgerService::class)->applyDateRange($duration, $request->from_date, $request->to_date); + + return PdfService::render('business::party-reports.customer-ledger.show-details.pdf', + compact('ledger', 'party', 'duration', 'fromDate', 'toDate'), strtolower($party->name).'-ledger.pdf' ); } diff --git a/Modules/Business/App/Http/Controllers/AcnooDayBookReportController.php b/Modules/Business/App/Http/Controllers/AcnooDayBookReportController.php index b55a1186..c39cd805 100644 --- a/Modules/Business/App/Http/Controllers/AcnooDayBookReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooDayBookReportController.php @@ -60,44 +60,31 @@ public function index(Request $request) $query->where('platform', $request->platform); } - // Calculate summary data - $all_summary_records = (clone $query)->get(); - - $total_amount = $all_summary_records->sum(function ($day_book) { - return match ($day_book->platform) { - 'sale' => $day_book->sale?->totalAmount ?? 0, - 'sale_return' => $day_book->saleReturn?->sale?->totalAmount ?? 0, - 'purchase' => $day_book->purchase?->totalAmount ?? 0, - 'purchase_return' => $day_book->purchaseReturn?->purchase?->totalAmount ?? 0, - 'due_collect', 'due_pay' => $day_book->dueCollect?->totalDue ?? 0, - default => 0 - }; - }); - - $total_money_in = $all_summary_records->where('type', 'credit')->sum(function ($day_book) { - return match ($day_book->platform) { - 'sale' => $day_book->sale?->totalAmount ?? 0, - 'sale_return' => $day_book->saleReturn?->sale?->totalAmount ?? 0, - 'purchase' => $day_book->purchase?->totalAmount ?? 0, - 'purchase_return' => $day_book->purchaseReturn?->purchase?->totalAmount ?? 0, - 'due_collect', 'due_pay' => $day_book->dueCollect?->totalDue ?? 0, - default => 0 - }; - }); - - $total_money_out = $all_summary_records->where('type', 'debit')->sum('amount'); - // Paginate data $perPage = $request->input('per_page', 20); $day_books = $query->latest()->paginate($perPage)->appends($request->query()); + // Calculate summary data + $total_money_in = (clone $query) + ->where('type', 'credit') + ->sum('amount'); + + $total_money_out = (clone $query) + ->where('type', 'debit') + ->sum('amount'); + + $total_money_in_out_amount = $total_money_in + $total_money_out; + if ($request->ajax()) { return response()->json([ - 'data' => view('business::day-book.datas', compact('day_books', 'total_amount', 'total_money_in', 'total_money_out', 'filter_from_date', 'filter_to_date', 'duration'))->render() + 'data' => view('business::day-book.datas', compact('day_books', 'total_money_in_out_amount', 'total_money_in', 'total_money_out', 'filter_from_date', 'filter_to_date', 'duration'))->render(), + 'total_money_in' => currency_format($total_money_in, 'icon', 2, business_currency()), + 'total_money_out' => currency_format($total_money_out, 'icon', 2, business_currency()), + 'total_money_in_out_amount' => currency_format($total_money_in_out_amount, 'icon', 2, business_currency()), ]); } - return view('business::day-book.index', compact('day_books', 'total_amount', 'total_money_in', 'total_money_out', 'filter_from_date', 'filter_to_date', 'duration')); + return view('business::day-book.index', compact('day_books', 'total_money_in_out_amount', 'total_money_in', 'total_money_out', 'filter_from_date', 'filter_to_date', 'duration')); } public function exportExcel() @@ -110,9 +97,9 @@ public function exportCsv() return Excel::download(new ExportDayBookReport, 'day-book.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { - $day_books = Transaction::with([ + $query = Transaction::with([ 'paymentType:id,name', 'sale:id,user_id,party_id,totalAmount', 'sale.party:id,name', @@ -127,10 +114,37 @@ public function exportPdf() 'dueCollect.user:id,name', ]) ->where('business_id', auth()->user()->business_id) - ->whereIn('type', ['debit', 'credit']) - ->latest() - ->get(); + ->whereIn('type', ['debit', 'credit']); - return PdfService::render('business::day-book.pdf', compact('day_books'), 'day-book-report.pdf'); + // Apply date filter + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + $this->applyDateFilter($query, $duration, 'date', $request->from_date, $request->to_date); + + // Search filter + if ($request->filled('search')) { + $query->where(function ($query) use ($request) { + $query->where('date', 'like', '%' . $request->search . '%') + ->orWhere('invoice_no', 'like', '%' . $request->search . '%') + ->orWhere('platform', 'like', '%' . $request->search . '%') + ->orWhere('amount', 'like', '%' . $request->search . '%') + ->orWhere('type', 'like', '%' . $request->search . '%') + ->orWhere('transaction_type', 'like', '%' . $request->search . '%') + ->orWhereHas('paymentType', function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%'); + }); + }); + } + + // Platform filter + if ($request->filled('platform')) { + $query->where('platform', $request->platform); + } + + // Paginate data + $day_books = $query->latest()->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::day-book.pdf', compact('day_books', 'duration', 'filter_from_date', 'filter_to_date'),'day-book-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooDiscountProductReportController.php b/Modules/Business/App/Http/Controllers/AcnooDiscountProductReportController.php index bc1fbbdf..a485947e 100644 --- a/Modules/Business/App/Http/Controllers/AcnooDiscountProductReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooDiscountProductReportController.php @@ -13,7 +13,6 @@ class AcnooDiscountProductReportController extends Controller { public function index(Request $request) { - $user = auth()->user(); $branchId = null; @@ -21,7 +20,7 @@ public function index(Request $request) $branchId = $user->branch_id ?? $user->active_branch_id; } - $discount_products = SaleDetails::with('product:id,productName,productCode') + $discount_products = SaleDetails::with('product:id,productName,productCode,hsn_code') ->whereHas('product', function ($q) { $q->where('business_id', auth()->user()->business_id); }) @@ -62,13 +61,35 @@ public function exportCsv() return Excel::download(new ExportDiscountProduct, 'discount-products.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { - $discount_products = SaleDetails::with('product:id,productName,productCode') + $user = auth()->user(); + + $branchId = null; + if (moduleCheck('MultiBranchAddon')) { + $branchId = $user->branch_id ?? $user->active_branch_id; + } + + $discount_products = SaleDetails::with('product:id,productName,productCode,hsn_code') ->whereHas('product', function ($q) { $q->where('business_id', auth()->user()->business_id); }) + ->when(request('search'), function ($q) use ($request) { + $q->whereHas('product', function ($q) use ($request) { + $q->where('productName', 'like', '%' . $request->search . '%') + ->orWhere('productCode', 'like', '%' . $request->search . '%') + ->orWhere('productPurchasePrice', 'like', '%' . $request->search . '%') + ->orWhere('price', 'like', '%' . $request->search . '%') + ->orWhere('discount', 'like', '%' . $request->search . '%'); + }); + }) + ->when($branchId, function ($q) use ($branchId) { + $q->whereHas('sale', function ($q) use ($branchId) { + $q->where('branch_id', $branchId); + }); + }) ->where('discount', '>', 0) + ->limit($request->per_page ?? 20) ->get(); return PdfService::render('business::reports.discount-products.pdf', compact('discount_products'),'discount-product-report.pdf'); diff --git a/Modules/Business/App/Http/Controllers/AcnooDueController.php b/Modules/Business/App/Http/Controllers/AcnooDueController.php index b62112a7..506834e2 100644 --- a/Modules/Business/App/Http/Controllers/AcnooDueController.php +++ b/Modules/Business/App/Http/Controllers/AcnooDueController.php @@ -2,21 +2,25 @@ namespace Modules\Business\App\Http\Controllers; +use App\Services\Invoice\DueInvoiceService; +use Carbon\Carbon; use App\Models\Sale; use App\Models\Party; +use App\Models\Business; use App\Models\Purchase; use App\Models\DueCollect; use App\Models\PaymentType; +use App\Models\Transaction; use App\Services\PdfService; use Illuminate\Http\Request; use Barryvdh\DomPDF\Facade\Pdf; -use App\Events\DuePaymentReceived; use Illuminate\Support\Facades\DB; use App\Http\Controllers\Controller; use Illuminate\Support\Facades\Mail; use App\Events\MultiPaymentProcessed; -use App\Models\Transaction; -use Carbon\Carbon; +use Maatwebsite\Excel\Facades\Excel; +use Modules\Business\App\Exports\ExportDueList; +use Modules\Business\App\Exports\ExportGuestDue; class AcnooDueController extends Controller { @@ -30,29 +34,6 @@ public function index(Request $request) $businessId = auth()->user()->business_id; $activeBranch = auth()->user()->active_branch; - if ($activeBranch) { - $total_supplier_due = Party::where('business_id', $businessId) - ->where('type', 'Supplier') - ->with('purchases_dues') - ->get() - ->sum(fn($p) => $p->purchases_dues->sum('dueAmount')); - - $total_customer_due = Party::where('business_id', $businessId) - ->where('type', '!=', 'Supplier') - ->with('sales_dues') - ->get() - ->sum(fn($p) => $p->sales_dues->sum('dueAmount')); - } else { - - $total_supplier_due = Party::where('business_id', $businessId) - ->where('type', 'Supplier') - ->sum('due'); - - $total_customer_due = Party::where('business_id', $businessId) - ->where('type', '!=', 'Supplier') - ->sum('due'); - } - $query = Party::where('business_id', $businessId) ->with(['purchases_dues', 'sales_dues']); @@ -69,42 +50,70 @@ public function index(Request $request) ->orWhere('phone', 'like', "%$search%") ->orWhere('email', 'like', "%$search%"); - if (!$activeBranch) { + if (! $activeBranch) { $q->orWhere('due', 'like', "%$search%"); } }); } + $totalQuery = clone $query; $parties = $query->latest()->paginate($request->per_page ?? 20)->appends($request->query()); if ($activeBranch) { - $filtered = $parties->getCollection() - ->map(function ($party) { + $total_supplier_due = (clone $totalQuery) + ->where('type', 'Supplier') + ->get() + ->sum(fn ($p) => $p->purchases_dues->sum('dueAmount')); - $party->due = $party->type === 'Supplier' - ? $party->purchases_dues->sum('dueAmount') - : $party->sales_dues->sum('dueAmount'); + $total_customer_due = (clone $totalQuery) + ->where('type', '!=', 'Supplier') + ->get() + ->sum(fn ($p) => $p->sales_dues->sum('dueAmount')); + } else { + $total_supplier_due = (clone $totalQuery) + ->where('type', 'Supplier') + ->sum('due'); + + $total_customer_due = (clone $totalQuery) + ->where('type', '!=', 'Supplier') + ->sum('due'); + } + + if ($activeBranch) { + $filtered = $parties->getCollection() + ->map(function ($party) use ($activeBranch) { + + $sale_dues = $party->type === 'Supplier' + ? $party->purchases_dues->where('branch_id', $activeBranch->id) + : $party->sales_dues->where('branch_id', $activeBranch->id); + + $party_due = $sale_dues->sum('dueAmount'); + + if ($party->branch_id === $activeBranch->id) { + $openingBalanceDue = $party->opening_balance_type === 'due' ? ($party->opening_balance ?? 0) : 0; + $party->due = $openingBalanceDue + $party_due; + } else { + $party->due = $party_due; + } return $party; }) - ->filter(fn($p) => $p->due > 0) + ->filter(fn ($p) => $p->due > 0) ->values(); $parties->setCollection($filtered); } else { $parties->setCollection( - $parties->getCollection()->filter(fn($p) => $p->due > 0)->values() + $parties->getCollection()->filter(fn ($p) => $p->due > 0)->values() ); } if ($request->ajax()) { return response()->json([ - 'data' => view('business::dues.datas', [ - 'parties' => $parties, - 'total_supplier_due' => $total_supplier_due, - 'total_customer_due' => $total_customer_due - ])->render() + 'data' => view('business::dues.datas', compact('parties'))->render(), + 'total_supplier_due' => currency_format($total_supplier_due, currency: business_currency()), + 'total_customer_due' => currency_format($total_customer_due, currency: business_currency()) ]); } @@ -115,24 +124,34 @@ public function index(Request $request) )); } - public function collectDue($id) + + public function collectDue(string $id) { + $activeBranch = auth()->user()->active_branch; $party = Party::where('business_id', auth()->user()->business_id)->with(['sales_dues', 'purchases_dues'])->findOrFail($id); $payment_types = PaymentType::where('business_id', auth()->user()->business_id)->whereStatus(1)->latest()->get(); $due_amount = 0; if ($party->type == 'Supplier') { foreach ($party->purchases_dues as $sales_due) { - $due_amount += $sales_due->dueAmount; + if (! $activeBranch || $sales_due->branch_id === $activeBranch->id) { + $due_amount += $sales_due->dueAmount; + } } } else { foreach ($party->sales_dues as $sales_due) { - $due_amount += $sales_due->dueAmount; + if (! $activeBranch || $sales_due->branch_id === $activeBranch->id) { + $due_amount += $sales_due->dueAmount; + } } } - if (auth()->user()->active_branch) { - $party_opening_due = 0; + if ($activeBranch) { + if ($party->branch_id === $activeBranch->id && $party->opening_balance_type === 'due') { + $party_opening_due = $party->opening_balance ?? 0; + } else { + $party_opening_due = 0; + } } else { $party_opening_due = $party->due - $due_amount; } @@ -140,28 +159,24 @@ public function collectDue($id) return view('business::dues.collect-due', compact('party', 'party_opening_due', 'payment_types')); } + public function collectDueStore(Request $request) { $party = Party::find($request->party_id); + $activeBranch = auth()->user()->active_branch; $request->validate([ 'paymentDate' => 'required|string', 'payDueAmount' => 'required|numeric', 'party_id' => 'required|exists:parties,id', - 'invoiceNumber' => 'nullable|exists:' . ($party->type == 'Supplier' ? 'purchases' : 'sales') . ',invoiceNumber', + 'invoiceNumber' => 'nullable|exists:'.($party->type == 'Supplier' ? 'purchases' : 'sales').',invoiceNumber', ]); - $business_id = auth()->user()->business_id; - - if (auth()->user()->active_branch && !$request->invoiceNumber) { - return response()->json([ - 'message' => __('You must select an invoice when login any branch.') - ], 400); - } + $business = Business::findOrFail(auth()->user()->business_id); DB::beginTransaction(); try { - $branch_id = null; + $branch_id = $activeBranch?->id; $invoice = null; $payments = $request->payments ?? []; @@ -173,81 +188,104 @@ public function collectDueStore(Request $request) } $payDueAmount = collect($payments) - ->reject(fn($p) => strtolower($p['type']) === 'cheque') + ->reject(fn ($p) => strtolower($p['type']) === 'cheque') ->sum('amount'); - // Get related invoice if exists + $invoice_due = 0; + $opening_balance_due = 0; + + if ($activeBranch) { + if ($party->type == 'Supplier') { + $invoice_due = Purchase::where('party_id', $request->party_id) + ->where('branch_id', $activeBranch->id) + ->sum('dueAmount'); + } else { + $invoice_due = Sale::where('party_id', $request->party_id) + ->where('branch_id', $activeBranch->id) + ->sum('dueAmount'); + } + + if ($party->branch_id === $activeBranch->id && $party->opening_balance_type === 'due') { + $opening_balance_due = $party->opening_balance ?? 0; + } + } else { + if ($party->type == 'Supplier') { + $invoice_due = Purchase::where('party_id', $request->party_id)->sum('dueAmount'); + } else { + $invoice_due = Sale::where('party_id', $request->party_id)->sum('dueAmount'); + } + $opening_balance_due = $party->opening_balance_type === 'due' ? ($party->opening_balance ?? 0) : 0; + } + + $total_due = $invoice_due + $opening_balance_due; + + if ($payDueAmount > $total_due) { + return response()->json([ + 'message' => __('You can pay maximum '.$total_due.' due amount.'), + ], 400); + } + + if ($activeBranch && ! $request->invoiceNumber && $invoice_due > 0 && $opening_balance_due == 0) { + return response()->json([ + 'message' => __('You must select an invoice when login any branch.'), + ], 400); + } + if ($request->invoiceNumber) { if ($party->type == 'Supplier') { - $invoice = Purchase::where('invoiceNumber', $request->invoiceNumber)->where('party_id', $request->party_id)->first(); + $invoice = Purchase::where('invoiceNumber', $request->invoiceNumber) + ->where('party_id', $request->party_id) + ->where('branch_id', $activeBranch?->id) + ->first(); } else { - $invoice = Sale::where('invoiceNumber', $request->invoiceNumber)->where('party_id', $request->party_id)->first(); + $invoice = Sale::where('invoiceNumber', $request->invoiceNumber) + ->where('party_id', $request->party_id) + ->where('branch_id', $activeBranch?->id) + ->first(); } - if (!isset($invoice)) { + if (! isset($invoice)) { return response()->json([ - 'message' => 'Invoice Not Found.' + 'message' => 'Invoice Not Found.', ], 404); } - if (!auth()->user()->active_branch) { - if (isset($invoice) && isset($invoice->branch_id)) { - $branch_id = $invoice->branch_id; - } - } - if ($invoice->dueAmount < $request->payDueAmount) { return response()->json([ - 'message' => 'Invoice due is ' . $invoice->dueAmount . '. You can not pay more then the invoice due amount.' + 'message' => 'Invoice due is '.$invoice->dueAmount.'. You can not pay more then the invoice due amount.', ], 400); } + + $branch_id = $invoice->branch_id; } - // Validation for all invoices due - if (!$request->invoiceNumber) { - if ($party->type == 'Supplier') { - $all_invoice_due = Purchase::where('party_id', $request->party_id)->sum('dueAmount'); - } else { - $all_invoice_due = Sale::where('party_id', $request->party_id)->sum('dueAmount'); - } - - if (($all_invoice_due + $request->payDueAmount) > $party->due) { - return response()->json([ - 'message' => __('You can pay only ' . $party->due - $all_invoice_due . ', without selecting an invoice.') - ], 400); - } - - if ($party->opening_balance_type == 'due') { - $party->update([ - 'opening_balance' => max(0, $party->opening_balance - $payDueAmount), - ]); - } + if (! $request->invoiceNumber && $opening_balance_due > 0) { + $party->update([ + 'opening_balance' => max(0, $party->opening_balance - $payDueAmount), + ]); } $data = DueCollect::create($request->except('user_id', 'business_id', 'sale_id', 'purchase_id', 'totalDue', 'dueAmountAfterPay', 'paymentDate') + [ 'user_id' => auth()->id(), - 'business_id' => $business_id, - 'sale_id' => $party->type != 'Supplier' && isset($invoice) ? $invoice->id : NULL, - 'purchase_id' => $party->type == 'Supplier' && isset($invoice) ? $invoice->id : NULL, - 'totalDue' => isset($invoice) ? $invoice->dueAmount : $party->due, - 'dueAmountAfterPay'=> isset($invoice) ? ($invoice->dueAmount - $payDueAmount) : ($party->due - $payDueAmount), - 'paymentDate' => $request->paymentDate ? Carbon::parse($request->paymentDate)->setTimeFromTimeString(now()->format('H:i:s')) : now() - ]); + 'business_id' => $business->id, + 'sale_id' => $party->type != 'Supplier' && isset($invoice) ? $invoice->id : null, + 'purchase_id' => $party->type == 'Supplier' && isset($invoice) ? $invoice->id : null, + 'totalDue' => $total_due, + 'dueAmountAfterPay' => $total_due - $payDueAmount, + 'paymentDate' => $request->paymentDate ? Carbon::parse($request->paymentDate)->setTimeFromTimeString(now()->format('H:i:s')) : now(), + ]); - // Update invoice due if (isset($invoice)) { $invoice->update([ 'dueAmount' => $invoice->dueAmount - $payDueAmount, - 'paidAmount' => $invoice->paidAmount + $payDueAmount + 'paidAmount' => $invoice->paidAmount + $payDueAmount, ]); } - // Update party due $party->update([ - 'due' => $party->due - $payDueAmount + 'due' => $party->due - $payDueAmount, ]); - // update balance & adjust platform if ($party->type == 'Supplier') { updateBalance($payDueAmount, 'decrement', $branch_id); $platform = 'due_pay'; @@ -256,7 +294,6 @@ public function collectDueStore(Request $request) $platform = 'due_collect'; } - // MultiPaymentProcessed event event(new MultiPaymentProcessed( $payments, $data->id, @@ -265,45 +302,30 @@ public function collectDueStore(Request $request) $party->id, )); - // Notify - event(new DuePaymentReceived($data)); - sendNotifyToUser($data->id, route('business.dues.index', ['id' => $data->id]), __('Due Collection has been created.'), $business_id); + sendNotifyToUser($data->id, route('business.dues.index', ['id' => $data->id]), __('Due Collection has been created.'), $business->id); + + if (moduleCheck('MarketingAddon')) { + $this->dueTransactionMessage($business, $party, $data); + } DB::commit(); return response()->json([ 'message' => __('Collect Due saved successfully'), 'redirect' => route('business.dues.index'), - 'secondary_redirect_url' => route('business.collect.dues.invoice', $party->id), + 'secondary_redirect_url' => route('business.collect.dues.invoice', $data->id), ]); } catch (\Exception $e) { DB::rollBack(); + return response()->json(['message' => $e->getMessage()], 404); } } - public function getInvoice($id) + public function getInvoice($party_id, DueInvoiceService $service) { - $due_collect = DueCollect::with('user:id,name,role', 'party:id,name,email,phone,type', 'payment_type:id,name', 'business:id,companyName,address,phoneNumber,email,vat_name,vat_no,meta', ) - ->where('business_id', auth()->user()->business_id) - ->where('party_id', $id) - ->latest() - ->first(); - - $transactionTypes = $due_collect->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(', '); - - $party = Party::with('dueCollect.business')->find($id); - $bank_detail = PaymentType::where('business_id', auth()->user()->business_id)->where('show_in_invoice', 1)->latest()->first(); - - return view('business::dues.invoice', compact('due_collect', 'party', 'transactionTypes', 'bank_detail')); + $data = $service->getInvoiceData($party_id); + return view('business::dues.invoice', $data); } public function generatePDF($id) @@ -391,10 +413,10 @@ public function partyDue(Request $request) // Apply search $query->when($request->search, function ($q) use ($request) { $q->where(function ($q) use ($request) { - $q->where('email', 'like', '%' . $request->search . '%') - ->orWhere('name', 'like', '%' . $request->search . '%') - ->orWhere('phone', 'like', '%' . $request->search . '%') - ->orWhere('due', 'like', '%' . $request->search . '%'); + $q->where('email', 'like', '%'.$request->search.'%') + ->orWhere('name', 'like', '%'.$request->search.'%') + ->orWhere('phone', 'like', '%'.$request->search.'%') + ->orWhere('due', 'like', '%'.$request->search.'%'); }); }); @@ -402,36 +424,46 @@ public function partyDue(Request $request) $parties = $query->latest()->paginate($request->per_page ?? 20)->appends($request->query()); if ($activeBranch) { - // Calculate branch-wise due and replace $party->due, remove zero-due parties $parties->setCollection( $parties->getCollection() - ->transform(function ($party) { - $party->due = $party->type === 'Supplier' - ? $party->purchases_dues->sum('dueAmount') - : $party->sales_dues->sum('dueAmount'); + ->transform(function ($party) use ($activeBranch) { + $sale_dues = $party->type === 'Supplier' + ? $party->purchases_dues->where('branch_id', $activeBranch->id) + : $party->sales_dues->where('branch_id', $activeBranch->id); + + $party_due = $sale_dues->sum('dueAmount'); + + if ($party->branch_id === $activeBranch->id) { + $openingBalanceDue = $party->opening_balance_type === 'due' ? ($party->opening_balance ?? 0) : 0; + $party->due = $openingBalanceDue + $party_due; + } else { + $party->due = $party_due; + } + return $party; }) - ->filter(fn($party) => $party->due > 0) + ->filter(fn ($party) => $party->due > 0) ->values() ); } else { // Non-active branch: ensure only parties with due > 0 $parties->setCollection( $parties->getCollection() - ->filter(fn($party) => $party->due > 0) + ->filter(fn ($party) => $party->due > 0) ->values() ); } if ($request->ajax()) { return response()->json([ - 'data' => view('business::dues.party.datas', compact('parties'))->render() + 'data' => view('business::dues.party.datas', compact('parties'))->render(), ]); } return view('business::dues.party.index', compact('parties', 'party_type')); } + public function walk_dues(Request $request) { $walk_in_customers = Sale::where('business_id', auth()->user()->business_id) @@ -483,8 +515,8 @@ public function walkDuesGetInvoice(string $id) $due_collect = DueCollect::with('business:id,companyName,address,phoneNumber,email,vat_name,vat_no,meta', 'user:id,name,role', 'payment_type:id,name') ->where('business_id', auth()->user()->business_id) ->whereNull('party_id') - ->latest() - ->first(); + ->where('id', $id) // 🔥 FIX HERE + ->firstOrFail(); $transactionTypes = $due_collect->transactions ->map(function ($transaction) { @@ -496,7 +528,8 @@ public function walkDuesGetInvoice(string $id) ->unique() ->implode(', '); - $walk_in_customer = Sale::with('dueCollect.business')->find($id); + // $walk_in_customer = Sale::with('dueCollect.business')->find($id); + $walk_in_customer = Sale::with('dueCollect.business')->find($due_collect->sale_id); $bank_detail = PaymentType::where('business_id', auth()->user()->business_id)->where('show_in_invoice', 1)->latest()->first(); @@ -527,7 +560,8 @@ public function collectWalkDueStore(Request $request) DB::beginTransaction(); try { $branch_id = null; - $invoice = Sale::where('invoiceNumber', $request->invoiceNumber)->whereNull('party_id')->first(); + $invoice = Sale::where('business_id', $business_id)->where('invoiceNumber', $request->invoiceNumber)->whereNull('party_id')->first(); + if (!$invoice) { return response()->json([ 'message' => 'Invoice Not Found.' @@ -565,23 +599,23 @@ public function collectWalkDueStore(Request $request) 'sale_id' => $invoice->id, 'invoiceNumber' => $request->invoiceNumber, 'totalDue' => $invoice->dueAmount, - 'dueAmountAfterPay' => $invoice->dueAmount - $request->payDueAmount, - 'payDueAmount' => $request->payDueAmount, + 'dueAmountAfterPay' => $invoice->dueAmount - $payDueAmount, + 'payDueAmount' => $payDueAmount, 'paymentDate' => $request->paymentDate, ]); $invoice->update([ - 'dueAmount' => $invoice->dueAmount - $request->payDueAmount + 'dueAmount' => $invoice->dueAmount - $payDueAmount ]); - updateBalance($request->payDueAmount, 'increment', $branch_id); + updateBalance($payDueAmount, 'increment', $branch_id); // MultiPaymentProcessed event event(new MultiPaymentProcessed( - $request->payments ?? [], + $payments, $data->id, 'due_collect', - $request->payDueAmount, + $payDueAmount, )); sendNotifyToUser($data->id, route('business.dues.index', ['id' => $data->id]), __('Due Collection has been created.'), $business_id); @@ -591,7 +625,7 @@ public function collectWalkDueStore(Request $request) return response()->json([ 'message' => __('Collect Due saved successfully'), 'redirect' => route('business.walk-dues.index'), - 'secondary_redirect_url' => route('business.collect.walk-dues.invoice', $invoice->id), + 'secondary_redirect_url' => route('business.collect.walk-dues.invoice', $data->id), ]); } catch (\Exception $e) { DB::rollBack(); @@ -660,4 +694,95 @@ public function viewDuePayment($id) ]); } } + + public function exportExcel() + { + return Excel::download(new ExportDueList, 'due-list.xlsx'); + } + + public function exportCsv() + { + return Excel::download(new ExportDueList, 'due-list.csv'); + } + + public function exportPdf() + { + $businessId = auth()->user()->business_id; + $activeBranch = auth()->user()->active_branch; + + $query = Party::where('business_id', $businessId) + ->with(['purchases_dues', 'sales_dues']); + + if (request('type')) { + $query->where('type', request('type')); + } + + if (request('search')) { + $search = request('search'); + + $query->where(function ($q) use ($search, $activeBranch) { + $q->where('type', 'like', "%$search%") + ->orWhere('name', 'like', "%$search%") + ->orWhere('phone', 'like', "%$search%") + ->orWhere('email', 'like', "%$search%"); + + if (!$activeBranch) { + $q->orWhere('due', 'like', "%$search%"); + } + }); + } + + $parties = $query->latest()->limit(request('per_page') ?? 20)->get(); + + if ($activeBranch) { + $parties = $parties->map(function ($party) use ($activeBranch) { + $sale_dues = $party->type === 'Supplier' + ? $party->purchases_dues->where('branch_id', $activeBranch->id) + : $party->sales_dues->where('branch_id', $activeBranch->id); + + $party_due = $sale_dues->sum('dueAmount'); + + if ($party->branch_id === $activeBranch->id) { + $openingBalanceDue = $party->opening_balance_type === 'due' ? ($party->opening_balance ?? 0) : 0; + $party->due = $openingBalanceDue + $party_due; + } else { + $party->due = $party_due; + } + + return $party; + })->filter(fn($p) => $p->due > 0)->values(); + } else { + $parties = $parties->filter(fn($p) => $p->due > 0)->values(); + } + + return PdfService::render('business::dues.pdf-export', compact('parties'),'due-list.pdf'); + } + + public function walkDueExportExcel() + { + return Excel::download(new ExportGuestDue, 'guest-due-list.xlsx'); + } + + public function walkDueExportCsv() + { + return Excel::download(new ExportGuestDue, 'guest-due-list.csv'); + } + + public function walkDueExportPdf(Request $request) + { + $walk_in_customers = Sale::where('business_id', auth()->user()->business_id) + ->with('dueCollect') + ->whereNull('party_id') + ->where('dueAmount', '>', 0) + ->when(request('search'), function ($query) { + $query->where(function ($q) { + $q->where('invoiceNumber', 'like', '%' . request('search') . '%') + ->orwhere('dueAmount', 'like', '%' . request('search') . '%'); + }); + }) + ->latest() + ->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::walk-dues.pdf', compact('walk_in_customers'),'guest-due-list.pdf'); + } } diff --git a/Modules/Business/App/Http/Controllers/AcnooDueReportController.php b/Modules/Business/App/Http/Controllers/AcnooDueReportController.php index 3add4533..02e66c10 100644 --- a/Modules/Business/App/Http/Controllers/AcnooDueReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooDueReportController.php @@ -32,79 +32,56 @@ public function index(Request $request) ->orWhere('credit_limit', 'like', '%' . $request->search . '%'); }); }) - ->when(request('type'), function ($q) use ($request) { - $q->where(function ($q) use ($request) { - $q->where('type', $request->type); - }); + ->when($request->type, function ($q) use ($request) { + $q->where('type', $request->type); }) ->with('sales_dues') ->latest(); - if ($activeBranch) { - $query->whereHas('sales_dues', function ($q) use ($activeBranch) { - $q->where('branch_id', $activeBranch->id) - ->where('dueAmount', '>', 0); - }); - } else { - $query->where('due', '>', 0); - } - - $parties = $query->paginate($request->per_page ?? 20); + $parties = $query->paginate($request->per_page ?? 20)->appends($request->query()); if ($activeBranch) { $parties->setCollection( $parties->getCollection() ->transform(function ($customer) use ($activeBranch) { - $customer->due = $customer->sales_dues + $party_due = $customer->sales_dues ->where('branch_id', $activeBranch->id) ->sum('dueAmount'); + + if ($customer->branch_id === $activeBranch->id) { + $openingBalanceDue = $customer->opening_balance_type === 'due' + ? ($customer->opening_balance ?? 0) + : 0; + + $customer->due = $openingBalanceDue + $party_due; + } else { + + $customer->due = $party_due; + } + return $customer; }) ->filter(fn($customer) => $customer->due > 0) ->values() ); + } else { + $parties->setCollection( + $parties->getCollection() + ->filter(fn($customer) => ($customer->due ?? 0) > 0) + ->values() + ); } - $total_due = $parties->sum(fn($customer) => $customer->due); + $customer_total_due = $parties->getCollection()->sum('due'); if ($request->ajax()) { return response()->json([ - 'data' => view('business::reports.due.datas', compact('parties', 'total_due'))->render() + 'data' => view('business::reports.due.datas', compact('parties'))->render(), + 'customer_total_due' => currency_format($customer_total_due, currency: business_currency()) ]); } - if ($activeBranch) { - // Filter customers that have branch-specific due > 0 - $query->whereHas('sales_dues', function ($q) use ($activeBranch) { - $q->where('branch_id', $activeBranch->id) - ->where('dueAmount', '>', 0); - }); - } else { - // global due > 0 - $query->where('due', '>', 0); - } - - $parties = $query->paginate(20)->appends($request->query()); - - // Replace customer due with branch-specific due if active branch exists - if ($activeBranch) { - $parties->setCollection( - $parties->getCollection() - ->transform(function ($customer) use ($activeBranch) { - $customer->due = $customer->sales_dues - ->where('branch_id', $activeBranch->id) - ->sum('dueAmount'); - return $customer; - }) - ->filter(fn($customer) => $customer->due > 0) - ->values() - ); - } - - // Calculate total_due - $total_due = $parties->sum(fn($customer) => $customer->due); - - return view('business::reports.due.due-reports', compact('parties', 'total_due')); + return view('business::reports.due.due-reports', compact('parties', 'customer_total_due')); } public function exportExcel() @@ -117,37 +94,59 @@ public function exportCsv() return Excel::download(new ExportDue, 'customer-due.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { $user = auth()->user(); - $businessId = $user->business_id; $activeBranch = $user->active_branch; + $business_id = $user->business_id; - $query = Party::where('business_id', $businessId) + $query = Party::where('business_id', $business_id) ->where('type', '!=', 'Supplier') + ->when($request->search, function ($q) use ($request) { + $q->where(function ($q2) use ($request) { + $q2->where('type', 'like', '%' . $request->search . '%') + ->orWhere('name', 'like', '%' . $request->search . '%') + ->orWhere('phone', 'like', '%' . $request->search . '%') + ->orWhere('credit_limit', 'like', '%' . $request->search . '%'); + }); + }) + ->when($request->type, function ($q) use ($request) { + $q->where('type', $request->type); + }) ->with('sales_dues') ->latest(); - if ($activeBranch) { - $query->whereHas('sales_dues', function ($q) use ($activeBranch) { - $q->where('branch_id', $activeBranch->id) - ->where('dueAmount', '>', 0); - }); - } else { - $query->where('due', '>', 0); - } - $parties = $query->get(); if ($activeBranch) { - $parties->transform(function ($supplier) use ($activeBranch) { - $supplier->due = $supplier->sales_dues - ->where('branch_id', $activeBranch->id) - ->sum('dueAmount'); - return $supplier; - })->filter(fn($supplier) => $supplier->due > 0); + $parties = $parties + ->transform(function ($customer) use ($activeBranch) { + $party_due = $customer->sales_dues + ->where('branch_id', $activeBranch->id) + ->sum('dueAmount'); + + if ($customer->branch_id === $activeBranch->id) { + $openingBalanceDue = $customer->opening_balance_type === 'due' + ? ($customer->opening_balance ?? 0) + : 0; + + $customer->due = $openingBalanceDue + $party_due; + } else { + + $customer->due = $party_due; + } + return $customer; + }) + ->filter(fn($customer) => $customer->due > 0) + ->take($request->per_page ?? 20) + ->values(); + } else { + $parties = $parties + ->filter(fn($customer) => ($customer->due ?? 0) > 0) + ->take($request->per_page ?? 20) + ->values(); } - return PdfService::render('business::reports.due.pdf', compact('parties'),'customer-due-report.pdf'); + return PdfService::render('business::reports.due.pdf', compact('parties'), 'customer-due-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooEstimateController.php b/Modules/Business/App/Http/Controllers/AcnooEstimateController.php new file mode 100644 index 00000000..08266a57 --- /dev/null +++ b/Modules/Business/App/Http/Controllers/AcnooEstimateController.php @@ -0,0 +1,25 @@ +user()->business_id; - $total_expense = Expense::where('business_id', $businessId) - ->whereDate('expenseDate', Carbon::today()->format('Y-m-d')) - ->sum('amount'); - $expenseQuery = Expense::with('category:id,categoryName', 'payment_type:id,name', 'branch:id,name', 'transactions') ->where('business_id', $businessId); @@ -90,10 +85,40 @@ public function exportCsv() public function exportPdf(Request $request) { - $expense_reports = Expense::with('category:id,categoryName', 'payment_type:id,name', 'branch:id,name', 'transactions')->where('business_id', auth()->user()->business_id) - ->latest() - ->get(); + $expenseQuery = Expense::with('category:id,categoryName', 'payment_type:id,name', 'branch:id,name', 'transactions') + ->where('business_id', auth()->user()->business_id); - return PdfService::render('business::reports.expense.pdf', compact('expense_reports'),'expenses-report.pdf'); + $expenseQuery->when($request->branch_id, function ($q) use ($request) { + $q->where('branch_id', $request->branch_id); + }); + + // Date Filter + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + $this->applyDateFilter($expenseQuery, $duration, 'expenseDate', $request->from_date, $request->to_date); + + // Search Filter + if ($request->filled('search')) { + $expenseQuery->where(function ($query) use ($request) { + $query->where('expanseFor', 'like', '%' . $request->search . '%') + ->orWhere('paymentType', 'like', '%' . $request->search . '%') + ->orWhere('referenceNo', 'like', '%' . $request->search . '%') + ->orWhere('amount', 'like', '%' . $request->search . '%') + ->orWhereHas('category', function ($q) use ($request) { + $q->where('categoryName', '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 . '%'); + }); + }); + } + + $expense_reports = $expenseQuery->latest()->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::reports.expense.pdf', compact('expense_reports', 'duration', 'filter_from_date', 'filter_to_date'),'expenses-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooExpireProductController.php b/Modules/Business/App/Http/Controllers/AcnooExpireProductController.php index d933f17a..60415547 100644 --- a/Modules/Business/App/Http/Controllers/AcnooExpireProductController.php +++ b/Modules/Business/App/Http/Controllers/AcnooExpireProductController.php @@ -5,6 +5,7 @@ use App\Models\Product; use Illuminate\Http\Request; use App\Http\Controllers\Controller; +use App\Services\PdfService; use Maatwebsite\Excel\Facades\Excel; use Modules\Business\App\Exports\ExportExpiredProduct; @@ -64,4 +65,38 @@ public function exportCsv() { return Excel::download(new ExportExpiredProduct, 'expired-products.csv'); } + + public function exportPdf(Request $request) + { + $expired_products = Product::with('unit:id,unitName', 'brand:id,brandName', 'category:id,categoryName', 'stocks') + ->where('business_id', auth()->user()->business_id) + ->withSum('stocks as total_stock', 'productStock') + ->whereHas('stocks', function ($query) { + $query->whereDate('expire_date', '<', today()) + ->where('productStock', '>', 0); + }) + ->when($request->search, function ($q) use ($request) { + $q->where(function ($q) use ($request) { + $q->where('type', 'like', '%' . $request->search . '%') + ->orWhere('productName', 'like', '%' . $request->search . '%') + ->orWhere('productCode', 'like', '%' . $request->search . '%') + ->orWhere('hsn_code', 'like', '%' . $request->search . '%') + ->orWhere('productSalePrice', 'like', '%' . $request->search . '%') + ->orWhere('productPurchasePrice', 'like', '%' . $request->search . '%') + ->orWhereHas('unit', function ($q) use ($request) { + $q->where('unitName', 'like', '%' . $request->search . '%'); + }) + ->orWhereHas('brand', function ($q) use ($request) { + $q->where('brandName', 'like', '%' . $request->search . '%'); + }) + ->orWhereHas('category', function ($q) use ($request) { + $q->where('categoryName', 'like', '%' . $request->search . '%'); + }); + }); + }) + ->latest() + ->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::expired-products.pdf', compact('expired_products'),'expired-products.pdf'); + } } diff --git a/Modules/Business/App/Http/Controllers/AcnooExpireProductReportController.php b/Modules/Business/App/Http/Controllers/AcnooExpireProductReportController.php index 975884dc..d631f2af 100644 --- a/Modules/Business/App/Http/Controllers/AcnooExpireProductReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooExpireProductReportController.php @@ -80,17 +80,46 @@ public function exportCsv() return Excel::download(new ExportExpiredProductReport, 'expired-product-reports.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { - $expired_products = Product::with('unit:id,unitName', 'brand:id,brandName', 'category:id,categoryName', 'stocks') + $businessId = auth()->user()->business_id; + $expiredProductsQuery = Product::with(['unit:id,unitName', 'brand:id,brandName', 'category:id,categoryName', 'stocks']) ->withSum('stocks', 'productStock') - ->where('business_id', auth()->user()->business_id) + ->where('business_id', $businessId) ->whereHas('stocks', function ($query) { - $query->whereDate('expire_date', '<', today())->where('productStock', '>', 0); - }) - ->latest() - ->get(); + $query->whereDate('expire_date', '<=', today())->where('productStock', '>', 0); + }); - return PdfService::render('business::reports.expired-products.pdf', compact('expired_products'),'expired-product-report.pdf'); + // Date Filter + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + $expiredProductsQuery->whereHas('stocks', function ($q) use ($duration, $request) { + $this->applyDateFilter($q, $duration, 'expire_date', $request->from_date, $request->to_date); + }); + + // Search Filter + if ($request->filled('search')) { + $search = $request->search; + $expiredProductsQuery->where(function ($query) use ($search) { + $query->where('productName', 'like', '%' . $search . '%') + ->orWhere('productCode', 'like', '%' . $search . '%') + ->orWhere('productPurchasePrice', 'like', '%' . $search . '%') + ->orWhere('productSalePrice', 'like', '%' . $search . '%') + ->orWhereHas('category', function ($q) use ($search) { + $q->where('categoryName', 'like', '%' . $search . '%'); + }) + ->orWhereHas('brand', function ($q) use ($search) { + $q->where('brandName', 'like', '%' . $search . '%'); + }) + ->orWhereHas('unit', function ($q) use ($search) { + $q->where('unitName', 'like', '%' . $search . '%'); + }); + }); + } + + $expired_products = $expiredProductsQuery->latest()->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::reports.expired-products.pdf', compact('expired_products', 'duration', 'filter_from_date', 'filter_to_date'),'expired-product-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooGetStateController.php b/Modules/Business/App/Http/Controllers/AcnooGetStateController.php new file mode 100644 index 00000000..16643031 --- /dev/null +++ b/Modules/Business/App/Http/Controllers/AcnooGetStateController.php @@ -0,0 +1,20 @@ +country_id)->where('status', 1)->orderBy('name')->get(['id', 'name']); + + return response()->json([ + 'status' => true, + 'states' => $states + ]); + } +} diff --git a/Modules/Business/App/Http/Controllers/AcnooIncomeReportController.php b/Modules/Business/App/Http/Controllers/AcnooIncomeReportController.php index 8a2199c5..7ecc20ba 100644 --- a/Modules/Business/App/Http/Controllers/AcnooIncomeReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooIncomeReportController.php @@ -83,10 +83,36 @@ public function exportCsv() public function exportPdf(Request $request) { - $income_reports = Income::with('category:id,categoryName', 'payment_type:id,name', 'branch:id,name', 'transactions')->where('business_id', auth()->user()->business_id) - ->latest() - ->get(); + $incomeQuery = Income::with('category:id,categoryName', 'payment_type:id,name', 'branch:id,name', 'transactions') + ->where('business_id', auth()->user()->business_id); - return PdfService::render('business::reports.income.pdf', compact('income_reports'),'incomes-report.pdf'); + // Branch filter + if ($request->branch_id) { + $incomeQuery->where('branch_id', $request->branch_id); + } + + // Date Filter + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + $this->applyDateFilter($incomeQuery, $duration, 'incomeDate', $request->from_date, $request->to_date); + + // Search filter + if ($request->filled('search')) { + $search = $request->search; + $incomeQuery->where(function ($query) use ($search) { + $query->where('incomeFor', 'like', '%' . $search . '%') + ->orWhere('paymentType', 'like', '%' . $search . '%') + ->orWhere('amount', 'like', '%' . $search . '%') + ->orWhere('referenceNo', 'like', '%' . $search . '%') + ->orWhereHas('category', fn($q) => $q->where('categoryName', 'like', '%' . $search . '%')) + ->orWhereHas('payment_type', fn($q) => $q->where('name', 'like', '%' . $search . '%')) + ->orWhereHas('branch', fn($q) => $q->where('name', 'like', '%' . $search . '%')); + }); + } + + $income_reports = $incomeQuery->latest()->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::reports.income.pdf', compact('income_reports', 'duration', 'filter_from_date', 'filter_to_date'),'incomes-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooLossProfitHistoryController.php b/Modules/Business/App/Http/Controllers/AcnooLossProfitHistoryController.php index 6449ae7f..99cd35b6 100644 --- a/Modules/Business/App/Http/Controllers/AcnooLossProfitHistoryController.php +++ b/Modules/Business/App/Http/Controllers/AcnooLossProfitHistoryController.php @@ -7,17 +7,19 @@ use Illuminate\Support\Facades\DB; use App\Http\Controllers\Controller; use App\Services\PdfService; +use App\Traits\DateRangeTrait; use Maatwebsite\Excel\Facades\Excel; use Modules\Business\App\Exports\ExportLossProfitHistory; class AcnooLossProfitHistoryController extends Controller { - use DateFilterTrait; + use DateFilterTrait, DateRangeTrait; public function index(Request $request) { $user = auth()->user(); $businessId = $user->business_id; + $perPage = request('per_page') ?? 20; $branchId = null; if (moduleCheck('MultiBranchAddon')) { @@ -25,25 +27,42 @@ public function index(Request $request) } $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); $salesQuery = DB::table('sales') ->select( DB::raw('"saleDate"::date as date'), + DB::raw(" + CASE + WHEN \"lossProfit\" >= 0 THEN 'positive' + ELSE 'negative' + END as profit_type + "), DB::raw('SUM(actual_total_amount) as total_sales'), DB::raw('SUM("lossProfit") as total_sale_income') ) ->where('business_id', $businessId) ->when($branchId, fn ($q) => - $q->where('branch_id', $branchId) + $q->where('branch_id', $branchId) ) - ->groupBy(DB::raw('"saleDate"::date')); + ->groupBy( + DB::raw('"saleDate"::date'), + DB::raw(" + CASE + WHEN \"lossProfit\" >= 0 THEN 'positive' + ELSE 'negative' + END + ") + ); $this->applyDateFilter($salesQuery, $duration, 'saleDate', $request->from_date, $request->to_date); - $dailySales = $salesQuery->get(); + + $dailySales = $salesQuery->limit($perPage)->get(); $sale_datas = $dailySales->map(fn ($sale) => (object)[ 'type' => 'Sale', 'date' => $sale->date, + 'profit_type' => $sale->profit_type, 'total_sales' => $sale->total_sales, 'total_incomes' => $sale->total_sale_income, ]); @@ -60,7 +79,7 @@ public function index(Request $request) ->groupBy(DB::raw('"incomeDate"::date')); $this->applyDateFilter($incomeQuery, $duration, 'incomeDate', $request->from_date, $request->to_date); - $dailyIncomes = $incomeQuery->get(); + $dailyIncomes = $incomeQuery->limit($perPage)->get(); $income_datas = $dailyIncomes->map(fn ($income) => (object)[ 'type' => 'Income', @@ -80,7 +99,10 @@ public function index(Request $request) $mergedIncomeSaleData->push($income); } - if ($sale = $sale_datas->firstWhere('date', $date)) { + // multiple sale rows handle + $salesOfDate = $sale_datas->where('date', $date); + + foreach ($salesOfDate as $sale) { $mergedIncomeSaleData->push($sale); } } @@ -100,7 +122,7 @@ public function index(Request $request) ->groupBy(DB::raw('"date"::date')); $this->applyDateFilter($payrollQuery, $duration, 'date', $request->from_date, $request->to_date); - $dailyPayrolls = $payrollQuery->get(); + $dailyPayrolls = $payrollQuery->limit($perPage)->get(); } $expenseQuery = DB::table('expenses') @@ -115,7 +137,7 @@ public function index(Request $request) ->groupBy(DB::raw('"expenseDate"::date')); $this->applyDateFilter($expenseQuery, $duration, 'expenseDate', $request->from_date, $request->to_date); - $dailyExpenses = $expenseQuery->get(); + $dailyExpenses = $expenseQuery->limit($perPage)->get(); $mergedExpenseData = collect(); $allExpenseDates = $dailyExpenses->pluck('date') @@ -148,34 +170,6 @@ public function index(Request $request) $totalExpenses = $mergedExpenseData->sum('total_expenses'); $netProfit = $grossIncomeProfit - $totalExpenses; - $allTimeSales = DB::table('sales') - ->where('business_id', $businessId) - ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) - ->sum('actual_total_amount'); - - $allTimeIncomes = DB::table('incomes') - ->where('business_id', $businessId) - ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) - ->sum('amount'); - - $allTimePayrolls = moduleCheck('HrmAddon') - ? DB::table('payrolls') - ->where('business_id', $businessId) - ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) - ->sum('amount') - : 0; - - $allTimeExpensesOnly = DB::table('expenses') - ->where('business_id', $businessId) - ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) - ->sum('amount'); - - $allTimeSaleProfit = DB::table('sales')->where('business_id', $businessId)->when($branchId, fn ($q) => $q->where('branch_id', $branchId))->sum('lossProfit'); - - $cardGrossProfit = $allTimeIncomes + $allTimeSaleProfit; - $totalCardExpenses = $allTimePayrolls + $allTimeExpensesOnly; - $cardNetProfit = $cardGrossProfit - $totalCardExpenses; - return view('business::loss-profit-histories.index', compact( 'mergedIncomeSaleData', 'mergedExpenseData', @@ -183,9 +177,9 @@ public function index(Request $request) 'grossIncomeProfit', 'totalExpenses', 'netProfit', - 'cardGrossProfit', - 'totalCardExpenses', - 'cardNetProfit' + 'duration', + 'filter_from_date', + 'filter_to_date' )); } @@ -199,111 +193,158 @@ public function exportCsv() return Excel::download(new ExportLossProfitHistory, 'loss-profit-history.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { $user = auth()->user(); $businessId = $user->business_id; + $perPage = request('per_page') ?? 20; $branchId = null; if (moduleCheck('MultiBranchAddon')) { $branchId = $user->branch_id ?? $user->active_branch_id; } - // SALES - $dailySales = DB::table('sales') + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + $salesQuery = DB::table('sales') ->select( DB::raw('"saleDate"::date as date'), + DB::raw(" + CASE + WHEN \"lossProfit\" >= 0 THEN 'positive' + ELSE 'negative' + END as profit_type + "), DB::raw('SUM(actual_total_amount) as total_sales'), DB::raw('SUM("lossProfit") as total_sale_income') ) ->where('business_id', $businessId) - ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) - ->groupBy(DB::raw('"saleDate"::date')) - ->get(); + ->when($branchId, fn ($q) => + $q->where('branch_id', $branchId) + ) + ->groupBy( + DB::raw('"saleDate"::date'), + DB::raw(" + CASE + WHEN \"lossProfit\" >= 0 THEN 'positive' + ELSE 'negative' + END + ") + ); + + $this->applyDateFilter($salesQuery, $duration, 'saleDate', $request->from_date, $request->to_date); + + $dailySales = $salesQuery->limit($perPage)->get(); $sale_datas = $dailySales->map(fn ($sale) => (object)[ - 'type' => 'Sale', - 'date' => $sale->date, - 'total_sales' => $sale->total_sales, - 'total_incomes' => $sale->total_sale_income, + 'type' => 'Sale', + 'date' => $sale->date, + 'profit_type' => $sale->profit_type, + 'total_sales' => $sale->total_sales, + 'total_incomes' => $sale->total_sale_income, ]); - // INCOME - $dailyIncomes = DB::table('incomes') + $incomeQuery = DB::table('incomes') ->select( DB::raw('"incomeDate"::date as date'), DB::raw('SUM(amount) as total_incomes') ) ->where('business_id', $businessId) - ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) - ->groupBy(DB::raw('"incomeDate"::date')) - ->get(); + ->when($branchId, fn ($q) => + $q->where('branch_id', $branchId) + ) + ->groupBy(DB::raw('"incomeDate"::date')); + + $this->applyDateFilter($incomeQuery, $duration, 'incomeDate', $request->from_date, $request->to_date); + $dailyIncomes = $incomeQuery->limit($perPage)->get(); $income_datas = $dailyIncomes->map(fn ($income) => (object)[ - 'type' => 'Income', - 'date' => $income->date, + 'type' => 'Income', + 'date' => $income->date, 'total_incomes' => $income->total_incomes, ]); - // MERGE SALE + INCOME $mergedIncomeSaleData = collect(); - $allDates = $dailySales->pluck('date')->merge($dailyIncomes->pluck('date'))->unique()->sort(); + $allDates = $dailySales->pluck('date') + ->merge($dailyIncomes->pluck('date')) + ->unique() + ->sort(); + foreach ($allDates as $date) { + if ($income = $income_datas->firstWhere('date', $date)) { $mergedIncomeSaleData->push($income); } - if ($sale = $sale_datas->firstWhere('date', $date)) { + + // multiple sale rows handle + $salesOfDate = $sale_datas->where('date', $date); + + foreach ($salesOfDate as $sale) { $mergedIncomeSaleData->push($sale); } } - // PAYROLL $dailyPayrolls = collect(); + if (moduleCheck('HrmAddon')) { - $dailyPayrolls = DB::table('payrolls') + $payrollQuery = DB::table('payrolls') ->select( DB::raw('"date"::date as date'), DB::raw('SUM(amount) as total_payrolls') ) ->where('business_id', $businessId) - ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) - ->groupBy(DB::raw('"date"::date')) - ->get(); + ->when($branchId, fn ($q) => + $q->where('branch_id', $branchId) + ) + ->groupBy(DB::raw('"date"::date')); + + $this->applyDateFilter($payrollQuery, $duration, 'date', $request->from_date, $request->to_date); + $dailyPayrolls = $payrollQuery->limit($perPage)->get(); } - // EXPENSES - $dailyExpenses = DB::table('expenses') + $expenseQuery = DB::table('expenses') ->select( DB::raw('"expenseDate"::date as date'), DB::raw('SUM(amount) as total_expenses_only') ) ->where('business_id', $businessId) - ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) - ->groupBy(DB::raw('"expenseDate"::date')) - ->get(); + ->when($branchId, fn ($q) => + $q->where('branch_id', $branchId) + ) + ->groupBy(DB::raw('"expenseDate"::date')); + + $this->applyDateFilter($expenseQuery, $duration, 'expenseDate', $request->from_date, $request->to_date); + $dailyExpenses = $expenseQuery->limit($perPage)->get(); $mergedExpenseData = collect(); - $allExpenseDates = $dailyExpenses->pluck('date')->merge($dailyPayrolls->pluck('date'))->unique()->sort(); + $allExpenseDates = $dailyExpenses->pluck('date') + ->merge($dailyPayrolls->pluck('date')) + ->unique() + ->sort(); + foreach ($allExpenseDates as $date) { + if ($expense = $dailyExpenses->firstWhere('date', $date)) { $mergedExpenseData->push((object)[ - 'type' => 'Expense', - 'date' => $date, + 'type' => 'Expense', + 'date' => $date, 'total_expenses' => $expense->total_expenses_only, ]); } + if ($payroll = $dailyPayrolls->firstWhere('date', $date)) { $mergedExpenseData->push((object)[ - 'type' => 'Payroll', - 'date' => $date, + 'type' => 'Payroll', + 'date' => $date, 'total_expenses' => $payroll->total_payrolls, ]); } } - // SUMMARY $grossSaleProfit = $sale_datas->sum('total_sales'); $grossIncomeProfit = $income_datas->sum('total_incomes') + $sale_datas->sum('total_incomes'); + $totalExpenses = $mergedExpenseData->sum('total_expenses'); $netProfit = $grossIncomeProfit - $totalExpenses; @@ -316,7 +357,10 @@ public function exportPdf() 'grossSaleProfit', 'grossIncomeProfit', 'totalExpenses', - 'netProfit' + 'netProfit', + 'duration', + 'filter_to_date', + 'filter_from_date' ), 'loss-profit-history.pdf' ); diff --git a/Modules/Business/App/Http/Controllers/AcnooLossProfitHistoryReportController.php b/Modules/Business/App/Http/Controllers/AcnooLossProfitHistoryReportController.php index 84b56247..8164ccca 100644 --- a/Modules/Business/App/Http/Controllers/AcnooLossProfitHistoryReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooLossProfitHistoryReportController.php @@ -6,10 +6,11 @@ use App\Traits\DateFilterTrait; use Illuminate\Support\Facades\DB; use App\Http\Controllers\Controller; +use App\Traits\DateRangeTrait; class AcnooLossProfitHistoryReportController extends Controller { - use DateFilterTrait; + use DateFilterTrait, DateRangeTrait; public function index(Request $request) { @@ -22,25 +23,42 @@ public function index(Request $request) } $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); $salesQuery = DB::table('sales') ->select( DB::raw('"saleDate"::date as date'), + DB::raw(" + CASE + WHEN \"lossProfit\" >= 0 THEN 'positive' + ELSE 'negative' + END as profit_type + "), DB::raw('SUM(actual_total_amount) as total_sales'), DB::raw('SUM("lossProfit") as total_sale_income') ) ->where('business_id', $businessId) ->when($branchId, fn ($q) => - $q->where('branch_id', $branchId) + $q->where('branch_id', $branchId) ) - ->groupBy(DB::raw('"saleDate"::date')); + ->groupBy( + DB::raw('"saleDate"::date'), + DB::raw(" + CASE + WHEN \"lossProfit\" >= 0 THEN 'positive' + ELSE 'negative' + END + ") + ); $this->applyDateFilter($salesQuery, $duration, 'saleDate', $request->from_date, $request->to_date); + $dailySales = $salesQuery->get(); $sale_datas = $dailySales->map(fn ($sale) => (object)[ 'type' => 'Sale', 'date' => $sale->date, + 'profit_type' => $sale->profit_type, 'total_sales' => $sale->total_sales, 'total_incomes' => $sale->total_sale_income, ]); @@ -77,7 +95,10 @@ public function index(Request $request) $mergedIncomeSaleData->push($income); } - if ($sale = $sale_datas->firstWhere('date', $date)) { + // multiple sale rows handle + $salesOfDate = $sale_datas->where('date', $date); + + foreach ($salesOfDate as $sale) { $mergedIncomeSaleData->push($sale); } } @@ -87,14 +108,14 @@ public function index(Request $request) if (moduleCheck('HrmAddon')) { $payrollQuery = DB::table('payrolls') ->select( - DB::raw('DATE(date) as date'), + DB::raw('"date"::date as date'), DB::raw('SUM(amount) as total_payrolls') ) ->where('business_id', $businessId) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId) ) - ->groupBy(DB::raw('DATE(date)')); + ->groupBy(DB::raw('"date"::date')); $this->applyDateFilter($payrollQuery, $duration, 'date', $request->from_date, $request->to_date); $dailyPayrolls = $payrollQuery->get(); @@ -145,34 +166,6 @@ public function index(Request $request) $totalExpenses = $mergedExpenseData->sum('total_expenses'); $netProfit = $grossIncomeProfit - $totalExpenses; - $allTimeSales = DB::table('sales') - ->where('business_id', $businessId) - ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) - ->sum('actual_total_amount'); - - $allTimeIncomes = DB::table('incomes') - ->where('business_id', $businessId) - ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) - ->sum('amount'); - - $allTimePayrolls = moduleCheck('HrmAddon') - ? DB::table('payrolls') - ->where('business_id', $businessId) - ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) - ->sum('amount') - : 0; - - $allTimeExpensesOnly = DB::table('expenses') - ->where('business_id', $businessId) - ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) - ->sum('amount'); - - $allTimeSaleProfit = DB::table('sales')->where('business_id', $businessId)->when($branchId, fn ($q) => $q->where('branch_id', $branchId))->sum('lossProfit'); - - $cardGrossProfit = $allTimeIncomes + $allTimeSaleProfit; - $totalCardExpenses = $allTimePayrolls + $allTimeExpensesOnly; - $cardNetProfit = $cardGrossProfit - $totalCardExpenses; - return view('business::loss-profit-histories.index', compact( 'mergedIncomeSaleData', 'mergedExpenseData', @@ -180,9 +173,9 @@ public function index(Request $request) 'grossIncomeProfit', 'totalExpenses', 'netProfit', - 'cardGrossProfit', - 'totalCardExpenses', - 'cardNetProfit' + 'duration', + 'filter_from_date', + 'filter_to_date' )); } diff --git a/Modules/Business/App/Http/Controllers/AcnooPartyController.php b/Modules/Business/App/Http/Controllers/AcnooPartyController.php index e0c04ea7..73e025fb 100644 --- a/Modules/Business/App/Http/Controllers/AcnooPartyController.php +++ b/Modules/Business/App/Http/Controllers/AcnooPartyController.php @@ -3,11 +3,18 @@ namespace Modules\Business\App\Http\Controllers; use App\Models\Party; +use App\Models\PaymentType; use App\Helpers\HasUploader; +use App\Imports\PartyImport; use Illuminate\Http\Request; -use App\Http\Controllers\Controller; -use Illuminate\Support\Facades\Storage; use Illuminate\Validation\Rule; +use Illuminate\Support\Facades\DB; +use App\Http\Controllers\Controller; +use Maatwebsite\Excel\Facades\Excel; +use App\Events\MultiPaymentProcessed; +// use App\Models\Country; +// use App\Models\State; +use Illuminate\Support\Facades\Storage; class AcnooPartyController extends Controller { @@ -30,16 +37,16 @@ public function index(Request $request) $party_type = $request->input('type'); $query = Party::where('business_id', $business_id) - ->when($search, function ($q) use ($search) { - $q->where(function ($q) use ($search) { - $q->where('name', 'like', '%' . $search . '%') - ->orWhere('credit_limit', 'like', '%' . $search . '%') - ->orWhere('phone', 'like', '%' . $search . '%') - ->orWhere('type', 'like', '%' . $search . '%') - ->orWhere('address', 'like', '%' . $search . '%') - ->orWhere('due', 'like', '%' . $search . '%'); - }); + ->when($search, function ($q) use ($search) { + $q->where(function ($q) use ($search) { + $q->where('name', 'like', '%'.$search.'%') + ->orWhere('credit_limit', 'like', '%'.$search.'%') + ->orWhere('phone', 'like', '%'.$search.'%') + ->orWhere('type', 'like', '%'.$search.'%') + ->orWhere('address', 'like', '%'.$search.'%') + ->orWhere('due', 'like', '%'.$search.'%'); }); + }); // Filter by party type if ($party_type === 'Customer') { @@ -54,11 +61,19 @@ public function index(Request $request) $parties->setCollection( $parties->getCollection() ->transform(function ($party) use ($activeBranch) { - $party_due = $party->type === 'Supplier' - ? $party->purchases_dues->sum('dueAmount') - : $party->sales_dues->sum('dueAmount'); + $originalDue = $party->getRawOriginal('due') ?? 0; + $originalOpeningBalance = $party->opening_balance ?? 0; - $party->due = $party->branch_id === $activeBranch->id ? $party_due + $party->due : $party_due; + $party_due = $party->type === 'Supplier' + ? $party->purchases_dues->where('branch_id', $activeBranch->id)->sum('dueAmount') + : $party->sales_dues->where('branch_id', $activeBranch->id)->sum('dueAmount'); + + if ($party->branch_id === $activeBranch->id) { + $openingBalanceDue = $party->opening_balance_type === 'due' ? $originalOpeningBalance : 0; + $party->due = $openingBalanceDue + $party_due; + } else { + $party->due = $party_due; + } return $party; }) @@ -67,16 +82,20 @@ public function index(Request $request) if ($request->ajax()) { return response()->json([ - 'data' => view('business::parties.datas', compact('parties'))->render() + 'data' => view('business::parties.datas', compact('parties'))->render(), ]); } return view('business::parties.index', compact('parties', 'party_type')); } + public function create() { - return view('business::parties.create'); + $countries = []; + $states = []; + + return view('business::parties.create', compact('countries', 'states')); } public function store(Request $request) @@ -88,13 +107,12 @@ public function store(Request $request) 'email' => 'nullable|email', 'image' => 'nullable|image|mimes:jpeg,png,jpg,svg', 'address' => 'nullable|string|max:255', + 'tax_no' => 'nullable|string|max:255', 'due' => 'nullable|numeric|min:0', 'billing_address' => 'nullable|array', 'billing_address.address' => 'nullable|string|max:255', 'billing_address.city' => 'nullable|string|max:255', - 'billing_address.state' => 'nullable|string|max:255', 'billing_address.zip_code' => 'nullable|string|max:20', - 'billing_address.country' => 'nullable|string|max:255', 'shipping_address' => 'nullable|array', 'shipping_address.address' => 'nullable|string|max:255', 'shipping_address.city' => 'nullable|string|max:255', @@ -127,7 +145,10 @@ public function store(Request $request) public function edit($id) { $party = Party::where('business_id', auth()->user()->business_id)->findOrFail($id); - return view('business::parties.edit', compact('party')); + $countries = []; + $states = []; + + return view('business::parties.edit', compact('party', 'countries', 'states')); } public function update(Request $request, $id) @@ -141,13 +162,12 @@ public function update(Request $request, $id) 'email' => 'nullable|email', 'image' => 'nullable|image|mimes:jpeg,png,jpg,svg', 'address' => 'nullable|string|max:255', + 'tax_no' => 'nullable|string|max:255', 'due' => 'nullable|numeric|min:0', 'billing_address' => 'nullable|array', 'billing_address.address' => 'nullable|string|max:255', 'billing_address.city' => 'nullable|string|max:255', - 'billing_address.state' => 'nullable|string|max:255', 'billing_address.zip_code' => 'nullable|string|max:20', - 'billing_address.country' => 'nullable|string|max:255', 'shipping_address' => 'nullable|array', 'shipping_address.address' => 'nullable|string|max:255', 'shipping_address.city' => 'nullable|string|max:255', @@ -210,6 +230,79 @@ public function update(Request $request, $id) ]); } + public function advancePayment(string $id) + { + $party = Party::where('business_id', auth()->user()->business_id)->with(['sales_dues', 'purchases_dues'])->findOrFail($id); + $payment_types = PaymentType::where('business_id', auth()->user()->business_id)->whereStatus(1)->latest()->get(); + + return view('business::parties.advance-payment', compact('party', 'payment_types')); + } + + public function advancePaymentStore(Request $request) + { + $request->validate([ + 'party_id' => 'required|exists:parties,id', + 'amount' => 'required|numeric|min:0.01', + 'date' => 'nullable|date', + 'note' => 'nullable|string|max:255', + ]); + + DB::beginTransaction(); + + try { + $party = Party::findOrFail($request->party_id); + $payments = $request->payments ?? []; + + if (isset($payments['main'])) { + $mainPayment = $payments['main']; + $mainPayment['amount'] = $request->amount ?? 0; + $payments = [$mainPayment]; + } + + // Only count actual received money (exclude cheque) + $payAmount = collect($payments) + ->reject(fn($p) => strtolower($p['type'] ?? '') === 'cheque') + ->sum(fn($p) => $p['amount'] ?? 0); + + if ($payAmount <= 0) { + return response()->json([ + 'message' => __('No valid payment amount received (cheques are pending).') + ], 400); + } + + // Determine platform based on party type + $platform = $party->type === 'Supplier' ? 'advance_pay' : 'advance_collect'; + + $party->increment('wallet', $payAmount); + + event(new MultiPaymentProcessed( + $payments, + $party->id, + $platform, + $payAmount, + $party->id, + $request->date ?? now(), + $request->note ?? null + )); + + DB::commit(); + + $type = in_array($party->type, ['Retailer', 'Dealer', 'Wholesaler']) ? 'Customer' : ($party->type === 'Supplier' ? 'Supplier' : ''); + $message = $type === 'Supplier' ? __('Advance amount paid successfully.') : __('Advance amount collected successfully.'); + + return response()->json([ + 'message' => $message, + 'redirect' => route('business.parties.index', ['type' => $type]) + ]); + + } catch (\Exception $e) { + DB::rollback(); + return response()->json([ + 'error' => 'Transaction failed: ' . $e->getMessage() + ], 500); + } + } + public function destroy($id) { $party = Party::findOrFail($id); @@ -266,4 +359,28 @@ public function deleteAll(Request $request) ]); } + public function bulkStore(Request $request, $type) + { + $request->validate([ + 'file' => 'required|file|mimes:xlsx,xls,csv' + ]); + + $businessId = auth()->user()->business_id; + $import = new PartyImport($businessId); + + try { + Excel::import($import, $request->file('file')); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 422); + } + + return response()->json([ + 'success' => true, + 'message' => __('Bulk upload successfully.'), + 'redirect' => route('business.parties.index', ['type' => $type]) + ]); + } } diff --git a/Modules/Business/App/Http/Controllers/AcnooPartyLossProfitController.php b/Modules/Business/App/Http/Controllers/AcnooPartyLossProfitController.php index 904b9189..c873fb1c 100644 --- a/Modules/Business/App/Http/Controllers/AcnooPartyLossProfitController.php +++ b/Modules/Business/App/Http/Controllers/AcnooPartyLossProfitController.php @@ -5,7 +5,6 @@ use Illuminate\Http\Request; use App\Http\Controllers\Controller; use App\Models\Party; -use App\Models\Sale; use App\Services\PdfService; use Maatwebsite\Excel\Facades\Excel; use Modules\Business\App\Exports\ExportPartyLossProfit; @@ -14,12 +13,6 @@ class AcnooPartyLossProfitController extends Controller { public function index(Request $request) { - $sale = Sale::where('business_id', auth()->user()->business_id)->whereNotNull('party_id')->get(); - - $totalAmount = $sale->sum('totalAmount'); - $totalProfit = $sale->where('lossProfit', '>', 0)->sum('lossProfit') ?? 0; - $totalLoss = $sale->where('lossProfit', '<', 0)->sum('lossProfit') ?? 0; - $parties = Party::with('sales') ->where('business_id', auth()->user()->business_id) ->where('type', '!=', 'Supplier') @@ -32,13 +25,20 @@ public function index(Request $request) ->paginate($request->per_page ?? 20) ->appends($request->query()); + $totalSaleAmount = $parties->sum(fn($party) => $party->sales?->sum('totalAmount') ?? 0); + $totalProfit = $parties->sum(fn($party) => $party->sales?->where('lossProfit', '>', 0)->sum('lossProfit') ?? 0); + $totalLoss = $parties->sum(fn($party) => $party->sales?->where('lossProfit', '<', 0)->sum('lossProfit') ?? 0); + if ($request->ajax()) { return response()->json([ - 'data' => view('business::party-reports.loss-profit.datas', compact('parties'))->render() + 'data' => view('business::party-reports.loss-profit.datas', compact('parties', 'totalSaleAmount', 'totalProfit', 'totalLoss'))->render(), + 'totalSaleAmount' => currency_format($totalSaleAmount, currency: business_currency()), + 'totalProfit' => currency_format($totalProfit, currency: business_currency()), + 'totalLoss' => currency_format($totalLoss, currency: business_currency()), ]); } - return view('business::party-reports.loss-profit.index', compact('parties', 'totalAmount', 'totalProfit', 'totalLoss')); + return view('business::party-reports.loss-profit.index', compact('parties', 'totalSaleAmount', 'totalProfit', 'totalLoss')); } public function exportExcel() @@ -51,18 +51,24 @@ public function exportCsv() return Excel::download(new ExportPartyLossProfit, 'party-loss-profit.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { $parties = Party::with('sales') ->where('business_id', auth()->user()->business_id) ->where('type', '!=', 'Supplier') + ->when($request->search, function ($query) use ($request) { + $query->where(function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%'); + }); + }) ->latest() + ->limit($request->per_page ?? 20) ->get(); return PdfService::render('business::party-reports.loss-profit.pdf', compact('parties'),'party-loss-profit-report.pdf'); } - public function view($id) + public function view(string $id) { $party = Party::with('sales.details', 'sales.details.product') ->where('id', $id) diff --git a/Modules/Business/App/Http/Controllers/AcnooProductController.php b/Modules/Business/App/Http/Controllers/AcnooProductController.php index 8e1a5d59..af3723b9 100644 --- a/Modules/Business/App/Http/Controllers/AcnooProductController.php +++ b/Modules/Business/App/Http/Controllers/AcnooProductController.php @@ -24,7 +24,6 @@ use Maatwebsite\Excel\Facades\Excel; use Illuminate\Support\Facades\Storage; use Modules\Business\App\Exports\ExportProduct; -use Modules\Business\App\Exports\ExportExpiredProduct; class AcnooProductController extends Controller { @@ -40,7 +39,7 @@ public function __construct() public function index(Request $request) { - $user = auth()->user(); + $user = auth()->user(); $search = $request->input('search'); $products = Product::query() @@ -66,13 +65,14 @@ public function index(Request $request) ->when($search, function ($q) use ($search) { $q->where(function ($q) use ($search) { $q->where('productName', 'like', "%{$search}%") - ->orWhere('productCode', 'like', "%{$search}%") - ->orWhere('productPurchasePrice', 'like', "%{$search}%") - ->orWhere('productSalePrice', 'like', "%{$search}%") - ->orWhere('product_type', 'like', "%{$search}%") - ->orWhereHas('category', fn($q) => $q->where('categoryName', 'like', "%{$search}%")) - ->orWhereHas('brand', fn($q) => $q->where('brandName', 'like', "%{$search}%")) - ->orWhereHas('unit', fn($q) => $q->where('unitName', 'like', "%{$search}%")); + ->orWhere('productCode', 'like', "%{$search}%") + ->orWhere('hsn_code', 'like', "%{$search}%") + ->orWhere('productPurchasePrice', 'like', "%{$search}%") + ->orWhere('productSalePrice', 'like', "%{$search}%") + ->orWhere('product_type', 'like', "%{$search}%") + ->orWhereHas('category', fn ($q) => $q->where('categoryName', 'like', "%{$search}%")) + ->orWhereHas('brand', fn ($q) => $q->where('brandName', 'like', "%{$search}%")) + ->orWhereHas('unit', fn ($q) => $q->where('unitName', 'like', "%{$search}%")); }); }) ->latest() @@ -82,22 +82,22 @@ public function index(Request $request) $products->getCollection()->transform(function ($product) { if ($product->product_type === 'combo') { $product->total_stock = $product->combo_products->sum( - fn($combo) => $combo->stock?->productStock ?? 0 + fn ($combo) => $combo->stock?->productStock ?? 0 ); $product->total_cost = $product->combo_products->sum( - fn($combo) => ($combo->quantity ?? 0) * ($combo->purchase_price ?? 0) + fn ($combo) => ($combo->quantity ?? 0) * ($combo->purchase_price ?? 0) ); $product->combo_items = $product->combo_products->map(function ($combo) { return [ - 'name' => $combo->stock?->product?->productName ?? 'N/A', - 'quantity' => $combo->quantity ?? 0, + 'name' => $combo->stock?->product?->productName ?? 'N/A', + 'quantity' => $combo->quantity ?? 0, 'purchase_price' => currency_format( ($combo->purchase_price ?? 0) * ($combo->quantity ?? 0), currency: business_currency() ), - 'stock' => $combo->stock?->productStock ?? 0, + 'stock' => $combo->stock?->productStock ?? 0, ]; }); } @@ -114,7 +114,6 @@ public function index(Request $request) return view('business::products.index', compact('products')); } - public function create() { $business_id = auth()->user()->business_id; @@ -122,7 +121,7 @@ public function create() $brands = Brand::where('business_id', $business_id)->whereStatus(1)->latest()->get(); $units = Unit::where('business_id', $business_id)->whereStatus(1)->latest()->get(); $product_id = (Product::where('business_id', $business_id)->count() ?? 0) + 1; - $vats = Vat::where('business_id', $business_id)->latest()->get(); + $vats = Vat::whereStatus(1)->where('business_id', $business_id)->latest()->get(); $code = str_pad($product_id, 4, '0', STR_PAD_LEFT); $product_models = ProductModel::where('business_id', $business_id)->latest()->get(); $warehouses = Warehouse::where('business_id', $business_id)->latest()->get(); @@ -130,9 +129,10 @@ public function create() $racks = Rack::where('business_id', $business_id)->latest()->get(); $shelves = Shelf::where('business_id', $business_id)->latest()->get(); $profit_option = Option::where('key', 'business-settings') - ->where('value', 'LIKE', '%"business_id":%' . $business_id . '%') + ->where('value', 'LIKE', '%"business_id":' . $business_id . '%') ->get() - ->firstWhere('value.business_id', $business_id)['product_profit_option'] ?? ''; + ->firstWhere('value.business_id', $business_id) + ->value['product_profit_option'] ?? ''; return view('business::products.create', compact('categories', 'brands', 'units', 'code', 'vats', 'product_models', 'warehouses', 'racks', 'shelves', 'profit_option', 'variations')); } @@ -159,6 +159,7 @@ public function store(Request $request) return $query->where('business_id', $business_id); }), ], + 'hsn_code' => 'nullable|string|max:255', 'alert_qty' => 'nullable|numeric|min:0', 'size' => 'nullable|string|max:255', 'type' => 'nullable|string|max:255', @@ -206,13 +207,21 @@ function ($attribute, $value, $fail) use ($request) { $vat = Vat::find($request->vat_id); $vat_rate = $vat->rate ?? 0; + $combo_tax_inclusive = $request->productSalePrice ?? 0; + $combo_tax_exclusive = $request->productSalePrice ?? 0; + + if ($request->vat_id && $request->product_type === 'combo') { + $combo_tax_inclusive = number_format($combo_tax_exclusive * (1 + ($vat_rate / 100)), 3, '.', ''); + } + // Create the product - $product = Product::create($request->only('productName', 'unit_id', 'brand_id', 'vat_id', 'vat_type', 'category_id', 'productCode', 'product_type', 'rack_id', 'shelf_id', 'model_id', 'variation_ids', 'warranty_guarantee_info') + [ + $product = Product::create($request->only('productName', 'unit_id', 'brand_id', 'vat_id', 'vat_type', 'category_id', 'productCode', 'hsn_code', 'product_type', 'rack_id', 'shelf_id', 'model_id', 'variation_ids', 'warranty_guarantee_info') + [ 'business_id' => $business_id, 'alert_qty' => $request->alert_qty ?? 0, - 'is_displayed_in_pos' => $request->has('is_displayed_in_pos') ? 1 : 0, + 'has_serial' => $request->product_type == 'combo' ? 0 : ($request->has_serial ?? 0), 'profit_percent' => $request->product_type == 'combo' ? $request->profit_percent ?? 0 : 0, - 'productSalePrice' => $request->product_type == 'combo' ? $request->productSalePrice ?? 0 : 0, + 'productSalePrice' => $request->product_type == 'combo' ? $combo_tax_inclusive : 0, // inclusive sales price + 'price_without_tax' => $request->product_type == 'combo' ? $combo_tax_exclusive : 0, 'productPicture' => $request->productPicture ? $this->upload($request, 'productPicture') : NULL, ]); @@ -220,18 +229,15 @@ function ($attribute, $value, $fail) use ($request) { if (in_array($request->product_type, ['single', 'variant']) && !empty($request->stocks)) { $stockData = []; foreach ($request->stocks as $stock) { - $base_price = $stock['exclusive_price'] ?? 0; - $purchasePrice = $request->vat_type === 'inclusive' - ? $base_price + ($base_price * $vat_rate / 100) - : $base_price; $stockData[] = [ 'business_id' => $business_id, 'product_id' => $product->id, + 'exclusive_price' => $stock['exclusive_price'] ?? 0, 'batch_no' => $stock['batch_no'] ?? null, 'warehouse_id' => $stock['warehouse_id'] ?? null, 'productStock' => $stock['productStock'] ?? 0, - 'productPurchasePrice' => $purchasePrice, + 'productPurchasePrice' => $stock['inclusive_price'] ?? 0, // inclusive purchase price / net cost price 'profit_percent' => $stock['profit_percent'] ?? 0, 'productSalePrice' => $stock['productSalePrice'] ?? 0, 'productWholeSalePrice' => $stock['productWholeSalePrice'] ?? 0, @@ -281,16 +287,17 @@ public function edit($id) $categories = Category::where('business_id', $business_id)->whereStatus(1)->latest()->get(); $brands = Brand::where('business_id', $business_id)->whereStatus(1)->latest()->get(); $units = Unit::where('business_id', $business_id)->whereStatus(1)->latest()->get(); - $vats = Vat::where('business_id', $business_id)->latest()->get(); + $vats = Vat::whereStatus(1)->where('business_id', $business_id)->latest()->get(); $product_models = ProductModel::where('business_id', $business_id)->latest()->get(); $warehouses = Warehouse::where('business_id', $business_id)->latest()->get(); $racks = Rack::where('business_id', $business_id)->latest()->get(); $shelves = Shelf::where('business_id', $business_id)->latest()->get(); $variations = Variation::where('business_id', auth()->user()->business_id)->where('status', 1)->get(); $profit_option = Option::where('key', 'business-settings') - ->where('value', 'LIKE', '%"business_id":%' . $business_id . '%') + ->where('value', 'LIKE', '%"business_id":' . $business_id . '%') ->get() - ->firstWhere('value.business_id', $business_id)['product_profit_option'] ?? ''; + ->firstWhere('value.business_id', $business_id) + ->value['product_profit_option'] ?? ''; $product = Product::with([ 'stocks' => function ($query) { @@ -334,6 +341,7 @@ public function update(Request $request, $id) return $query->where('business_id', $business_id); }), ], + 'hsn_code' => 'nullable|string|max:255', 'alert_qty' => 'nullable|numeric|min:0', 'size' => 'nullable|string|max:255', 'type' => 'nullable|string|max:255', @@ -381,12 +389,21 @@ function ($attribute, $value, $fail) use ($request) { $vat = Vat::find($request->vat_id); $vat_rate = $vat->rate ?? 0; + $combo_tax_exclusive = $request->productSalePrice ?? 0; + $combo_tax_inclusive = $request->productSalePrice ?? 0; + + if ($request->vat_id && $request->product_type === 'combo') { + $combo_tax_inclusive = number_format($combo_tax_exclusive * (1 + ($vat_rate / 100)), 3, '.', ''); + } + // Update product - $product->update($request->except(['productPicture', 'productPurchasePrice', 'productDealerPrice', 'productWholeSalePrice', 'alert_qty', 'stocks', 'vat_amount', 'profit_percent', 'is_displayed_in_pos']) + [ + $product->update($request->except(['productPicture', 'productPurchasePrice', 'productDealerPrice', 'productWholeSalePrice', 'alert_qty', 'stocks', 'vat_amount', 'profit_percent', 'productSalePrice', 'price_without_tax']) + [ 'business_id' => $business_id, 'alert_qty' => $request->alert_qty ?? 0, - 'is_displayed_in_pos' => $request->has('is_displayed_in_pos') ? 1 : 0, - 'profit_percent' => $request->profit_percent ?? 0, + 'has_serial' => $request->product_type == 'combo' ? 0 : ($request->has_serial ?? 0), + 'profit_percent' => $request->product_type == 'combo' ? $request->profit_percent ?? 0 : 0, + 'productSalePrice' => $request->product_type == 'combo' ? $combo_tax_inclusive : 0, // inclusive sales price + 'price_without_tax' => $request->product_type == 'combo' ? $combo_tax_exclusive : 0, 'productPicture' => $request->productPicture ? $this->upload($request, 'productPicture', $product->productPicture) : $product->productPicture, ]); @@ -406,22 +423,16 @@ function ($attribute, $value, $fail) use ($request) { // Insert or Update foreach ($request->stocks as $stock) { - $stockId = $stock['stock_id'] ?? null; - // Recalculate price - $base_price = $stock['exclusive_price'] ?? 0; - $purchasePrice = $request->vat_type === 'inclusive' - ? $base_price + ($base_price * $vat_rate / 100) - : $base_price; - $payload = [ 'business_id' => $business_id, 'product_id' => $product->id, 'batch_no' => $stock['batch_no'] ?? null, 'warehouse_id' => $stock['warehouse_id'] ?? null, 'productStock' => $stock['productStock'] ?? 0, - 'productPurchasePrice' => $purchasePrice, + 'exclusive_price' => $stock['exclusive_price'] ?? 0, + 'productPurchasePrice' =>$stock['inclusive_price'] ?? 0, 'profit_percent' => $stock['profit_percent'] ?? 0, 'productSalePrice' => $stock['productSalePrice'] ?? 0, 'productWholeSalePrice' => $stock['productWholeSalePrice'] ?? 0, @@ -431,7 +442,7 @@ function ($attribute, $value, $fail) use ($request) { 'variation_data' => $stock['variation_data'] ?? null, 'variant_name' => $stock['variant_name'] ?? null, 'branch_id' => auth()->user()->branch_id ?? auth()->user()->active_branch_id, - 'serial_numbers' => $request->has_serial ? $stock['serial_numbers'] : null, + 'serial_numbers' => $request->has_serial ? $stock['serial_numbers'] ?? null : null, ]; if ($stockId) { @@ -463,13 +474,12 @@ function ($attribute, $value, $fail) use ($request) { } catch (\Exception $e) { DB::rollback(); return response()->json([ - 'message' => __('Something went wrong.'), - 'error' => $e->getMessage(), + 'message' => $e->getMessage(), ], 406); } } - public function destroy($id) + public function destroy(string $id) { $product = Product::findOrFail($id); if (file_exists($product->productPicture)) { @@ -494,33 +504,47 @@ public function deleteAll(Request $request) } Product::whereIn('id', $request->ids)->delete(); return response()->json([ - 'message' => __('Selected product deleted successfully'), - 'redirect' => route('business.products.index') + 'message' => __('Selected product deleted successfully'), + 'redirect' => route('business.products.index') ]); } public function getAllProduct() { + $isTransfer = request()->is_transfer == 1; + $products = Product::with([ - 'stocks' => function ($query) { + 'stocks' => function ($query) use ($isTransfer) { $query->where('productStock', '>', 0); + if (!$isTransfer) { + $query->where(function ($q) { + $q->whereNull('serial_numbers') + ->orWhereJsonLength('serial_numbers', 0); + }); + } }, 'category:id,categoryName', 'unit:id,unitName', - 'stocks.warehouse:id,name', - 'vat:id,rate' + 'stocks.warehouse:id,name' ]) ->where('business_id', auth()->user()->business_id) - ->withSum('stocks as total_stock', 'productStock') + ->withSum(['stocks as total_stock' => function ($query) use ($isTransfer) { + $query->where('productStock', '>', 0); + if (!$isTransfer) { + $query->where(function ($q) { + $q->whereNull('serial_numbers') + ->orWhereJsonLength('serial_numbers', 0); + }); + } + }], 'productStock') + ->having('total_stock', '>', 0) ->latest() - ->get() - ->where('total_stock', '>', 0) - ->values(); + ->get(); return response()->json($products); } - public function getByCategory($category_id) + public function getByCategory(string $category_id) { $products = Product::where('business_id', auth()->user()->business_id)->where('category_id', $category_id)->get(); return response()->json($products); @@ -536,15 +560,63 @@ public function exportCsv() return Excel::download(new ExportProduct, 'product.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { - $products = Product::with('unit:id,unitName', 'brand:id,brandName', 'category:id,categoryName') - ->where('business_id', auth()->user()->business_id) + $user = auth()->user(); + $search = $request->input('search'); + + $products = Product::query() + ->where('business_id', $user->business_id) + ->with([ + 'stocks', + 'unit:id,unitName', + 'brand:id,brandName', + 'category:id,categoryName', + 'warehouse:id,name', + 'rack:id,name', + 'shelf:id,name', + 'combo_products.stock.product:id,productName' + ]) ->withSum('stocks as total_stock', 'productStock') + ->where(function ($query) { + $query->where('product_type', '!=', 'combo') + ->orWhere(function ($q) { + $q->where('product_type', 'combo') + ->whereHas('combo_products'); + }); + }) + ->when($search, function ($q) use ($search) { + $q->where(function ($q) use ($search) { + $q->where('productName', 'like', "%{$search}%") + ->orWhere('productCode', 'like', "%{$search}%") + ->orWhere('hsn_code', 'like', "%{$search}%") + ->orWhere('productPurchasePrice', 'like', "%{$search}%") + ->orWhere('productSalePrice', 'like', "%{$search}%") + ->orWhere('product_type', 'like', "%{$search}%") + ->orWhereHas('category', fn ($q) => $q->where('categoryName', 'like', "%{$search}%")) + ->orWhereHas('brand', fn ($q) => $q->where('brandName', 'like', "%{$search}%")) + ->orWhereHas('unit', fn ($q) => $q->where('unitName', 'like', "%{$search}%")); + }); + }) ->latest() + ->limit($request->per_page ?? 20) ->get(); - return PdfService::render('business::products.pdf', compact('products'), 'product-list.pdf'); + $products = $products->transform(function ($product) { + if ($product->product_type === 'combo') { + $product->total_stock = $product->combo_products->sum( + fn ($combo) => $combo->stock?->productStock ?? 0 + ); + + $product->total_cost = $product->combo_products->sum( + fn ($combo) => ($combo->quantity ?? 0) * ($combo->purchase_price ?? 0) + ); + } + + return $product; + }); + + return PdfService::render('business::products.pdf', compact('products'),'product-list.pdf'); } public function expiredProduct(Request $request) @@ -561,6 +633,7 @@ public function expiredProduct(Request $request) $q->where('type', 'like', '%' . $request->search . '%') ->orWhere('productName', 'like', '%' . $request->search . '%') ->orWhere('productCode', 'like', '%' . $request->search . '%') + ->orWhere('hsn_code', 'like', '%' . $request->search . '%') ->orWhere('productSalePrice', 'like', '%' . $request->search . '%') ->orWhere('productPurchasePrice', 'like', '%' . $request->search . '%') ->orWhereHas('unit', function ($q) use ($request) { @@ -586,103 +659,23 @@ public function expiredProduct(Request $request) return view('business::expired-products.index', compact('expired_products')); } - public function exportExpireProductExcel() - { - return Excel::download(new ExportExpiredProduct, 'expired-product.xlsx'); - } - - public function exportExpireProductCsv() - { - return Excel::download(new ExportExpiredProduct, 'expired-product.csv'); - } - - public function show($id) + public function show(string $id) { $business_id = auth()->user()->business_id; $product = Product::with('stocks')->where('business_id', $business_id)->findOrFail($id); $categories = Category::where('business_id', $business_id)->whereStatus(1)->latest()->get(); $brands = Brand::where('business_id', $business_id)->whereStatus(1)->latest()->get(); $units = Unit::where('business_id', $business_id)->whereStatus(1)->latest()->get(); - $vats = Vat::where('business_id', $business_id)->latest()->get(); + $vats = Vat::whereStatus(1)->where('business_id', $business_id)->latest()->get(); $profit_option = Option::where('key', 'business-settings') - ->where('value', 'LIKE', '%"business_id":%' . $business_id . '%') + ->where('value', 'LIKE', '%"business_id":' . $business_id . '%') ->get() - ->firstWhere('value.business_id', $business_id)['product_profit_option'] ?? ''; + ->firstWhere('value.business_id', $business_id) + ->value['product_profit_option'] ?? ''; return view('business::products.create-stock', compact('categories', 'brands', 'units', 'product', 'vats', 'profit_option')); } - public function CreateStock(Request $request, string $id) - { - $product = Product::findOrFail($id); - $business_id = auth()->user()->business_id; - - $request->validate([ - 'vat_id' => 'nullable|exists:vats,id', - 'vat_type' => 'nullable|in:inclusive,exclusive', - 'productDealerPrice' => 'nullable|numeric|min:0', - 'exclusive_price' => 'required|numeric|min:0', - 'inclusive_price' => 'required|numeric|min:0', - 'profit_percent' => 'nullable|numeric', - 'productSalePrice' => 'required|numeric|min:0', - 'productWholeSalePrice' => 'nullable|numeric|min:0', - 'productStock' => 'required|numeric|min:0', - 'expire_date' => 'nullable|date', - 'batch_no' => 'nullable|string', - 'productCode' => [ - 'nullable', - 'unique:products,productCode,' . $product->id . ',id,business_id,' . $business_id, - ], - ]); - - DB::beginTransaction(); - try { - // Calculate purchase price including VAT if applicable - $vat = Vat::find($request->vat_id); - $exclusive_price = $request->exclusive_price ?? 0; - $vat_amount = ($exclusive_price * ($vat->rate ?? 0)) / 100; - - // Determine final purchase price based on VAT type - $purchase_price = $request->vat_type === 'exclusive' ? $exclusive_price : $exclusive_price + $vat_amount; - - $batchNo = $request->batch_no ?? null; - $stock = Stock::where(['batch_no' => $batchNo, 'product_id' => $product->id])->first(); - - if ($stock) { - $stock->update($request->except('productStock', 'productPurchasePrice', 'productSalePrice', 'productDealerPrice', 'productWholeSalePrice') + [ - 'productStock' => $stock->productStock + $request->productStock, - 'productPurchasePrice' => $purchase_price, - 'productSalePrice' => $request->productSalePrice, - 'productDealerPrice' => $request->productDealerPrice ?? 0, - 'productWholeSalePrice' => $request->productWholeSalePrice ?? 0, - ]); - } else { - Stock::create($request->except('productStock', 'productPurchasePrice', 'productSalePrice', 'productDealerPrice', 'productWholeSalePrice') + [ - 'product_id' => $product->id, - 'branch_id' => auth()->user()->branch_id ?? auth()->user()->active_branch_id, - 'business_id' => $business_id, - 'productStock' => $request->productStock ?? 0, - 'productPurchasePrice' => $purchase_price, - 'productSalePrice' => $request->productSalePrice, - 'productDealerPrice' => $request->productDealerPrice ?? 0, - 'productWholeSalePrice' => $request->productWholeSalePrice ?? 0, - ]); - } - - DB::commit(); - - return response()->json([ - 'message' => __('Data saved successfully.'), - 'redirect' => route('business.products.index'), - ]); - } catch (\Exception $e) { - DB::rollBack(); - return response()->json([ - 'message' => __('Something went wrong.'), - ], 406); - } - } - public function getShelf(Request $request) { $rack = Rack::with('shelves')->find($request->rack_id); @@ -698,4 +691,53 @@ public function getProductVariants($product_id) return response()->json($variant_stocks); } + + public function checkSerial(Request $request) + { + $request->validate([ + 'product_id' => 'required|exists:products,id', + 'serial' => 'required|string' + ]); + + $exists = Stock::where('business_id', auth()->user()->business_id) + ->where('product_id', $request->product_id) + ->whereNotNull('serial_numbers') + ->whereJsonContains('serial_numbers', $request->serial) + ->exists(); + + return response()->json([ + 'exists' => $exists + ]); + } + + public function getProductSerials(Request $request) + { + $request->validate([ + 'product_id' => 'required|exists:products,id', + 'warehouse_id' => 'nullable|exists:warehouses,id', + 'batch_no' => 'nullable|string', + ]); + + $business_id = auth()->user()->business_id; + + // Get all serial_numbers from stocks + $serials = Stock::where('business_id', $business_id) + ->where('product_id', $request->product_id) + ->when($request->filled('batch_no'), function ($q) use ($request) { + $q->where('batch_no', $request->batch_no); + }) + ->when($request->filled('warehouse_id'), function ($q) use ($request) { + $q->where('warehouse_id', $request->warehouse_id); + }) + ->whereNotNull('serial_numbers') + ->pluck('serial_numbers') + ->flatten() + ->values(); + + return response()->json([ + 'success' => true, + 'serials' => $serials + ]); + } + } diff --git a/Modules/Business/App/Http/Controllers/AcnooProductLossProfitReportController.php b/Modules/Business/App/Http/Controllers/AcnooProductLossProfitReportController.php index cef86071..1ad4a45c 100644 --- a/Modules/Business/App/Http/Controllers/AcnooProductLossProfitReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooProductLossProfitReportController.php @@ -18,18 +18,15 @@ public function index(Request $request) $branchId = $user->branch_id ?? $user->active_branch_id; $baseQuery = SaleDetails::query() - ->whereHas('product', fn ($q) => - $q->where('business_id', $user->business_id) - ) + ->whereHas('product', fn ($q) => $q->where('business_id', $user->business_id)) ->when(moduleCheck('MultiBranchAddon') && $branchId, function ($q) use ($branchId) { - $q->whereHas('sale', fn ($q) => - $q->where('branch_id', $branchId) - ); + $q->whereHas('sale', fn ($q) => $q->where('branch_id', $branchId)); }) ->when($request->filled('search'), function ($q) use ($request) { $q->whereHas('product', function ($q) use ($request) { $q->where('productName', 'like', "%{$request->search}%") - ->orWhere('productCode', 'like', "%{$request->search}%"); + ->orWhere('productCode', 'like', "%{$request->search}%") + ->orWhere('hsn_code', 'like', "%{$request->search}%"); }); }); @@ -42,7 +39,7 @@ public function index(Request $request) ->sum('lossProfit'); $product_lossProfits = $baseQuery - ->with('product:id,productName,productCode') + ->with('product:id,productName,productCode,hsn_code') ->select( 'product_id', DB::raw('SUM(CASE WHEN "lossProfit" > 0 THEN "lossProfit" ELSE 0 END) AS profit'), @@ -54,17 +51,13 @@ public function index(Request $request) if ($request->ajax()) { return response()->json([ - 'data' => view( - 'business::reports.product-loss-profit.datas', - compact('product_lossProfits') - )->render() + 'data' => view('business::reports.product-loss-profit.datas', compact('product_lossProfits'))->render(), + 'loss' => currency_format($loss, currency: business_currency()), + 'profit' => currency_format($profit, currency: business_currency()) ]); } - return view( - 'business::reports.product-loss-profit.index', - compact('product_lossProfits', 'profit', 'loss') - ); + return view('business::reports.product-loss-profit.index', compact('product_lossProfits', 'profit', 'loss')); } @@ -78,26 +71,36 @@ public function exportCsv() return Excel::download(new ExportProductLossProfit, 'product-lossProfit.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { - $branchId = moduleCheck('MultiBranchAddon') ? auth()->user()->branch_id ?? auth()->user()->active_branch_id : null; + $user = auth()->user(); + $branchId = $user->branch_id ?? $user->active_branch_id; - $product_lossProfits = SaleDetails::with('product:id,productName,productCode') - ->whereHas('product', function ($q) { - $q->where('business_id', auth()->user()->business_id); - }) - ->when($branchId, function ($q) use ($branchId) { - $q->whereHas('sale', function ($sale) use ($branchId) { - $sale->where('branch_id', $branchId); - }); - }) - ->select( - 'product_id', - DB::raw('SUM(CASE WHEN "lossProfit" > 0 THEN "lossProfit" ELSE 0 END) as profit'), - DB::raw('SUM(CASE WHEN "lossProfit" < 0 THEN "lossProfit" ELSE 0 END) as loss') - ) - ->groupBy('product_id') - ->get(); + $baseQuery = SaleDetails::query() + ->whereHas('product', fn ($q) => + $q->where('business_id', $user->business_id) + ) + ->when(moduleCheck('MultiBranchAddon') && $branchId, function ($q) use ($branchId) { + $q->whereHas('sale', fn ($q) => $q->where('branch_id', $branchId)); + }) + ->when($request->filled('search'), function ($q) use ($request) { + $q->whereHas('product', function ($q) use ($request) { + $q->where('productName', 'like', "%{$request->search}%") + ->orWhere('productCode', 'like', "%{$request->search}%") + ->orWhere('hsn_code', 'like', "%{$request->search}%"); + }); + }); + + $product_lossProfits = $baseQuery + ->with('product:id,productName,productCode,hsn_code') + ->select( + 'product_id', + DB::raw('SUM(CASE WHEN "lossProfit" > 0 THEN "lossProfit" ELSE 0 END) AS profit'), + DB::raw('SUM(CASE WHEN "lossProfit" < 0 THEN "lossProfit" ELSE 0 END) AS loss') + ) + ->groupBy('product_id') + ->limit($request->per_page ?? 20) + ->get(); return PdfService::render('business::reports.product-loss-profit.pdf', compact('product_lossProfits'), 'product-loss-profit-report.pdf'); } diff --git a/Modules/Business/App/Http/Controllers/AcnooProductPurchaseHistoryReportController.php b/Modules/Business/App/Http/Controllers/AcnooProductPurchaseHistoryReportController.php index 20216ca1..10ec585b 100644 --- a/Modules/Business/App/Http/Controllers/AcnooProductPurchaseHistoryReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooProductPurchaseHistoryReportController.php @@ -51,7 +51,7 @@ public function index(Request $request) return view('business::product-purchase-history-report.index', compact('products', 'total_purchase_qty', 'total_sale_qty', 'filter_from_date', 'filter_to_date', 'duration')); } - public function show(Request $request, $id) + public function show(Request $request, string $id) { $businessId = auth()->user()->business_id; $duration = $request->custom_days ?: 'today'; @@ -65,7 +65,7 @@ public function show(Request $request, $id) ->whereHas('purchase', function ($purchase) use ($duration, $request) { $this->applyDateFilter($purchase, $duration, 'purchaseDate', $request->from_date, $request->to_date); }) - ->with('purchase:id,invoiceNumber,purchaseDate') + ->with('purchase:id,party_id,invoiceNumber,purchaseDate', 'purchase.party:id,name') ->select('id', 'purchase_id', 'product_id', 'quantities', 'productPurchasePrice'); $purchaseDetailsQuery->when(filled($request->search), function ($q) use ($request) { @@ -76,6 +76,9 @@ public function show(Request $request, $id) ->orWhereHas('purchase', function ($q) use ($search) { $q->where('invoiceNumber', 'like', "%{$search}%") ->orWhere('purchaseDate', 'like', "%{$search}%"); + }) + ->orWhereHas('purchase.party', function ($q) use ($search) { + $q->where('name', 'like', "%{$search}%"); }); }); } @@ -89,8 +92,7 @@ public function show(Request $request, $id) ]); } - return view( - 'business::product-purchase-history-report.details', + return view('business::product-purchase-history-report.details', compact('product', 'purchaseDetails', 'filter_from_date', 'filter_to_date', 'duration') ); } @@ -108,9 +110,20 @@ public function exportCsv() public function exportPdf(Request $request) { $businessId = auth()->user()->business_id; + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); - $productQuery = Product::with('saleDetails', 'purchaseDetails', 'stocks', 'combo_products')->where('business_id', $businessId); - $products = $productQuery->get(); + $productQuery = Product::with(['saleDetails', 'purchaseDetails', 'purchaseDetails.purchase', 'stocks', 'combo_products']) + ->where('business_id', $businessId) + ->whereHas('purchaseDetails.purchase', function ($purchase) use ($duration, $request) { + $this->applyDateFilter($purchase, $duration, 'purchaseDate', $request->from_date, $request->to_date); + }); + + $productQuery->when($request->search, function ($q) use ($request) { + $q->where('productName', 'like', '%' . $request->search . '%'); + }); + + $products = $productQuery->limit($request->per_page ?? 20)->get(); $total_purchase_qty = $products->sum(function ($product) { return $product->purchaseDetails->sum('quantities'); @@ -120,31 +133,54 @@ public function exportPdf(Request $request) return $product->saleDetails->sum('quantities'); }); - return PdfService::render('business::product-purchase-history-report.pdf', compact('products', 'total_purchase_qty', 'total_sale_qty'), 'product-purchase-history.pdf'); + return PdfService::render('business::product-purchase-history-report.pdf', compact('products', 'total_purchase_qty', 'total_sale_qty', 'duration', 'filter_from_date', 'filter_to_date'), 'product-purchase-history.pdf'); } - public function exportDetailExcel($id) + public function exportDetailExcel(string $id) { return Excel::download(new ExportProductPurchaseHistoryDetailReport($id), 'product-purchase-history-details.xlsx'); } - public function exportDetailCsv($id) + public function exportDetailCsv(string $id) { return Excel::download(new ExportProductPurchaseHistoryDetailReport($id), 'product-purchase-history-details.csv'); } - public function exportDetailPdf(Request $request, $id) + public function exportDetailPdf(Request $request, string $id) { + $businessId = auth()->user()->business_id; + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + $product = Product::select('id', 'business_id', 'productName') - ->where('business_id', auth()->user()->business_id) + ->where('business_id', $businessId) ->findOrFail($id); $purchaseDetailsQuery = $product->purchaseDetails() - ->with('purchase:id,invoiceNumber,purchaseDate') + ->whereHas('purchase', function ($purchase) use ($duration, $request) { + $this->applyDateFilter($purchase, $duration, 'purchaseDate', $request->from_date, $request->to_date); + }) + ->with('purchase:id,party_id,invoiceNumber,purchaseDate', 'purchase.party:id,name') ->select('id', 'purchase_id', 'product_id', 'quantities', 'productPurchasePrice'); - $purchaseDetails = $purchaseDetailsQuery->get(); + $purchaseDetailsQuery->when(filled($request->search), function ($q) use ($request) { + $search = $request->search; + $q->where(function ($q) use ($search) { + $q->where('productPurchasePrice', 'like', "%{$search}%") + ->orWhere('quantities', 'like', "%{$search}%") + ->orWhereHas('purchase', function ($q) use ($search) { + $q->where('invoiceNumber', 'like', "%{$search}%") + ->orWhere('purchaseDate', 'like', "%{$search}%"); + }) + ->orWhereHas('purchase.party', function ($q) use ($search) { + $q->where('name', 'like', "%{$search}%"); + }); + }); + } + ); - return PdfService::render('business::product-purchase-history-report.pdf-detail', compact('product', 'purchaseDetails'), 'product-purchase-history-details.pdf'); + $purchaseDetails = $purchaseDetailsQuery->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::product-purchase-history-report.pdf-detail', compact('product', 'purchaseDetails', 'duration', 'filter_from_date', 'filter_to_date'), 'product-purchase-history-details.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooProductPurchaseReportController.php b/Modules/Business/App/Http/Controllers/AcnooProductPurchaseReportController.php index 9bbce330..927f905b 100644 --- a/Modules/Business/App/Http/Controllers/AcnooProductPurchaseReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooProductPurchaseReportController.php @@ -22,19 +22,6 @@ public function index(Request $request) $q->where('business_id', auth()->user()->business_id); }); - $query->when(request('search'), function ($q) use ($request) { - $q->whereHas('product', function ($q) use ($request) { - $q->where('productName', 'like', '%' . $request->search . '%'); - }) - ->orWhereHas('purchase', function ($q) use ($request) { - $q->where('invoiceNumber', 'like', '%' . $request->search . '%') - ->orWhere('purchaseDate', 'like', '%' . $request->search . '%'); - }) - ->orWhereHas('purchase.party', function ($q) use ($request) { - $q->where('name', 'like', '%' . $request->search . '%'); - }); - }); - // Date Filter $duration = $request->custom_days ?: 'today'; [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); @@ -43,6 +30,21 @@ public function index(Request $request) $this->applyDateFilter($q, $duration, 'purchaseDate', $request->from_date, $request->to_date); }); + $query->when(request('search'), function ($q) use ($request) { + $q->where(function ($q) use ($request) { + $q->whereHas('product', function ($q) use ($request) { + $q->where('productName', 'like', '%' . $request->search . '%'); + }) + ->orWhereHas('purchase', function ($q) use ($request) { + $q->where('invoiceNumber', 'like', '%' . $request->search . '%') + ->orWhere('purchaseDate', 'like', '%' . $request->search . '%'); + }) + ->orWhereHas('purchase.party', function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%'); + }); + }); + }); + $product_purchases = $query->paginate($request->per_page ?? 20)->appends($request->query()); if ($request->ajax()) { @@ -64,14 +66,38 @@ public function exportCsv() return Excel::download(new ExportProductPurchaseReport, 'product-purchase.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { - $product_purchases = PurchaseDetails::with('product:id,productName', 'purchase:id,party_id,invoiceNumber,purchaseDate', 'purchase.party:id,name') + $query = PurchaseDetails::with('product:id,productName', 'purchase:id,party_id,invoiceNumber,purchaseDate', 'purchase.party:id,name') ->whereHas('purchase', function ($q) { $q->where('business_id', auth()->user()->business_id); - }) - ->get(); + }); - return PdfService::render('business::reports.product-purchase.pdf', compact('product_purchases'),'product-purchase-report.pdf'); + // Date Filter + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + $query->whereHas('purchase', function ($q) use ($duration, $request) { + $this->applyDateFilter($q, $duration, 'purchaseDate', $request->from_date, $request->to_date); + }); + + $query->when(request('search'), function ($q) use ($request) { + $q->where(function ($q) use ($request) { + $q->whereHas('product', function ($q) use ($request) { + $q->where('productName', 'like', '%' . $request->search . '%'); + }) + ->orWhereHas('purchase', function ($q) use ($request) { + $q->where('invoiceNumber', 'like', '%' . $request->search . '%') + ->orWhere('purchaseDate', 'like', '%' . $request->search . '%'); + }) + ->orWhereHas('purchase.party', function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%'); + }); + }); + }); + + $product_purchases = $query->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::reports.product-purchase.pdf', compact('product_purchases', 'duration', 'filter_from_date', 'filter_to_date'), 'product-purchase-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooProductSaleHistoryReportController.php b/Modules/Business/App/Http/Controllers/AcnooProductSaleHistoryReportController.php index e741f8a2..4e0bbabb 100644 --- a/Modules/Business/App/Http/Controllers/AcnooProductSaleHistoryReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooProductSaleHistoryReportController.php @@ -57,7 +57,7 @@ public function index(Request $request) return view('business::product-sale-history-report.index', compact('products', 'total_purchase_qty', 'total_sale_qty', 'total_sale_price', 'filter_from_date', 'filter_to_date', 'duration')); } - public function show(Request $request, $id) + public function show(Request $request, string $id) { $businessId = auth()->user()->business_id; $duration = $request->custom_days ?: 'today'; @@ -82,6 +82,9 @@ public function show(Request $request, $id) ->orWhereHas('sale', function ($q) use ($search) { $q->where('invoiceNumber', 'like', "%{$search}%") ->orWhere('saleDate', 'like', "%{$search}%"); + }) + ->orWhereHas('sale.party', function ($q) use ($search) { + $q->where('name', 'like', "%{$search}%"); }); }); } @@ -111,9 +114,20 @@ public function exportCsv() public function exportPdf(Request $request) { $businessId = auth()->user()->business_id; + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); - $productQuery = Product::with('saleDetails', 'purchaseDetails', 'stocks', 'combo_products')->where('business_id', $businessId); - $products = $productQuery->get(); + $productQuery = Product::with(['saleDetails', 'purchaseDetails', 'saleDetails.sale', 'stocks', 'combo_products']) + ->where('business_id', $businessId) + ->whereHas('saleDetails.sale', function ($sale) use ($duration, $request) { + $this->applyDateFilter($sale, $duration, 'saleDate', $request->from_date, $request->to_date); + }); + + $productQuery->when($request->search, function ($q) use ($request) { + $q->where('productName', 'like', '%' . $request->search . '%'); + }); + + $products = $productQuery->limit($request->per_page ?? 20)->get(); $total_single_sale_price = $products->sum(function ($product) { return $product->saleDetails->sum('price'); @@ -129,31 +143,54 @@ public function exportPdf(Request $request) return $product->saleDetails->sum('quantities'); }); - return PdfService::render('business::product-sale-history-report.pdf', compact('products', 'total_purchase_qty', 'total_sale_qty', 'total_sale_price'), 'product-sale-history.pdf'); + return PdfService::render('business::product-sale-history-report.pdf', compact('products', 'total_purchase_qty', 'total_sale_qty', 'total_sale_price', 'filter_from_date', 'filter_to_date', 'duration'), 'product-sale-history.pdf'); } - public function exportDetailExcel($id) + public function exportDetailExcel(string $id) { return Excel::download(new ExportProductSaleHistoryDetailReport($id), 'product-sale-history-details.xlsx'); } - public function exportDetailCsv($id) + public function exportDetailCsv(string $id) { return Excel::download(new ExportProductSaleHistoryDetailReport($id), 'product-sale-history-details.csv'); } - public function exportDetailPdf(Request $request, $id) + public function exportDetailPdf(Request $request, string $id) { + $businessId = auth()->user()->business_id; + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + $product = Product::select('id', 'business_id', 'productName') - ->where('business_id', auth()->user()->business_id) + ->where('business_id', $businessId) ->findOrFail($id); $saleDetailsQuery = $product->saleDetails() + ->whereHas('sale', function ($sale) use ($duration, $request) { + $this->applyDateFilter($sale, $duration, 'saleDate', $request->from_date, $request->to_date); + }) ->with('sale:id,party_id,invoiceNumber,saleDate', 'sale.party:id,name') ->select('id', 'sale_id', 'product_id', 'quantities', 'lossprofit', 'price', 'productPurchasePrice'); - $saleDetails = $saleDetailsQuery->get(); + $saleDetailsQuery->when(filled($request->search), function ($q) use ($request) { + $search = $request->search; + $q->where(function ($q) use ($search) { + $q->where('price', 'like', "%{$search}%") + ->orWhere('quantities', 'like', "%{$search}%") + ->orWhereHas('sale', function ($q) use ($search) { + $q->where('invoiceNumber', 'like', "%{$search}%") + ->orWhere('saleDate', 'like', "%{$search}%"); + }) + ->orWhereHas('sale.party', function ($q) use ($search) { + $q->where('name', 'like', "%{$search}%"); + }); + }); + } + ); - return PdfService::render('business::product-sale-history-report.pdf-detail', compact('product', 'saleDetails'), 'product-sale-history-details.pdf'); + $saleDetails = $saleDetailsQuery->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::product-sale-history-report.pdf-detail', compact('product', 'saleDetails', 'duration', 'filter_from_date', 'filter_to_date'), 'product-sale-history-details.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooProductSaleReportController.php b/Modules/Business/App/Http/Controllers/AcnooProductSaleReportController.php index 469792ab..a439ce6e 100644 --- a/Modules/Business/App/Http/Controllers/AcnooProductSaleReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooProductSaleReportController.php @@ -3,7 +3,7 @@ namespace Modules\Business\App\Http\Controllers; use App\Http\Controllers\Controller; -use App\Models\Sale; +use App\Models\SaleDetails; use App\Services\PdfService; use App\Traits\DateFilterTrait; use App\Traits\DateRangeTrait; @@ -17,26 +17,34 @@ class AcnooProductSaleReportController extends Controller public function index(Request $request) { - $query = Sale::with('details:id,sale_id,product_id,quantities,price', 'details.product:id,productName', 'party:id,name') - ->where('business_id', auth()->user()->business_id); - - $query->when(request('search'), function ($q) use ($request) { - $q->where(function ($q) use ($request) { - $q->where('invoiceNumber', 'like', '%' . $request->search . '%') - ->orWhere('saleDate', 'like', '%' . $request->search . '%'); - }) - ->orWhereHas('details.product', function ($q) use ($request) { - $q->where('productName', 'like', '%' . $request->search . '%'); - }) - ->orWhereHas('party', function ($q) use ($request) { - $q->where('name', 'like', '%' . $request->search . '%'); - }); - }); + $query = SaleDetails::with('product:id,productName', 'sale:id,party_id,invoiceNumber,saleDate', 'sale.party:id,name') + ->whereHas('sale', function ($q) { + $q->where('business_id', auth()->user()->business_id); + }); + // Date Filter $duration = $request->custom_days ?: 'today'; [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); - $this->applyDateFilter($query, $duration, 'saleDate', $request->from_date, $request->to_date); + $query->whereHas('sale', function ($q) use ($duration, $request) { + $this->applyDateFilter($q, $duration, 'saleDate', $request->from_date, $request->to_date); + }); + + // Search Filter + $query->when(request('search'), function ($q) use ($request) { + $q->where(function ($q) use ($request) { + $q->whereHas('product', function ($q) use ($request) { + $q->where('productName', 'like', '%' . $request->search . '%'); + }) + ->orWhereHas('sale', function ($q) use ($request) { + $q->where('invoiceNumber', 'like', '%' . $request->search . '%') + ->orWhere('saleDate', 'like', '%' . $request->search . '%'); + }) + ->orWhereHas('sale.party', function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%'); + }); + }); + }); $product_sales = $query->paginate($request->per_page ?? 20)->appends($request->query()); @@ -59,13 +67,39 @@ public function exportCsv() return Excel::download(new ExportProductSaleReport, 'product-sales.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { - $product_sales = Sale::with('details:id,sale_id,product_id,quantities,price', 'details.product:id,productName', 'party:id,name') - ->where('business_id', auth()->user()->business_id) - ->latest() - ->get(); + $query = SaleDetails::with('product:id,productName', 'sale:id,party_id,invoiceNumber,saleDate', 'sale.party:id,name') + ->whereHas('sale', function ($q) { + $q->where('business_id', auth()->user()->business_id); + }); - return PdfService::render('business::reports.product-sale.pdf', compact('product_sales'),'product-sale-report.pdf'); + // Date Filter + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + $query->whereHas('sale', function ($q) use ($duration, $request) { + $this->applyDateFilter($q, $duration, 'saleDate', $request->from_date, $request->to_date); + }); + + // Search Filter + $query->when(request('search'), function ($q) use ($request) { + $q->where(function ($q) use ($request) { + $q->whereHas('product', function ($q) use ($request) { + $q->where('productName', 'like', '%' . $request->search . '%'); + }) + ->orWhereHas('sale', function ($q) use ($request) { + $q->where('invoiceNumber', 'like', '%' . $request->search . '%') + ->orWhere('saleDate', 'like', '%' . $request->search . '%'); + }) + ->orWhereHas('sale.party', function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%'); + }); + }); + }); + + $product_sales = $query->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::reports.product-sale.pdf', compact('product_sales', 'duration', 'filter_from_date', 'filter_to_date'), 'product-sale-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooPurchaseController.php b/Modules/Business/App/Http/Controllers/AcnooPurchaseController.php index e2dd118b..c8024758 100644 --- a/Modules/Business/App/Http/Controllers/AcnooPurchaseController.php +++ b/Modules/Business/App/Http/Controllers/AcnooPurchaseController.php @@ -2,34 +2,39 @@ namespace Modules\Business\App\Http\Controllers; -use Carbon\Carbon; -use App\Models\Vat; -use App\Models\Brand; -use App\Models\Party; -use App\Models\Stock; +use App\Events\MultiPaymentProcessed; +use App\Helpers\HasUploader; +use App\Http\Controllers\Controller; +use App\Imports\PurchaseProductImport; use App\Models\Branch; -use App\Models\Product; +use App\Models\Brand; use App\Models\Business; use App\Models\Category; -use App\Models\Purchase; -use App\Models\Warehouse; -use App\Events\PurchaseSms; +use App\Models\Option; +use App\Models\Party; use App\Models\PaymentType; -use App\Models\Transaction; -use App\Helpers\HasUploader; -use App\Services\PdfService; -use Illuminate\Http\Request; -use App\Models\PurchaseReturn; +use App\Models\Product; +use App\Models\Purchase; use App\Models\PurchaseDetails; +use App\Models\PurchaseReturn; +use App\Models\PurchaseReturnDetail; +use App\Models\Stock; +use App\Models\Transaction; +use App\Models\Vat; +use App\Models\VatTransaction; +use App\Models\Warehouse; +use App\Services\Invoice\PurchaseInvoiceService; +use App\Services\Invoice\PurchaseTaxInvoiceService; +use App\Services\PdfService; +use App\Services\PurchaseVatTransaction; use Barryvdh\DomPDF\Facade\Pdf; -use Illuminate\Validation\Rule; -use Illuminate\Support\Facades\DB; -use App\Http\Controllers\Controller; -use Illuminate\Support\Facades\Mail; -use Maatwebsite\Excel\Facades\Excel; -use App\Events\MultiPaymentProcessed; -use App\Imports\PurchaseProductImport; +use Carbon\Carbon; use Gloudemans\Shoppingcart\Facades\Cart; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Mail; +use Illuminate\Validation\Rule; +use Maatwebsite\Excel\Facades\Excel; class AcnooPurchaseController extends Controller { @@ -89,16 +94,16 @@ public function index(Request $request) // Search Filter if ($request->filled('search')) { $purchasesQuery->where(function ($query) use ($request) { - $query->where('paymentType', 'like', '%' . $request->search . '%') - ->orWhere('invoiceNumber', 'like', '%' . $request->search . '%') + $query->where('paymentType', 'like', '%'.$request->search.'%') + ->orWhere('invoiceNumber', 'like', '%'.$request->search.'%') ->orWhereHas('party', function ($q) use ($request) { - $q->where('name', 'like', '%' . $request->search . '%'); + $q->where('name', 'like', '%'.$request->search.'%'); }) ->orWhereHas('payment_type', function ($q) use ($request) { - $q->where('name', 'like', '%' . $request->search . '%'); + $q->where('name', 'like', '%'.$request->search.'%'); }) ->orWhereHas('branch', function ($q) use ($request) { - $q->where('name', 'like', '%' . $request->search . '%'); + $q->where('name', 'like', '%'.$request->search.'%'); }); }); } @@ -106,7 +111,6 @@ public function index(Request $request) $perPage = $request->input('per_page', 20); $purchases = $purchasesQuery->latest()->paginate($perPage)->appends($request->query()); - if ($request->ajax()) { return response()->json([ 'data' => view('business::purchases.datas', compact('purchases', 'purchasesWithReturns'))->render(), @@ -122,6 +126,7 @@ public function index(Request $request) public function create() { $business_id = auth()->user()->business_id; + $activeBranchId = auth()->user()->active_branch?->id; // Clears all cart items Cart::destroy(); @@ -129,27 +134,38 @@ public function create() $suppliers = Party::where('type', 'Supplier') ->where('business_id', $business_id) ->latest() - ->get(); + ->get() + ->transform(function ($supplier) use ($activeBranchId) { + $supplier->due = $supplier->dueForBranch($activeBranchId); - $products = Product::with(['category:id,categoryName', 'unit:id,unitName', 'brand:id,brandName',]) + return $supplier; + }); + + $products = Product::with(['category:id,categoryName', 'unit:id,unitName', 'brand:id,brandName']) ->withSum('stocks', 'productStock') ->where('business_id', $business_id) ->whereNot('product_type', 'combo') ->latest() ->get(); - $cart_contents = Cart::content()->filter(fn($item) => $item->options->type == 'purchase'); + $cart_contents = Cart::content()->filter(fn ($item) => $item->options->type == 'purchase'); $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(); + $profit_option = Option::where('key', 'business-settings') + ->where('value', 'LIKE', '%"business_id":' . $business_id . '%') + ->get() + ->firstWhere('value.business_id', $business_id) + ->value['product_profit_option'] ?? ''; + // Generate a unique invoice number $purchase_id = (Purchase::max('id') ?? 0) + 1; - $invoice_no = 'P-' . str_pad($purchase_id, 5, '0', STR_PAD_LEFT); + $invoice_no = 'P-'.str_pad($purchase_id, 5, '0', STR_PAD_LEFT); - return view('business::purchases.create', compact('suppliers', 'products', 'cart_contents', 'invoice_no', 'categories', 'brands', 'vats', 'payment_types', 'warehouses')); + return view('business::purchases.create', compact('suppliers', 'products', 'cart_contents', 'invoice_no', 'categories', 'brands', 'vats', 'payment_types', 'warehouses', 'profit_option')); } public function store(Request $request) @@ -166,10 +182,11 @@ public function store(Request $request) ]); $user = auth()->user(); + $business = Business::findOrFail($user->business_id); $branch_id = $user->branch_id ?? $user->active_branch_id; // Check each cart item for batch duplication in Stock table - $carts = Cart::content()->filter(fn($item) => $item->options->type == 'purchase'); + $carts = Cart::content()->filter(fn ($item) => $item->options->type == 'purchase'); if ($carts->count() < 1) { return response()->json(['message' => __('Cart is empty. Add items first!')], 400); @@ -178,27 +195,27 @@ public function store(Request $request) DB::beginTransaction(); try { // Subtotal - $subtotal = $carts->sum(fn($cartItem) => (float)$cartItem->subtotal); + $subtotal = $carts->sum(fn ($cartItem) => (float) $cartItem->subtotal); - // VAT - $vat = Vat::find($request->vat_id); - $vatAmount = $vat ? ($subtotal * $vat->rate) / 100 : 0; - - // Subtotal with VAT - $subtotalWithVat = $subtotal + $vatAmount; - - // Discount + // Discount (applied first on subtotal) $discountAmount = $request->discountAmount ?? 0; if ($request->discount_type === 'percent') { - $discountAmount = ($subtotalWithVat * $discountAmount) / 100; + $discountAmount = ($subtotal * $discountAmount) / 100; } - if ($discountAmount > $subtotalWithVat) { - return response()->json(['message' => __('Discount cannot be more than subtotal with VAT!')], 400); + if ($discountAmount > $subtotal) { + return response()->json(['message' => __('Discount cannot be more than subtotal!')], 400); } + // Subtotal after discount (before VAT) + $subtotalAfterDiscount = $subtotal - $discountAmount; + + // VAT (applied on subtotal after discount) + $vat = Vat::find($request->vat_id); + $vatAmount = $vat ? ($subtotalAfterDiscount * $vat->rate) / 100 : 0; + // Charges and totals $shippingCharge = $request->shipping_charge ?? 0; - $totalAmount = $subtotalWithVat + $shippingCharge - $discountAmount; + $totalAmount = $subtotalAfterDiscount + $vatAmount + $shippingCharge; $receiveAmount = $request->receive_amount ?? 0; $changeAmount = max($receiveAmount - $totalAmount, 0); @@ -213,12 +230,12 @@ public function store(Request $request) $newTotalDue = $party->due + $dueAmount; if ($party->credit_limit > 0 && $newTotalDue > $party->credit_limit) { return response()->json([ - 'message' => __('Cannot create purchase. Party due will exceed credit limit!') + 'message' => __('Cannot create purchase. Party due will exceed credit limit!'), ], 400); } $party->update([ - 'due' => $newTotalDue + 'due' => $newTotalDue, ]); } @@ -227,37 +244,103 @@ public function store(Request $request) // Create Purchase $purchase = Purchase::create($request->except('discountAmount', 'discount_type', 'discount_percent', 'shipping_charge', 'payment_type_id') + [ - 'user_id' => $user->id, - 'vat_id' => $request->vat_id, - 'vat_amount' => $vatAmount, - 'business_id' => $user->business_id, - 'discountAmount' => $discountAmount, - 'discount_type' => $request->discount_type ?? 'flat', - 'discount_percent' => $request->discount_type == 'percent' ? $request->discountAmount : 0, - 'totalAmount' => $totalAmount, - 'paidAmount' => $paidAmount, - 'change_amount' => $changeAmount, - 'dueAmount' => $dueAmount, - 'shipping_charge' => $shippingCharge, - 'purchaseDate' => $request->purchaseDate ? Carbon::parse($request->purchaseDate)->setTimeFromTimeString(now()->format('H:i:s')) : now(), - 'isPaid' => $dueAmount > 0 ? 0 : 1, - ]); - - $purchase_details = []; + 'user_id' => $user->id, + 'vat_id' => $request->vat_id, + 'vat_amount' => $vatAmount, + 'business_id' => $user->business_id, + 'discountAmount' => $discountAmount, + 'discount_type' => $request->discount_type ?? 'flat', + 'discount_percent' => $request->discount_type == 'percent' ? $request->discountAmount : 0, + 'totalAmount' => $totalAmount, + 'paidAmount' => $paidAmount, + 'change_amount' => $changeAmount, + 'dueAmount' => $dueAmount, + 'shipping_charge' => $shippingCharge, + 'purchaseDate' => $request->purchaseDate ? Carbon::parse($request->purchaseDate)->setTimeFromTimeString(now()->format('H:i:s')) : now(), + 'isPaid' => $dueAmount > 0 ? 0 : 1, + ]); // Insert Purchase Details and Create Stocks foreach ($carts as $cartItem) { + $product = Product::findOrFail($cartItem->id); $batchNo = $cartItem->options['batch_no'] ?? null; $expireDate = $cartItem->options['expire_date'] ?? null; $variantName = $cartItem->options['variant_name'] ?? null; - $existingStock = Stock::where(['batch_no' => $batchNo, 'product_id' => $cartItem->id]) - ->when($variantName, fn($q) => $q->where('variant_name', $variantName)) + $existingStock = Stock::where([ + 'business_id' => $user->business_id, + 'batch_no' => $batchNo, + 'product_id' => $cartItem->id, + 'branch_id' => $branch_id, + ]) + ->when($variantName, fn ($q) => $q->where('variant_name', $variantName)) ->first(); $warehouseId = $cartItem->options['warehouse_id'] ?? ($existingStock->warehouse_id ?? null); + $newSerials = $cartItem->options['serial_numbers'] ?? []; + $existingSerials = $existingStock?->serial_numbers ?? []; + + // Only serials that don't already exist + $uniqueNewSerials = array_diff($newSerials, $existingSerials); + + if (! empty($newSerials) && count($uniqueNewSerials) === 0) { + return response()->json([ + 'success' => false, + 'message' => 'Serial already exists in stock', + 'duplicates' => array_values(array_intersect($newSerials, $existingSerials)), + ], 400); + } + + $mergedSerials = array_values( + array_unique(array_merge($existingSerials, $uniqueNewSerials)) + ); + + // qty depends on serial + $qtyToAdd = count($uniqueNewSerials) > 0 ? count($uniqueNewSerials) : $cartItem->qty; + + // price calculation + $vatRate = 0; + if ($product->vat_id) { + $vatData = Vat::find($product->vat_id); + $vatRate = $vatData?->rate ?? 0; + } + + $purchasePrice = $cartItem->price; + + // convert inclusive to exclusive + $exclusive_price = $vatRate > 0 + ? round($purchasePrice / (1 + ($vatRate / 100)), 4) + : $purchasePrice; + + $isSerialProduct = ! empty($newSerials); + + if ($isSerialProduct) { + // For serial products, use cart sales_price directly + $salesPrice = $cartItem->options['sales_price']; + $profitPercent = $cartItem->options['profit_percent']; + } else { + $profitPercent = ($cartItem->options['profit_percent'] ?? 0); + + $profitOption = Option::where('key', 'business-settings') + ->where('value', 'LIKE', '%"business_id":' . $business->id . '%') + ->get() + ->firstWhere('value.business_id', $business->id) + ->value['product_profit_option'] ?? 'markup'; + + if ($profitOption === 'margin') { + $salesPrice = $exclusive_price / (1 - ($profitPercent / 100)); + } else { + $salesPrice = $exclusive_price + ($exclusive_price * $profitPercent / 100); + } + + if ($product->vat_type === 'inclusive' && $vatRate > 0) { + $salesPrice = $salesPrice * (1 + ($vatRate / 100)); + } + $salesPrice = round($salesPrice, 2); + } + $stock = Stock::updateOrCreate( [ 'batch_no' => $batchNo, @@ -267,32 +350,40 @@ public function store(Request $request) 'business_id' => $user->business_id, ], [ - 'product_id' => $cartItem->id, 'expire_date' => $expireDate, - 'productPurchasePrice' => $cartItem->price, - 'productSalePrice' => $cartItem->options['sales_price'], - 'productWholeSalePrice' => $cartItem->options['whole_sale_price'], - 'productDealerPrice' => $cartItem->options['dealer_price'], - 'productStock' => $cartItem->qty + ($existingStock->productStock ?? 0), + 'product_id' => $cartItem->id, 'warehouse_id' => $warehouseId, + 'exclusive_price' => $exclusive_price, + 'productPurchasePrice' => $purchasePrice, + 'productSalePrice' => $salesPrice, + 'profit_percent' => $profitPercent, + 'productDealerPrice' => $cartItem->options['dealer_price'], + 'serial_numbers' => ! empty($newSerials) ? $mergedSerials : null, + 'productWholeSalePrice' => $cartItem->options['whole_sale_price'], + 'productStock' => $cartItem->qty + ($existingStock->productStock ?? 0), ] ); // purchase detail - $purchase_details[] = [ + $detail = PurchaseDetails::create([ 'stock_id' => $stock->id, 'purchase_id' => $purchase->id, 'product_id' => $cartItem->id, - 'quantities' => $cartItem->qty, - 'productPurchasePrice' => $cartItem->price, - 'productDealerPrice' => $cartItem->options['dealer_price'], + 'quantities' => $qtyToAdd, 'expire_date' => $expireDate, - 'productSalePrice' => $cartItem->options['sales_price'], + 'productPurchasePrice' => $purchasePrice, + 'productSalePrice' => $salesPrice, + 'profit_percent' => $profitPercent, + 'productDealerPrice' => $cartItem->options['dealer_price'], 'productWholeSalePrice' => $cartItem->options['whole_sale_price'], - ]; - } + 'serial_numbers' => ! empty($uniqueNewSerials) ? array_values($uniqueNewSerials) : null, + ]); - PurchaseDetails::insert($purchase_details); + $vatAmount = PurchaseVatTransaction::storeVatTransactions($business, $detail, $product, $party ?? null); + $detail->update([ + 'price_without_tax' => number_format($purchasePrice - ($vatAmount / $cartItem->qty), 3, '.', ''), + ]); + } // MultiPaymentProcessed Event event(new MultiPaymentProcessed( @@ -308,20 +399,26 @@ public function store(Request $request) Cart::remove($cartItem->rowId); } - event(new PurchaseSms($purchase)); - sendNotifyToUser($purchase->id, route('business.purchases.index', ['id' => $purchase->id]), __('New Purchase created.'), $user->business_id); + if (moduleCheck('MarketingAddon')) { + $this->purchaseMessage($business, $party ?? null, $purchase); + } + + $tax_invoice = moduleCheck('TaxInvoiceAddon') && invoice_setting($purchase->business_id) == 'standard_a4'; + $invoice_route = $tax_invoice ? route('business.purchases.tax.invoice', $purchase->id) : route('business.purchases.invoice', $purchase->id); + DB::commit(); return response()->json([ 'message' => __('Purchase created successfully.'), 'redirect' => route('business.purchases.index'), - 'secondary_redirect_url' => route('business.purchases.invoice', $purchase->id), + 'secondary_redirect_url' => $invoice_route, ]); } catch (\Exception $e) { DB::rollback(); - return response()->json(['message' => __('Something went wrong!')], 404); + + return response()->json(['message' => $e->getMessage()], 500); } } @@ -330,6 +427,7 @@ public function edit($id) // Clears all cart items Cart::destroy(); $business_id = auth()->user()->business_id; + $activeBranchId = auth()->user()->active_branch?->id; $purchase = Purchase::with('details', 'details.product', 'details.stock:id,batch_no', 'details.product.unit', 'payment_type:id,name', 'transactions') ->where('business_id', $business_id) @@ -338,9 +436,14 @@ public function edit($id) $suppliers = Party::where('type', 'Supplier') ->where('business_id', $business_id) ->latest() - ->get(); + ->get() + ->transform(function ($supplier) use ($activeBranchId) { + $supplier->due = $supplier->dueForBranch($activeBranchId); - $products = Product::with(['category:id,categoryName', 'unit:id,unitName', 'brand:id,brandName',]) + return $supplier; + }); + + $products = Product::with(['category:id,categoryName', 'unit:id,unitName', 'brand:id,brandName']) ->withSum('stocks', 'productStock') ->where('business_id', $business_id) ->whereNot('product_type', 'combo') @@ -353,6 +456,12 @@ public function edit($id) $payment_types = PaymentType::where('business_id', $business_id)->whereStatus(1)->latest()->get(); $warehouses = Warehouse::select('id', 'name')->where('business_id', $business_id)->latest()->get(); + $profit_option = Option::where('key', 'business-settings') + ->where('value', 'LIKE', '%"business_id":' . $business_id . '%') + ->get() + ->firstWhere('value.business_id', $business_id) + ->value['product_profit_option'] ?? ''; + // Add purchase details to the cart foreach ($purchase->details as $detail) { $stock = Stock::where('id', $detail->stock_id)->first(); @@ -369,19 +478,23 @@ public function edit($id) 'product_unit_name' => $detail->product->unit->unitName ?? '', 'product_image' => $detail->product->productPicture ?? '', 'sales_price' => $detail->productSalePrice, + 'profit_percent' => $detail->profit_percent, 'whole_sale_price' => $detail->productWholeSalePrice, 'dealer_price' => $detail->productDealerPrice, 'batch_no' => $detail->stock->batch_no ?? null, 'expire_date' => $detail->expire_date, 'variant_name' => $stock->variant_name ?? null, 'warehouse_id' => $stock->warehouse_id ?? null, + 'price_without_tax' => $detail->price_without_tax ?? 0, + 'serial_numbers' => $detail->serial_numbers ?? null, + 'has_serial' => $detail->product->has_serial ?? null, ], ]); } - $cart_contents = Cart::content()->filter(fn($item) => $item->options->type == 'purchase'); + $cart_contents = Cart::content()->filter(fn ($item) => $item->options->type == 'purchase'); - return view('business::purchases.edit', compact('purchase', 'suppliers', 'products', 'cart_contents', 'categories', 'brands', 'vats', 'payment_types', 'warehouses')); + return view('business::purchases.edit', compact('purchase', 'suppliers', 'products', 'cart_contents', 'categories', 'brands', 'vats', 'payment_types', 'warehouses', 'profit_option')); } public function update(Request $request, $id) @@ -398,9 +511,8 @@ public function update(Request $request, $id) ]); $user = auth()->user(); - $branch_id = $user->branch_id ?? $user->active_branch_id; - - $carts = Cart::content()->filter(fn($item) => $item->options->type === 'purchase'); + $business = Business::findOrFail($user->business_id); + $carts = Cart::content()->filter(fn ($item) => $item->options->type === 'purchase'); if ($carts->count() < 1) { return response()->json(['message' => __('Cart is empty. Add items first!')], 400); @@ -411,34 +523,34 @@ public function update(Request $request, $id) $purchase = Purchase::with('details')->findOrFail($id); // Calculate amounts - $subtotal = $carts->sum(fn($cartItem) => (float)$cartItem->subtotal); + $subtotal = $carts->sum(fn ($cartItem) => (float) $cartItem->subtotal); - // VAT + // Discount (applied first on subtotal) + $discountAmount = $request->discountAmount ?? 0; + if ($request->discount_type == 'percent') { + $discountAmount = ($subtotal * $discountAmount) / 100; + } + if ($discountAmount > $subtotal) { + return response()->json([ + 'message' => __('Discount cannot be more than subtotal!'), + ], 400); + } + + // Subtotal after discount (before VAT) + $subtotalAfterDiscount = $subtotal - $discountAmount; + + // VAT (applied on subtotal after discount) $vat = Vat::find($request->vat_id); $vatAmount = 0; if ($vat) { - $vatAmount = ($subtotal * $vat->rate) / 100; - } - - // Subtotal with VAT - $subtotalWithVat = $subtotal + $vatAmount; - - // Discount - $discountAmount = $request->discountAmount ?? 0; - 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); + $vatAmount = ($subtotalAfterDiscount * $vat->rate) / 100; } // Shipping Charge $shippingCharge = $request->shipping_charge ?? 0; // Total Amount - $totalAmount = $subtotalWithVat - $discountAmount + $shippingCharge; + $totalAmount = $subtotalAfterDiscount + $vatAmount + $shippingCharge; // Receive, Change, Due Amount Calculation $receiveAmount = $request->receive_amount ?? 0; @@ -471,48 +583,132 @@ public function update(Request $request, $id) // Update purchase details $purchase->update($request->except('discountAmount', 'discount_type', 'discount_percent', 'shipping_charge') + [ - 'business_id' => $user->business_id, - 'user_id' => $user->id, - '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' => $totalAmount, - 'paidAmount' => $paidAmount, - 'change_amount' => $changeAmount, - 'dueAmount' => $dueAmount, - 'payment_type_id' => $request->payment_type_id, - 'shipping_charge' => $shippingCharge, - 'purchaseDate' => $request->purchaseDate ? Carbon::parse($request->purchaseDate)->setTimeFromTimeString(now()->format('H:i:s')) : now(), - 'isPaid' => $dueAmount > 0 ? 0 : 1, - ]); + 'business_id' => $user->business_id, + 'user_id' => $user->id, + 'vat_id' => $request->vat_id, + 'vat_amount' => $vatAmount, + 'discountAmount' => $discountAmount, + 'totalAmount' => $totalAmount, + 'paidAmount' => $paidAmount, + 'change_amount' => $changeAmount, + 'dueAmount' => $dueAmount, + 'isPaid' => $dueAmount > 0 ? 0 : 1, + 'shipping_charge' => $shippingCharge, + 'payment_type_id' => $request->payment_type_id, + 'discount_type' => $request->discount_type ?? 'flat', + 'discount_percent' => $request->discount_type == 'percent' ? $request->discountAmount : 0, + 'purchaseDate' => $request->purchaseDate ? Carbon::parse($request->purchaseDate)->setTimeFromTimeString(now()->format('H:i:s')) : now(), + ]); // Revert previous stock changes foreach ($purchase->details as $detail) { - Stock::where('id', $detail->stock_id)->decrement('productStock', $detail->quantities); + $stock = Stock::find($detail->stock_id); + if ($stock) { + if (! empty($detail->serial_numbers)) { + $stock->serial_numbers = array_values(array_diff($stock->serial_numbers, $detail->serial_numbers)); + $stock->productStock = count($stock->serial_numbers); + } else { + $stock->productStock -= $detail->quantities; + } + $stock->save(); + } } + // Store old purchase details before deleting + $oldDetails = $purchase->details->keyBy('product_id'); + // Delete existing purchase details - $purchase->details()->delete(); + $prev_details_ids = $oldDetails->pluck('id')->toArray(); + PurchaseDetails::where('purchase_id', $purchase->id)->delete(); + + // remove previous vat transactions + VatTransaction::where('vatable_id', $prev_details_ids) + ->where('vatable_type', PurchaseDetails::class) + ->delete(); // Insert updated purchase details and adjust stock - $purchaseDetailsData = []; foreach ($carts as $cartItem) { - $batch_no = $cartItem->options['batch_no'] ?? NULL; - $expire_date = $cartItem->options['expire_date'] ?? NULL; - $dealer_price = $cartItem->options['dealer_price'] ?? 0.0; - $sales_price = $cartItem->options['sales_price'] ?? 0.0; - $whole_sale_price = $cartItem->options['whole_sale_price'] ?? 0.0; + $product = Product::findOrFail($cartItem->id); + $batch_no = $cartItem->options['batch_no'] ?? null; + $expire_date = $cartItem->options['expire_date'] ?? null; $variantName = $cartItem->options['variant_name'] ?? null; + $dealer_price = $cartItem->options['dealer_price'] ?? 0.0; + $whole_sale_price = $cartItem->options['whole_sale_price'] ?? 0.0; + $existingStock = Stock::where(['batch_no' => $batch_no, 'product_id' => $cartItem->id]) - ->when($variantName, fn($q) => $q->where('variant_name', $variantName)) + ->when($variantName, fn ($q) => $q->where('variant_name', $variantName)) ->first(); $warehouseId = $cartItem->options['warehouse_id'] ?? ($existingStock->warehouse_id ?? null); + // serial logic + $newSerials = $cartItem->options['serial_numbers'] ?? []; + $purchaseSerials = ! empty($newSerials) ? array_values($newSerials) : null; + $existingSerials = $existingStock?->serial_numbers ?? []; + + // get previous purchase serials + $oldPurchaseSerials = $oldDetails[$cartItem->id]->serial_numbers ?? []; + + $remainingSerials = array_diff($existingSerials, $oldPurchaseSerials); + + $mergedSerials = ! empty($newSerials) + ? array_values(array_unique(array_merge($remainingSerials, $newSerials))) + : array_values($remainingSerials); + + if (! empty($newSerials)) { + // Serial Product + $stockQty = count($mergedSerials); + $purchaseQty = count($newSerials); + + } else { + // Non Serial Product + $stockQty = ($existingStock->productStock ?? 0) + $cartItem->qty; + $purchaseQty = $cartItem->qty; + } + + // Price Calculation + + $purchasePrice = $cartItem->price; + // Exclusive Price + $exclusive_price = $purchasePrice; + $vatRate = 0; + if ($product->vat_id) { + $vat = Vat::find($product->vat_id); + $vatRate = $vat?->rate ?? 0; + if ($vatRate > 0) { + $exclusive_price = round($purchasePrice / (1 + ($vatRate / 100)), 3); + } + } + + $isSerialProduct = ! empty($newSerials); + + if ($isSerialProduct) { + // For serial products, use cart sales_price directly + $salesPrice = $cartItem->options['sales_price'] ?? $purchasePrice; + $profitPercent = 0; + } else { + $profitPercent = $cartItem->options['profit_percent'] ?? 0; + + $profitOption = Option::where('key', 'business-settings') + ->where('value', 'LIKE', '%"business_id":' . $business->id . '%') + ->get() + ->firstWhere('value.business_id', $business->id) + ->value['product_profit_option'] ?? 'markup'; + + if ($profitOption === 'margin') { + $salesPrice = $exclusive_price / (1 - ($profitPercent / 100)); + } else { + $salesPrice = $exclusive_price + ($exclusive_price * $profitPercent / 100); + } + + if ($product->vat_type === 'inclusive' && $vatRate > 0) { + $salesPrice = $salesPrice * (1 + ($vatRate / 100)); + } + $salesPrice = round($salesPrice, 3); + } + // update or create stock $stock = Stock::updateOrCreate( [ @@ -524,37 +720,35 @@ public function update(Request $request, $id) [ 'product_id' => $cartItem->id, 'expire_date' => $expire_date, - 'productSalePrice' => $sales_price, 'productDealerPrice' => $dealer_price, - 'productPurchasePrice' => $cartItem->price, + 'productPurchasePrice' => $purchasePrice, + 'exclusive_price' => $exclusive_price, + 'profit_percent' => $profitPercent, + 'productSalePrice' => $salesPrice, 'productWholeSalePrice' => $whole_sale_price, - 'productStock' => $cartItem->qty + ($existingStock->productStock ?? 0), + 'productStock' => $stockQty, 'warehouse_id' => $warehouseId, + 'serial_numbers' => $mergedSerials, ] ); - $purchaseDetailsData[] = [ + $detail = PurchaseDetails::create([ 'purchase_id' => $purchase->id, 'product_id' => $cartItem->id, - 'quantities' => $cartItem->qty, - 'productPurchasePrice' => $cartItem->price, - 'productDealerPrice' => $dealer_price, + 'quantities' => $purchaseQty, 'stock_id' => $stock->id, 'expire_date' => $expire_date, - 'productSalePrice' => $sales_price, + 'productPurchasePrice' => $purchasePrice, + 'profit_percent' => $profitPercent, + 'productSalePrice' => $salesPrice, + 'serial_numbers' => $purchaseSerials, + 'productDealerPrice' => $dealer_price, 'productWholeSalePrice' => $whole_sale_price, - ]; - } + ]); - PurchaseDetails::insert($purchaseDetailsData); - - foreach ($purchaseDetailsData as $item) { - $product = Product::findOrFail($item['product_id']); - $product->update([ - 'productSalePrice' => $item['productSalePrice'] ?? $product->productSalePrice, - 'productDealerPrice' => $item['productDealerPrice'] ?? $product->productDealerPrice, - 'productPurchasePrice' => $item['productPurchasePrice'] ?? $product->productPurchasePrice, - 'productWholeSalePrice' => $item['productWholeSalePrice'] ?? $product->productWholeSalePrice, + $vatAmount = PurchaseVatTransaction::storeVatTransactions($business, $detail, $product, $party ?? null); + $detail->update([ + 'price_without_tax' => number_format($purchasePrice - ($vatAmount / $purchaseQty), 3, '.', ''), ]); } @@ -609,15 +803,20 @@ public function update(Request $request, $id) sendNotifyToUser($purchase->id, route('business.purchases.index', ['id' => $purchase->id]), __('Purchase has been updated.'), $user->business_id); + $tax_invoice = moduleCheck('TaxInvoiceAddon') && invoice_setting($purchase->business_id) == 'standard_a4'; + $invoice_route = $tax_invoice ? route('business.purchases.tax.invoice', $purchase->id) : route('business.purchases.invoice', $purchase->id); + DB::commit(); + return response()->json([ 'message' => __('Purchase updated successfully.'), 'redirect' => route('business.purchases.index'), - 'secondary_redirect_url' => route('business.purchases.invoice', $purchase->id), + 'secondary_redirect_url' => $invoice_route, ]); } catch (\Exception $e) { DB::rollback(); - return response()->json(['message' => __('Something went wrong!')], 500); + + return response()->json(['message' => $e->getMessage()], 500); } } @@ -628,13 +827,17 @@ public function destroy($id) $purchase = Purchase::with('details')->findOrFail($id); foreach ($purchase->details as $detail) { + VatTransaction::where('vatable_id', $detail->id) + ->where('vatable_type', get_class($detail)) + ->delete(); + Stock::where('id', $detail->stock_id)->decrement('productStock', $detail->quantities); } if ($purchase->party_id) { $party = Party::findOrFail($purchase->party_id); $party->update([ - 'due' => $party->due - $purchase->dueAmount + 'due' => $party->due - $purchase->dueAmount, ]); } @@ -651,11 +854,12 @@ public function destroy($id) return response()->json([ 'message' => __('Purchase deleted successfully.'), - 'redirect' => route('business.purchases.index') + 'redirect' => route('business.purchases.index'), ]); } catch (\Exception $e) { DB::rollback(); - return response()->json(['message' => __('Something went wrong!')], 404); + + return response()->json(['message' => $e->getMessage()], 404); } } @@ -664,21 +868,25 @@ public function deleteAll(Request $request) DB::beginTransaction(); try { - $purchases = Purchase::whereIn('id', $request->ids)->get(); + $purchases = Purchase::where('business_id', auth()->user()->business_id)->whereIn('id', $request->ids)->get(); foreach ($purchases as $purchase) { foreach ($purchase->details as $detail) { + VatTransaction::where('vatable_id', $detail->id) + ->where('vatable_type', get_class($detail)) + ->delete(); + Stock::where('id', $detail->stock_id)->decrement('productStock', $detail->quantities); } if ($purchase->party_id) { $party = Party::findOrFail($purchase->party_id); $party->update([ - 'due' => $party->due - $purchase->dueAmount + 'due' => $party->due - $purchase->dueAmount, ]); } - updateBalance($purchases->paidAmount, 'decrement'); + updateBalance($purchase->paidAmount, 'increment'); sendNotifyToUser($purchase->id, route('business.purchases.index', ['id' => $purchase->id]), __('Purchases has been deleted.'), $purchase->business_id); @@ -692,11 +900,12 @@ public function deleteAll(Request $request) return response()->json([ 'message' => __('Selected purchases deleted successfully.'), - 'redirect' => route('business.purchases.index') + 'redirect' => route('business.purchases.index'), ]); } catch (\Exception $e) { DB::rollback(); - return response()->json(['message' => __('Something went wrong!')], 404); + + return response()->json(['message' => $e->getMessage()], 404); } } @@ -746,16 +955,16 @@ public function acnooFilter(Request $request) // Search Filter if ($request->filled('search')) { $purchasesQuery->where(function ($query) use ($request) { - $query->where('paymentType', 'like', '%' . $request->search . '%') - ->orWhere('invoiceNumber', 'like', '%' . $request->search . '%') + $query->where('paymentType', 'like', '%'.$request->search.'%') + ->orWhere('invoiceNumber', 'like', '%'.$request->search.'%') ->orWhereHas('party', function ($q) use ($request) { - $q->where('name', 'like', '%' . $request->search . '%'); + $q->where('name', 'like', '%'.$request->search.'%'); }) ->orWhereHas('payment_type', function ($q) use ($request) { - $q->where('name', 'like', '%' . $request->search . '%'); + $q->where('name', 'like', '%'.$request->search.'%'); }) ->orWhereHas('branch', function ($q) use ($request) { - $q->where('name', 'like', '%' . $request->search . '%'); + $q->where('name', 'like', '%'.$request->search.'%'); }); }); } @@ -765,7 +974,7 @@ public function acnooFilter(Request $request) if ($request->ajax()) { return response()->json([ - 'data' => view('business::purchases.datas', compact('purchases', 'purchasesWithReturns'))->render() + 'data' => view('business::purchases.datas', compact('purchases', 'purchasesWithReturns'))->render(), ]); } @@ -778,8 +987,8 @@ public function productFilter(Request $request) $products = Product::where('business_id', $business_id) ->when($request->search, function ($query) use ($request) { $query->where(function ($q) use ($request) { - $q->where('productName', 'like', '%' . $request->search . '%') - ->orWhere('productCode', 'like', '%' . $request->search . '%'); + $q->where('productName', 'like', '%'.$request->search.'%') + ->orWhere('productCode', 'like', '%'.$request->search.'%'); }); }) ->when($request->category_id, function ($query) use ($request) { @@ -795,14 +1004,14 @@ public function productFilter(Request $request) // Query categories for search options $categories = Category::where('business_id', $business_id) ->when($request->search, function ($query) use ($request) { - $query->where('categoryName', 'like', '%' . $request->search . '%'); + $query->where('categoryName', 'like', '%'.$request->search.'%'); }) ->get(); // Query brands for search options $brands = Brand::where('business_id', $business_id) ->when($request->search, function ($query) use ($request) { - $query->where('brandName', 'like', '%' . $request->search . '%'); + $query->where('brandName', 'like', '%'.$request->search.'%'); }) ->get(); @@ -827,7 +1036,7 @@ 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 . '%'); + $query->where('categoryName', 'like', '%'.$search.'%'); }) ->get(); @@ -842,7 +1051,7 @@ 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 . '%'); + $query->where('brandName', 'like', '%'.$search.'%'); }) ->get(); @@ -851,90 +1060,30 @@ public function brandFilter(Request $request) ]); } - public function getInvoice($purchase_id) + public function getInvoice($purchase_id, PurchaseInvoiceService $service) { - $purchase = Purchase::with('user:id,name,role', 'party:id,name,phone,address', 'business:id,phoneNumber,companyName,vat_name,vat_no,address,email,meta', 'details:id,productPurchasePrice,quantities,product_id,purchase_id', 'details.stock:id,batch_no', 'details.product:id,productName', 'payment_type:id,name') - ->findOrFail($purchase_id); + $data = $service->getInvoiceData($purchase_id); - $transactionTypes = $purchase->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(', '); + return view('business::purchases.invoice', $data); + } - $purchase_returns = PurchaseReturn::with('purchase:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber', 'purchase.party:id,name', 'details', 'details.purchaseDetail.product:id,productName', 'details.purchaseDetail.stock:id,batch_no') - ->where('business_id', auth()->user()->business_id) - ->where('purchase_id', $purchase_id) - ->latest() - ->get(); + public function getTaxInvoice($purchase_id, PurchaseTaxInvoiceService $service) + { + $data = $service->getInvoiceData($purchase_id); - $returnTransaction = $purchase_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 - $purchase->details = $purchase->details->map(function ($detail) use ($purchase_returns) { - $return_qty_sum = $purchase_returns->flatMap(function ($return) use ($detail) { - return $return->details->where('purchaseDetail.id', $detail->id)->pluck('return_qty'); - })->sum(); - - $detail->quantities = $detail->quantities + $return_qty_sum; - - return $detail; - }); - - // Calculate total discount based on return quantities and amounts - $total_discount = 0; - $product_discounts = []; - - foreach ($purchase_returns as $return) { - foreach ($return->details as $detail) { - // Initialize discount tracking for the first occurrence of each purchase_detail_id - if (!isset($product_discounts[$detail->purchase_detail_id])) { - $product_discounts[$detail->purchase_detail_id] = [ - 'return_qty' => 0, - 'return_amount' => 0, - 'price' => $detail->purchaseDetail->productPurchasePrice, - ]; - } - - // Accumulate return quantities and return amounts - $product_discounts[$detail->purchase_detail_id]['return_qty'] += $detail->return_qty; - $product_discounts[$detail->purchase_detail_id]['return_amount'] += $detail->return_amount; - } - } - - // Calculate the total discount for each returned product - foreach ($product_discounts as $data) { - $product_price = $data['price'] * $data['return_qty']; - $discount = $product_price - $data['return_amount']; - - $total_discount += $discount; - } - - $bank_detail = PaymentType::where('business_id', auth()->user()->business_id)->where('show_in_invoice', 1)->latest()->first(); - - return view('business::purchases.invoice', compact('purchase', 'purchase_returns', 'total_discount', 'transactionTypes', 'bank_detail', 'returnTransactionType')); + return view('taxinvoiceaddon::purchases.tax', $data); } public function showPurchaseCart() { - $cart_contents = Cart::content()->filter(fn($item) => $item->options->type == 'purchase'); + $cart_contents = Cart::content()->filter(fn ($item) => $item->options->type == 'purchase'); + return view('business::purchases.cart-list', compact('cart_contents')); } public function getCartData() { - $cart_contents = Cart::content()->filter(fn($item) => $item->options->type == 'purchase'); + $cart_contents = Cart::content()->filter(fn ($item) => $item->options->type == 'purchase'); $data['sub_total'] = 0; foreach ($cart_contents as $cart) { @@ -970,13 +1119,13 @@ public function generatePDF($purchase_id) * Purchase returns */ $purchase_returns = PurchaseReturn::with( - 'purchase:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber', - 'purchase.party:id,name', - 'details', - 'details.purchaseDetail:id,productPurchasePrice,product_id,stock_id', - 'details.purchaseDetail.product:id,productName', - 'details.purchaseDetail.stock:id,batch_no' - ) + 'purchase:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber', + 'purchase.party:id,name', + 'details', + 'details.purchaseDetail:id,productPurchasePrice,product_id,stock_id', + 'details.purchaseDetail.product:id,productName', + 'details.purchaseDetail.stock:id,batch_no' + ) ->where('business_id', auth()->user()->business_id) ->where('purchase_id', $purchase_id) ->latest() @@ -1006,6 +1155,7 @@ public function generatePDF($purchase_id) })->sum(); $detail->quantities += $return_qty_sum; + return $detail; }); @@ -1017,7 +1167,7 @@ public function generatePDF($purchase_id) foreach ($purchase_returns as $return) { foreach ($return->details as $detail) { - if (!isset($product_discounts[$detail->purchase_detail_id])) { + if (! isset($product_discounts[$detail->purchase_detail_id])) { $product_discounts[$detail->purchase_detail_id] = [ 'return_qty' => 0, 'return_amount' => 0, @@ -1059,6 +1209,150 @@ public function generatePDF($purchase_id) ); } + public function generateTaxPDF($purchase_id) + { + $purchase = Purchase::where('business_id', auth()->user()->business_id) + ->with( + 'user:id,name,role', 'party:id,name,phone,address,email', 'business:id,phoneNumber,companyName,vat_name,vat_no,address,email,meta', 'details:id,price_without_tax,productPurchasePrice,quantities,product_id,purchase_id,serial_numbers', 'details.stock:id,batch_no', 'details.product:id,vat_id,productName,has_serial', 'payment_type:id,name' + ) + ->findOrFail($purchase_id); + + $business_id = auth()->check() ? auth()->user()->business_id : null; + + /** + * Transaction types (same helper used in Sale) + */ + $detail_ids = $purchase->details->pluck('id'); + $tax_items = VatTransaction::selectRaw('vat_id, vat_rate, SUM(vat_amount) as vat_amount') + ->with('vat:id,name') + ->whereIn('vatable_id', $detail_ids) + ->where('vatable_type', PurchaseDetails::class) + ->groupBy('vat_id', 'vat_rate') + ->get(); + + $vatRowMap = VatTransaction::whereIn('vatable_id', $detail_ids) + ->where('vatable_type', PurchaseDetails::class) + ->selectRaw('vatable_id, vat_id, SUM(vat_amount) as total') + ->groupBy('vatable_id', 'vat_id') + ->get() + ->groupBy('vatable_id') + ->map(function ($items) { + return $items->pluck('total', 'vat_id')->toArray(); + }) + ->toArray(); + + $transactionTypes = transaction_types($purchase->transactions); + + /** + * Purchase returns + */ + $purchase_returns = PurchaseReturn::with( + 'purchase:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber', 'purchase.party:id,name,email', 'details', 'details.purchaseDetail.product:id,productName,has_serial', 'details.purchaseDetail.stock:id,batch_no' + ) + ->where('business_id', auth()->user()->business_id) + ->where('purchase_id', $purchase_id) + ->latest() + ->get(); + + // collect return detail ids + $return_detail_ids = $purchase_returns->flatMap(function ($return) { + return $return->details->pluck('id'); + }); + + // get VAT for purchase return details + $returnVatRowMap = VatTransaction::whereIn('vatable_id', $return_detail_ids) + ->where('vatable_type', PurchaseReturnDetail::class) + ->selectRaw('vatable_id, vat_id, SUM(vat_amount) as total') + ->groupBy('vatable_id', 'vat_id') + ->get() + ->groupBy('vatable_id') + ->map(function ($items) { + return $items->pluck('total', 'vat_id')->toArray(); + }) + ->toArray(); + + /** + * Return transaction type (same logic as Sale) + */ + $returnTransaction = $purchase_returns->first()?->transactions->first(); + $returnTransactionType = ''; + + if ($returnTransaction) { + $returnTransactionType = + $returnTransaction->transaction_type === 'bank_payment' + ? ($returnTransaction->paymentType?->name) + : ucfirst(explode('_', $returnTransaction->transaction_type)[0]); + } + + /** + * Merge return qty into purchase details (same as Sale) + */ + $purchase->details = $purchase->details->map(function ($detail) use ($purchase_returns) { + $return_qty_sum = $purchase_returns->flatMap(function ($return) use ($detail) { + return $return->details + ->where('purchaseDetail.id', $detail->id) + ->pluck('return_qty'); + })->sum(); + + $detail->quantities += $return_qty_sum; + + return $detail; + }); + + /** + * Discount calculation from returned items (same pattern as Sale) + */ + $total_discount = 0; + $product_discounts = []; + + foreach ($purchase_returns as $return) { + foreach ($return->details as $detail) { + if (! isset($product_discounts[$detail->purchase_detail_id])) { + $product_discounts[$detail->purchase_detail_id] = [ + 'return_qty' => 0, + 'return_amount' => 0, + 'price' => $detail->purchaseDetail->productPurchasePrice, + ]; + } + + $product_discounts[$detail->purchase_detail_id]['return_qty'] += $detail->return_qty; + $product_discounts[$detail->purchase_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']); + } + + /** + * Bank / Payment info + */ + $bank_detail = PaymentType::where('business_id', auth()->user()->business_id) + ->latest() + ->first(); + + /** + * CENTRALIZED PDF RENDER (same as Sale) + */ + return PdfService::render( + 'taxinvoiceaddon::purchases.pdf', + compact( + 'purchase', + 'purchase_returns', + 'total_discount', + 'transactionTypes', + 'returnTransactionType', + 'bank_detail', + 'tax_items', + 'business_id', + 'vatRowMap', + 'returnVatRowMap' + ), + 'purchase-tax-invoice.pdf' + ); + } + public function sendMail($purchase_id) { $purchase = Purchase::with('user:id,name,role', 'party:id,name,phone,address', 'business:id,phoneNumber,companyName,vat_name,vat_no,address,email,meta', 'details:id,productPurchasePrice,quantities,product_id,purchase_id', 'details.stock:id,batch_no', 'details.product:id,productName', 'payment_type:id,name') @@ -1066,9 +1360,10 @@ public function sendMail($purchase_id) $transactionTypes = $purchase->transactions ->map(function ($transaction) { - if ($transaction->transaction_type === 'bank_payment' && !empty($transaction->paymentType?->name)) { + 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() @@ -1107,7 +1402,7 @@ public function sendMail($purchase_id) foreach ($purchase_returns as $return) { foreach ($return->details as $detail) { // Initialize discount tracking for the first occurrence of each purchase_detail_id - if (!isset($product_discounts[$detail->purchase_detail_id])) { + if (! isset($product_discounts[$detail->purchase_detail_id])) { $product_discounts[$detail->purchase_detail_id] = [ 'return_qty' => 0, 'return_amount' => 0, @@ -1148,10 +1443,137 @@ public function sendMail($purchase_id) ]); } + public function sendTaxMail($purchase_id) + { + $purchase = Purchase::with('user:id,name,role', 'party:id,name,phone,address,email', 'business:id,phoneNumber,companyName,vat_name,vat_no,address,email,meta', 'details:id,price_without_tax,productPurchasePrice,quantities,product_id,purchase_id,serial_numbers', 'details.stock:id,batch_no', 'details.product:id,vat_id,productName,has_serial', 'payment_type:id,name') + ->findOrFail($purchase_id); + + $business_id = auth()->check() ? auth()->user()->business_id : null; + + $detail_ids = $purchase->details->pluck('id'); + $tax_items = VatTransaction::selectRaw('vat_id, vat_rate, SUM(vat_amount) as vat_amount') + ->with('vat:id,name') + ->whereIn('vatable_id', $detail_ids) + ->where('vatable_type', PurchaseDetails::class) + ->groupBy('vat_id', 'vat_rate') + ->get(); + + $vatRowMap = VatTransaction::whereIn('vatable_id', $detail_ids) + ->where('vatable_type', PurchaseDetails::class) + ->selectRaw('vatable_id, vat_id, SUM(vat_amount) as total') + ->groupBy('vatable_id', 'vat_id') + ->get() + ->groupBy('vatable_id') + ->map(function ($items) { + return $items->pluck('total', 'vat_id')->toArray(); + }) + ->toArray(); + + $transactionTypes = $purchase->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(', '); + + $purchase_returns = PurchaseReturn::with('purchase:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber', 'purchase.party:id,name,email', 'details', 'details.purchaseDetail.product:id,productName,has_serial', 'details.purchaseDetail.stock:id,batch_no') + ->where('business_id', auth()->user()->business_id) + ->where('purchase_id', $purchase_id) + ->latest() + ->get(); + + $return_detail_ids = $purchase_returns->flatMap(function ($return) { + return $return->details->pluck('id'); + }); + + // get VAT for purchase return details + $returnVatRowMap = VatTransaction::whereIn('vatable_id', $return_detail_ids) + ->where('vatable_type', PurchaseReturnDetail::class) + ->selectRaw('vatable_id, vat_id, SUM(vat_amount) as total') + ->groupBy('vatable_id', 'vat_id') + ->get() + ->groupBy('vatable_id') + ->map(function ($items) { + return $items->pluck('total', 'vat_id')->toArray(); + }) + ->toArray(); + + $returnTransaction = $purchase_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 + $purchase->details = $purchase->details->map(function ($detail) use ($purchase_returns) { + $return_qty_sum = $purchase_returns->flatMap(function ($return) use ($detail) { + return $return->details->where('purchaseDetail.id', $detail->id)->pluck('return_qty'); + })->sum(); + + $detail->quantities = $detail->quantities + $return_qty_sum; + + return $detail; + }); + + // Calculate total discount based on return quantities and amounts + $total_discount = 0; + $product_discounts = []; + + foreach ($purchase_returns as $return) { + foreach ($return->details as $detail) { + // Initialize discount tracking for the first occurrence of each purchase_detail_id + if (! isset($product_discounts[$detail->purchase_detail_id])) { + $product_discounts[$detail->purchase_detail_id] = [ + 'return_qty' => 0, + 'return_amount' => 0, + 'price' => $detail->purchaseDetail->productPurchasePrice, + ]; + } + + // Accumulate return quantities and return amounts + $product_discounts[$detail->purchase_detail_id]['return_qty'] += $detail->return_qty; + $product_discounts[$detail->purchase_detail_id]['return_amount'] += $detail->return_amount; + } + } + + // Calculate the total discount for each returned product + foreach ($product_discounts as $data) { + $product_price = $data['price'] * $data['return_qty']; + $discount = $product_price - $data['return_amount']; + + $total_discount += $discount; + } + + $bank_detail = PaymentType::where('business_id', auth()->user()->business_id)->where('show_in_invoice', 1)->latest()->first(); + + $pdf = Pdf::loadView('taxinvoiceaddon::purchases.pdf', compact('purchase', 'purchase_returns', 'total_discount', 'transactionTypes', 'bank_detail', 'returnTransactionType', 'tax_items', 'business_id', 'vatRowMap', 'returnVatRowMap')); + + // Send email with PDF attachment + Mail::raw('Please find attached your Purchase invoice.', function ($message) use ($pdf) { + $message->to(auth()->user()->email) + ->subject('Purchase Invoice') + ->attachData($pdf->output(), 'purchase-tax-invoice.pdf', [ + 'mime' => 'application/pdf', + ]); + }); + + return response()->json([ + 'message' => __('Email Sent Successfully.'), + 'redirect' => route('business.purchases.index'), + ]); + } + public function createSupplier(Request $request) { $request->validate([ - 'phone' => 'nullable|max:20|' . Rule::unique('parties')->where('business_id', auth()->user()->business_id), + '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', @@ -1161,14 +1583,14 @@ public function createSupplier(Request $request) ]); Party::create($request->except('image', 'due') + [ - 'due' => $request->due ?? 0, - 'image' => $request->image ? $this->upload($request, 'image') : NULL, - 'business_id' => auth()->user()->business_id - ]); + 'due' => $request->due ?? 0, + 'image' => $request->image ? $this->upload($request, 'image') : null, + 'business_id' => auth()->user()->business_id, + ]); return response()->json([ 'message' => __('Supplier created successfully'), - 'redirect' => route('business.purchases.create') + 'redirect' => route('business.purchases.create'), ]); } @@ -1180,7 +1602,7 @@ public function bulkIndex() public function bulkCartStore(Request $request) { $request->validate([ - 'file' => 'required|file|mimes:xlsx,xls,csv' + 'file' => 'required|file|mimes:xlsx,xls,csv', ]); $businessId = auth()->user()->business_id; @@ -1198,7 +1620,7 @@ public function bulkCartStore(Request $request) return response()->json([ 'success' => true, 'message' => __('Bulk upload successfully.'), - 'redirect' => route('business.purchases.create') + 'redirect' => route('business.purchases.create'), ]); } diff --git a/Modules/Business/App/Http/Controllers/AcnooPurchaseReportController.php b/Modules/Business/App/Http/Controllers/AcnooPurchaseReportController.php index 1c6a8db6..b2e30939 100644 --- a/Modules/Business/App/Http/Controllers/AcnooPurchaseReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooPurchaseReportController.php @@ -26,10 +26,6 @@ public function index(Request $request) { $businessId = auth()->user()->business_id; - $total_purchase = Purchase::where('business_id', $businessId) - ->whereDate('purchaseDate', Carbon::today()) - ->sum('totalAmount'); - $purchasesQuery = Purchase::with('user:id,name', 'party:id,name,email,phone,type', 'payment_type:id,name', 'branch:id,name', 'transactions') ->where('business_id', $businessId); @@ -89,11 +85,38 @@ public function exportCsv() public function exportPdf(Request $request) { - $purchases = Purchase::with('user:id,name', 'party:id,name,email,phone,type', 'payment_type:id,name', 'branch:id,name', 'transactions') - ->where('business_id', auth()->user()->business_id) - ->latest() - ->get(); + $purchasesQuery = Purchase::with('user:id,name', 'party:id,name,email,phone,type', 'payment_type:id,name', 'branch:id,name', 'transactions') + ->where('business_id', auth()->user()->business_id); - return PdfService::render('business::reports.purchase.pdf', compact('purchases'), 'purchases-report.pdf'); + $purchasesQuery->when($request->branch_id, function ($q) use ($request) { + $q->where('branch_id', $request->branch_id); + }); + + // Date Filter + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + $this->applyDateFilter($purchasesQuery, $duration, 'purchaseDate', $request->from_date, $request->to_date); + + // Search Filter + if ($request->filled('search')) { + $purchasesQuery->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 . '%'); + }); + }); + } + + $purchases = $purchasesQuery->latest()->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::reports.purchase.pdf', compact('purchases', 'duration', 'filter_from_date', 'filter_to_date'), 'purchases-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooPurchaseReturnReportController.php b/Modules/Business/App/Http/Controllers/AcnooPurchaseReturnReportController.php index 75bedc30..1596811c 100644 --- a/Modules/Business/App/Http/Controllers/AcnooPurchaseReturnReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooPurchaseReturnReportController.php @@ -6,7 +6,6 @@ use App\Models\Purchase; use Illuminate\Http\Request; use App\Http\Controllers\Controller; -use App\Models\PurchaseReturnDetail; use App\Services\PdfService; use App\Traits\DateFilterTrait; use App\Traits\DateRangeTrait; @@ -26,12 +25,6 @@ public function index(Request $request) { $businessId = auth()->user()->business_id; - $total_purchase_return = PurchaseReturnDetail::whereHas('purchaseReturn', function ($query) use ($businessId) { - $query->whereHas('purchase', function ($q) use ($businessId) { - $q->where('business_id', $businessId); - }); - })->sum('return_amount'); - $purchasesQuery = Purchase::with([ 'user:id,name', 'branch:id,name', @@ -54,7 +47,9 @@ public function index(Request $request) $duration = $request->custom_days ?: 'today'; [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); - $this->applyDateFilter($purchasesQuery, $duration, 'purchaseDate', $request->from_date, $request->to_date); + $purchasesQuery->whereHas('purchaseReturns', function ($query) use ($duration, $request) { + $this->applyDateFilter($query, $duration, 'return_date', $request->from_date, $request->to_date); + }); // Search Filter if ($request->filled('search')) { @@ -69,17 +64,19 @@ public function index(Request $request) }); } - // Calculate Total Purchase Return Amount in the Selected Date Range - $total_purchase_return = PurchaseReturnDetail::whereHas('purchaseReturn', function ($query) use ($businessId) { - $query->whereHas('purchase', function ($q) use ($businessId) { - $q->where('business_id', $businessId); - }); - })->sum('return_amount'); - // Pagination $perPage = $request->input('per_page', 20); $purchases = $purchasesQuery->latest()->paginate($perPage)->appends($request->query()); + $total_purchase_return = (clone $purchasesQuery) + ->with('purchaseReturns.details') + ->get() + ->sum(function ($sale) { + return $sale->purchaseReturns->sum(function ($return) { + return $return->details->sum('return_amount'); + }); + }); + // Handle AJAX Request if ($request->ajax()) { return response()->json([ @@ -105,7 +102,7 @@ public function exportCsv() public function exportPdf(Request $request) { - $purchases = Purchase::with([ + $purchasesQuery = Purchase::with([ 'user:id,name', 'branch:id,name', 'party:id,name,email,phone,type', @@ -117,10 +114,35 @@ public function exportPdf(Request $request) } ]) ->where('business_id', auth()->user()->business_id) - ->whereHas('purchaseReturns') - ->latest() - ->get(); + ->whereHas('purchaseReturns'); - return PdfService::render('business::reports.purchase-return.pdf', compact('purchases'), 'purchase-return-report.pdf'); + $purchasesQuery->when($request->branch_id, function ($q) use ($request) { + $q->where('branch_id', $request->branch_id); + }); + + // Date Filter + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + $purchasesQuery->whereHas('purchaseReturns', function ($query) use ($duration, $request) { + $this->applyDateFilter($query, $duration, 'return_date', $request->from_date, $request->to_date); + }); + + // Search Filter + if ($request->filled('search')) { + $purchasesQuery->where(function ($query) use ($request) { + $query->where('invoiceNumber', 'like', '%' . $request->search . '%') + ->orWhereHas('party', function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%'); + }) + ->orWhereHas('branch', function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%'); + }); + }); + } + + $purchases = $purchasesQuery->latest()->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::reports.purchase-return.pdf', compact('purchases', 'duration', 'filter_from_date', 'filter_to_date'), 'purchase-return-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooSaleController.php b/Modules/Business/App/Http/Controllers/AcnooSaleController.php index 8de01143..0c61354b 100644 --- a/Modules/Business/App/Http/Controllers/AcnooSaleController.php +++ b/Modules/Business/App/Http/Controllers/AcnooSaleController.php @@ -2,35 +2,40 @@ namespace Modules\Business\App\Http\Controllers; -use Carbon\Carbon; -use App\Models\Vat; -use App\Models\Sale; -use App\Models\Brand; -use App\Models\Party; -use App\Models\Stock; -use App\Models\Branch; -use App\Models\Income; -use App\Events\SaleSms; -use App\Models\Expense; -use App\Models\Product; -use App\Models\Category; -use App\Models\Purchase; -use App\Models\Warehouse; -use App\Models\DueCollect; -use App\Models\SaleReturn; -use App\Models\PaymentType; -use App\Models\SaleDetails; -use App\Models\Transaction; -use App\Helpers\HasUploader; -use App\Services\PdfService; -use Illuminate\Http\Request; -use Barryvdh\DomPDF\Facade\Pdf; -use Illuminate\Validation\Rule; -use Illuminate\Support\Facades\DB; -use App\Http\Controllers\Controller; -use Illuminate\Support\Facades\Mail; use App\Events\MultiPaymentProcessed; +use App\Helpers\HasUploader; +use App\Http\Controllers\Controller; +use App\Models\Branch; +use App\Models\Brand; +use App\Models\Business; +use App\Models\Category; +use App\Models\DueCollect; +use App\Models\Expense; +use App\Models\Income; +use App\Models\Party; +use App\Models\PaymentType; +use App\Models\Product; +use App\Models\Purchase; +use App\Models\Sale; +use App\Models\SaleDetails; +use App\Models\SaleReturn; +use App\Models\SaleReturnDetails; +use App\Models\Stock; +use App\Models\Transaction; +use App\Models\Vat; +use App\Models\VatTransaction; +use App\Models\Warehouse; +use App\Services\Invoice\SaleInvoiceService; +use App\Services\Invoice\SaleTaxInvoiceService; +use App\Services\PdfService; +use App\Services\VatTransactionService; +use Barryvdh\DomPDF\Facade\Pdf; +use Carbon\Carbon; use Gloudemans\Shoppingcart\Facades\Cart; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Mail; +use Illuminate\Validation\Rule; class AcnooSaleController extends Controller { @@ -89,16 +94,16 @@ public function index(Request $request) // Search Filter if ($request->filled('search')) { $salesQuery->where(function ($query) use ($request) { - $query->where('paymentType', 'like', '%' . $request->search . '%') - ->orWhere('invoiceNumber', 'like', '%' . $request->search . '%') + $query->where('paymentType', 'like', '%'.$request->search.'%') + ->orWhere('invoiceNumber', 'like', '%'.$request->search.'%') ->orWhereHas('party', function ($q) use ($request) { - $q->where('name', 'like', '%' . $request->search . '%'); + $q->where('name', 'like', '%'.$request->search.'%'); }) ->orWhereHas('payment_type', function ($q) use ($request) { - $q->where('name', 'like', '%' . $request->search . '%'); + $q->where('name', 'like', '%'.$request->search.'%'); }) ->orWhereHas('branch', function ($q) use ($request) { - $q->where('name', 'like', '%' . $request->search . '%'); + $q->where('name', 'like', '%'.$request->search.'%'); }); }); } @@ -108,7 +113,7 @@ public function index(Request $request) if ($request->ajax()) { return response()->json([ - 'data' => view('business::sales.datas', compact('sales', 'salesWithReturns'))->render() + 'data' => view('business::sales.datas', compact('sales', 'salesWithReturns'))->render(), ]); } @@ -121,14 +126,20 @@ public function index(Request $request) public function create() { $business_id = auth()->user()->business_id; + $activeBranchId = auth()->user()->active_branch?->id; // Clears all cart items Cart::destroy(); - $customers = Party::where('type', '!=', 'supplier') + $customers = Party::where('type', '!=', 'Supplier') ->where('business_id', $business_id) ->latest() - ->get(); + ->get() + ->transform(function ($customer) use ($activeBranchId) { + $customer->due = $customer->dueForBranch($activeBranchId); + + return $customer; + }); $products = Product::with([ 'unit:id,unitName', @@ -139,13 +150,12 @@ public function create() 'stocks', 'combo_products.stock.product:id,productName,productCode', 'rack:id,name', - 'shelf: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') @@ -169,7 +179,7 @@ public function create() // Generate a unique invoice number $sale_id = (Sale::max('id') ?? 0) + 1; - $invoice_no = 'S-' . str_pad($sale_id, 5, '0', STR_PAD_LEFT); + $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')); } @@ -188,10 +198,10 @@ public function store(Request $request) 'saleDate' => 'nullable|date', ]); - $business_id = auth()->user()->business_id; + $business = Business::findOrFail(auth()->user()->business_id); // Get only 'sale' type items from cart - $carts = Cart::content()->filter(fn($item) => $item->options->type == 'sale'); + $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); @@ -200,33 +210,36 @@ public function store(Request $request) DB::beginTransaction(); try { // Calculation: subtotal, vat, discount, shipping, rounding - $rawSubtotal = $carts->sum(fn($item) => (float) $item->subtotal); + $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 + // overall discount (applied first on subtotal) $discountAmount = $request->discountAmount ?? 0; - $subtotalWithVat = $subtotal + $vatAmount; if ($request->discount_type == 'percent') { - $discountAmount = ($subtotalWithVat * $discountAmount) / 100; + $discountAmount = ($subtotal * $discountAmount) / 100; } - if ($discountAmount > $subtotalWithVat) { - return response()->json(['message' => __('Discount cannot be more than subtotal with VAT!')], 400); + if ($discountAmount > $subtotal) { + return response()->json(['message' => __('Discount cannot be more than subtotal!')], 400); } + // Subtotal after discount (before VAT) + $subtotalAfterDiscount = $subtotal - $discountAmount; + + // VAT (applied on subtotal after discount) + $vat = Vat::find($request->vat_id); + $vatAmount = $vat ? ($subtotalAfterDiscount * $vat->rate) / 100 : 0; + $shippingCharge = $request->shipping_charge ?? 0; - $actualTotalAmount = $subtotalWithVat - $discountAmount + $shippingCharge; + $actualTotalAmount = $subtotalAfterDiscount + $vatAmount + $shippingCharge; $roundingTotalAmount = sale_rounding($actualTotalAmount); $rounding_amount = $roundingTotalAmount - $actualTotalAmount; $rounding_option = sale_rounding(); @@ -240,8 +253,8 @@ public function store(Request $request) } $receiveAmount = collect($payments) - ->reject(fn($p) => strtolower($p['type'] ?? '') === 'cheque') - ->sum(fn($p) => $p['amount'] ?? 0); + ->reject(fn ($p) => strtolower($p['type'] ?? '') === 'cheque') + ->sum(fn ($p) => $p['amount'] ?? 0); $changeAmount = max($receiveAmount - $roundingTotalAmount, 0); $dueAmount = max($roundingTotalAmount - $receiveAmount, 0); @@ -253,7 +266,7 @@ public function store(Request $request) // Create sale record $sale = Sale::create([ 'user_id' => auth()->id(), - 'business_id' => $business_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, @@ -263,7 +276,7 @@ public function store(Request $request) 'vat_amount' => $vatAmount, 'discountAmount' => $discountAmount, 'discount_type' => $request->discount_type ?? 'flat', - 'discount_percent' => $request->discount_type == 'percent' ? $request->discountAmount : 0, + 'discount_percent' => $request->discount_type == 'percent' ? ($request->discountAmount ?? 0) : 0, 'totalAmount' => $roundingTotalAmount, 'actual_total_amount' => $actualTotalAmount, 'rounding_amount' => $rounding_amount, @@ -276,114 +289,17 @@ public function store(Request $request) '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) { + 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.') + 'message' => __('You have not allowed guest sales, so you cannot sell on due for a walking customer.'), ], 400); } } else { @@ -392,7 +308,7 @@ public function store(Request $request) 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!') + 'message' => __('Sale cannot be created. Party due will exceed credit limit!'), ], 400); } @@ -400,6 +316,130 @@ public function store(Request $request) } } + $totalLossProfit = 0; + $avgDiscount = $discountAmount / max($carts->count(), 1); + + 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); + } + + $discount = $cartItem->options->discount ?? 0; + $actualPrice = $cartItem->price - $discount; + + // Combo Products + if ($product->product_type == 'combo') { + + $combo_purchase_price = $product->combo_products->sum('purchase_price'); + + 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); + } + + // Store sale details for combo product itself + $detail = SaleDetails::create([ + 'sale_id' => $sale->id, + 'stock_id' => null, + 'product_id' => $cartItem->id, + 'price' => $cartItem->price, + 'quantities' => $qty, + 'expire_date' => null, + 'discount' => $discount, + 'productPurchasePrice' => $combo_purchase_price, + 'warranty_guarantee_info' => $product->warranty_guarantee_info ?? null, + ]); + + $vatAmount = VatTransactionService::storeVatTransactions($business, $detail, $product, $actualPrice, $party ?? null); + $price_without_tax = number_format($actualPrice - ($vatAmount / $qty), 3, '.', ''); + + $lossProfit = ($price_without_tax * $qty) - ($combo_purchase_price * $qty) - $avgDiscount; + $totalLossProfit += $lossProfit; + + $detail->update([ + 'lossProfit' => $lossProfit, + 'price_without_tax' => $price_without_tax, + ]); + } + // Single / Variant Products + else { + $stock = Stock::where('id', $cartItem->options->stock_id)->first(); + + $newSerials = $cartItem->options->serial_numbers ?? []; + $existingSerials = $stock->serial_numbers ?? []; + + $finalQty = ! empty($newSerials) ? count($newSerials) : $qty; + + if ($stock->productStock < $finalQty) { + $batchText = $stock->batch_no ? " ($stock->batch_no)" : ''; + + return response()->json([ + 'message' => __($cartItem->name.$batchText.' - stock not available. Available: '.$stock->productStock), + ], 400); + } + + if (! empty($newSerials)) { + + // Remove sold serials from stock + $remainingSerials = array_values(array_diff($existingSerials, $newSerials)); + + // Update stock serial list + $stock->update([ + 'serial_numbers' => $remainingSerials, + ]); + } + + $stock->decrement('productStock', $finalQty); + + $detail = SaleDetails::create([ + 'sale_id' => $sale->id, + 'product_id' => $cartItem->id, + 'price' => $cartItem->price, + 'quantities' => $finalQty, + 'discount' => $discount, + 'productPurchasePrice' => $purchase_price, + 'stock_id' => $cartItem->options->stock_id, + 'expire_date' => $cartItem->options->expire_date ?? null, + 'serial_numbers' => ! empty($newSerials) ? $newSerials : null, + 'warranty_guarantee_info' => $product->warranty_guarantee_info ?? null, + ]); + + $vatAmount = VatTransactionService::storeVatTransactions($business, $detail, $product, $actualPrice, $party ?? null); + $price_without_tax = number_format($actualPrice - ($vatAmount / $finalQty), 3, '.', ''); + + $lossProfit = ($price_without_tax * $finalQty) - ($stock->exclusive_price * $finalQty) - $avgDiscount; + $totalLossProfit += $lossProfit; + + $detail->update([ + 'lossProfit' => $lossProfit, + 'price_without_tax' => $price_without_tax, + ]); + } + } + + $sale->update([ + 'lossProfit' => $totalLossProfit, + ]); + // MultiPaymentProcessed Event event(new MultiPaymentProcessed( $payments ?? [], @@ -409,26 +449,31 @@ public function store(Request $request) $request->party_id ?? null, )); + // Notify user + sendNotifyToUser($sale->id, route('business.sales.index', ['id' => $sale->id]), __('New sale created.'), $business->id); + + if (moduleCheck('MarketingAddon')) { + $this->saleMessage($business, $party ?? null, $sale); + } + + $tax_invoice = moduleCheck('TaxInvoiceAddon') && invoice_setting($sale->business_id) == 'standard_a4'; + $invoice_route = $tax_invoice ? route('business.sales.tax.invoice', $sale->id) : route('business.sales.invoice', $sale->id); + // 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), + 'secondary_redirect_url' => $invoice_route, ]); } catch (\Exception $e) { DB::rollback(); + return response()->json(['message' => $e->getMessage()], 400); } } @@ -438,15 +483,21 @@ public function edit($id) // Clears all cart items Cart::destroy(); $business_id = auth()->user()->business_id; + $activeBranchId = auth()->user()->active_branch?->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') + $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,has_serial', 'details.product.category:id,categoryName', 'details.product.unit:id,unitName', 'payment_type:id,name', 'transactions') ->where('business_id', $business_id) ->findOrFail($id); - $customers = Party::where('type', '!=', 'supplier') + $customers = Party::where('type', '!=', 'Supplier') ->where('business_id', $business_id) ->latest() - ->get(); + ->get() + ->transform(function ($customer) use ($activeBranchId) { + $customer->due = $customer->dueForBranch($activeBranchId); + + return $customer; + }); $products = Product::with('category:id,categoryName', 'unit:id,unitName', 'stocks', 'stocks.warehouse') ->where('business_id', $business_id) @@ -485,14 +536,16 @@ public function edit($id) 'expire_date' => $detail->expire_date ?? '', 'purchase_price' => $detail->productPurchasePrice ?? 0, 'discount' => $detail->discount ?? 0, - 'vat_percent' => $detail->product->vat->rate ?? 0, + 'serial_numbers' => $detail->serial_numbers ?? null, + 'has_serial' => $detail->product->has_serial ?? null, ], ]); } - $cart_contents = Cart::content()->filter(fn($item) => $item->options->type == 'sale'); + $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')); } @@ -510,9 +563,9 @@ public function update(Request $request, $id) 'shipping_charge' => 'nullable|numeric', ]); - $business_id = auth()->user()->business_id; + $business = Business::findOrFail(auth()->user()->business_id); - $carts = Cart::content()->filter(fn($item) => $item->options->type == 'sale'); + $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); @@ -524,13 +577,47 @@ public function update(Request $request, $id) $sale_prev_due = $sale->dueAmount; $prevDetails = $sale->details; - $totalPurchaseAmount = 0; $subtotal = 0; - $saleDetailsData = []; $discountAmount = $request->discountAmount ?? 0; $shippingCharge = $request->shipping_charge ?? 0; + $vat = Vat::find($request->vat_id); + + // remove previous sale details + $prev_details_ids = SaleDetails::where('sale_id', $sale->id)->pluck('id')->toArray(); + SaleDetails::where('sale_id', $sale->id)->delete(); + + // remove previous vat transactions + VatTransaction::whereIn('vatable_id', $prev_details_ids) + ->where('vatable_type', SaleDetails::class) + ->delete(); + + // Restore previous stock quantities and serial numbers + foreach ($prevDetails as $prevItem) { + if ($prevItem->stock_id) { + $stock = Stock::find($prevItem->stock_id); + if ($stock) { + // Restore stock quantity + $stock->increment('productStock', $prevItem->quantities); + + // Restore serials if exists + if (! empty($prevItem->serial_numbers)) { + $prevSerials = $prevItem->serial_numbers; + + $existingSerials = $stock->serial_numbers ?? []; + + $mergedSerials = array_values(array_unique(array_merge($existingSerials, $prevSerials))); + + $stock->update(['serial_numbers' => $mergedSerials]); + } + } + } + } + + $totalLossProfit = 0; + $avgDiscount = $discountAmount / max($carts->count(), 1); + foreach ($carts as $cartItem) { $prevProduct = $prevDetails->firstWhere('product_id', $cartItem->id); $oldQty = $prevProduct ? $prevProduct->quantities : 0; @@ -538,107 +625,143 @@ public function update(Request $request, $id) $diffQty = $newQty - $oldQty; $product = Product::with('combo_products.product')->find($cartItem->id); - if (!$product) { + if (! $product) { return response()->json(['message' => __("Product not found: {$cartItem->name}")], 400); } - $itemCartDiscount = ((float) $cartItem->options->discount ?? 0) * $newQty; + $discount = $cartItem->options->discount ?? 0; + $actualPrice = $cartItem->price - $discount; if ($product->product_type === 'combo') { - $combo_total_purchase = 0; - $combo_total_sale = $cartItem->price * $newQty; + + $combo_purchase_price = $product->combo_products->sum('purchase_price'); foreach ($product->combo_products as $comboItem) { $stock = Stock::where('id', $comboItem->stock_id)->first(); - if (!$stock) { + if (! $stock) { return response()->json([ - 'message' => __("Stock not found for combo item '{$comboItem->product->productName}'") + '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})" : ""; + $batchText = $stock->batch_no ? " ({$stock->batch_no})" : ''; + return response()->json([ - 'message' => __("Combo item '{$stock->product->productName}'{$batchText} - stock not available. Available: {$stock->productStock}") + '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; + if ($diffQty != 0) { + $stock->productStock -= $diffQty * $comboItem->quantity; + $stock->save(); + } } - $lossProfit = $combo_total_sale - $combo_total_purchase - ($discountAmount / max($carts->count(), 1)) - $itemCartDiscount; - - $saleDetailsData[] = [ + $detail = SaleDetails::create([ 'sale_id' => $sale->id, 'stock_id' => null, 'product_id' => $cartItem->id, 'price' => $cartItem->price, - 'discount' => $cartItem->options->discount ?? 0, - 'lossProfit' => $lossProfit, + 'discount' => $discount, 'quantities' => $newQty, - 'productPurchasePrice' => $combo_total_purchase, 'expire_date' => null, - 'warranty_guarantee_info' => $product->warranty_guarantee_info ? json_encode($product->warranty_guarantee_info) : null, - ]; + 'productPurchasePrice' => $combo_purchase_price, + 'warranty_guarantee_info' => $product->warranty_guarantee_info ?? null, + ]); + + $vatAmount = VatTransactionService::storeVatTransactions($business, $detail, $product, $actualPrice, $party ?? null); + $price_without_tax = number_format($actualPrice - ($vatAmount / $newQty), 3, '.', ''); + + $lossProfit = ($price_without_tax * $newQty) - ($combo_purchase_price * $newQty) - $avgDiscount; + $totalLossProfit += $lossProfit; + + $detail->update([ + 'lossProfit' => $lossProfit, + 'price_without_tax' => $price_without_tax, + ]); + } else { $stock = Stock::where('id', $cartItem->options->stock_id ?? null) ->first() ?? Stock::where('product_id', $cartItem->id)->orderBy('id', 'asc')->first(); - if (!$stock) { + if (! $stock) { return response()->json([ - 'message' => __($cartItem->name . ' - no stock found.') + 'message' => __($cartItem->name.' - no stock found.'), ], 400); } + $newSerials = $cartItem->options->serial_numbers ?? []; + $existingSerials = $stock->serial_numbers ?? []; + + $finalQty = ! empty($newSerials) ? count($newSerials) : $newQty; + + if ($stock->productStock < $finalQty) { + return response()->json([ + 'message' => __($cartItem->name.' - stock not available. Available: '.$stock->productStock), + ], 400); + } + + // Remove new serials from stock + if (! empty($newSerials)) { + $remainingSerials = array_values(array_diff($existingSerials, $newSerials)); + $stock->update(['serial_numbers' => $remainingSerials]); + } + $availableStock = $stock->productStock + $oldQty; if ($availableStock < $newQty) { return response()->json([ - 'message' => __($cartItem->name . ' - stock not available. Available: ' . $availableStock) + 'message' => __($cartItem->name.' - stock not available. Available: '.$availableStock), ], 400); } - $stock->productStock -= $diffQty; - $stock->save(); + $stock->decrement('productStock', $finalQty); - $lossProfit = (($cartItem->price - $cartItem->options->purchase_price) * $newQty) - ($discountAmount / max($carts->count(), 1)) - $itemCartDiscount; - $totalPurchaseAmount += $cartItem->options->purchase_price * $newQty; - - $saleDetailsData[] = [ + $detail = SaleDetails::create([ 'sale_id' => $sale->id, 'stock_id' => $stock->id, 'product_id' => $cartItem->id, 'price' => $cartItem->price, - 'discount' => $cartItem->options->discount ?? 0, - 'lossProfit' => $lossProfit, - 'quantities' => $newQty, + 'discount' => $discount, + 'quantities' => $finalQty, '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, - ]; + 'warranty_guarantee_info' => $product->warranty_guarantee_info ?? null, + 'serial_numbers' => ! empty($newSerials) ? $newSerials : null, + ]); + + $vatAmount = VatTransactionService::storeVatTransactions($business, $detail, $product, $actualPrice, $party ?? null); + $price_without_tax = number_format($actualPrice - ($vatAmount / $finalQty), 3, '.', ''); + + $lossProfit = ($price_without_tax * $finalQty) - ($stock->exclusive_price * $finalQty) - $avgDiscount; + $totalLossProfit += $lossProfit; + + $detail->update([ + 'lossProfit' => $lossProfit, + 'price_without_tax' => $price_without_tax, + ]); } $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; - + // Discount (applied first on subtotal) + $discountAmount = $request->discountAmount ?? 0; if ($request->discount_type == 'percent') { - $discountAmount = ($subtotalWithVat * $discountAmount) / 100; + $discountAmount = ($subtotal * $discountAmount) / 100; } - if ($discountAmount > $subtotalWithVat) { - return response()->json(['message' => __('Discount cannot be more than subtotal with VAT!')], 400); + if ($discountAmount > $subtotal) { + return response()->json(['message' => __('Discount cannot be more than subtotal!')], 400); } - $actualTotalAmount = $subtotalWithVat - $discountAmount + $shippingCharge; + // Subtotal after discount (before VAT) + $subtotalAfterDiscount = $subtotal - $discountAmount; + + // VAT & total calculations (applied on subtotal after discount) + $vatAmount = $vat ? ($subtotalAfterDiscount * $vat->rate) / 100 : 0; + + $actualTotalAmount = $subtotalAfterDiscount + $vatAmount + $shippingCharge; $roundingTotalAmount = sale_rounding($actualTotalAmount, $sale->rounding_option); $rounding_amount = $roundingTotalAmount - $actualTotalAmount; @@ -652,7 +775,7 @@ public function update(Request $request, $id) } // Check if any old transaction type = deposit - $hasDeposit = Transaction::where('business_id', $business_id) + $hasDeposit = Transaction::where('business_id', $business->id) ->where('reference_id', $sale->id) ->where('platform', 'sale') ->where('type', 'deposit') @@ -662,9 +785,9 @@ public function update(Request $request, $id) $receiveAmount = collect($payments) ->reject(function ($p) use ($hasDeposit) { // exclude cheque only if not deposit - return !$hasDeposit && strtolower($p['type'] ?? '') === 'cheque'; + return ! $hasDeposit && strtolower($p['type'] ?? '') === 'cheque'; }) - ->sum(fn($p) => $p['amount'] ?? 0); + ->sum(fn ($p) => $p['amount'] ?? 0); $changeAmount = $receiveAmount > $roundingTotalAmount ? $receiveAmount - $roundingTotalAmount : 0; $dueAmount = max($roundingTotalAmount - $receiveAmount, 0); @@ -680,11 +803,12 @@ public function update(Request $request, $id) 'vat_amount' => $vatAmount, 'discountAmount' => $discountAmount, 'discount_type' => $request->discount_type ?? 'flat', - 'discount_percent' => $request->discount_type == 'percent' ? $request->discountAmount : 0, + 'discount_percent' => $request->discount_type == 'percent' ? ($request->discountAmount ?? 0) : 0, 'totalAmount' => $roundingTotalAmount, 'actual_total_amount' => $actualTotalAmount, 'rounding_amount' => $rounding_amount, - 'lossProfit' => $subtotal - $totalPurchaseAmount - $discountAmount, + 'lossProfit' => $totalLossProfit, + 'shipping_charge' => $shippingCharge, 'paidAmount' => $paidAmount > $roundingTotalAmount ? $roundingTotalAmount : $paidAmount, 'change_amount' => $changeAmount, 'dueAmount' => $dueAmount, @@ -692,22 +816,18 @@ public function update(Request $request, $id) '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 (! $request->party_id) { - if (!$allowDueForGuest) { + if (! $allowDueForGuest) { return response()->json([ - 'message' => __('You have not allowed guest sales. So you cannot sell on due for a walking customer.') + 'message' => __('You have not allowed guest sales. So you cannot sell on due for a walking customer.'), ], 400); } } else { @@ -720,7 +840,7 @@ public function update(Request $request, $id) // 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!') + 'message' => __('Cannot update sale. Party due will exceed credit limit!'), ], 400); } @@ -736,7 +856,7 @@ public function update(Request $request, $id) } // Multiple Payment Process - $oldTransactions = Transaction::where('business_id', $business_id) + $oldTransactions = Transaction::where('business_id', $business->id) ->where('reference_id', $sale->id) ->where('platform', 'sale') ->get(); @@ -768,7 +888,7 @@ public function update(Request $request, $id) } // Delete old transactions - Transaction::where('business_id', $business_id) + Transaction::where('business_id', $business->id) ->where('reference_id', $sale->id) ->where('platform', 'sale') ->delete(); @@ -786,18 +906,22 @@ public function update(Request $request, $id) Cart::remove($cartItem->rowId); } - sendNotifyToUser($sale->id, route('business.sales.index', ['id' => $sale->id]), __('Sale has been updated.'), $business_id); + sendNotifyToUser($sale->id, route('business.sales.index', ['id' => $sale->id]), __('Sale has been updated.'), $business->id); + + $tax_invoice = moduleCheck('TaxInvoiceAddon') && invoice_setting($sale->business_id) == 'standard_a4'; + $invoice_route = $tax_invoice ? route('business.sales.tax.invoice', $sale->id) : route('business.sales.invoice', $sale->id); DB::commit(); return response()->json([ 'message' => __('Sales updated successfully.'), 'redirect' => route('business.sales.index'), - 'secondary_redirect_url' => route('business.sales.invoice', $sale->id), + 'secondary_redirect_url' => $invoice_route, ]); } catch (\Exception $e) { DB::rollback(); - return response()->json(['message' => __('Something went wrong!')], 404); + + return response()->json(['message' => $e->getMessage()], 404); } } @@ -805,12 +929,16 @@ public function destroy($id) { DB::beginTransaction(); try { - $sale = Sale::findOrFail($id); + $sale = Sale::where('business_id', auth()->user()->business_id)->findOrFail($id); foreach ($sale->details as $detail) { + VatTransaction::where('vatable_id', $detail->id) + ->where('vatable_type', get_class($detail)) + ->delete(); + $stock = Stock::find($detail->stock_id); - if (!$stock) { + if (! $stock) { $stock = Stock::where('product_id', $detail->product_id)->orderBy('id', 'asc')->first(); } @@ -837,10 +965,11 @@ public function destroy($id) return response()->json([ 'message' => __('Sale deleted successfully.'), - 'redirect' => route('business.sales.index') + 'redirect' => route('business.sales.index'), ]); } catch (\Exception $e) { DB::rollback(); + return response()->json(['message' => __('Something went wrong!')], 404); } } @@ -850,14 +979,18 @@ public function deleteAll(Request $request) DB::beginTransaction(); try { - $sales = Sale::whereIn('id', $request->ids)->get(); + $sales = Sale::where('business_id', auth()->user()->business_id)->whereIn('id', $request->ids)->get(); foreach ($sales as $sale) { // Restore stock foreach ($sale->details as $detail) { + VatTransaction::where('vatable_id', $detail->id) + ->where('vatable_type', get_class($detail)) + ->delete(); + $stock = Stock::find($detail->stock_id); - if (!$stock) { + if (! $stock) { $stock = Stock::where('product_id', $detail->product_id)->orderBy('id', 'asc')->first(); } @@ -886,10 +1019,11 @@ public function deleteAll(Request $request) return response()->json([ 'message' => __('Selected sales deleted successfully.'), - 'redirect' => route('business.sales.index') + 'redirect' => route('business.sales.index'), ]); } catch (\Exception $e) { DB::rollback(); + return response()->json(['message' => __('Something went wrong!')], 404); } } @@ -937,16 +1071,16 @@ public function acnooFilter(Request $request) // Search Filter if ($request->filled('search')) { $salesQuery->where(function ($query) use ($request) { - $query->where('paymentType', 'like', '%' . $request->search . '%') - ->orWhere('invoiceNumber', 'like', '%' . $request->search . '%') + $query->where('paymentType', 'like', '%'.$request->search.'%') + ->orWhere('invoiceNumber', 'like', '%'.$request->search.'%') ->orWhereHas('party', function ($q) use ($request) { - $q->where('name', 'like', '%' . $request->search . '%'); + $q->where('name', 'like', '%'.$request->search.'%'); }) ->orWhereHas('payment_type', function ($q) use ($request) { - $q->where('name', 'like', '%' . $request->search . '%'); + $q->where('name', 'like', '%'.$request->search.'%'); }) ->orWhereHas('branch', function ($q) use ($request) { - $q->where('name', 'like', '%' . $request->search . '%'); + $q->where('name', 'like', '%'.$request->search.'%'); }); }); } @@ -956,7 +1090,7 @@ public function acnooFilter(Request $request) if ($request->ajax()) { return response()->json([ - 'data' => view('business::sales.datas', compact('sales', 'salesWithReturns'))->render() + 'data' => view('business::sales.datas', compact('sales', 'salesWithReturns'))->render(), ]); } @@ -972,26 +1106,22 @@ public function productFilter(Request $request) ->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); - } - ]) + ->with(['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 . '%'); + $q->where('productName', 'like', '%'.$request->search.'%') + ->orWhere('productCode', 'like', '%'.$request->search.'%'); }); }) ->when($request->category_id, function ($query) use ($request) { @@ -1006,14 +1136,14 @@ public function productFilter(Request $request) // 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 . '%'); + $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 . '%'); + $query->where('brandName', 'like', '%'.$request->search.'%'); }) ->get(); @@ -1024,6 +1154,7 @@ public function productFilter(Request $request) 'total_products' => $total_products, 'total_products_count' => $total_products_count, 'product_id' => $total_products == 1 ? $products->first()->id : null, + 'has_serial' => $total_products == 1 ? $products->first()->has_serial : 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(), @@ -1039,7 +1170,7 @@ 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 . '%'); + $query->where('categoryName', 'like', '%'.$search.'%'); }) ->get(); @@ -1054,7 +1185,7 @@ 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 . '%'); + $query->where('brandName', 'like', '%'.$search.'%'); }) ->get(); @@ -1081,6 +1212,7 @@ public function getProductPrices(Request $request) $prices[$product->id] = currency_format($product->productSalePrice, currency: business_currency()); } } + return response()->json($prices); } @@ -1090,6 +1222,7 @@ public function getStockPrices(Request $request) $businessId = auth()->user()->business_id; $customerType = $request->input('type'); $cartStocks = $request->input('stocks', []); // optional, only cart rows + $forInventory = $request->input('inventory', false); // if true, return all stocks $currency = business_currency(); @@ -1111,9 +1244,26 @@ public function getStockPrices(Request $request) $productPrices[$stock->product_id] = currency_format($price, currency: $currency); } - // Fetch stocks for cart list prices + // For inventory sale, return all stock prices; otherwise only cart stocks $stockPrices = []; - if (!empty($cartStocks)) { + + if ($forInventory) { + // Return all stocks for inventory sale + foreach ($allStocks 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); + } + } elseif (! empty($cartStocks)) { + // Return only cart stocks for normal sale/transfer $cartStockIds = collect($cartStocks)->pluck('stock_id')->toArray(); $cartStockQuery = Stock::where('business_id', $businessId) @@ -1146,7 +1296,7 @@ public function getStockPrices(Request $request) /** Get cart info */ public function getCartData() { - $cart_contents = Cart::content()->filter(fn($item) => $item->options->type == 'sale'); + $cart_contents = Cart::content()->filter(fn ($item) => $item->options->type == 'sale'); $data['sub_total'] = 0; @@ -1158,100 +1308,21 @@ public function getCartData() return response()->json($data); } - public function getInvoice($sale_id) + public function getInvoice(string $sale_id, SaleInvoiceService $service) { - $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); + $data = $service->getInvoiceData($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')); + return view('business::sales.invoice', $data); } - public function generatePDF(Request $request, $sale_id) + public function getTaxInvoice($sale_id, SaleTaxInvoiceService $service) + { + $data = $service->getInvoiceData($sale_id); + + return view('taxinvoiceaddon::sales.tax', $data); + } + + public function generatePDF($sale_id) { $sale = Sale::where('business_id', auth()->user()->business_id) ->with( @@ -1260,8 +1331,7 @@ public function generatePDF(Request $request, $sale_id) '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', + 'details.product:id,productName', 'payment_type:id,name', 'transactions:id,reference_id,transaction_type,payment_type_id', 'transactions.paymentType:id,name' @@ -1290,8 +1360,8 @@ public function generatePDF(Request $request, $sale_id) if ($returnTransaction) { $returnTransactionType = $returnTransaction->transaction_type === 'bank_payment' - ? ($returnTransaction->paymentType?->name) - : ucfirst(explode('_', $returnTransaction->transaction_type)[0]); + ? ($returnTransaction->paymentType?->name) + : ucfirst(explode('_', $returnTransaction->transaction_type)[0]); } // Return qty merge (unchanged) @@ -1303,6 +1373,7 @@ public function generatePDF(Request $request, $sale_id) })->sum(); $detail->quantities += $return_qty_sum; + return $detail; }); @@ -1312,7 +1383,7 @@ public function generatePDF(Request $request, $sale_id) foreach ($sale_returns as $return) { foreach ($return->details as $detail) { - if (!isset($product_discounts[$detail->sale_detail_id])) { + if (! isset($product_discounts[$detail->sale_detail_id])) { $product_discounts[$detail->sale_detail_id] = [ 'return_qty' => 0, 'return_amount' => 0, @@ -1337,12 +1408,15 @@ public function generatePDF(Request $request, $sale_id) foreach ($sale->details as $detail) { $info = $detail->warranty_guarantee_info ?? []; - if (!empty($info['warranty_duration'])) + if (! empty($info['warranty_duration'])) { $hasWarranty = true; - if (!empty($info['guarantee_duration'])) + } + if (! empty($info['guarantee_duration'])) { $hasGuarantee = true; - if ($hasWarranty && $hasGuarantee) + } + if ($hasWarranty && $hasGuarantee) { break; + } } $bank_detail = PaymentType::where('business_id', auth()->user()->business_id)->where('show_in_invoice', 1)->latest()->first(); @@ -1364,21 +1438,185 @@ public function generatePDF(Request $request, $sale_id) ); } - public function sendMail(Request $request, $sale_id) + public function generateTaxPDF(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); + $sale = Sale::where('business_id', auth()->user()->business_id) + ->with( + 'user:id,name,role', + 'party:id,name,phone,address,email', + 'business:id,phoneNumber,companyName,vat_name,vat_no,address,email,meta', + 'details:id,price,price_without_tax,discount,quantities,product_id,sale_id,stock_id,warranty_guarantee_info,serial_numbers', + 'details.stock:id,batch_no', + 'details.product:id,productName,has_serial', + 'payment_type:id,name', + 'transactions:id,reference_id,transaction_type,payment_type_id', + 'transactions.paymentType:id,name' + ) + ->findOrFail($sale_id); + $business_id = auth()->check() ? auth()->user()->business_id : null; + + $detail_ids = $sale->details->pluck('id'); + $tax_items = VatTransaction::selectRaw('vat_id, vat_rate, SUM(vat_amount) as vat_amount') + ->with('vat:id,name') + ->whereIn('vatable_id', $detail_ids) + ->where('vatable_type', SaleDetails::class) + ->groupBy('vat_id', 'vat_rate') + ->get(); + + $vatRowMap = VatTransaction::whereIn('vatable_id', $detail_ids) + ->where('vatable_type', SaleDetails::class) + ->selectRaw('vatable_id, vat_id, SUM(vat_amount) as total') + ->groupBy('vatable_id', 'vat_id') + ->get() + ->groupBy('vatable_id') + ->map(function ($items) { + return $items->pluck('total', 'vat_id')->toArray(); + }) + ->toArray(); + + // 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,has_serial' + ) + ->where('business_id', auth()->user()->business_id) + ->where('sale_id', $sale_id) + ->latest() + ->get(); + + // collect return detail ids + $return_detail_ids = $sale_returns->flatMap(function ($return) { + return $return->details->pluck('id'); + }); + + // get VAT for sale return details + $returnVatRowMap = VatTransaction::whereIn('vatable_id', $return_detail_ids) + ->where('vatable_type', SaleReturnDetails::class) + ->selectRaw('vatable_id, vat_id, SUM(vat_amount) as total') + ->groupBy('vatable_id', 'vat_id') + ->get() + ->groupBy('vatable_id') + ->map(function ($items) { + return $items->pluck('total', 'vat_id')->toArray(); + }) + ->toArray(); + + // 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( + 'taxinvoiceaddon::sales.pdf', + compact( + 'sale', + 'tax_items', + 'sale_returns', + 'total_discount', + 'transactionTypes', + 'returnTransactionType', + 'bank_detail', + 'hasWarranty', + 'hasGuarantee', + 'business_id', + 'vatRowMap', + 'returnVatRowMap' + ), + 'sales-tax-invoice.pdf' + ); + } + + public function sendMail(string $sale_id) + { + $sale = Sale::where('business_id', auth()->user()->business_id)->with('user:id,name,role', 'party:id,name,phone,address,email', '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,has_serial', 'payment_type:id,name', 'transactions:id,reference_id,transaction_type,payment_type_id', 'transactions.paymentType:id,name')->findOrFail($sale_id); + + if (!$sale->party?->email) { + return response()->json([ + 'message' => __('Customer email not found. Please update customer information with an email address to send the invoice.'), + ], 404); + } $transactionTypes = $sale->transactions ->map(function ($transaction) { - if ($transaction->transaction_type === 'bank_payment' && !empty($transaction->paymentType?->name)) { + 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') + $sale_returns = SaleReturn::with('sale:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber', 'sale.party:id,name', 'details', 'details.saleDetail.product:id,productName,has_serial') ->where('business_id', auth()->user()->business_id) ->where('sale_id', $sale_id) ->latest() @@ -1400,6 +1638,7 @@ public function sendMail(Request $request, $sale_id) })->sum(); $detail->quantities = $detail->quantities + $return_qty_sum; + return $detail; }); @@ -1410,7 +1649,7 @@ public function sendMail(Request $request, $sale_id) 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])) { + if (! isset($product_discounts[$detail->sale_detail_id])) { // Initialize the first occurrence $product_discounts[$detail->sale_detail_id] = [ 'return_qty' => 0, @@ -1439,28 +1678,220 @@ public function sendMail(Request $request, $sale_id) foreach ($sale->details as $detail) { $info = $detail->warranty_guarantee_info ?? []; - if (!empty($info['warranty_duration'])) { + if (! empty($info['warranty_duration'])) { $hasWarranty = true; } - if (!empty($info['guarantee_duration'])) { + if (! empty($info['guarantee_duration'])) { $hasGuarantee = true; } - if ($hasWarranty && $hasGuarantee) + 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')); + $pdf = PdfService::render( + 'business::sales.pdf', + compact( + 'sale', + 'sale_returns', + 'total_discount', + 'transactionTypes', + 'returnTransactionType', + 'bank_detail', + 'hasWarranty', + 'hasGuarantee' + ), + 'sales-invoice.pdf' + ); + + $pdfContent = $pdf->getContent(); // Send email with PDF attachment - Mail::raw('Please find attached your sales invoice.', function ($message) use ($pdf) { - $message->to(auth()->user()->email) + Mail::raw('Please find attached your sales invoice.', function ($message) use ($sale, $pdfContent) { + $message->to($sale->party->email) ->subject('Sales Invoice') - ->attachData($pdf->output(), 'sales-invoice.pdf', [ + ->attachData($pdfContent, 'sales-invoice.pdf', [ + 'mime' => 'application/pdf', + ]); + }); + + return response()->json([ + 'message' => __('Email Sent Successfully.'), + 'redirect' => route('business.sales.index'), + ]); + } + + public function sendTaxMail(string $sale_id) + { + $sale = Sale::where('business_id', auth()->user()->business_id)->with('user:id,name,role', + 'party:id,name,phone,address,email', + 'business:id,phoneNumber,companyName,vat_name,vat_no,address,email,meta', + 'details:id,price,price_without_tax,discount,quantities,product_id,sale_id,stock_id,warranty_guarantee_info,serial_numbers', + 'details.stock:id,batch_no', + 'details.product:id,productName,has_serial', + 'payment_type:id,name', + 'transactions:id,reference_id,transaction_type,payment_type_id', + 'transactions.paymentType:id,name')->findOrFail($sale_id); + + if (!$sale->party?->email) { + return response()->json([ + 'message' => __('Customer email not found. Please update customer information with an email address to send the invoice.'), + ], 404); + } + + $business_id = auth()->check() ? auth()->user()->business_id : null; + $detail_ids = $sale->details->pluck('id'); + $tax_items = VatTransaction::selectRaw('vat_id, vat_rate, SUM(vat_amount) as vat_amount') + ->with('vat:id,name') + ->whereIn('vatable_id', $detail_ids) + ->where('vatable_type', SaleDetails::class) + ->groupBy('vat_id', 'vat_rate') + ->get(); + + $vatRowMap = VatTransaction::whereIn('vatable_id', $detail_ids) + ->where('vatable_type', SaleDetails::class) + ->selectRaw('vatable_id, vat_id, SUM(vat_amount) as total') + ->groupBy('vatable_id', 'vat_id') + ->get() + ->groupBy('vatable_id') + ->map(function ($items) { + return $items->pluck('total', 'vat_id')->toArray(); + }) + ->toArray(); + + $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,has_serial') + ->where('business_id', auth()->user()->business_id) + ->where('sale_id', $sale_id) + ->latest() + ->get(); + + $return_detail_ids = $sale_returns->flatMap(function ($return) { + return $return->details->pluck('id'); + }); + + $returnVatRowMap = VatTransaction::whereIn('vatable_id', $return_detail_ids) + ->where('vatable_type', SaleReturnDetails::class) + ->selectRaw('vatable_id, vat_id, SUM(vat_amount) as total') + ->groupBy('vatable_id', 'vat_id') + ->get() + ->groupBy('vatable_id') + ->map(function ($items) { + return $items->pluck('total', 'vat_id')->toArray(); + }) + ->toArray(); + + $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 = PdfService::render( + 'taxinvoiceaddon::sales.pdf', + compact( + 'sale', + 'sale_returns', + 'total_discount', + 'transactionTypes', + 'returnTransactionType', + 'bank_detail', + 'hasWarranty', + 'hasGuarantee', + 'tax_items', + 'business_id', + 'vatRowMap', + 'returnVatRowMap' + ), + 'sales-tax-invoice.pdf' + ); + + $pdfContent = $pdf->getContent(); + + // Send email with PDF attachment + Mail::raw('Please find attached your sales invoice.', function ($message) use ($sale, $pdfContent) { + $message->to($sale->party->email) + ->subject('Sales Tax Invoice') + ->attachData($pdfContent, 'sales-tax-invoice.pdf', [ 'mime' => 'application/pdf', ]); }); @@ -1474,7 +1905,7 @@ public function sendMail(Request $request, $sale_id) public function createCustomer(Request $request) { $request->validate([ - 'phone' => 'nullable|max:20|' . Rule::unique('parties')->where('business_id', auth()->user()->business_id), + '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', @@ -1485,13 +1916,13 @@ public function createCustomer(Request $request) Party::create($request->except('image', 'due') + [ 'due' => $request->due ?? 0, - 'image' => $request->image ? $this->upload($request, 'image') : NULL, - 'business_id' => auth()->user()->business_id + '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') + 'redirect' => route('business.sales.create'), ]); } @@ -1499,36 +1930,28 @@ public function createInventory() { // Clears all cart items Cart::destroy(); + $business_id = auth()->user()->business_id; + $activeBranchId = auth()->user()->active_branch?->id; - $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') + $customers = Party::where('type', '!=', 'Supplier') + ->where('business_id', $business_id) ->latest() ->get() - ->where('total_stock', '>', 0) - ->values(); + ->transform(function ($customer) use ($activeBranchId) { + $customer->due = $customer->dueForBranch($activeBranchId); - $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(); + return $customer; + }); + + $categories = Category::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(); // Generate a unique invoice number $sale_id = (Sale::max('id') ?? 0) + 1; - $invoice_no = 'S-' . str_pad($sale_id, 5, '0', STR_PAD_LEFT); + $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')); + return view('business::sales.inventory', compact('customers', 'invoice_no', 'categories', 'vats', 'payment_types')); } public function viewPayment(string $type, string $id) @@ -1541,7 +1964,7 @@ public function viewPayment(string $type, string $id) 'due' => DueCollect::class, ]; - if (!isset($modelMap[$type])) { + if (! isset($modelMap[$type])) { return response()->json(['message' => 'Invalid type'], 400); } @@ -1584,7 +2007,7 @@ public function deletePayment(string $type, string $id) 'due' => DueCollect::class, ]; - if (!isset($modelMap[$type])) { + if (! isset($modelMap[$type])) { return response()->json(['message' => 'Invalid type'], 400); } diff --git a/Modules/Business/App/Http/Controllers/AcnooSaleReportController.php b/Modules/Business/App/Http/Controllers/AcnooSaleReportController.php index 536e0bf1..f730f02c 100644 --- a/Modules/Business/App/Http/Controllers/AcnooSaleReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooSaleReportController.php @@ -5,7 +5,6 @@ use App\Models\Sale; use App\Models\Branch; use Illuminate\Http\Request; -use Illuminate\Support\Carbon; use App\Http\Controllers\Controller; use App\Services\PdfService; use App\Traits\DateFilterTrait; @@ -26,11 +25,8 @@ public function index(Request $request) { $businessId = auth()->user()->business_id; - $total_sale = Sale::where('business_id', $businessId) - ->whereDate('saleDate', Carbon::today()) - ->sum('totalAmount'); - $salesQuery = Sale::with('user:id,name', 'party:id,name,email,phone,type', 'payment_type:id,name', 'branch:id,name', 'transactions')->where('business_id', $businessId); + $salesQuery->when($request->branch_id, function ($q) use ($request) { $q->where('branch_id', $request->branch_id); }); @@ -88,10 +84,38 @@ public function exportCsv() public function exportPdf(Request $request) { - $sales = Sale::with('user:id,name', 'party:id,name,email,phone,type', 'payment_type:id,name', 'branch:id,name', 'transactions')->where('business_id', auth()->user()->business_id) - ->latest() - ->get(); + $salesQuery = Sale::with('user:id,name', 'party:id,name,email,phone,type', 'payment_type:id,name', 'branch:id,name', 'transactions') + ->where('business_id', auth()->user()->business_id); - return PdfService::render('business::reports.sales.pdf', compact('sales'),'sales-report.pdf'); + $salesQuery->when($request->branch_id, function ($q) use ($request) { + $q->where('branch_id', $request->branch_id); + }); + + // Date Filter + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + $this->applyDateFilter($salesQuery, $duration, 'saleDate', $request->from_date, $request->to_date); + + // 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 . '%'); + }); + }); + } + + $sales = $salesQuery->latest()->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::reports.sales.pdf', compact('sales', 'duration', 'filter_from_date', 'filter_to_date'),'sales-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooSaleReturnReportController.php b/Modules/Business/App/Http/Controllers/AcnooSaleReturnReportController.php index b99f85f2..3aa618e6 100644 --- a/Modules/Business/App/Http/Controllers/AcnooSaleReturnReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooSaleReturnReportController.php @@ -5,7 +5,6 @@ use App\Models\Sale; use App\Models\Branch; use Illuminate\Http\Request; -use App\Models\SaleReturnDetails; use App\Http\Controllers\Controller; use App\Services\PdfService; use App\Traits\DateFilterTrait; @@ -24,12 +23,6 @@ public function __construct() public function index(Request $request) { - $total_sale_return = SaleReturnDetails::whereHas('saleReturn', function ($query) { - $query->whereHas('sale', function ($q) { - $q->where('business_id', auth()->user()->business_id); - }); - })->sum('return_amount'); - $businessId = auth()->user()->business_id; $salesQuery = Sale::with([ @@ -44,9 +37,7 @@ public function index(Request $request) ->with('branch:id,name'); } ]) - ->where('business_id', $businessId) - ->when($request->branch_id, function ($q) use ($request) { $q->where('branch_id', $request->branch_id); })->whereHas('saleReturns'); @@ -55,7 +46,9 @@ public function index(Request $request) $duration = $request->custom_days ?: 'today'; [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); - $this->applyDateFilter($salesQuery, $duration, 'saleDate', $request->from_date, $request->to_date); + $salesQuery->whereHas('saleReturns', function ($query) use ($request, $duration) { + $this->applyDateFilter($query, $duration, 'return_date', $request->from_date, $request->to_date); + }); // Search Filter if ($request->filled('search')) { @@ -70,15 +63,18 @@ public function index(Request $request) }); } - $total_sale_return = SaleReturnDetails::whereHas('saleReturn', function ($query) use ($businessId) { - $query->whereHas('sale', function ($q) use ($businessId) { - $q->where('business_id', $businessId); - }); - })->sum('return_amount'); - $perPage = $request->input('per_page', 20); $sales = $salesQuery->latest()->paginate($perPage)->appends($request->query()); + $total_sale_return = (clone $salesQuery) + ->with('saleReturns.details') + ->get() + ->sum(function ($sale) { + return $sale->saleReturns->sum(function ($return) { + return $return->details->sum('return_amount'); + }); + }); + if ($request->ajax()) { return response()->json([ 'data' => view('business::reports.sales-return.datas', compact('sales', 'filter_from_date', 'filter_to_date', 'duration'))->render(), @@ -103,7 +99,7 @@ public function exportCsv() public function exportPdf(Request $request) { - $sales = Sale::with([ + $salesQuery = Sale::with([ 'user:id,name', 'party:id,name', 'branch:id,name', @@ -116,10 +112,33 @@ public function exportPdf(Request $request) } ]) ->where('business_id', auth()->user()->business_id) - ->whereHas('saleReturns') - ->latest() - ->get(); + ->when($request->branch_id, function ($q) use ($request) { + $q->where('branch_id', $request->branch_id); + })->whereHas('saleReturns'); - return PdfService::render('business::reports.sales-return.pdf', compact('sales'), 'sales-return-report.pdf'); + // Date Filter + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + $salesQuery->whereHas('saleReturns', function ($query) use ($duration, $request) { + $this->applyDateFilter($query, $duration, 'return_date', $request->from_date, $request->to_date); + }); + + // Search Filter + if ($request->filled('search')) { + $salesQuery->where(function ($query) use ($request) { + $query->where('invoiceNumber', 'like', '%' . $request->search . '%') + ->orWhereHas('party', function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%'); + }) + ->orWhereHas('branch', function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%'); + }); + }); + } + + $sales = $salesQuery->latest()->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::reports.sales-return.pdf', compact('sales', 'duration', 'filter_from_date', 'filter_to_date'), 'sales-return-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooSettingsManagerController.php b/Modules/Business/App/Http/Controllers/AcnooSettingsManagerController.php index ee8e0cd7..123e8c55 100644 --- a/Modules/Business/App/Http/Controllers/AcnooSettingsManagerController.php +++ b/Modules/Business/App/Http/Controllers/AcnooSettingsManagerController.php @@ -2,10 +2,10 @@ namespace Modules\Business\App\Http\Controllers; -use App\Models\Option; -use Illuminate\Http\Request; -use App\Models\ProductSetting; use App\Http\Controllers\Controller; +use App\Models\Option; +use App\Models\ProductSetting; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; use Illuminate\Validation\ValidationException; @@ -14,23 +14,29 @@ class AcnooSettingsManagerController extends Controller public function index() { $businessId = auth()->user()->business_id; - $invoiceSettingKey = 'invoice_setting_' . $businessId; + $invoiceSettingKey = 'invoice_setting_'.$businessId; $invoice_setting = Option::where('key', $invoiceSettingKey)->first(); $product_setting = ProductSetting::where('business_id', auth()->user()->business_id)->first(); - $currencySettingKey = 'currency_setting_' . $businessId; + $currencySettingKey = 'currency_setting_'.$businessId; $currency_setting = Option::where('key', $currencySettingKey)->first(); - return view('business::manage-settings.index', compact('invoice_setting', 'product_setting', 'currency_setting')); + $invoiceImgaeSettingKey = 'invoice_setting_images'; + $invoice_setting_image = Option::where('key', $invoiceImgaeSettingKey)->first(); + + $themesettingkey = 'theme_setting_'.$businessId; + $theme_settings = Option::where('key', $themesettingkey)->first()?->value ?? []; + + return view('business::manage-settings.index', compact('invoice_setting', 'theme_settings', 'product_setting', 'currency_setting', 'invoice_setting_image')); } public function updateInvoice(Request $request) { $request->validate([ - 'invoice_size' => 'required|string|max:100|in:a4,3_inch_80mm', + 'invoice_size' => 'required|string|max:100|in:a4,3_inch_80mm,standard_a4', ]); - $key = 'invoice_setting_' . auth()->user()->business_id; + $key = 'invoice_setting_'.auth()->user()->business_id; Option::updateOrCreate( ['key' => $key], @@ -45,8 +51,8 @@ public function updateInvoice(Request $request) public function updateProductSetting(Request $request) { $request->validate([ - 'show_product_price' => 'nullable|boolean', 'show_product_code' => 'nullable|boolean', + 'show_hsn_code' => 'nullable|boolean', 'show_product_stock' => 'nullable|boolean', 'show_product_sale_price' => 'nullable|boolean', 'show_product_dealer_price' => 'nullable|boolean', @@ -94,9 +100,9 @@ public function updateProductSetting(Request $request) ]); if ( - !$request->boolean('show_product_type_single') && - !$request->boolean('show_product_type_variant') && - !$request->boolean('show_product_type_combo') + ! $request->boolean('show_product_type_single') && + ! $request->boolean('show_product_type_variant') && + ! $request->boolean('show_product_type_combo') ) { throw ValidationException::withMessages([ 'product_type' => ['At least one product type must be selected: Single, Variant or Combo.'], @@ -133,7 +139,7 @@ public function updateProductSetting(Request $request) ['modules' => $modules] ); - Cache::forget('product_setting_' . $businessId); + Cache::forget('product_setting_'.$businessId); return response()->json(__('Product setting updated successfully.')); } @@ -144,7 +150,7 @@ public function updateCurrency(Request $request) 'currency' => 'required|string|max:100|in:us,european', ]); - $key = 'currency_setting_' . auth()->user()->business_id; + $key = 'currency_setting_'.auth()->user()->business_id; Option::updateOrCreate( ['key' => $key], @@ -174,13 +180,39 @@ public function updateSerial(Request $request) $modules['show_serial'] = $request->boolean('show_serial') ? '1' : '0'; $setting->update([ - 'modules' => $modules + 'modules' => $modules, ]); - Cache::forget('product_setting_' . $businessId); + Cache::forget('product_setting_'.$businessId); return response()->json([ 'message' => 'Serial setting updated successfully.', ]); } + + public function updateTheme(Request $request) + { + $request->validate([ + 'primary_color' => 'nullable|string|max:100', + 'secondary_color' => 'nullable|string|max:100', + 'sidebar_color' => 'nullable|string|max:100', + 'accent_color' => 'nullable|string|max:100', + ]); + + $businessId = auth()->user()->business_id; + + $setting = Option::updateOrCreate( + ['key' => 'theme_setting_'.$businessId], + ['value' => [ + 'primary_color' => $request->primary_color, + 'secondary_color' => $request->secondary_color, + 'sidebar_color' => $request->sidebar_color, + 'accent_color' => $request->accent_color, + ]] + ); + + Cache::forget('theme_setting_'.$businessId); + + return response()->json(__('Theme updated successfully.')); + } } diff --git a/Modules/Business/App/Http/Controllers/AcnooStockController.php b/Modules/Business/App/Http/Controllers/AcnooStockController.php index 88fdf571..925ecf78 100644 --- a/Modules/Business/App/Http/Controllers/AcnooStockController.php +++ b/Modules/Business/App/Http/Controllers/AcnooStockController.php @@ -2,13 +2,10 @@ namespace Modules\Business\App\Http\Controllers; -use App\Models\Stock; use App\Models\Product; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\DB; use App\Http\Controllers\Controller; +use App\Services\PdfService; use Maatwebsite\Excel\Facades\Excel; -use Illuminate\Pagination\LengthAwarePaginator; use Modules\Business\App\Exports\ExportCurrentStock; class AcnooStockController extends Controller @@ -25,81 +22,130 @@ public function index() $search_term = request('search'); $per_page = request('per_page', 20); - // Base query - $query = Product::with('stocks','warehouse:id,name', 'rack:id,name', 'shelf:id,name')->where('product_type', '!=', 'combo')->where('business_id', $businessId); + $query = Product::with('stocks', 'category:id,categoryName') + ->where('product_type', '!=', 'combo') + ->where('business_id', $businessId); - // For Low Stock view - apply search if search term exists - if ($alert_qty_filter) { - // Apply search filter if search term exists - if ($search_term) { - $query->where(function ($q) use ($search_term) { - $q->where('productName', 'like', '%' . $search_term . '%') - ->orWhere('productPurchasePrice', 'like', '%' . $search_term . '%') - ->orWhere('productSalePrice', 'like', '%' . $search_term . '%'); - }); - } - - // Get all products (with search applied if any) then filter for low stock - $allProducts = $query->latest()->get(); - $filteredProducts = $allProducts->filter(function ($product) { - $totalStock = $product->stocks->sum('productStock'); - return $totalStock <= $product->alert_qty; + if ($search_term) { + $query->where(function ($q) use ($search_term) { + $q->where('productName', 'like', '%' . $search_term . '%') + ->orWhere('productCode', 'like', '%' . $search_term . '%') + ->orWhere('hsn_code', 'like', '%' . $search_term . '%') + ->orWhereHas('category', function ($q) use ($search_term) { + $q->where('categoryName', 'like', '%' . $search_term . '%'); + }); }); + } - // Manual pagination for filtered collection - $currentPage = request('page', 1); - $products = new LengthAwarePaginator( - $filteredProducts->forPage($currentPage, $per_page), - $filteredProducts->count(), - $per_page, - $currentPage, - ['path' => request()->url(), 'query' => request()->query()] - ); + if ($alert_qty_filter) { + $products = $query + ->withSum('stocks as total_stock', 'productStock') + ->whereRaw('(SELECT COALESCE(SUM("productStock"),0) FROM stocks WHERE stocks.product_id = products.id) <= products.alert_qty') + ->latest() + ->paginate($per_page) + ->appends(request()->query()); - // Calculate totals for low stock view - $total_stock_value = $filteredProducts->sum(function ($product) { + $total_stock_qty = $products->sum('total_stock'); + + $total_stock_value = $products->sum(function ($product) { return $product->stocks->sum(function ($stock) { return $stock->productStock * $stock->productPurchasePrice; }); }); - $total_qty = $filteredProducts->sum(function ($product) { - return $product->stocks->sum('productStock'); - }); } else { - // For All Stock view - NO search, just regular pagination - $products = $query->latest()->paginate($per_page)->appends(request()->query()); + $products = $query + ->withSum('stocks as total_stock', 'productStock') + ->latest() + ->paginate($per_page) + ->appends(request()->query()); - // Calculate totals for all stock - $total_stock_value = Stock::whereHas('product', function ($q) use ($businessId) { - $q->where('business_id', $businessId); - })->sum(DB::raw('"productPurchasePrice" * "productStock"')); + $total_stock_qty = $products->sum('total_stock'); - $total_qty = Stock::whereHas('product', function ($q) use ($businessId) { - $q->where('business_id', $businessId); - })->sum('productStock'); + $total_stock_value = $products->sum(function ($product) { + return $product->stocks->sum(function ($stock) { + return $stock->productStock * $stock->productPurchasePrice; + }); + }); } - // Handle AJAX request if (request()->ajax()) { return response()->json([ - 'data' => view('business::stocks.datas', compact('products', 'total_stock_value', 'total_qty'))->render() + 'data' => view('business::stocks.datas', compact('products', 'total_stock_value', 'total_stock_qty'))->render(), + 'total_stock_qty' => $total_stock_qty, + 'total_stock_value' => currency_format($total_stock_value, currency: business_currency()) ]); } return view('business::stocks.index', [ 'products' => $products, 'total_stock_value' => $total_stock_value, - 'total_qty' => $total_qty, + 'total_stock_qty' => $total_stock_qty, ]); } public function exportExcel() { - return Excel::download(new ExportCurrentStock, 'current-stock.xlsx'); + return Excel::download(new ExportCurrentStock, 'stock-list.xlsx'); } public function exportCsv() { - return Excel::download(new ExportCurrentStock, 'current-stock.csv'); + return Excel::download(new ExportCurrentStock, 'stock-list.csv'); + } + + public function exportPdf() + { + $businessId = auth()->user()->business_id; + $alert_qty_filter = request('alert_qty'); + $search_term = request('search'); + $per_page = request('per_page', 20); + + $query = Product::with('stocks', 'category:id,categoryName') + ->where('product_type', '!=', 'combo') + ->where('business_id', $businessId); + + if ($search_term) { + $query->where(function ($q) use ($search_term) { + $q->where('productName', 'like', '%' . $search_term . '%') + ->orWhere('productCode', 'like', '%' . $search_term . '%') + ->orWhere('hsn_code', 'like', '%' . $search_term . '%') + ->orWhereHas('category', function ($q) use ($search_term) { + $q->where('categoryName', 'like', '%' . $search_term . '%'); + }); + }); + } + + if ($alert_qty_filter) { + $products = $query + ->withSum('stocks as total_stock', 'productStock') + ->havingRaw('total_stock <= products.alert_qty') + ->latest() + ->limit($per_page) + ->get(); + + $total_stock_qty = $products->sum('total_stock'); + + $total_stock_value = $products->sum(function ($product) { + return $product->stocks->sum(function ($stock) { + return $stock->productStock * $stock->productPurchasePrice; + }); + }); + } else { + $products = $query + ->withSum('stocks as total_stock', 'productStock') + ->latest() + ->limit($per_page) + ->get(); + + $total_stock_qty = $products->sum('total_stock'); + + $total_stock_value = $products->sum(function ($product) { + return $product->stocks->sum(function ($stock) { + return $stock->productStock * $stock->productPurchasePrice; + }); + }); + } + + return PdfService::render('business::stocks.pdf', compact('products', 'total_stock_qty', 'total_stock_value'), 'stock-list.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooStockReportController.php b/Modules/Business/App/Http/Controllers/AcnooStockReportController.php index d035bfd8..20994a48 100644 --- a/Modules/Business/App/Http/Controllers/AcnooStockReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooStockReportController.php @@ -2,10 +2,8 @@ namespace Modules\Business\App\Http\Controllers; -use App\Models\Stock; use App\Models\Product; use Illuminate\Http\Request; -use Illuminate\Support\Facades\DB; use App\Http\Controllers\Controller; use App\Services\PdfService; use Maatwebsite\Excel\Facades\Excel; @@ -22,31 +20,42 @@ public function index(Request $request) { $businessId = auth()->user()->business_id; - $total_stock_value = Stock::whereHas('product', function ($q) use ($businessId) { - $q->where('business_id', $businessId); - })->sum(DB::raw('"productPurchasePrice" * "productStock"')); + $productsQuery = Product::with(['stocks' => function ($q) { + $q->select('id', 'product_id', 'productStock', 'productPurchasePrice', 'productSalePrice'); + }]) + ->where('business_id', $businessId) + ->where('product_type', '!=', 'combo'); - $total_qty = Stock::whereHas('product', function ($q) use ($businessId) { - $q->where('business_id', $businessId); - })->sum('productStock'); + if ($request->search) { + $productsQuery->where(function ($q) use ($request) { + $q->where('productName', 'like', '%' . $request->search . '%') + ->orWhereHas('stocks', function ($stock) use ($request) { + $stock->where('productSalePrice', 'like', '%' . $request->search . '%') + ->orWhere('productPurchasePrice', 'like', '%' . $request->search . '%'); + }); + }); + } - $stock_reports = Product::with('stocks') - ->where('business_id', auth()->user()->business_id) - ->where('product_type', '!=', 'combo') - ->when($request->search, function ($query) use ($request) { - $query->where(function ($q) use ($request) { - $q->where('productName', 'like', '%' . $request->search . '%') - ->orwhere('productSalePrice', 'like', '%' . $request->search . '%') - ->orwhere('productPurchasePrice', 'like', '%' . $request->search . '%'); - }); - }) - ->withSum('stocks', 'productStock') + $stock_reports = $productsQuery ->latest() - ->paginate($request->per_page ?? 20)->appends($request->query()); + ->paginate($request->per_page ?? 20) + ->appends($request->query()); + + $total_qty = $stock_reports->getCollection()->sum(function ($product) { + return $product->stocks->sum('productStock'); + }); + + $total_stock_value = $stock_reports->getCollection()->sum(function ($product) { + return $product->stocks->sum(function ($stock) { + return $stock->productStock * $stock->productPurchasePrice; + }); + }); if ($request->ajax()) { return response()->json([ - 'data' => view('business::reports.stocks.datas', compact('stock_reports'))->render() + 'data' => view('business::reports.stocks.datas', compact('stock_reports'))->render(), + 'total_stock_value' => currency_format($total_stock_value, currency: business_currency()), + 'total_qty' => $total_qty ]); } @@ -63,21 +72,29 @@ public function exportCsv() return Excel::download(new ExportCurrentStockReport, 'current-stock-report.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { - $query = Product::with('stocks') + $productsQuery = Product::with(['stocks' => function ($q) { + $q->select('id', 'product_id', 'productStock', 'productPurchasePrice', 'productSalePrice'); + }]) ->where('business_id', auth()->user()->business_id) ->where('product_type', '!=', 'combo'); - if (request('alert_qty')) { - $stock_reports = $query->get()->filter(function ($product) { - $totalStock = $product->stocks->sum('productStock'); - return $totalStock <= $product->alert_qty; + if ($request->search) { + $productsQuery->where(function ($q) use ($request) { + $q->where('productName', 'like', '%' . $request->search . '%') + ->orWhereHas('stocks', function ($stock) use ($request) { + $stock->where('productSalePrice', 'like', '%' . $request->search . '%') + ->orWhere('productPurchasePrice', 'like', '%' . $request->search . '%'); + }); }); - } else { - $stock_reports = $query->latest()->get(); } + $stock_reports = $productsQuery + ->latest() + ->limit($request->per_page ?? 20) + ->get(); + return PdfService::render('business::reports.stocks.pdf', compact('stock_reports'), 'stock-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooSubscriptionReportController.php b/Modules/Business/App/Http/Controllers/AcnooSubscriptionReportController.php index be840d4d..a520a83c 100644 --- a/Modules/Business/App/Http/Controllers/AcnooSubscriptionReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooSubscriptionReportController.php @@ -72,16 +72,42 @@ public function exportCsv() return Excel::download(new ExportSubscription, 'subscribers.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { - $subscribers = PlanSubscribe::with(['plan:id,subscriptionName','business:id,companyName,business_category_id,pictureUrl','business.category:id,name','gateway:id,name'])->where('business_id', auth()->user()->business_id) - ->latest() - ->get(); + $subscriberQuery = PlanSubscribe::with(['plan:id,subscriptionName','business:id,companyName,business_category_id,pictureUrl','business.category:id,name','gateway:id,name'])->where('business_id', auth()->user()->business_id); - return PdfService::render('business::reports.subscription-reports.pdf', compact('subscribers'),'subscription-reports.pdf'); + // Date Filter + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + $this->applyDateFilter($subscriberQuery, $duration, 'created_at', $request->from_date, $request->to_date); + + // Search Filter + if ($request->filled('search')) { + $search = $request->search; + $subscriberQuery->where(function ($query) use ($search) { + $query->where('duration', 'like', '%' . $search . '%') + ->orWhereHas('plan', function ($q) use ($search) { + $q->where('subscriptionName', 'like', '%' . $search . '%'); + }) + ->orWhereHas('gateway', function ($q) use ($search) { + $q->where('name', 'like', '%' . $search . '%'); + }) + ->orWhereHas('business', function ($q) use ($search) { + $q->where('companyName', 'like', '%' . $search . '%') + ->orWhereHas('category', function ($q) use ($search) { + $q->where('name', 'like', '%' . $search . '%'); + }); + }); + }); + } + + $subscribers = $subscriberQuery->latest()->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::reports.subscription-reports.pdf', compact('subscribers', 'duration', 'filter_from_date', 'filter_to_date'),'subscription-reports.pdf'); } - public function getInvoice($invoice_id) + public function getInvoice(string $invoice_id) { $subscriber = PlanSubscribe::with(['plan:id,subscriptionName','business:id,companyName,business_category_id,pictureUrl,phoneNumber,email,address,meta','business.category:id,name','gateway:id,name'])->where('business_id', auth()->user()->business_id)->findOrFail($invoice_id); return view('business::reports.subscription-reports.invoice', compact('subscriber')); diff --git a/Modules/Business/App/Http/Controllers/AcnooSupplierDueReportController.php b/Modules/Business/App/Http/Controllers/AcnooSupplierDueReportController.php index 2391cb78..e57e5c2b 100644 --- a/Modules/Business/App/Http/Controllers/AcnooSupplierDueReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooSupplierDueReportController.php @@ -35,82 +35,52 @@ public function index(Request $request) ->with('purchases_dues') ->latest(); - // Branch-aware due filter - if ($activeBranch) { - $query->whereHas('purchases_dues', function ($q) use ($activeBranch) { - $q->where('branch_id', $activeBranch->id) - ->where('dueAmount', '>', 0); - }); - } else { - $query->where('due', '>', 0); - } + $parties = $query + ->paginate($request->per_page ?? 20) + ->appends($request->query()); - $parties = $query->paginate($request->per_page ?? 20); - - // Replace $supplier->due with branch-specific due if active branch exists if ($activeBranch) { $parties->setCollection( $parties->getCollection() ->transform(function ($supplier) use ($activeBranch) { - $supplier->due = $supplier->purchases_dues + $party_due = $supplier->purchases_dues ->where('branch_id', $activeBranch->id) ->sum('dueAmount'); + + if ($supplier->branch_id === $activeBranch->id) { + $openingBalanceDue = $supplier->opening_balance_type === 'due' + ? ($supplier->opening_balance ?? 0) + : 0; + + $supplier->due = $openingBalanceDue + $party_due; + } else { + $supplier->due = $party_due; + } + return $supplier; }) ->filter(fn($supplier) => $supplier->due > 0) ->values() ); + } else { + $parties->setCollection( + $parties->getCollection() + ->filter(fn($supplier) => ($supplier->due ?? 0) > 0) + ->values() + ); } - // Calculate total_due - $total_due = $parties->sum(function ($supplier) { - return $supplier->due; - }); + $supplier_total_due = $parties->getCollection()->sum('due'); if ($request->ajax()) { + return response()->json([ - 'data' => view('business::reports.supplier-due.datas', compact('parties', 'total_due'))->render() + 'data' => view('business::reports.supplier-due.datas', compact('parties'))->render(), + 'supplier_total_due' => currency_format($supplier_total_due, currency: business_currency()) ]); } - if ($activeBranch) { - // Filter parties that have branch-specific due > 0 - $query->whereHas('purchases_dues', function ($q) use ($activeBranch) { - $q->where('branch_id', $activeBranch->id) - ->where('dueAmount', '>', 0); - }); - } else { - $query->where('due', '>', 0); - } - - $parties = $query->paginate(20)->appends($request->query()); - - // Calculate total due - $total_due = $parties->sum(function ($supplier) use ($activeBranch) { - if ($activeBranch) { - return $supplier->purchases_dues - ->where('branch_id', $activeBranch->id) - ->sum('dueAmount'); - } - return $supplier->due; - }); - - // Replace $supplier->due with branch-specific due if active branch exists - if ($activeBranch) { - $parties->setCollection( - $parties->getCollection() - ->transform(function ($supplier) use ($activeBranch) { - $supplier->due = $supplier->purchases_dues - ->where('branch_id', $activeBranch->id) - ->sum('dueAmount'); - return $supplier; - }) - ->filter(fn($supplier) => $supplier->due > 0) - ->values() - ); - } - - return view('business::reports.supplier-due.due-reports', compact('parties', 'total_due')); + return view('business::reports.supplier-due.due-reports', compact('parties', 'supplier_total_due')); } public function exportExcel() @@ -123,7 +93,7 @@ public function exportCsv() return Excel::download(new ExportSupplierDue, 'supplier-due.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { $user = auth()->user(); $businessId = $user->business_id; @@ -131,29 +101,48 @@ public function exportPdf() $query = Party::where('business_id', $businessId) ->where('type', 'Supplier') + ->when($request->search, function ($q) use ($request) { + $q->where(function ($q2) use ($request) { + $q2->where('type', 'like', '%' . $request->search . '%') + ->orWhere('name', 'like', '%' . $request->search . '%') + ->orWhere('phone', 'like', '%' . $request->search . '%') + ->orWhere('credit_limit', 'like', '%' . $request->search . '%'); + }); + }) ->with('purchases_dues') ->latest(); - if ($activeBranch) { - $query->whereHas('purchases_dues', function ($q) use ($activeBranch) { - $q->where('branch_id', $activeBranch->id) - ->where('dueAmount', '>', 0); - }); - } else { - $query->where('due', '>', 0); - } - $parties = $query->get(); if ($activeBranch) { - $parties->transform(function ($supplier) use ($activeBranch) { - $supplier->due = $supplier->purchases_dues - ->where('branch_id', $activeBranch->id) - ->sum('dueAmount'); - return $supplier; - })->filter(fn($supplier) => $supplier->due > 0); + $parties = $parties + ->transform(function ($supplier) use ($activeBranch) { + $party_due = $supplier->purchases_dues + ->where('branch_id', $activeBranch->id) + ->sum('dueAmount'); + + if ($supplier->branch_id === $activeBranch->id) { + $openingBalanceDue = $supplier->opening_balance_type === 'due' + ? ($supplier->opening_balance ?? 0) + : 0; + + $supplier->due = $openingBalanceDue + $party_due; + } else { + $supplier->due = $party_due; + } + + return $supplier; + }) + ->filter(fn($supplier) => $supplier->due > 0) + ->take($request->per_page ?? 20) + ->values(); + } else { + $parties = $parties + ->filter(fn($supplier) => ($supplier->due ?? 0) > 0) + ->take($request->per_page ?? 20) + ->values(); } - return PdfService::render('business::reports.supplier-due.pdf', compact('parties'),'supplier-due-report.pdf'); + return PdfService::render('business::reports.supplier-due.pdf', compact('parties'), 'supplier-due-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooSupplierLedgerController.php b/Modules/Business/App/Http/Controllers/AcnooSupplierLedgerController.php index 9c268a08..80a4e4f4 100644 --- a/Modules/Business/App/Http/Controllers/AcnooSupplierLedgerController.php +++ b/Modules/Business/App/Http/Controllers/AcnooSupplierLedgerController.php @@ -50,7 +50,7 @@ public function index(Request $request) } - public function show(Request $request, $partyId, PartyLedgerService $service) + public function show(Request $request, string $partyId, PartyLedgerService $service) { $party = Party::find($partyId); $ledger = $service->list($request, $partyId); @@ -74,18 +74,25 @@ public function exportCsv() return Excel::download(new ExportSupplierLedger, 'supplier-ledger.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { $suppliers = Party::with('purchases') ->where('business_id', auth()->user()->business_id) ->where('type', '=', 'Supplier') + ->when(request('search'), function ($q) use ($request) { + $q->where(function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%') + ->orWhere('phone', 'like', '%' . $request->search . '%') + ->orWhere('type', 'like', '%' . $request->search . '%'); + }); + }) ->latest() - ->get(); + ->limit($request->per_page ?? 20)->get(); return PdfService::render('business::party-reports.supplier-ledger.pdf', compact('suppliers'),'supplier-ledger-report.pdf'); } - public function exportLedgerExcel(Request $request, $partyId, PartyLedgerService $service) + public function exportLedgerExcel(Request $request, string $partyId, PartyLedgerService $service) { $party = Party::findOrFail($partyId); $ledger = $service->list($request, $partyId); @@ -93,7 +100,7 @@ public function exportLedgerExcel(Request $request, $partyId, PartyLedgerService return Excel::download(new ExportSingleSupplierLedger($ledger, $party), 'single-supplier-ledger.xlsx'); } - public function exportLedgerCsv(Request $request, $partyId, PartyLedgerService $service) + public function exportLedgerCsv(Request $request, string $partyId, PartyLedgerService $service) { $party = Party::findOrFail($partyId); $ledger = $service->list($request, $partyId); @@ -101,14 +108,18 @@ public function exportLedgerCsv(Request $request, $partyId, PartyLedgerService $ return Excel::download(new ExportSingleSupplierLedger($ledger, $party), 'single-supplier-ledger.csv'); } - public function exportLedgerPdf(Request $request, $partyId, PartyLedgerService $service) + public function exportLedgerPdf(Request $request, string $partyId, PartyLedgerService $service) { $party = Party::find($partyId); $ledger = $service->list($request, $partyId); + $duration = $request->custom_days ?: $request->duration ?: 'today'; + + [$fromDate, $toDate] = app(PartyLedgerService::class)->applyDateRange($duration, $request->from_date, $request->to_date); + return PdfService::render( 'business::party-reports.supplier-ledger.show-details.pdf', - compact('ledger', 'party'), + compact('ledger', 'party', 'duration', 'fromDate', 'toDate'), strtolower($party->name).'-supplier.pdf' ); } diff --git a/Modules/Business/App/Http/Controllers/AcnooTopCustomerController.php b/Modules/Business/App/Http/Controllers/AcnooTopCustomerController.php index 96904a04..117bfd66 100644 --- a/Modules/Business/App/Http/Controllers/AcnooTopCustomerController.php +++ b/Modules/Business/App/Http/Controllers/AcnooTopCustomerController.php @@ -56,16 +56,29 @@ public function exportCsv() return Excel::download(new ExportTopCustomer, 'top-customers.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { $customers = Party::with('sales') ->where('business_id', auth()->user()->business_id) ->where('type', '!=', 'Supplier') ->whereHas('sales') + ->when(request('search'), function ($q) use ($request) { + $q->where(function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%') + ->orWhere('phone', 'like', '%' . $request->search . '%') + ->orWhere('email', 'like', '%' . $request->search . '%') + ->orWhere('type', 'like', '%' . $request->search . '%'); + }); + }) ->withCount('sales') ->withSum('sales', 'totalAmount') ->orderByDesc('sales_count') ->orderByDesc('sales_sum_total_amount') + ->when(request('type'), function ($q) use ($request) { + $q->where(function ($q) use ($request) { + $q->where('type', $request->type); + }); + }) ->take(5) ->get(); diff --git a/Modules/Business/App/Http/Controllers/AcnooTopProductReportController.php b/Modules/Business/App/Http/Controllers/AcnooTopProductReportController.php index 92835766..334a7232 100644 --- a/Modules/Business/App/Http/Controllers/AcnooTopProductReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooTopProductReportController.php @@ -23,7 +23,7 @@ public function index(Request $request) } $top_products = SaleDetails::query() - ->with('product:id,productName,productCode') + ->with('product:id,productName,productCode,hsn_code') ->whereHas('product', function ($q) use ($businessId) { $q->where('business_id', $businessId); }) @@ -67,28 +67,41 @@ public function exportCsv() return Excel::download(new ExportTopProduct, 'top-products.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { - $branchId = moduleCheck('MultiBranchAddon') ? auth()->user()->branch_id ?? auth()->user()->active_branch_id : null; + $user = auth()->user(); + $businessId = $user->business_id; - $top_products = SaleDetails::with('product:id,productName,productCode') - ->whereHas('product', function ($q) { - $q->where('business_id', auth()->user()->business_id); - }) - ->when($branchId, function ($q) use ($branchId) { - $q->whereHas('sale', function ($sale) use ($branchId) { - $sale->where('branch_id', $branchId); - }); - }) - ->select( - 'product_id', - DB::raw('SUM(quantities) as total_sold_qty'), - DB::raw('SUM(price * quantities) as total_sale_amount') - ) - ->groupBy('product_id') - ->orderByDesc('total_sold_qty') - ->take(5) - ->get(); + $branchId = null; + if (moduleCheck('MultiBranchAddon')) { + $branchId = $user->branch_id ?? $user->active_branch_id; + } + + $top_products = SaleDetails::query() + ->with('product:id,productName,productCode,hsn_code') + ->whereHas('product', function ($q) use ($businessId) { + $q->where('business_id', $businessId); + }) + ->when($request->filled('search'), function ($q) use ($request) { + $q->whereHas('product', function ($q) use ($request) { + $q->where('productName', 'like', "%{$request->search}%") + ->orWhere('productCode', 'like', "%{$request->search}%"); + }); + }) + ->when($branchId, function ($q) use ($branchId) { + $q->whereHas('sale', function ($q) use ($branchId) { + $q->where('branch_id', $branchId); + }); + }) + ->select( + 'product_id', + DB::raw('SUM(quantities) as total_sold_qty'), + DB::raw('SUM((price - discount) * quantities) as total_sale_amount') + ) + ->groupBy('product_id') + ->orderByDesc('total_sold_qty') + ->limit(5) + ->get(); return PdfService::render('business::reports.top-products.pdf', compact('top_products'),'top-product-report.pdf'); } diff --git a/Modules/Business/App/Http/Controllers/AcnooTopSupplierController.php b/Modules/Business/App/Http/Controllers/AcnooTopSupplierController.php index f0a37e81..57a433e1 100644 --- a/Modules/Business/App/Http/Controllers/AcnooTopSupplierController.php +++ b/Modules/Business/App/Http/Controllers/AcnooTopSupplierController.php @@ -51,12 +51,20 @@ public function exportCsv() return Excel::download(new ExportTopSupplier, 'top-suppliers.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { $suppliers = Party::with('purchases') ->where('business_id', auth()->user()->business_id) ->where('type', '=', 'Supplier') ->whereHas('purchases') + ->when(request('search'), function ($q) use ($request) { + $q->where(function ($q) use ($request) { + $q->where('name', 'like', '%' . $request->search . '%') + ->orWhere('phone', 'like', '%' . $request->search . '%') + ->orWhere('email', 'like', '%' . $request->search . '%') + ->orWhere('type', 'like', '%' . $request->search . '%'); + }); + }) ->withCount('purchases') ->withSum('purchases', 'totalAmount') ->orderByDesc('purchases_count') diff --git a/Modules/Business/App/Http/Controllers/AcnooTransactionController.php b/Modules/Business/App/Http/Controllers/AcnooTransactionController.php index e968789b..7cf70f9f 100644 --- a/Modules/Business/App/Http/Controllers/AcnooTransactionController.php +++ b/Modules/Business/App/Http/Controllers/AcnooTransactionController.php @@ -124,13 +124,78 @@ public function exportCsv() return Excel::download(new ExportTransactionReport, 'bill-wise-profit.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { - $transactions = Transaction::with('paymentType') - ->where('business_id', auth()->user()->business_id) - ->latest() - ->get(); + $businessId = auth()->user()->business_id; - return PdfService::render('business::transactions.pdf', compact('transactions'),'transactions-report.pdf'); + $transactionsQuery = Transaction::with('paymentType') + ->where('business_id', $businessId); + + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + $this->applyDateFilter($transactionsQuery, $duration, 'date', $request->from_date, $request->to_date); + + // Search filter + if ($request->filled('search')) { + $transactionsQuery->where(function ($query) use ($request) { + $query->where('amount', 'like', '%' . $request->search . '%') + ->orWhere('invoice_no', 'like', '%' . $request->search . '%') + ->orWhere('platform', 'like', '%' . $request->search . '%') + ->orWhere('type', 'like', '%' . $request->search . '%'); + }); + } + + // Platform filter + if ($request->filled('platform')) { + $transactionsQuery->where('platform', $request->platform); + } + + // Party Filter + if ($request->filled('party_id')) { + + $transactionsQuery->where(function ($q) use ($request) { + + $q->where(function ($saleQ) use ($request) { + $saleQ->where('platform', 'sale') + ->whereHas('sale', function ($s) use ($request) { + $s->where('party_id', $request->party_id); + }); + }); + + $q->where(function ($saleRtnQ) use ($request) { + $saleRtnQ->where('platform', 'sale_return') + ->whereHas('saleReturn.sale', function ($s) use ($request) { + $s->where('party_id', $request->party_id); + }); + }); + + $q->orWhere(function ($purQ) use ($request) { + $purQ->where('platform', 'purchase') + ->whereHas('purchase', function ($p) use ($request) { + $p->where('party_id', $request->party_id); + }); + }); + + $q->orWhere(function ($purRtnQ) use ($request) { + $purRtnQ->where('platform', 'purchase_return') + ->whereHas('purchaseReturn.purchase', function ($p) use ($request) { + $p->where('party_id', $request->party_id); + }); + }); + + $q->orWhere(function ($dueQ) use ($request) { + $dueQ->where('platform', 'due_collect') + ->whereHas('dueCollect', function ($d) use ($request) { + $d->where('party_id', $request->party_id); + }); + }); + + }); + } + + $transactions = $transactionsQuery->latest()->limit($request->per_page ?? 20)->get(); + + return PdfService::render('business::transactions.pdf', compact('transactions', 'duration', 'filter_from_date', 'filter_to_date'),'transactions-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooTransactionHistoryReportController.php b/Modules/Business/App/Http/Controllers/AcnooTransactionHistoryReportController.php index 8f11d392..2340e7da 100644 --- a/Modules/Business/App/Http/Controllers/AcnooTransactionHistoryReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooTransactionHistoryReportController.php @@ -8,7 +8,6 @@ use App\Services\PdfService; use App\Traits\DateFilterTrait; use App\Traits\DateRangeTrait; -use Illuminate\Support\Carbon; use Maatwebsite\Excel\Facades\Excel; use Modules\Business\App\Exports\ExportTransaction; @@ -24,15 +23,6 @@ public function __construct() public function index(Request $request) { $businessId = auth()->user()->business_id; - $today = Carbon::today()->format('Y-m-d'); - - $total_due = DueCollect::where('business_id', $businessId) - ->whereDate('paymentDate', $today) - ->sum('totalDue'); - - $total_paid = DueCollect::where('business_id', $businessId) - ->whereDate('paymentDate', $today) - ->sum('payDueAmount'); $transactionsQuery = DueCollect::with('party:id,name,type', 'payment_type:id,name', 'transactions')->where('business_id', $businessId); @@ -93,12 +83,44 @@ public function exportCsv() return Excel::download(new ExportTransaction, 'transaction-history.csv'); } - public function exportPdf() + public function exportPdf(Request $request) { - $transactions = DueCollect::with('party:id,name,type', 'payment_type:id,name', 'transactions')->where('business_id', auth()->user()->business_id) - ->latest() - ->get(); + $businessId = auth()->user()->business_id; - return PdfService::render('business::reports.transaction-history.pdf', compact('transactions'),'due-transactions-report.pdf'); + $transactionsQuery = DueCollect::with('party:id,name,type', 'payment_type:id,name', 'transactions')->where('business_id', $businessId); + + // Date Filter + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + $this->applyDateFilter($transactionsQuery, $duration, 'paymentDate', $request->from_date, $request->to_date); + + // Search Filter + if ($request->filled('search')) { + $transactionsQuery->where(function ($query) use ($request) { + $query->where('paymentType', 'like', '%' . $request->search . '%') + ->orWhere('totalDue', 'like', '%' . $request->search . '%') + ->orWhere('invoiceNumber', 'like', '%' . $request->search . '%') + ->orWhere('payDueAmount', '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 . '%'); + }); + }); + } + + if ($request->filled('type')) { + $transactionsQuery->whereHas('party', function ($q) use ($request) { + $q->where(function ($q) use ($request) { + $q->where('type', $request->type); + }); + }); + } + + $transactions = $transactionsQuery->latest()->limit($request->per_page)->get(); + + return PdfService::render('business::reports.transaction-history.pdf', compact('transactions', 'duration', 'filter_from_date', 'filter_to_date'),'due-transactions-report.pdf'); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooTransferController.php b/Modules/Business/App/Http/Controllers/AcnooTransferController.php index 2f4603a1..55fcae7f 100644 --- a/Modules/Business/App/Http/Controllers/AcnooTransferController.php +++ b/Modules/Business/App/Http/Controllers/AcnooTransferController.php @@ -10,6 +10,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use App\Http\Controllers\Controller; +use App\Services\PdfService; use Maatwebsite\Excel\Facades\Excel; use Modules\Business\App\Exports\ExportTransfer; @@ -25,11 +26,20 @@ public function __construct() public function index(Request $request) { - $branches = Branch::withTrashed()->where('business_id', auth()->user()->business_id)->latest()->get(); + $user = auth()->user(); + $activeBranchId = $user->active_branch_id; + + $branches = Branch::withTrashed()->where('business_id', $user->business_id)->latest()->get(); $transfers = Transfer::with(['fromWarehouse:id,name', 'toWarehouse:id,name', 'toBranch:id,name', 'fromBranch:id,name', 'transferProducts']) - ->where('business_id', auth()->user()->business_id) - ->when($request->branch_id, function ($q) use ($request) { + ->where('business_id', $user->business_id) + ->when($activeBranchId, function ($q) use ($activeBranchId) { + $q->where(function ($query) use ($activeBranchId) { + $query->where('from_branch_id', $activeBranchId) + ->orWhere('to_branch_id', $activeBranchId); + }); + }) + ->when($request->branch_id && !$activeBranchId, function ($q) use ($request) { $q->where(function ($query) use ($request) { $query->where('from_branch_id', $request->branch_id)->orWhere('to_branch_id', $request->branch_id); }); @@ -77,6 +87,19 @@ public function create() public function store(Request $request) { + // Normalize serial_numbers + if ($request->has('products')) { + $products = $request->products; + + foreach ($products as $key => $item) { + if (!empty($item['serial_numbers']) && is_string($item['serial_numbers'])) { + $products[$key]['serial_numbers'] = json_decode($item['serial_numbers'], true); + } + } + + $request->merge(['products' => $products]); + } + $request->validate([ 'from_branch_id' => 'nullable|exists:branches,id', 'to_branch_id' => 'nullable|exists:branches,id|required_with:from_branch_id', @@ -91,6 +114,7 @@ public function store(Request $request) 'products.*.unit_price' => 'required|numeric|min:0', 'products.*.discount' => 'nullable|numeric|min:0', 'products.*.tax' => 'nullable|numeric|min:0', + 'products.*.serial_numbers' => 'nullable|array', ]); $user = auth()->user(); @@ -99,72 +123,54 @@ public function store(Request $request) $fromWh = $request->from_warehouse_id; $toWh = $request->to_warehouse_id; - // Transfer validation logic + // Transfer validation if ($user->active_branch_id && $toBranch && $toWh) { - return response()->json([ - 'message' => 'You cannot transfer to another branch warehouse.' - ], 400); + return response()->json(['message' => 'You cannot transfer to another branch warehouse.'], 400); } if (!$toBranch && !$toWh) { - // to_branch or to_warehouse no one exist - return response()->json([ - 'message' => 'Invalid transfer request. Please select a destination branch or warehouse.' - ], 400); + return response()->json(['message' => 'Please select a destination branch or warehouse.'], 400); } - if ($fromBranch && !$fromWh) { - // Branch to Branch transfer only - if ($fromBranch == $toBranch) { - return response()->json([ - 'message' => 'Transfer not allowed: Same branch transfer is not possible.' - ], 400); - } - } elseif (!$fromBranch && $fromWh) { - // Warehouse to Warehouse transfer only - if ($fromWh == $toWh) { - return response()->json([ - 'message' => 'Transfer not allowed: Same warehouse transfer is not possible.' - ], 400); - } - } elseif ($fromBranch && $fromWh) { - // Both branch and warehouse present - if ($fromBranch == $toBranch && $fromWh == $toWh) { - return response()->json([ - 'message' => 'Transfer not allowed: Same warehouse transfer within the same branch.' - ], 400); - } - } else { - return response()->json([ - 'message' => 'Invalid transfer request. Please provide branch or warehouse information.' - ], 400); + if ($fromBranch && !$fromWh && $fromBranch == $toBranch) { + return response()->json(['message' => 'Same branch transfer is not allowed.'], 400); + } + if ($fromWh && $fromWh == $toWh) { + return response()->json(['message' => 'Same warehouse transfer is not allowed.'], 400); } DB::beginTransaction(); try { - $subTotal = 0; - $totalDiscount = 0; - $totalTax = 0; + $subTotal = $totalDiscount = $totalTax = 0; - foreach ($request->products as $item) { - $qty = $item['quantity']; - $price = $item['unit_price']; - $discount = $item['discount'] ?? 0; - $tax = $item['tax'] ?? 0; + // products re-arrange + $products = collect($request->products)->map(function ($item, $key) { + return array_merge($item, ['stock_id' => $item['stock_id'] ?? $key]); + }); - $subTotal += ($qty * $price); - $totalTax += $tax; - $totalDiscount += $discount; + // Calculate subtotal using serial count if available + foreach ($products as $item) { + $serials = !empty($item['serial_numbers']) + ? (is_array($item['serial_numbers']) ? $item['serial_numbers'] : json_decode($item['serial_numbers'], true)) + : []; + $qty = !empty($serials) ? count($serials) : $item['quantity']; + $subTotal += $qty * $item['unit_price']; + $totalTax += $item['tax'] ?? 0; + $totalDiscount += $item['discount'] ?? 0; } $shipping = $request->shipping_charge ?? 0; $grandTotal = $subTotal + $totalTax - $totalDiscount + $shipping; - $transfer = Transfer::create($request->except('business_id', 'shipping_charge', 'sub_total', 'total_discount', 'total_tax', 'grand_total', 'from_warehouse_id', 'to_warehouse_id', 'to_branch_id', 'from_branch_id') + [ - 'business_id' => auth()->user()->business_id, + $transfer = Transfer::create([ + 'business_id' => $user->business_id, + 'user_id' => auth()->id(), 'from_branch_id' => $fromBranch, 'from_warehouse_id' => $fromWh, 'to_branch_id' => $toBranch, 'to_warehouse_id' => $toWh, + 'transfer_date' => $request->transfer_date, + 'status' => $request->status, + 'note' => $request->note, 'shipping_charge' => $shipping, 'sub_total' => $subTotal, 'total_discount' => $totalDiscount, @@ -172,115 +178,120 @@ public function store(Request $request) 'grand_total' => $grandTotal, ]); - $transferProductData = []; - - foreach ($request->products as $stockId => $item) { - // Find product_id from stock_id - $stock = Stock::find($stockId); + $transferProducts = []; + foreach ($products as $item) { + $stock = Stock::find($item['stock_id']); if (!$stock) { - return response()->json([ - 'message' => "Invalid stock ID: {$stockId}" - ], 400); + return response()->json(['message' => "Invalid stock ID: {$item['stock_id']}"], 400); } - $transferProductData[] = [ + // Ensure serial_numbers is array + $serials = !empty($item['serial_numbers']) + ? (is_array($item['serial_numbers']) ? $item['serial_numbers'] : json_decode($item['serial_numbers'], true)) + : []; + + $quantity = !empty($serials) ? count($serials) : $item['quantity']; + + $transferProducts[] = [ 'transfer_id' => $transfer->id, - 'stock_id' => $stockId, + 'stock_id' => $stock->id, 'product_id' => $stock->product_id, - 'quantity' => $item['quantity'] ?? 0, - 'unit_price' => $item['unit_price'] ?? 0, + 'quantity' => $quantity, + 'unit_price' => $item['unit_price'], 'discount' => $item['discount'] ?? 0, 'tax' => $item['tax'] ?? 0, + 'serial_numbers' => !empty($serials) ? json_encode($serials) : null, 'created_at' => now(), 'updated_at' => now(), ]; if ($request->status === 'completed') { - foreach ($request->products as $stockId => $item) { - // Get the actual FROM stock - $fromStock = Stock::where('id', $stockId) - ->when($fromBranch, fn($q) => $q->where('branch_id', $fromBranch)) - ->when($fromWh, fn($q) => $q->where('warehouse_id', $fromWh)) - ->first(); + // FROM stock + $fromStock = Stock::where('id', $stock->id) + ->when($fromBranch, fn($q) => $q->where('branch_id', $fromBranch)) + ->when($fromWh, fn($q) => $q->where('warehouse_id', $fromWh)) + ->first(); - if (!$fromStock) { - return response()->json([ - 'message' => "Stock not found in source (branch/warehouse) for stock ID: {$stockId}" - ], 400); - } + if (!$fromStock || $fromStock->productStock < $quantity) { + return response()->json(['message' => 'Insufficient stock for product ID: ' . $stock->product_id], 400); + } - if ($fromStock->productStock < $item['quantity']) { - return response()->json([ - 'message' => "Insufficient stock in source for product ID: {$fromStock->product_id}, available: {$fromStock->productStock}" - ], 400); - } + // Remove serials from source + if (!empty($serials)) { + $existingSerials = is_array($fromStock->serial_numbers) ? $fromStock->serial_numbers : json_decode($fromStock->serial_numbers ?? '[]', true); + $fromStock->serial_numbers = array_values(array_diff($existingSerials, $serials)); + } - // Decrease FROM stock - $fromStock->decrement('productStock', $item['quantity']); + $fromStock->decrement('productStock', $quantity); + $fromStock->save(); - // Get the TO stock - $toStock = Stock::where('product_id', $fromStock->product_id) - ->when($toBranch, fn($q) => $q->where('branch_id', $toBranch)) - ->when($toWh, fn($q) => $q->where('warehouse_id', $toWh)) - ->when(!is_null($fromStock->batch_no), fn($q) => $q->where('batch_no', $fromStock->batch_no)) - ->first(); + // TO stock + $toStock = Stock::where('product_id', $fromStock->product_id) + ->when($toBranch, fn($q) => $q->where('branch_id', $toBranch)) + ->when($toWh, fn($q) => $q->where('warehouse_id', $toWh)) + ->where(function ($q) use ($fromStock) { + // if destination stock batch is NULL, skip batch compare + $q->whereNull('batch_no') + // otherwise match same batch + ->orWhere('batch_no', $fromStock->batch_no); + }) + ->first(); - if (!$toStock) { - $toStock = new Stock([ - 'business_id' => auth()->user()->business_id, - 'product_id' => $fromStock->product_id, - 'warehouse_id' => $toWh, - 'batch_no' => $fromStock->batch_no, - 'productStock' => 0, - 'productPurchasePrice' => $fromStock->productPurchasePrice, - 'profit_percent' => $fromStock->profit_percent, - 'productSalePrice' => $fromStock->productSalePrice, - 'productWholeSalePrice' => $fromStock->productWholeSalePrice, - 'productDealerPrice' => $fromStock->productDealerPrice, - 'mfg_date' => $fromStock->mfg_date, - 'expire_date' => $fromStock->expire_date, - ]); + if (!$toStock) { + $toStock = new Stock([ + 'business_id' => $user->business_id, + 'product_id' => $fromStock->product_id, + 'warehouse_id' => $toWh, + 'productStock' => 0, + 'productPurchasePrice' => $fromStock->productPurchasePrice, + 'profit_percent' => $fromStock->profit_percent, + 'productSalePrice' => $fromStock->productSalePrice, + 'exclusive_price' => $fromStock->exclusive_price, + 'productWholeSalePrice' => $fromStock->productWholeSalePrice, + 'productDealerPrice' => $fromStock->productDealerPrice, + 'mfg_date' => $fromStock->mfg_date, + 'expire_date' => $fromStock->expire_date, + 'serial_numbers' => !empty($serials) ? $serials : null, + ]); - // if active branch and to branch is null then use active branch id - if ($toBranch == null && $user->active_branch_id){ - $toStock->branch_id = $user->active_branch_id; - }else{ - $toStock->branch_id = $toBranch; - } + // Safe branch assignment + $toStock->branch_id = $toBranch ?? $user->active_branch_id; - // Update product_type - if ($fromStock->product->product_type !== 'variant') { - $fromStock->product->update([ - 'product_type' => 'variant' - ]); - } + // Save without events/global scopes + Stock::withoutEvents(function () use ($toStock) { + $toStock->save(); + }); + } - // Skip booted from model - Stock::withoutEvents(function () use ($toStock) { - $toStock->save(); - }); - } + // Increment destination stock safely + $toStock->increment('productStock', $quantity); - // Increase TO stock safely - $toStock->increment('productStock', $item['quantity']); + // Merge serial numbers if exist + if (!empty($serials)) { + $toStockSerials = is_array($toStock->serial_numbers) + ? $toStock->serial_numbers + : json_decode($toStock->serial_numbers ?? '[]', true); + $toStock->serial_numbers = array_values(array_unique(array_merge($toStockSerials, $serials))); + $toStock->save(); } } } - TransferProduct::insert($transferProductData); + TransferProduct::insert($transferProducts); DB::commit(); - return response()->json([ 'message' => __('Transfer saved successfully.'), + 'data' => $transfer, 'redirect' => route('business.transfers.index'), ]); } catch (\Exception $e) { DB::rollBack(); return response()->json([ - 'message' => 'Error: ' . $e->getMessage(), + 'success' => false, + 'message' => $e->getMessage() ], 500); } } @@ -313,6 +324,19 @@ public function edit($id) public function update(Request $request, $id) { + // Normalize serial_numbers + if ($request->has('products')) { + $products = $request->products; + + foreach ($products as $key => $item) { + if (!empty($item['serial_numbers']) && is_string($item['serial_numbers'])) { + $products[$key]['serial_numbers'] = json_decode($item['serial_numbers'], true); + } + } + + $request->merge(['products' => $products]); + } + $request->validate([ 'from_branch_id' => 'nullable|exists:branches,id', 'to_branch_id' => 'nullable|exists:branches,id|required_with:from_branch_id', @@ -327,11 +351,12 @@ public function update(Request $request, $id) 'products.*.unit_price' => 'required|numeric|min:0', 'products.*.discount' => 'nullable|numeric|min:0', 'products.*.tax' => 'nullable|numeric|min:0', + 'products.*.serial_numbers' => 'nullable|array', ]); $transfer = Transfer::findOrFail($id); - if ($request->status == 'cancelled') { + if ($request->status === 'cancelled') { $transfer->update(['status' => 'cancelled']); return response()->json([ 'message' => __('Transfer cancelled successfully.'), @@ -345,49 +370,28 @@ public function update(Request $request, $id) $fromWh = $request->from_warehouse_id; $toWh = $request->to_warehouse_id; - // Transfer validation - if ($user->active_branch_id && $toBranch && $toWh) { - return response()->json([ - 'message' => 'You cannot transfer to another branch warehouse.' - ], 400); - } - if (!$toBranch && !$toWh) { - // to_branch or to_warehouse no one exist - return response()->json([ - 'message' => 'Invalid transfer request. Please select a destination branch or warehouse.' - ], 400); - } - - if ($fromBranch && !$fromWh) { - if ($fromBranch == $toBranch) { - return response()->json(['message' => 'Transfer not allowed: Same branch transfer is not possible.'], 400); - } - } elseif (!$fromBranch && $fromWh) { - if ($fromWh == $toWh) { - return response()->json(['message' => 'Transfer not allowed: Same warehouse transfer is not possible.'], 400); - } - } elseif ($fromBranch && $fromWh) { - if ($fromBranch == $toBranch && $fromWh == $toWh) { - return response()->json(['message' => 'Transfer not allowed: Same warehouse transfer within the same branch.'], 400); - } - } else { - return response()->json(['message' => 'Invalid transfer request. Please provide branch or warehouse information.'], 400); - } - DB::beginTransaction(); + try { $oldStatus = $transfer->status; $subTotal = $totalDiscount = $totalTax = 0; + + // Calculate totals using serial count foreach ($request->products as $item) { - $subTotal += $item['quantity'] * $item['unit_price']; + $serials = !empty($item['serial_numbers']) ? $item['serial_numbers'] : []; + $qty = !empty($serials) ? count($serials) : $item['quantity']; + + $subTotal += $qty * $item['unit_price']; $totalDiscount += $item['discount'] ?? 0; $totalTax += $item['tax'] ?? 0; } + $shipping = $request->shipping_charge ?? 0; $grandTotal = $subTotal + $totalTax - $totalDiscount + $shipping; // Update transfer $transfer->update([ + 'user_id' => auth()->id(), 'from_branch_id' => $fromBranch, 'to_branch_id' => $toBranch, 'from_warehouse_id' => $fromWh, @@ -402,25 +406,25 @@ public function update(Request $request, $id) 'grand_total' => $grandTotal, ]); - // Update TransferProduct + // Delete old transfer products TransferProduct::where('transfer_id', $transfer->id)->delete(); - $transferProductData = []; + foreach ($request->products as $stockId => $item) { - $transferProductData[] = [ + + $serials = !empty($item['serial_numbers']) ? $item['serial_numbers'] : []; + $quantity = !empty($serials) ? count($serials) : $item['quantity']; + + // Insert updated transfer product + TransferProduct::create([ 'transfer_id' => $transfer->id, 'stock_id' => $stockId, - 'quantity' => $item['quantity'] ?? 0, + 'quantity' => $quantity, 'unit_price' => $item['unit_price'] ?? 0, 'discount' => $item['discount'] ?? 0, 'tax' => $item['tax'] ?? 0, - 'created_at' => now(), - 'updated_at' => now(), - ]; - } - TransferProduct::insert($transferProductData); + 'serial_numbers' => !empty($serials) ? $serials : null, + ]); - // Stock Handling - foreach ($request->products as $stockId => $item) { $fromStock = Stock::where('id', $stockId) ->when($fromBranch, fn($q) => $q->where('branch_id', $fromBranch)) ->when($fromWh, fn($q) => $q->where('warehouse_id', $fromWh)) @@ -431,20 +435,26 @@ public function update(Request $request, $id) 'message' => "From stock not found for stock ID: {$stockId}" ], 400); } + $toStock = Stock::where('product_id', $fromStock->product_id) ->when($toBranch, fn($q) => $q->where('branch_id', $toBranch)) ->when($toWh, fn($q) => $q->where('warehouse_id', $toWh)) - ->when(!is_null($fromStock->batch_no), fn($q) => $q->where('batch_no', $fromStock->batch_no)) + ->where(function ($q) use ($fromStock) { + // if destination stock batch is NULL, skip batch compare + $q->whereNull('batch_no') + // otherwise match same batch + ->orWhere('batch_no', $fromStock->batch_no); + }) ->first(); if (!$toStock) { $toStock = new Stock([ - 'business_id' => auth()->user()->business_id, + 'business_id' => $user->business_id, 'product_id' => $fromStock->product_id, 'warehouse_id' => $toWh, - 'batch_no' => $fromStock->batch_no, 'productStock' => 0, 'productPurchasePrice' => $fromStock->productPurchasePrice, + 'exclusive_price' => $fromStock->exclusive_price, 'profit_percent' => $fromStock->profit_percent, 'productSalePrice' => $fromStock->productSalePrice, 'productWholeSalePrice' => $fromStock->productWholeSalePrice, @@ -453,31 +463,43 @@ public function update(Request $request, $id) 'expire_date' => $fromStock->expire_date, ]); - // if active branch and to branch is null then use active branch id - if ($toBranch == null && $user->active_branch_id){ - $toStock->branch_id = $user->active_branch_id; - }else{ - $toStock->branch_id = $toBranch; - } - - // Update product_type - if ($fromStock->product->product_type !== 'variant') { - $fromStock->product->update([ - 'product_type' => 'variant' - ]); - } - + $toStock->branch_id = $toBranch ?? $user->active_branch_id; Stock::withoutEvents(fn() => $toStock->save()); } - // Stock movement based on status change - if ($oldStatus !== $request->status) { - if ($oldStatus === 'pending' && $request->status === 'completed') { - if ($fromStock->productStock >= $item['quantity']) { - $fromStock->decrement('productStock', $item['quantity']); - $toStock->increment('productStock', $item['quantity']); + // Only move stock if changing pending to completed + if ($oldStatus === 'pending' && $request->status === 'completed') { + + // Handle serials for source + if (!empty($serials)) { + $existing = $fromStock->serial_numbers ?? []; + foreach ($serials as $s) { + if (!in_array($s, $existing)) { + return response()->json([ + 'message' => "Serial {$s} not found in source stock." + ], 400); + } } + // Remove from source + $fromStock->serial_numbers = array_values(array_diff($existing, $serials)); + $fromStock->productStock = count($fromStock->serial_numbers); + if ($fromStock->productStock === 0) { + $fromStock->serial_numbers = null; + } + } else { + $fromStock->decrement('productStock', $quantity); } + $fromStock->save(); + + // Handle serials for destination + if (!empty($serials)) { + $toExisting = $toStock->serial_numbers ?? []; + $toStock->serial_numbers = array_values(array_unique(array_merge($toExisting, $serials))); + $toStock->productStock = count($toStock->serial_numbers); + } else { + $toStock->increment('productStock', $quantity); + } + $toStock->save(); } } @@ -487,6 +509,7 @@ public function update(Request $request, $id) 'message' => __('Transfer updated successfully.'), 'redirect' => route('business.transfers.index'), ]); + } catch (\Exception $e) { DB::rollBack(); return response()->json([ @@ -494,8 +517,7 @@ public function update(Request $request, $id) ], 500); } } - - public function destroy($id) + public function destroy(string $id) { $business_id = auth()->user()->business_id; $transfer = Transfer::with('transferProducts')->where('business_id', $business_id)->findOrFail($id); @@ -511,7 +533,10 @@ public function destroy($id) $toStock = Stock::where('product_id', $tp->product_id) ->where('warehouse_id', $transfer->to_warehouse_id) ->where('branch_id', $transfer->to_branch_id) - ->where('batch_no', optional($stock)->batch_no) + ->where(function ($q) use ($stock) { + $q->whereNull('batch_no') // if batch NULL → skip compare + ->orWhere('batch_no', optional($stock)->batch_no); + }) ->first(); if (!$toStock || $toStock->quantity < $tp->quantity) { @@ -528,8 +553,12 @@ public function destroy($id) $toStock = Stock::where('product_id', $tp->product_id) ->where('warehouse_id', $transfer->to_warehouse_id) ->where('branch_id', $transfer->to_branch_id) - ->where('batch_no', optional($stock)->batch_no) + ->where(function ($q) use ($stock) { + $q->whereNull('batch_no') + ->orWhere('batch_no', optional($stock)->batch_no); + }) ->first(); + if ($toStock) { $toStock->decrement('quantity', $tp->quantity); } @@ -538,8 +567,12 @@ public function destroy($id) $fromStock = Stock::where('product_id', $tp->product_id) ->where('warehouse_id', $transfer->from_warehouse_id) ->where('branch_id', $transfer->from_branch_id) - ->where('batch_no', optional($stock)->batch_no) + ->where(function ($q) use ($stock) { + $q->whereNull('batch_no') + ->orWhere('batch_no', optional($stock)->batch_no); + }) ->first(); + if ($fromStock) { $fromStock->increment('quantity', $tp->quantity); } @@ -583,7 +616,10 @@ public function deleteAll(Request $request) $toStock = Stock::where('product_id', $tp->product_id) ->where('warehouse_id', $transfer->to_warehouse_id) ->where('branch_id', $transfer->to_branch_id) - ->where('batch_no', optional($stock)->batch_no) + ->where(function ($q) use ($stock) { + $q->whereNull('batch_no') + ->orWhere('batch_no', optional($stock)->batch_no); + }) ->first(); if (!$toStock || $toStock->quantity < $tp->quantity) { @@ -604,8 +640,12 @@ public function deleteAll(Request $request) $toStock = Stock::where('product_id', $tp->product_id) ->where('warehouse_id', $transfer->to_warehouse_id) ->where('branch_id', $transfer->to_branch_id) - ->where('batch_no', optional($stock)->batch_no) + ->where(function ($q) use ($stock) { + $q->whereNull('batch_no') + ->orWhere('batch_no', optional($stock)->batch_no); + }) ->first(); + if ($toStock) { $toStock->decrement('quantity', $tp->quantity); } @@ -614,8 +654,12 @@ public function deleteAll(Request $request) $fromStock = Stock::where('product_id', $tp->product_id) ->where('warehouse_id', $transfer->from_warehouse_id) ->where('branch_id', $transfer->from_branch_id) - ->where('batch_no', optional($stock)->batch_no) + ->where(function ($q) use ($stock) { + $q->whereNull('batch_no') + ->orWhere('batch_no', optional($stock)->batch_no); + }) ->first(); + if ($fromStock) { $fromStock->increment('quantity', $tp->quantity); } @@ -648,4 +692,49 @@ public function exportCsv() { return Excel::download(new ExportTransfer, 'transfer.csv'); } + + public function exportPdf(Request $request) + { + $user = auth()->user(); + $activeBranchId = $user->active_branch_id; + + $transfers = Transfer::with(['fromWarehouse:id,name', 'toWarehouse:id,name', 'toBranch:id,name', 'fromBranch:id,name', 'transferProducts']) + ->where('business_id', $user->business_id) + ->when($activeBranchId, function ($q) use ($activeBranchId) { + $q->where(function ($query) use ($activeBranchId) { + $query->where('from_branch_id', $activeBranchId) + ->orWhere('to_branch_id', $activeBranchId); + }); + }) + ->when($request->branch_id && !$activeBranchId, function ($q) use ($request) { + $q->where(function ($query) use ($request) { + $query->where('from_branch_id', $request->branch_id)->orWhere('to_branch_id', $request->branch_id); + }); + }) + ->when($request->search, function ($q) use ($request) { + $search = $request->search; + + $q->where(function ($q) use ($search) { + $q->where('note', 'like', '%' . $search . '%') + ->orWhere('status', 'like', '%' . $search . '%') + ->orWhere('invoice_no', 'like', '%' . $search . '%') + ->orWhereHas('fromWarehouse', function ($q) use ($search) { + $q->where('name', 'like', '%' . $search . '%'); + }) + ->orWhereHas('toWarehouse', function ($q) use ($search) { + $q->where('name', 'like', '%' . $search . '%'); + }) + ->orWhereHas('toBranch', function ($q) use ($search) { + $q->where('name', 'like', '%' . $search . '%'); + }) + ->orWhereHas('fromBranch', function ($q) use ($search) { + $q->where('name', 'like', '%' . $search . '%'); + }); + }); + }) + ->latest() + ->limit(request('per_page') ?? 20)->get(); + + return PdfService::render('business::transfers.pdf', compact('transfers'),'transfers.pdf'); + } } diff --git a/Modules/Business/App/Http/Controllers/AcnooVatController.php b/Modules/Business/App/Http/Controllers/AcnooVatController.php index 3ae43b0c..397400a6 100644 --- a/Modules/Business/App/Http/Controllers/AcnooVatController.php +++ b/Modules/Business/App/Http/Controllers/AcnooVatController.php @@ -4,6 +4,7 @@ use App\Http\Controllers\Controller; use App\Models\Vat; +use App\Models\VatStateItem; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; @@ -19,22 +20,41 @@ public function __construct() public function index(Request $request) { - $vats = Vat::where('business_id', auth()->user()->business_id)->orderBy('status', 'desc')->whereNull('sub_vat')->latest()->paginate(20); - $vat_groups = Vat::where('business_id', auth()->user()->business_id)->orderBy('status', 'desc')->whereNotNull('sub_vat')->latest()->paginate(20); + $business_id = auth()->user()->business_id; + + $vats = Vat::where('business_id', $business_id) + ->orderBy('status', 'desc') + ->whereNull('sub_vat') + ->where('manage_state', 0) + ->latest() + ->paginate(20); + + $vat_groups = Vat::where('business_id', $business_id) + ->where(function ($query) { + $query->whereNotNull('sub_vat') + ->orWhere('manage_state', 1); + }) + ->with('stateVats.childVat:id,name,rate') + ->orderBy('status', 'desc') + ->latest() + ->paginate(20); + return view('business::vats.index', compact('vats', 'vat_groups')); } public function acnooFilter(Request $request) { - $vats = Vat::where('business_id', auth()->user()->business_id)->whereNull('sub_vat') - ->when($request->search, function ($query) use ($request) { - $query->where(function ($q) use ($request) { - $search = $request->search; - $q->where('name', 'like', "%$search%"); - }); - }) - ->latest() - ->paginate(20); + $vats = Vat::where('business_id', auth()->user()->business_id) + ->whereNull('sub_vat') + ->when($request->search, function ($query) use ($request) { + $query->where(function ($q) use ($request) { + $search = $request->search; + $q->where('name', 'like', "%$search%"); + }); + }) + ->where('manage_state', 0) + ->latest() + ->paginate(20); if ($request->ajax()) { return response()->json([ @@ -47,75 +67,135 @@ public function acnooFilter(Request $request) public function VatGroupFilter(Request $request) { - $vat_groups = Vat::where('business_id', auth()->user()->business_id)->whereNotNull('sub_vat') - ->when($request->search, function ($query) use ($request) { - $query->where(function ($q) use ($request) { - $search = $request->search; - $q->where('name', 'like', "%$search%"); - }); - }) - ->latest() - ->paginate(20); + $vat_groups = Vat::where('business_id', auth()->user()->business_id) + ->when($request->search, function ($query) use ($request) { + $query->where(function ($q) use ($request) { + $search = $request->search; + $q->where('name', 'like', "%$search%"); + }); + }) + ->with('stateVats.childVat:id,name') + ->where(function ($query) { + $query->whereNotNull('sub_vat') + ->orWhere('manage_state', 1); + }) + ->latest() + ->paginate(20); if ($request->ajax()) { return response()->json([ 'data' => view('business::vat-groups.datas', compact('vat_groups'))->render() ]); } + return redirect(url()->previous()); - } - // Vat Group Create public function create() { - $vats = Vat::where('business_id', auth()->user()->business_id)->where('status','1')->whereNull('sub_vat')->latest()->get(); + $vats = Vat::where('business_id', auth()->user()->business_id) + ->where('status','1') + ->whereNull('sub_vat') + ->where('manage_state', 0) + ->latest() + ->get(); + return view('business::vat-groups.create',compact('vats')); } public function store(Request $request) { $request->validate([ + 'vat_ids' => 'array', + 'rate' => 'nullable|numeric', 'name' => 'required|string|max:255', - 'vat_ids' => 'required_if:rate,null', - 'rate' => 'required_if:rate,null|numeric', + 'manage_state' => 'nullable|integer|in:0,1', ]); - // single vat - if ($request->rate && !$request->vat_ids) { - Vat::create($request->all() + [ - 'business_id' => auth()->user()->business_id, - ]); + $businessId = auth()->user()->business_id; - } - // group vat - elseif (!$request->rate && $request->vat_ids) { + // SINGLE VAT + if ($request->filled('rate') && !$request->vat_ids) { - $vats = Vat::whereIn('id', $request->vat_ids)->select('id', 'name', 'rate')->get(); + $vat = Vat::create([ + 'name' => $request->name, + 'rate' => $request->rate, + 'business_id' => $businessId, + 'manage_state' => 0, + ]); + } elseif ($request->vat_ids && !$request->manage_state) { // GROUP VAT (OLD SYSTEM) + + $vats = Vat::whereIn('id', $request->vat_ids) + ->where('business_id', $businessId) + ->select('id', 'name', 'rate') + ->get(); $tax_rate = 0; $sub_vats = []; - foreach ($vats as $vat) { + foreach ($vats as $vatItem) { $sub_vats[] = [ - 'id' => $vat->id, - 'name' => $vat->name, - 'rate' => $vat->rate, + 'id' => $vatItem->id, + 'name' => $vatItem->name, + 'rate' => $vatItem->rate, ]; - $tax_rate += $vat->rate; + $tax_rate += $vatItem->rate; } - Vat::create([ - 'rate' => $tax_rate, - 'sub_vat' => $sub_vats, - 'name' => $request->name, - 'business_id' => auth()->user()->business_id, + $vat = Vat::create([ + 'rate' => $tax_rate, + 'sub_vat' => $sub_vats, + 'name' => $request->name, + 'business_id' => $businessId, + 'manage_state' => 0, + ]); + } elseif ($request->manage_state == 1) { // STATE WISE VAT (NEW PIVOT SYSTEM) + + $request->validate([ + 'inner_vat_ids' => 'required|array', + 'outer_vat_ids' => 'required|array', ]); - } else { + $innerIds = $request->inner_vat_ids ?? []; + $outerIds = $request->outer_vat_ids ?? []; + + $innerRate = Vat::whereIn('id', $innerIds) + ->where('business_id', $businessId) + ->sum('rate'); + + $outerRate = Vat::whereIn('id', $outerIds) + ->where('business_id', $businessId) + ->sum('rate'); + + $vat = Vat::create([ + 'name' => $request->name, + 'rate' => $innerRate ?: $outerRate, + 'business_id' => $businessId, + 'manage_state' => 1, + ]); + + // Save pivot inner + foreach ($innerIds as $id) { + VatStateItem::create([ + 'parent_vat_id' => $vat->id, + 'child_vat_id' => $id, + 'type' => 'inner' + ]); + } + + // Save pivot outer + foreach ($outerIds as $id) { + VatStateItem::create([ + 'parent_vat_id' => $vat->id, + 'child_vat_id' => $id, + 'type' => 'outer' + ]); + } + } + else { return response()->json([ - 'message' => 'Invalid data format.', + 'message' => 'Invalid data format.' ], 406); } @@ -128,27 +208,43 @@ public function store(Request $request) // Vat Group Edit public function edit($id) { - $vat = Vat::where('business_id', auth()->user()->business_id)->findOrFail($id); - $vats = Vat::where('business_id', auth()->user()->business_id)->where('status','1')->whereNull('sub_vat')->latest()->paginate(20); + $vat = Vat::with(['innerStateVats', 'outerStateVats'])->where('business_id', auth()->user()->business_id)->findOrFail($id); + $vats = Vat::where('business_id', auth()->user()->business_id) + ->where('status','1') + ->whereNull('sub_vat') + ->where('manage_state', 0) + ->latest() + ->get(); + return view('business::vat-groups.edit', compact('vat', 'vats')); } - public function update(Request $request, Vat $vat) { $request->validate([ + 'vat_ids' => 'array', + 'rate' => 'nullable|numeric', 'name' => 'required|string|max:255', - 'vat_ids' => 'required_if:rate,null', - 'rate' => 'required_if:rate,null|numeric', + 'manage_state' => 'nullable|integer|in:0,1', ]); DB::beginTransaction(); try { - // Single VAT update - if ($request->rate && !$request->vat_ids) { + $businessId = auth()->user()->business_id; - $vat->update($request->only(['name', 'rate', 'status'])); + VatStateItem::where('parent_vat_id', $vat->id)->delete(); + // SINGLE VAT + if ($request->filled('rate') && !$request->vat_ids && !$request->manage_state) { + + $vat->update([ + 'name' => $request->name, + 'rate' => $request->rate, + 'status' => $request->status ?? $vat->status, + 'manage_state' => 0, + ]); + + // Update group VATs containing this single VAT $vatGroupExist = Vat::where('sub_vat', 'LIKE', '%"id":' . $vat->id . '%')->get(); foreach ($vatGroupExist as $group) { $subVats = collect($group->sub_vat)->map(function ($subVat) use ($vat) { @@ -158,7 +254,6 @@ public function update(Request $request, Vat $vat) } return $subVat; }); - $group->update([ 'rate' => $subVats->sum('rate'), 'sub_vat' => $subVats->toArray(), @@ -166,30 +261,79 @@ public function update(Request $request, Vat $vat) } } - // Group VAT update - elseif (!$request->rate && $request->vat_ids) { + // GROUP VAT (OLD SYSTEM) + elseif ($request->vat_ids && !$request->manage_state) { - $vats = Vat::whereIn('id', $request->vat_ids)->select('id', 'name', 'rate')->get(); + $vats = Vat::whereIn('id', $request->vat_ids) + ->where('business_id', $businessId) + ->select('id', 'name', 'rate') + ->get(); $tax_rate = 0; $sub_vats = []; - - foreach ($vats as $single_tax) { + foreach ($vats as $vatItem) { $sub_vats[] = [ - 'id' => $single_tax->id, - 'name' => $single_tax->name, - 'rate' => $single_tax->rate, + 'id' => $vatItem->id, + 'name' => $vatItem->name, + 'rate' => $vatItem->rate, ]; - $tax_rate += $single_tax->rate; + $tax_rate += $vatItem->rate; } $vat->update([ + 'name' => $request->name, 'rate' => $tax_rate, 'sub_vat' => $sub_vats, - 'name' => $request->name, 'status' => $request->status ?? $vat->status, + 'manage_state' => 0, ]); - } else { + } + + // STATE-WISE VAT (NEW SYSTEM) + elseif ($request->manage_state == 1) { + + $innerIds = $request->inner_vat_ids ?? []; + $outerIds = $request->outer_vat_ids ?? []; + + $innerRate = Vat::whereIn('id', $innerIds) + ->where('business_id', $businessId) + ->sum('rate'); + + $outerRate = Vat::whereIn('id', $outerIds) + ->where('business_id', $businessId) + ->sum('rate'); + + $vat->update([ + 'name' => $request->name, + 'rate' => $innerRate ?: $outerRate, + 'status' => $request->status ?? $vat->status, + 'manage_state' => 1, + 'sub_vat' => null, + ]); + + // Delete old pivot entries + VatStateItem::where('parent_vat_id', $vat->id)->delete(); + + // Insert inner VAT pivot + foreach ($innerIds as $id) { + VatStateItem::create([ + 'parent_vat_id' => $vat->id, + 'child_vat_id' => $id, + 'type' => 'inner', + ]); + } + + // Insert outer VAT pivot + foreach ($outerIds as $id) { + VatStateItem::create([ + 'parent_vat_id' => $vat->id, + 'child_vat_id' => $id, + 'type' => 'outer', + ]); + } + } + + else { DB::rollBack(); return response()->json([ 'message' => 'Invalid data format.', @@ -197,14 +341,17 @@ public function update(Request $request, Vat $vat) } DB::commit(); - return response()->json([ 'message' => 'Vat updated successfully', 'redirect' => route('business.vats.index'), ]); + } catch (\Exception $e) { DB::rollback(); - return response()->json(['message' => __('Somethings went wrong!')], 404); + return response()->json([ + 'message' => __('Somethings went wrong!'), + 'error' => $e->getMessage(), + ], 500); } } diff --git a/Modules/Business/App/Http/Controllers/AcnooVatReportController.php b/Modules/Business/App/Http/Controllers/AcnooVatReportController.php index db1f7fa7..7d247a94 100644 --- a/Modules/Business/App/Http/Controllers/AcnooVatReportController.php +++ b/Modules/Business/App/Http/Controllers/AcnooVatReportController.php @@ -2,16 +2,20 @@ namespace Modules\Business\App\Http\Controllers; -use App\Models\Vat; -use App\Models\Sale; -use App\Models\Purchase; use App\Http\Controllers\Controller; +use App\Models\Purchase; +use App\Models\PurchaseReturnDetail; +use App\Models\Sale; +use App\Models\SaleDetails; +use App\Models\SaleReturnDetails; +use App\Models\Vat; +use App\Models\VatTransaction; use App\Services\PdfService; use App\Traits\DateFilterTrait; use App\Traits\DateRangeTrait; use Illuminate\Http\Request; use Maatwebsite\Excel\Facades\Excel; -use Modules\Business\App\Exports\ExportVatReport; +use Modules\Business\App\Exports\ExportTaxReport; class AcnooVatReportController extends Controller { @@ -25,134 +29,441 @@ public function __construct() public function index(Request $request) { $businessId = auth()->user()->business_id; - $vats = Vat::where('business_id', $businessId)->whereStatus(1)->get(); + + $vats = Vat::where('business_id', $businessId) + ->whereStatus(1) + ->get(); $duration = $request->custom_days ?: 'today'; [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); - $salesQuery = Sale::with('user:id,name', 'party:id,name,email,phone,type', 'business:id,companyName', 'payment_type:id,name', 'transactions') - ->where('business_id', $businessId) - ->where('vat_amount', '>', 0); + $salesQuery = Sale::where('business_id', $businessId) + ->with([ + 'user:id,name', + 'party:id,name,email,phone,type', + 'business:id,companyName', + 'payment_type:id,name', + 'transactions' + ]); - // Date Filter - $this->applyDateFilter($salesQuery, $duration, 'saleDate', $request->from_date, $request->to_date); + $this->applyDateFilter( + $salesQuery, + $duration, + 'saleDate', + $request->from_date, + $request->to_date + ); - $sales = $salesQuery->latest()->paginate(20, ['*'], 'sales'); - - $salesVatTotals = []; - foreach ($vats as $vat) { - $salesVatTotals[$vat->id] = (clone $salesQuery)->where('vat_id', $vat->id)->sum('vat_amount'); + if ($request->filled('search')) { + $search = $request->search; + $salesQuery->where(function ($query) use ($search) { + $query->where('invoiceNumber', 'like', '%' . $search . '%') + ->orWhere('totalAmount', 'like', '%' . $search . '%') + ->orWhere('discountAmount', 'like', '%' . $search . '%') + ->orWhereHas('party', fn($q) => $q->where('name', 'like', '%' . $search . '%')->orWhere('tax_no', 'like', '%' . $search . '%')); + }); } - $purchasesQuery = Purchase::with('details', 'party', 'details.product', 'details.product.category', 'payment_type:id,name', 'transactions') - ->where('business_id', $businessId) - ->where('vat_amount', '>', 0); + $sales = $salesQuery->latest()->paginate($request->per_page ?? 20, ['*'], 'sales')->appends($request->query()); - // Date Filter - $this->applyDateFilter($purchasesQuery, $duration, 'purchaseDate', $request->from_date, $request->to_date); + $salesVatTotals = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', SaleDetails::class) + ->join('sale_details', 'sale_details.id', '=', 'vat_transactions.vatable_id') + ->join('sales', 'sales.id', '=', 'sale_details.sale_id') + ->where('sales.business_id', $businessId) + ->selectRaw('vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('vat_transactions.vat_id') + ->pluck('total','vat_transactions.vat_id') + ->toArray(); - $purchases = $purchasesQuery->latest()->paginate(20, ['*'], 'purchases'); + $salesReturnVatTotals = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', SaleReturnDetails::class) + ->join('sale_return_details', 'sale_return_details.id', '=', 'vat_transactions.vatable_id') + ->join('sale_details', 'sale_details.id', '=', 'sale_return_details.sale_detail_id') + ->join('sales', 'sales.id', '=', 'sale_details.sale_id') + ->where('sales.business_id', $businessId) + ->selectRaw('vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('vat_transactions.vat_id') + ->pluck('total', 'vat_transactions.vat_id') + ->toArray(); - $purchasesVatTotals = []; - foreach ($vats as $vat) { - $purchasesVatTotals[$vat->id] = (clone $purchasesQuery)->where('vat_id', $vat->id)->sum('vat_amount'); + $saleVatRowMap = VatTransaction::where('business_id', $businessId) + ->where('vatable_type', \App\Models\SaleDetails::class) + ->whereIn('vatable_id', function ($query) use ($businessId, $duration, $request) { + $query->select('sale_details.id') + ->from('sale_details') + ->join('sales', 'sales.id', '=', 'sale_details.sale_id') + ->where('sales.business_id', $businessId); + + if ($duration === 'custom_days' && $request->from_date && $request->to_date) { + $query->whereBetween('sales.saleDate', [$request->from_date, $request->to_date]); + } + }) + ->join('sale_details', 'sale_details.id', '=', 'vat_transactions.vatable_id') + ->selectRaw('sale_details.sale_id, vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('sale_details.sale_id', 'vat_transactions.vat_id') + ->get() + ->groupBy('sale_id') + ->map(function ($items) { + return $items->pluck('total', 'vat_id')->toArray(); + }) + ->toArray(); + + $saleReturnVatRows = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', SaleReturnDetails::class) + + ->join('sale_return_details','sale_return_details.id','=','vat_transactions.vatable_id') + ->join('sale_details','sale_details.id','=','sale_return_details.sale_detail_id') + ->join('sales','sales.id','=','sale_details.sale_id') + + ->selectRaw('sales.id as sale_id, vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as vat_amount') + ->groupBy('sales.id','vat_transactions.vat_id') + ->get(); + + $saleReturnVatRowMap = $saleReturnVatRows + ->groupBy('sale_id') + ->map(fn($rows)=>$rows->pluck('vat_amount','vat_id')) + ->toArray(); + + $purchasesQuery = Purchase::with([ + 'details', + 'party', + 'details.product', + 'details.product.category', + 'payment_type:id,name', + 'transactions' + ]) + ->where('business_id', $businessId); + + $this->applyDateFilter( + $purchasesQuery, + $duration, + 'purchaseDate', + $request->from_date, + $request->to_date + ); + + if ($request->filled('search')) { + $search = $request->search; + $purchasesQuery->where(function ($query) use ($search) { + $query->where('invoiceNumber', 'like', '%' . $search . '%') + ->orWhere('totalAmount', 'like', '%' . $search . '%') + ->orWhere('discountAmount', 'like', '%' . $search . '%') + ->orWhereHas('party', fn($q) => $q->where('name', 'like', '%' . $search . '%')->orWhere('tax_no', 'like', '%' . $search . '%')); + }); } + $purchases = $purchasesQuery->latest()->paginate($request->per_page ?? 20, ['*'], 'purchases')->appends($request->query()); + + $purchasesVatTotals = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', \App\Models\PurchaseDetails::class) + ->join('purchase_details', 'purchase_details.id', '=', 'vat_transactions.vatable_id') + ->join('purchases', 'purchases.id', '=', 'purchase_details.purchase_id') + ->where('purchases.business_id', $businessId) + ->selectRaw('vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('vat_transactions.vat_id') + ->pluck('total','vat_transactions.vat_id') + ->toArray(); + + + $purchaseReturnVatTotals = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', PurchaseReturnDetail::class) + ->join('purchase_return_details', 'purchase_return_details.id', '=', 'vat_transactions.vatable_id') + ->join('purchase_details', 'purchase_details.id', '=', 'purchase_return_details.purchase_detail_id') + ->join('purchases', 'purchases.id', '=', 'purchase_details.purchase_id') + ->where('purchases.business_id', $businessId) + ->selectRaw('vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('vat_transactions.vat_id') + ->pluck('total', 'vat_transactions.vat_id') + ->toArray(); + + $purchaseVatRowMap = VatTransaction::where('business_id', $businessId) + ->where('vatable_type', \App\Models\PurchaseDetails::class) + ->whereIn('vatable_id', function ($query) use ($businessId, $duration, $request) { + $query->select('purchase_details.id') + ->from('purchase_details') + ->join('purchases', 'purchases.id', '=', 'purchase_details.purchase_id') + ->where('purchases.business_id', $businessId); + + if ($duration === 'custom_date' && $request->from_date && $request->to_date) { + $query->whereBetween('purchases.purchaseDate', [$request->from_date, $request->to_date]); + } + }) + ->join('purchase_details', 'purchase_details.id', '=', 'vat_transactions.vatable_id') + ->selectRaw('purchase_details.purchase_id, vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('purchase_details.purchase_id', 'vat_transactions.vat_id') + ->get() + ->groupBy('purchase_id') + ->map(function ($items) { + return $items->pluck('total', 'vat_id')->toArray(); + }) + ->toArray(); + + $purchaseReturnVatRows = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', PurchaseReturnDetail::class) + + ->join('purchase_return_details','purchase_return_details.id','=','vat_transactions.vatable_id') + ->join('purchase_details','purchase_details.id','=','purchase_return_details.purchase_detail_id') + ->join('purchases','purchases.id','=','purchase_details.purchase_id') + + ->selectRaw('purchases.id as purchase_id, vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as vat_amount') + ->groupBy('purchases.id','vat_transactions.vat_id') + ->get(); + + $purchaseReturnVatRowMap = $purchaseReturnVatRows + ->groupBy('purchase_id') + ->map(fn($rows) => $rows->pluck('vat_amount','vat_id')) + ->toArray(); + if ($request->ajax()) { return response()->json([ - 'sale_data' => view('business::reports.vats.sale-datas', compact('sales', 'salesVatTotals', 'vats', 'filter_from_date', 'filter_to_date', 'duration'))->render(), - 'purchase_data' => view('business::reports.vats.purchase-datas', compact('purchases', 'purchasesVatTotals', 'vats', 'filter_from_date', 'filter_to_date', 'duration'))->render(), + 'sale_data' => view('business::reports.vats.sale-datas', compact( + 'sales', + 'salesVatTotals', + 'salesReturnVatTotals', + 'saleVatRowMap', + 'saleReturnVatRowMap', + 'vats', + 'filter_from_date', + 'filter_to_date', + 'duration' + ))->render(), + + 'purchase_data' => view('business::reports.vats.purchase-datas', compact( + 'purchases', + 'purchasesVatTotals', + 'purchaseReturnVatTotals', + 'purchaseVatRowMap', + 'purchaseReturnVatRowMap', + 'vats', + 'filter_from_date', + 'filter_to_date', + 'duration' + ))->render(), ]); } - return view('business::reports.vats.index', compact('sales', 'salesVatTotals', 'purchases', 'purchasesVatTotals', 'vats', 'filter_from_date', 'filter_to_date', 'duration')); + return view('business::reports.vats.index', compact( + 'sales', + 'salesVatTotals', + 'salesReturnVatTotals', + 'saleVatRowMap', + 'saleReturnVatRowMap', + 'purchases', + 'purchasesVatTotals', + 'purchaseReturnVatTotals', + 'purchaseVatRowMap', + 'purchaseReturnVatRowMap', + 'vats', + 'filter_from_date', + 'filter_to_date', + 'duration' + )); } - public function exportExcel($type = 'all') + public function exportExcel() { - return $this->exportFile($type, 'vat-report.xlsx'); + return Excel::download(new ExportTaxReport, 'tax-report.xlsx'); } - public function exportCsv($type = 'all') + public function exportCsv() { - return $this->exportFile($type, 'vat-report.csv'); + return Excel::download(new ExportTaxReport, 'tax-report.csv'); } - private function exportFile($type, $filename, $format = null) + public function exportPdf(Request $request) { $businessId = auth()->user()->business_id; + $type = $request->type ?? 'sales'; + $duration = $request->custom_days ?: 'today'; + [$filter_from_date, $filter_to_date] = $this->applyDateRange($duration, $request->from_date, $request->to_date); + + // SALES QUERY + $salesQuery = Sale::with('party', 'payment_type', 'transactions') + ->where('business_id', $businessId); + + $this->applyDateFilter($salesQuery, $duration, 'saleDate', $request->from_date, $request->to_date); + + if ($request->filled('search')) { + $search = $request->search; + $salesQuery->where(function ($query) use ($search) { + $query->where('invoiceNumber', 'like', '%' . $search . '%') + ->orWhere('totalAmount', 'like', '%' . $search . '%') + ->orWhere('discountAmount', 'like', '%' . $search . '%') + ->orWhereHas('party', fn($q) => $q->where('name', 'like', '%' . $search . '%')->orWhere('tax_no', 'like', '%' . $search . '%')); + }); + } + + $salesVatTotals = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', SaleDetails::class) + ->join('sale_details', 'sale_details.id', '=', 'vat_transactions.vatable_id') + ->join('sales', 'sales.id', '=', 'sale_details.sale_id') + ->where('sales.business_id', $businessId) + ->selectRaw('vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('vat_transactions.vat_id') + ->pluck('total','vat_transactions.vat_id') + ->toArray(); + + $salesReturnVatTotals = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', SaleReturnDetails::class) + ->join('sale_return_details', 'sale_return_details.id', '=', 'vat_transactions.vatable_id') + ->join('sale_details', 'sale_details.id', '=', 'sale_return_details.sale_detail_id') + ->join('sales', 'sales.id', '=', 'sale_details.sale_id') + ->where('sales.business_id', $businessId) + ->selectRaw('vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('vat_transactions.vat_id') + ->pluck('total', 'vat_transactions.vat_id') + ->toArray(); + + $saleVatRowMap = VatTransaction::where('business_id', $businessId) + ->where('vatable_type', \App\Models\SaleDetails::class) + ->whereIn('vatable_id', function ($query) use ($businessId, $duration, $request) { + $query->select('sale_details.id') + ->from('sale_details') + ->join('sales', 'sales.id', '=', 'sale_details.sale_id') + ->where('sales.business_id', $businessId); + + if ($duration === 'custom_days' && $request->from_date && $request->to_date) { + $query->whereBetween('sales.saleDate', [$request->from_date, $request->to_date]); + } + }) + ->join('sale_details', 'sale_details.id', '=', 'vat_transactions.vatable_id') + ->selectRaw('sale_details.sale_id, vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('sale_details.sale_id', 'vat_transactions.vat_id') + ->get() + ->groupBy('sale_id') + ->map(function ($items) { + return $items->pluck('total', 'vat_id')->toArray(); + }) + ->toArray(); + + $saleReturnVatRows = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', SaleReturnDetails::class) + + ->join('sale_return_details','sale_return_details.id','=','vat_transactions.vatable_id') + ->join('sale_details','sale_details.id','=','sale_return_details.sale_detail_id') + ->join('sales','sales.id','=','sale_details.sale_id') + + ->selectRaw('sales.id as sale_id, vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as vat_amount') + ->groupBy('sales.id','vat_transactions.vat_id') + ->get(); + + $saleReturnVatRowMap = $saleReturnVatRows + ->groupBy('sale_id') + ->map(fn($rows)=>$rows->pluck('vat_amount','vat_id')) + ->toArray(); + + // PURCHASE QUERY + $purchasesQuery = Purchase::with('party', 'payment_type', 'transactions') + ->where('business_id', $businessId); + + $this->applyDateFilter($purchasesQuery, $duration, 'purchaseDate', $request->from_date, $request->to_date); + + if ($request->filled('search')) { + $search = $request->search; + $purchasesQuery->where(function ($query) use ($search) { + $query->where('invoiceNumber', 'like', '%' . $search . '%') + ->orWhere('totalAmount', 'like', '%' . $search . '%') + ->orWhere('discountAmount', 'like', '%' . $search . '%') + ->orWhereHas('party', fn($q) => $q->where('name', 'like', '%' . $search . '%')->orWhere('tax_no', 'like', '%' . $search . '%')); + }); + } + + $purchasesVatTotals = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', \App\Models\PurchaseDetails::class) + ->join('purchase_details', 'purchase_details.id', '=', 'vat_transactions.vatable_id') + ->join('purchases', 'purchases.id', '=', 'purchase_details.purchase_id') + ->where('purchases.business_id', $businessId) + ->selectRaw('vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('vat_transactions.vat_id') + ->pluck('total','vat_transactions.vat_id') + ->toArray(); + + + $purchaseReturnVatTotals = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', PurchaseReturnDetail::class) + ->join('purchase_return_details', 'purchase_return_details.id', '=', 'vat_transactions.vatable_id') + ->join('purchase_details', 'purchase_details.id', '=', 'purchase_return_details.purchase_detail_id') + ->join('purchases', 'purchases.id', '=', 'purchase_details.purchase_id') + ->where('purchases.business_id', $businessId) + ->selectRaw('vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('vat_transactions.vat_id') + ->pluck('total', 'vat_transactions.vat_id') + ->toArray(); + + $purchaseVatRowMap = VatTransaction::where('business_id', $businessId) + ->where('vatable_type', \App\Models\PurchaseDetails::class) + ->whereIn('vatable_id', function ($query) use ($businessId, $duration, $request) { + $query->select('purchase_details.id') + ->from('purchase_details') + ->join('purchases', 'purchases.id', '=', 'purchase_details.purchase_id') + ->where('purchases.business_id', $businessId); + + if ($duration === 'custom_date' && $request->from_date && $request->to_date) { + $query->whereBetween('purchases.purchaseDate', [$request->from_date, $request->to_date]); + } + }) + ->join('purchase_details', 'purchase_details.id', '=', 'vat_transactions.vatable_id') + ->selectRaw('purchase_details.purchase_id, vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as total') + ->groupBy('purchase_details.purchase_id', 'vat_transactions.vat_id') + ->get() + ->groupBy('purchase_id') + ->map(function ($items) { + return $items->pluck('total', 'vat_id')->toArray(); + }) + ->toArray(); + + $purchaseReturnVatRows = VatTransaction::where('vat_transactions.business_id', $businessId) + ->where('vat_transactions.vatable_type', PurchaseReturnDetail::class) + + ->join('purchase_return_details','purchase_return_details.id','=','vat_transactions.vatable_id') + ->join('purchase_details','purchase_details.id','=','purchase_return_details.purchase_detail_id') + ->join('purchases','purchases.id','=','purchase_details.purchase_id') + + ->selectRaw('purchases.id as purchase_id, vat_transactions.vat_id, SUM(vat_transactions.vat_amount) as vat_amount') + ->groupBy('purchases.id','vat_transactions.vat_id') + ->get(); + + $purchaseReturnVatRowMap = $purchaseReturnVatRows + ->groupBy('purchase_id') + ->map(fn($rows) => $rows->pluck('vat_amount','vat_id')) + ->toArray(); + + // TAB CONTROL $sales = collect(); $purchases = collect(); - if ($type === 'sales' || $type === 'all') { - $sales = Sale::with('user:id,name', 'party:id,name,email,phone,type', 'payment_type:id,name', 'transactions') - ->where('business_id', $businessId) - ->where('vat_amount', '>', 0) - ->latest() - ->get(); + $limit = $request->per_page ?? 20; + + if ($type === 'sales') { + $sales = $salesQuery->latest()->limit($limit)->get(); } - if ($type === 'purchases' || $type === 'all') { - $purchases = Purchase::with('details', 'party', 'details.product', 'details.product.category', 'payment_type:id,name', 'transactions') - ->where('business_id', $businessId) - ->where('vat_amount', '>', 0) - ->latest() - ->get(); + if ($type === 'purchases') { + $purchases = $purchasesQuery->latest()->limit($limit)->get(); } $vats = Vat::where('business_id', $businessId)->get(); - $salesVatTotals = []; - foreach ($vats as $vat) { - $salesVatTotals[$vat->id] = $sales->where('vat_id', $vat->id)->sum('vat_amount'); - } - - $purchasesVatTotals = []; - foreach ($vats as $vat) { - $purchasesVatTotals[$vat->id] = $purchases->where('vat_id', $vat->id)->sum('vat_amount'); - } - - $export = new ExportVatReport($sales, $purchases, $vats, $salesVatTotals, $purchasesVatTotals); - - return Excel::download($export, $filename, $format); - } - - public function exportPdf($type = 'all') - { - $businessId = auth()->user()->business_id; - - $sales = collect(); - $purchases = collect(); - - if ($type === 'sales' || $type === 'all') { - $sales = Sale::with('party', 'payment_type') - ->where('business_id', $businessId) - ->where('vat_amount', '>', 0) - ->latest() - ->get(); - } - - if ($type === 'purchases' || $type === 'all') { - $purchases = Purchase::with('party', 'payment_type') - ->where('business_id', $businessId) - ->where('vat_amount', '>', 0) - ->latest() - ->get(); - } - - $vats = Vat::where('business_id', $businessId)->get(); - - $salesVatTotals = []; - foreach ($vats as $vat) { - $salesVatTotals[$vat->id] = $sales->where('vat_id', $vat->id)->sum('vat_amount'); - } - - $purchasesVatTotals = []; - foreach ($vats as $vat) { - $purchasesVatTotals[$vat->id] = $purchases->where('vat_id', $vat->id)->sum('vat_amount'); - } - - return PdfService::render('business::reports.vats.pdf', compact('sales', 'salesVatTotals', 'purchases', 'purchasesVatTotals', 'vats', 'type'),'vats-report.pdf'); + return PdfService::render('business::reports.vats.pdf', + compact( + 'sales', + 'salesVatTotals', + 'salesReturnVatTotals', + 'saleVatRowMap', + 'saleReturnVatRowMap', + 'purchases', + 'purchasesVatTotals', + 'purchaseReturnVatTotals', + 'purchaseVatRowMap', + 'purchaseReturnVatRowMap', + 'vats', + 'type', + 'filter_from_date', + 'filter_to_date', + 'duration' + ), 'tax-report.pdf', 'A4', 'L' + ); } } diff --git a/Modules/Business/App/Http/Controllers/CartController.php b/Modules/Business/App/Http/Controllers/CartController.php index e117eabd..a8050c7f 100644 --- a/Modules/Business/App/Http/Controllers/CartController.php +++ b/Modules/Business/App/Http/Controllers/CartController.php @@ -40,65 +40,212 @@ public function store(Request $request) 'warehouse_id' => 'nullable|exists:warehouses,id', 'type' => 'nullable|string|in:sale,purchase', 'id' => 'required|integer', - 'name' => 'required|string', + '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', - 'vat_percent' => 'nullable|numeric', + 'serial_numbers' => 'nullable|array', + 'serial_numbers.*' => 'string', + 'has_serial' => 'nullable|boolean', + 'price_without_tax' => 'required_without:serial_numbers|numeric|nullable', + 'customer_type' => 'nullable|string' ]); - // Check for existing item in cart by type - $existingCartItem = Cart::search( - fn($item) => $item->id == $request->id && - $item->options->type == $request->type && - match ($request->type) { - 'purchase' => $item->options->batch_no == $request->batch_no, - 'sale' => $item->options->stock_id == $request->stock_id, - default => false, + $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, + ] + ]); + } + } } - )->first(); + // 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) { - $newQty = $existingCartItem->qty + $request->quantity; - Cart::update($existingCartItem->rowId, ['qty' => $newQty]); + + Cart::update($existingCartItem->rowId, [ + 'qty' => $existingCartItem->qty + $request->quantity + ]); + } else { - // Add new item to cart - $mainItemData = [ + + Cart::add([ 'id' => $request->id, 'name' => $request->name, 'qty' => $request->quantity, - 'price' => $request->price, + '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, - 'product_image' => $request->product_image, '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, - 'product_type' => $request->product_type, 'warehouse_id' => $request->warehouse_id, - 'variant_name' => $request->variant_name, - 'vat_percent' => $request->vat_percent, + 'has_serial' => $request->has_serial ?? 0, + 'price_without_tax' => $request->price_without_tax, ] - ]; - Cart::add($mainItemData); + ]); } + return response()->json([ 'success' => true, 'message' => 'Item added to cart successfully.' @@ -108,20 +255,45 @@ public function store(Request $request) public function update(Request $request, $id) { $cart = Cart::get($id); - if (!$cart) { - return response()->json(['success' => false, 'message' => __('Item not found in 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')]); + 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' => $request->price ?? $cart->price, + 'price' => round($request->price ?? $cart->price, 2), 'options' => [ 'type' => $cart->options->type, 'expire_date' => $request->expire_date ?? $cart->options->expire_date, @@ -131,6 +303,7 @@ public function update(Request $request, $id) '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, @@ -139,7 +312,9 @@ public function update(Request $request, $id) 'product_type' => $cart->options->product_type, 'warehouse_id' => $cart->options->warehouse_id, 'variant_name' => $cart->options->variant_name, - 'vat_percent' => $cart->options->vat_percent, + 'price_without_tax' => $cart->options->price_without_tax, + 'has_serial' => $hasSerial, + 'serial_numbers' => $existingSerials, ] ]); diff --git a/Modules/Business/App/Http/Controllers/DashboardController.php b/Modules/Business/App/Http/Controllers/DashboardController.php index 861c5e6f..3bfb958d 100644 --- a/Modules/Business/App/Http/Controllers/DashboardController.php +++ b/Modules/Business/App/Http/Controllers/DashboardController.php @@ -196,17 +196,17 @@ public function revenue() $data['loss'] = Sale::where('business_id', auth()->user()->business_id) ->whereYear('created_at', request('year') ?? date('Y')) ->where('lossProfit', '<', 0) - ->selectRaw("TO_CHAR(created_at, 'Month') as month, SUM(ABS(\"lossProfit\")) as total") - ->orderBy('created_at') - ->groupBy('created_at') + ->selectRaw("TO_CHAR(created_at, 'FMMonth') as month, SUM(ABS(\"lossProfit\")) as total") + ->groupBy(DB::raw("TO_CHAR(created_at, 'FMMonth')")) + ->orderBy(DB::raw("MIN(created_at)")) ->get(); $data['profit'] = Sale::where('business_id', auth()->user()->business_id) ->whereYear('created_at', request('year') ?? date('Y')) ->where('lossProfit', '>=', 0) - ->selectRaw("TO_CHAR(created_at, 'Month') as month, SUM(ABS(\"lossProfit\")) as total") - ->orderBy('created_at') - ->groupBy('created_at') + ->selectRaw("TO_CHAR(created_at, 'FMMonth') as month, SUM(ABS(\"lossProfit\")) as total") + ->groupBy(DB::raw("TO_CHAR(created_at, 'FMMonth')")) + ->orderBy(DB::raw("MIN(created_at)")) ->get(); return response()->json($data); diff --git a/Modules/Business/App/Http/Controllers/PurchaseReturnController.php b/Modules/Business/App/Http/Controllers/PurchaseReturnController.php index 80c58599..31c1e313 100644 --- a/Modules/Business/App/Http/Controllers/PurchaseReturnController.php +++ b/Modules/Business/App/Http/Controllers/PurchaseReturnController.php @@ -3,16 +3,16 @@ namespace Modules\Business\App\Http\Controllers; use App\Events\MultiPaymentProcessed; +use App\Http\Controllers\Controller; +use App\Models\Branch; use App\Models\Party; use App\Models\PaymentType; -use App\Models\Stock; -use App\Models\Branch; use App\Models\Purchase; -use Illuminate\Http\Request; use App\Models\PurchaseReturn; -use Illuminate\Support\Facades\DB; -use App\Http\Controllers\Controller; use App\Models\PurchaseReturnDetail; +use App\Services\PurchaseVatTransaction; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; class PurchaseReturnController extends Controller { @@ -66,7 +66,6 @@ public function index(Request $request) return view('business::purchase-returns.index', compact('purchases', 'branches')); } - public function create(Request $request) { $business_id = auth()->user()->business_id; @@ -80,56 +79,90 @@ public function create(Request $request) $discount_per_unit_factor = $total_purchase_price > 0 ? $purchase->discountAmount / $total_purchase_price : 0; $payment_types = PaymentType::where('business_id', $business_id)->whereStatus(1)->latest()->get(); - return view('business::purchase-returns.create', compact('purchase', 'discount_per_unit_factor','payment_types')); } public function store(Request $request) { + // split-array structure from front-end request + $products = []; + for ($index = 0; $index < count($request->products); $index += 3) { + $products[] = [ + 'detail_id' => $request->products[$index]['detail_id'] ?? null, + 'return_qty' => $request->products[$index + 1]['return_qty'] ?? 0, + 'serial_numbers' => $request->products[$index + 2]['serial_numbers'] ?? '[]', + ]; + } + $request->merge(['products' => $products]); + $request->validate([ 'purchase_id' => 'required|exists:purchases,id', - 'return_qty' => 'required|array', + 'products' => 'required|array|min:1', + 'products.*.detail_id' => 'required|exists:purchase_details,id', + 'products.*.return_qty' => 'required|integer', + 'products.*.serial_numbers' => 'nullable|string', + 'payments' => 'nullable|array', ]); DB::beginTransaction(); - try { - $purchase = Purchase::with('details') - ->where('business_id', auth()->user()->business_id) + $business_id = auth()->user()->business_id; + + $purchase = Purchase::with('details.stock') + ->where('business_id', $business_id) ->findOrFail($request->purchase_id); - // Calculate total discount factor $total_discount = $purchase->discountAmount; - $total_purchase_amount = $purchase->details->sum(fn($detail) => $detail->productPurchasePrice * $detail->quantities); + $total_purchase_amount = $purchase->details->sum(fn($d) => $d->productPurchasePrice * $d->quantities); $discount_per_unit_factor = $total_purchase_amount > 0 ? $total_discount / $total_purchase_amount : 0; $purchase_return = PurchaseReturn::create([ - 'business_id' => auth()->user()->business_id, - 'purchase_id' => $request->purchase_id, + 'business_id' => $business_id, + 'purchase_id' => $purchase->id, 'invoice_no' => $purchase->invoiceNumber, 'return_date' => now(), ]); - $purchase_return_detail_data = []; $total_return_amount = 0; $total_return_discount = 0; - // Loop through each purchase detail and process the return - foreach ($purchase->details as $key => $detail) { - $requested_qty = $request->return_qty[$key]; + foreach ($request->products as $item) { - if ($requested_qty <= 0) { - continue; - } + $detail = $purchase->details->where('id', $item['detail_id'])->first(); + if (!$detail) continue; + + $requested_qty = (int) $item['return_qty']; + if ($requested_qty <= 0) continue; - // Check if return quantity exceeds the purchased quantity if ($requested_qty > $detail->quantities) { return response()->json([ - 'message' => "You can't return more than the ordered quantity of {$detail->quantities}.", + 'message' => "You can't return more than purchased quantity ({$detail->quantities})." ], 400); } - // Calculate per-unit discount and return amounts + $serials = !empty($item['serial_numbers']) ? json_decode($item['serial_numbers'], true) : []; + + // Serial number validation and stock update + if (!empty($serials)) { + if (count($serials) !== $requested_qty) { + return response()->json(['message' => 'Serial count must match return quantity.'], 400); + } + + $stockSerials = $detail->stock->serial_numbers ?? []; + $invalid = array_diff($serials, $stockSerials); + if (!empty($invalid)) { + return response()->json([ + 'message' => 'Invalid serial numbers detected.', + 'invalid_serials' => array_values($invalid), + ], 400); + } + + // Remove serials from stock + $stock_remaining = array_values(array_diff($stockSerials, $serials)); + $detail->stock->update(['serial_numbers' => $stock_remaining]); + } + + //Return amount calculation $unit_discount = $detail->productPurchasePrice * $discount_per_unit_factor; $return_discount = $unit_discount * $requested_qty; $return_amount = ($detail->productPurchasePrice - $unit_discount) * $requested_qty; @@ -137,100 +170,88 @@ public function store(Request $request) $total_return_amount += $return_amount; $total_return_discount += $return_discount; - // Update stock & purchase details - Stock::where('id', $detail->stock_id)->decrement('productStock', $requested_qty); + //Update stock & purchase detail + $detail->stock->decrement('productStock', $requested_qty); $detail->quantities -= $requested_qty; $detail->timestamps = false; $detail->save(); - // Collect return detail data - $purchase_return_detail_data[] = [ - 'purchase_detail_id' => $detail->id, - 'purchase_return_id' => $purchase_return->id, - 'return_qty' => $requested_qty, - 'business_id' => auth()->user()->business_id, - 'return_amount' => $return_amount, - ]; - } + // Remove serials from purchase detail + if (!empty($serials) && !empty($detail->serial_numbers)) { + $remainingDetailSerials = array_values(array_diff($detail->serial_numbers ?? [], $serials)); + $detail->update(['serial_numbers' => $remainingDetailSerials]); + } - // Insert purchase return details - if (!empty($purchase_return_detail_data)) { - PurchaseReturnDetail::insert($purchase_return_detail_data); + $returnDetail = PurchaseReturnDetail::create([ + 'business_id' => $business_id, + 'purchase_return_id' => $purchase_return->id, + 'purchase_detail_id' => $detail->id, + 'return_qty' => $requested_qty, + 'return_amount' => $return_amount, + 'serial_numbers' => $serials ?? null, + ]); + + $previous_returned_qty = PurchaseReturnDetail::where('purchase_detail_id', $detail->id)->sum('return_qty'); + $totalQty = $detail->quantities + $previous_returned_qty; + + PurchaseVatTransaction::productReturnVatCalculation($detail, $requested_qty, $returnDetail, $totalQty); } if ($total_return_amount <= 0) { - return response()->json("You cannot return an empty product.", 400); + return response()->json('You cannot return empty product.', 400); } - // Update party dues (if applicable) + // Adjust party balances $party = Party::find($purchase->party_id); - if ($party) { - $refund_amount = $total_return_amount; + $refund = $total_return_amount; - // If party has due, reduce it first if ($party->due > 0) { - if ($party->due >= $refund_amount) { - $party->decrement('due', $refund_amount); - $refund_amount = 0; - } else { - $refund_amount -= $party->due; - $party->update(['due' => 0]); - } + $deduct = min($party->due, $refund); + $party->decrement('due', $deduct); + $refund -= $deduct; } - // Any remaining amount should be deducted from wallet - if ($refund_amount > 0 && $party->wallet > 0) { - $deduct = min($party->wallet, $refund_amount); + if ($refund > 0 && $party->wallet > 0) { + $deduct = min($party->wallet, $refund); $party->decrement('wallet', $deduct); } } - // Calculate remaining return amount - $remaining_return_amount = max(0, $total_return_amount - $purchase->dueAmount); - $new_total_amount = max(0, $purchase->totalAmount - $total_return_amount); - - // Update purchase record $purchase->update([ 'change_amount' => 0, 'dueAmount' => max(0, $purchase->dueAmount - $total_return_amount), 'paidAmount' => max(0, $purchase->paidAmount - min($purchase->paidAmount, $total_return_amount)), - 'totalAmount' => $new_total_amount, + 'totalAmount' => max(0, $purchase->totalAmount - $total_return_amount), 'discountAmount' => max(0, $purchase->discountAmount - $total_return_discount), - 'isPaid' => $remaining_return_amount > 0 ? 1 : $purchase->isPaid, ]); - $payments = $request->payments; - - if (isset($payments['main'])) { - $mainPayment = $payments['main']; - $mainPayment['amount'] = $request->receive_amount ?? 0; - $payments = [$mainPayment]; - } - - $payments = collect($payments)->map(function ($payment) use ($total_return_amount) { - $payment['amount'] = $total_return_amount; - return $payment; - })->toArray(); + // Payment processing + $payments = $request->payments ?? []; + $payments = collect($payments)->map(fn($p) => array_merge($p, ['amount' => $total_return_amount]))->toArray(); event(new MultiPaymentProcessed( $payments, $purchase_return->id, 'purchase_return', - $total_return_amount ?? 0, + $total_return_amount )); + $tax_invoice = moduleCheck('TaxInvoiceAddon') && invoice_setting($purchase->business_id) == 'standard_a4'; + $invoice_route = $tax_invoice ? route('business.purchases.tax.invoice', $purchase->id) : route('business.purchases.invoice', $purchase->id); + DB::commit(); return response()->json([ 'message' => __('Purchase returned successfully.'), 'redirect' => route('business.purchase-returns.index'), - 'secondary_redirect_url' => route('business.purchases.invoice', $purchase->id), + 'secondary_redirect_url' => $invoice_route, ]); + } catch (\Exception $e) { DB::rollback(); - return response()->json(['message' => __('Something went wrong!')], 500); + return response()->json(['message' => __('Something went wrong!') . ' ' . $e->getMessage()], 500); } } } diff --git a/Modules/Business/App/Http/Controllers/SaleReturnController.php b/Modules/Business/App/Http/Controllers/SaleReturnController.php index a8f80dd0..60b6b512 100644 --- a/Modules/Business/App/Http/Controllers/SaleReturnController.php +++ b/Modules/Business/App/Http/Controllers/SaleReturnController.php @@ -3,16 +3,17 @@ namespace Modules\Business\App\Http\Controllers; use App\Events\MultiPaymentProcessed; +use App\Http\Controllers\Controller; +use App\Models\Branch; +use App\Models\Party; use App\Models\PaymentType; use App\Models\Sale; -use App\Models\Party; -use App\Models\Stock; -use App\Models\Branch; use App\Models\SaleReturn; -use Illuminate\Http\Request; use App\Models\SaleReturnDetails; +use App\Models\Stock; +use App\Services\VatTransactionService; +use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; -use App\Http\Controllers\Controller; class SaleReturnController extends Controller { @@ -84,46 +85,67 @@ public function create(Request $request) return view('business::sale-returns.create', compact('sale', 'discount_per_unit_factor', 'avg_rounding_amount', 'payment_types')); } - public function store(Request $request) { + // split-array structure from front-end request + $products = []; + for ($index = 0; $index < count($request->products); $index += 3) { + $products[] = [ + 'detail_id' => $request->products[$index]['detail_id'] ?? null, + 'return_qty' => $request->products[$index + 1]['return_qty'] ?? 0, + 'serial_numbers' => $request->products[$index + 2]['serial_numbers'] ?? '[]', + ]; + } + $request->merge(['products' => $products]); + $request->validate([ 'sale_id' => 'required|exists:sales,id', - 'return_qty' => 'required|array', + 'products' => 'required|array|min:1', + 'products.*.detail_id' => 'required|exists:sale_details,id', + 'products.*.return_qty' => 'required|integer', + 'products.*.serial_numbers' => 'nullable|string', ]); $business_id = auth()->user()->business_id; DB::beginTransaction(); try { - $sale = Sale::with('details:id,sale_id,product_id,price,discount,lossProfit,quantities,productPurchasePrice,stock_id,expire_date', 'details.product:id,product_type', 'details.product.combo_products') - ->where('business_id', $business_id) - ->findOrFail($request->sale_id); + // Load sale with details and product info + $sale = Sale::with( + 'details:id,sale_id,product_id,price,discount,lossProfit,quantities,productPurchasePrice,stock_id,expire_date,serial_numbers', + 'details.product:id,product_type,has_serial', + 'details.product.combo_products' + ) + ->where('business_id', $business_id) + ->findOrFail($request->sale_id); - // Calculate total discount factor with itemwise discount + $old_sale_due = $sale->dueAmount; + + // Calculate discount factors $total_discount = $sale->discountAmount; $total_sale_amount = $sale->details->sum(fn($detail) => $detail->price * $detail->quantities); $discount_per_unit_factor = $total_sale_amount > 0 ? $total_discount / $total_sale_amount : 0; $rounding_amount_per_unit = $sale->details->sum('quantities') > 0 ? $sale->rounding_amount / $sale->details->sum('quantities') : 0; + // Create sale return record $sale_return = SaleReturn::create([ + 'return_date' => now(), 'business_id' => $business_id, 'sale_id' => $request->sale_id, 'invoice_no' => $sale->invoiceNumber, - 'return_date' => now(), ]); - $sale_return_detail_data = []; $total_return_amount = 0; $total_return_discount = 0; $total_loss_profit_adjustment = 0; - foreach ($sale->details as $key => $detail) { - $requested_qty = $request->return_qty[$key]; + foreach ($request->products as $indextem) { - if ($requested_qty <= 0) { - continue; - } + $detail = $sale->details->where('id', $indextem['detail_id'])->first(); + if (!$detail) continue; // skip if detail not found + + $requested_qty = (int) ($indextem['return_qty'] ?? 0); + if ($requested_qty <= 0) continue; if ($requested_qty > $detail->quantities) { return response()->json([ @@ -131,12 +153,12 @@ public function store(Request $request) ], 400); } - $product = $detail->product; + $return_serials = !empty($indextem['serial_numbers']) ? json_decode($indextem['serial_numbers'], true) : []; // Include SaleDetails discount in return calculation $unit_discount = $detail->price * $discount_per_unit_factor; - $item_cart_discount = $detail->discount ?? 0; - $total_discount_per_unit = $unit_discount + $item_cart_discount; + $indextem_cart_discount = $detail->discount ?? 0; + $total_discount_per_unit = $unit_discount + $indextem_cart_discount; $return_discount = $total_discount_per_unit * $requested_qty; $return_amount = ($detail->price - $total_discount_per_unit + $rounding_amount_per_unit) * $requested_qty; @@ -144,70 +166,91 @@ public function store(Request $request) $total_return_amount += $return_amount; $total_return_discount += $return_discount; - if ($product && $product->product_type === 'combo') { + $product = $detail->product; - $combo_total_purchase = 0; - $combo_total_sale = $detail->price * $requested_qty; + // Combo products + if ($product && $product->product_type === 'combo') { foreach ($product->combo_products as $comboItem) { $stock = Stock::find($comboItem->stock_id); - if (!$stock) { return response()->json([ 'message' => __("Stock not found for combo item '{$comboItem->product->productName}'"), ], 400); } - // increase stock by combo component quantity * returned qty $restore_qty = $comboItem->quantity * $requested_qty; $stock->increment('productStock', $restore_qty); - - $combo_total_purchase += $comboItem->purchase_price * $restore_qty; } - - // loss/profit adjustment for combo - $loss_profit_adjustment = ($combo_total_sale - $combo_total_purchase) - $return_discount; - $total_loss_profit_adjustment += $loss_profit_adjustment; } - + // Single / variant products else { $stock = Stock::where('id', $detail->stock_id)->first(); - if (!$stock) { - return response()->json(['error' => 'Stock not found.'], 404); + if (!$stock) return response()->json(['error' => 'Stock not found.'], 404); + + if ($product->has_serial) { + + // Validate serial count + if (count($return_serials) !== $requested_qty) { + return response()->json([ + 'message' => 'Serial count must match return quantity' + ], 400); + } + + $sold_serials = is_array($detail->serial_numbers) ? $detail->serial_numbers : json_decode($detail->serial_numbers ?? '[]', true); + $indexnvalid = array_diff($return_serials, $sold_serials); + if (!empty($indexnvalid)) { + return response()->json(['message' => 'Invalid serial detected'], 400); + } + + // Restore stock + $stock->increment('productStock', $requested_qty); + + // Restore serials to stock + $stock_serials = $stock->serial_numbers ?? []; + $stock->update([ + 'serial_numbers' => array_values(array_unique(array_merge($stock_serials, $return_serials))) + ]); + + // Remove serials from sale detail + $detail->update([ + 'serial_numbers' => array_values(array_diff($sold_serials, $return_serials)) + ]); + } else { + $stock->increment('productStock', $requested_qty); } - - $stock->increment('productStock', $requested_qty); - - $loss_profit_adjustment = (($detail->price - $stock->productPurchasePrice) * $requested_qty) - $return_discount; - $total_loss_profit_adjustment += $loss_profit_adjustment; } - // Update sale details + $loss_profit_adjustment = $detail->lossProfit * ($requested_qty / $detail->quantities); + $total_loss_profit_adjustment += $loss_profit_adjustment; + + // Update sale detail $detail->quantities -= $requested_qty; $detail->lossProfit -= $loss_profit_adjustment; $detail->timestamps = false; $detail->save(); - $sale_return_detail_data[] = [ + $returnDetail = SaleReturnDetails::create([ 'business_id' => $business_id, 'sale_detail_id' => $detail->id, 'sale_return_id' => $sale_return->id, 'return_qty' => $requested_qty, 'return_amount' => $return_amount, - ]; - } + 'serial_numbers' => $return_serials ?? null, + ]); - if (!empty($sale_return_detail_data)) { - SaleReturnDetails::insert($sale_return_detail_data); + $previous_returned_qty = SaleReturnDetails::where('sale_detail_id', $detail->id)->sum('return_qty'); + $totalQty = $detail->quantities + $previous_returned_qty; + + VatTransactionService::productReturnVatCalculation($detail, $requested_qty, $returnDetail, $totalQty); } if ($total_return_amount <= 0) { return response()->json("You cannot return an empty product.", 400); } + // Remaining refund / party logic $remaining_refund = $total_return_amount; - - // Adjust Due $new_due = $sale->dueAmount; if ($remaining_refund > 0) { if ($remaining_refund >= $sale->dueAmount) { @@ -219,7 +262,6 @@ public function store(Request $request) } } - // Adjust Paid (refund part) $new_paid = $sale->paidAmount; if ($remaining_refund > 0) { if ($remaining_refund >= $sale->paidAmount) { @@ -231,9 +273,7 @@ public function store(Request $request) } } - // total sale amount $new_total_amount = max(0, $sale->totalAmount - $total_return_amount); - $sale->update([ 'change_amount' => 0, 'dueAmount' => $new_due, @@ -244,39 +284,38 @@ public function store(Request $request) 'lossProfit' => $sale->lossProfit - $total_loss_profit_adjustment, ]); - // Party Refund Logic - $party = Party::find($sale->party_id); + // Party refund logic + $due_reduction = $old_sale_due - $new_due; - if ($party) { + if ($due_reduction > 0) { + Party::where('business_id', $business_id) + ->where('id', $sale->party_id) + ->decrement('due', $due_reduction); + } - // use leftover refund after due/paid adjustments - $refund_amount = $remaining_refund; + $party = Party::where('business_id', $business_id)->find($sale->party_id); + + if ($party && $remaining_refund > 0) { - // Reduce party due if ($party->due > 0) { - if ($party->due >= $refund_amount) { - $party->decrement('due', $refund_amount); - $refund_amount = 0; + + if ($party->due >= $remaining_refund) { + $party->decrement('due', $remaining_refund); + $remaining_refund = 0; } else { - $refund_amount -= $party->due; + $remaining_refund -= $party->due; $party->update(['due' => 0]); } + } - // Add leftover refund to wallet - if ($refund_amount > 0) { - $party->increment('wallet', $refund_amount); + if ($remaining_refund > 0) { + $party->increment('wallet', $remaining_refund); } } - $payments = $request->payments; - - if (isset($payments['main'])) { - $mainPayment = $payments['main']; - $mainPayment['amount'] = $request->receive_amount ?? 0; - $payments = [$mainPayment]; - } - + // Payment event + $payments = $request->payments ?? []; $payments = collect($payments)->map(function ($payment) use ($total_return_amount) { $payment['amount'] = $total_return_amount; return $payment; @@ -286,20 +325,22 @@ public function store(Request $request) $payments, $sale_return->id, 'sale_return', - $total_return_amount ?? 0, + $total_return_amount, )); + $tax_invoice = moduleCheck('TaxInvoiceAddon') && invoice_setting($sale->business_id) == 'standard_a4'; + $invoice_route = $tax_invoice ? route('business.sales.tax.invoice', $sale->id) : route('business.sales.invoice', $sale->id); + DB::commit(); return response()->json([ 'message' => __('Sale returned successfully.'), 'redirect' => route('business.sale-returns.index'), - 'secondary_redirect_url' => route('business.sales.invoice', $sale->id), + 'secondary_redirect_url' => $invoice_route, ]); } catch (\Exception $e) { DB::rollback(); - return response()->json(['message' => __('Something went wrong!')], 500); + return response()->json(['message' => __('Something went wrong!') . ' ' . $e->getMessage()], 500); } } - } diff --git a/Modules/Business/App/Http/Controllers/SettingController.php b/Modules/Business/App/Http/Controllers/SettingController.php index 99a66e31..c8a94469 100644 --- a/Modules/Business/App/Http/Controllers/SettingController.php +++ b/Modules/Business/App/Http/Controllers/SettingController.php @@ -2,14 +2,16 @@ namespace Modules\Business\App\Http\Controllers; -use App\Models\Option; -use App\Models\Business; use App\Helpers\HasUploader; -use Illuminate\Http\Request; -use App\Models\BusinessCategory; -use Illuminate\Support\Facades\DB; use App\Http\Controllers\Controller; +use App\Models\Business; +use App\Models\BusinessCategory; +// use App\Models\Country; +use App\Models\Option; +// use App\Models\State; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\DB; class SettingController extends Controller { @@ -18,14 +20,16 @@ class SettingController extends Controller public function index() { $setting = Option::where('key', 'business-settings') - ->where('value', 'LIKE', '%"business_id":%' . auth()->user()->business_id . '%') + ->where('value', 'LIKE', '%"business_id":' . auth()->user()->business_id . '%') ->get() ->firstWhere('value.business_id', auth()->user()->business_id); $business_categories = BusinessCategory::whereStatus(1)->latest()->get(); $business = Business::findOrFail(auth()->user()->business_id); + $countries = []; + $states = []; - return view('business::settings.general', compact('setting', 'business_categories', 'business')); + return view('business::settings.general', compact('setting', 'business_categories', 'business', 'countries', 'states')); } public function update(Request $request, $id) @@ -56,6 +60,8 @@ public function update(Request $request, $id) 'show_invoice_scanner_logo' => 'nullable|boolean', 'show_a4_invoice_logo' => 'nullable|boolean', 'show_thermal_invoice_logo' => 'nullable|boolean', +// 'country_id' => 'nullable|exists:countries,id', +// 'state_id' => 'nullable|exists:states,id', ]); DB::beginTransaction(); @@ -71,14 +77,12 @@ public function update(Request $request, $id) 'email' => $request->email, 'vat_name' => $request->vat_name, 'vat_no' => $request->vat_no, +// 'country_id' => $request->country_id, +// 'state_id' => $request->state_id, ]); $moduleKeys = [ - 'show_company_name', - 'show_phone_number', - 'show_address', - 'show_email', - 'show_vat', + 'show_company_name', 'show_phone_number', 'show_address', 'show_email', 'show_vat', ]; $modules = []; @@ -96,7 +100,7 @@ public function update(Request $request, $id) if ($setting) { $setting->update($request->except($data) + [ - 'value' => $request->except('_token', '_method', 'a4_invoice_logo', 'thermal_invoice_logo', 'invoice_scanner_logo', 'address', 'companyName', 'business_category_id', 'phoneNumber', 'email', 'show_company_name', 'show_phone_number', 'show_address', 'show_email', 'show_vat') + [ + 'value' => $request->except('_token', '_method', 'a4_invoice_logo', 'thermal_invoice_logo', 'invoice_scanner_logo', 'address', 'companyName', 'business_category_id', 'phoneNumber', 'email', 'show_company_name', 'show_phone_number', 'show_address', 'show_email', 'show_vat' ) + [ 'business_id' => $business->id, 'a4_invoice_logo' => $request->a4_invoice_logo ? $this->upload($request, 'a4_invoice_logo', $setting->value['a4_invoice_logo'] ?? null) : ($setting->value['a4_invoice_logo'] ?? null), 'thermal_invoice_logo' => $request->thermal_invoice_logo ? $this->upload($request, 'thermal_invoice_logo', $setting->value['thermal_invoice_logo'] ?? null) : ($setting->value['thermal_invoice_logo'] ?? null), @@ -140,7 +144,7 @@ public function update(Request $request, $id) 'show_invoice_scanner_logo' => 1, 'show_a4_invoice_logo' => 1, 'show_thermal_invoice_logo' => 1, - 'show_warranty' => 1, + 'show_warranty' => 1 , ]), 'created_at' => now(), 'updated_at' => now(), diff --git a/Modules/Business/App/Providers/.gitkeep b/Modules/Business/App/Providers/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/Modules/Business/Database/Seeders/.gitkeep b/Modules/Business/Database/Seeders/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/Modules/Business/config/.gitkeep b/Modules/Business/config/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/Modules/Business/module.json b/Modules/Business/module.json index aa57b9fb..4a4bf9a4 100644 --- a/Modules/Business/module.json +++ b/Modules/Business/module.json @@ -1,7 +1,7 @@ { "name": "Business", "alias": "business", - "version": "3.1.1", + "version": "3.3", "description": "", "keywords": [], "priority": 0, diff --git a/Modules/Business/resources/assets/js/app.js b/Modules/Business/resources/assets/js/app.js new file mode 100644 index 00000000..e69de29b diff --git a/Modules/Business/resources/assets/sass/app.scss b/Modules/Business/resources/assets/sass/app.scss new file mode 100644 index 00000000..e69de29b diff --git a/Modules/Business/resources/views/.gitkeep b/Modules/Business/resources/views/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/Modules/Business/resources/views/balance-sheets/index.blade.php b/Modules/Business/resources/views/balance-sheets/index.blade.php index 5ee0cd25..0f11e59a 100644 --- a/Modules/Business/resources/views/balance-sheets/index.blade.php +++ b/Modules/Business/resources/views/balance-sheets/index.blade.php @@ -76,17 +76,17 @@
{{ __('Duration:') }} + @if ($duration === 'today') + {{ Carbon\Carbon::parse($filter_from_date)->format('d-m-Y') }} + @elseif ($duration === 'yesterday') + {{ Carbon\Carbon::parse($filter_from_date)->format('d-m-Y') }} + @else + {{ Carbon\Carbon::parse($filter_from_date)->format('d-m-Y') }} + {{ __('to') }} + {{ Carbon\Carbon::parse($filter_to_date)->format('d-m-Y') }} + @endif +
+ @endif