migrate to gtea from bistbucket

This commit is contained in:
2026-03-15 17:08:23 +07:00
commit 129ca2260c
3716 changed files with 566316 additions and 0 deletions

View File

@@ -0,0 +1,148 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Product;
use App\Models\PaymentType;
use Illuminate\Http\Request;
use App\Traits\DateFilterTrait;
use App\Http\Controllers\Controller;
use App\Services\PdfService;
use App\Traits\DateRangeTrait;
use Illuminate\Pagination\LengthAwarePaginator;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportBalanceSheet;
class AcnooBalanceSheetController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function index(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);
$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;
});
$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);
$page = LengthAwarePaginator::resolveCurrentPage();
$products = $asset_datas->slice(($page - 1) * $perPage, $perPage)->values();
$asset_datas = new LengthAwarePaginator(
$products,
$asset_datas->count(),
$perPage,
$page,
['path' => request()->url(), 'query' => request()->query()]
);
$total_stock_value = 0;
foreach ($products as $product) {
if (in_array($product->product_type, ['single', 'variant'])) {
foreach ($product->stocks as $stock) {
$total_stock_value += $stock->productStock * $stock->productPurchasePrice;
}
}
if ($product->product_type === 'combo') {
foreach ($product->combo_products as $combo) {
$childStock = $combo->stock;
if ($childStock) {
$total_stock_value += ($childStock->productStock / $combo->quantity) * $combo->purchase_price;
}
}
}
}
$totalBankBalance = $banks->sum('balance');
$total_asset = $total_stock_value + $totalBankBalance;
if ($request->ajax()) {
return response()->json([
'data' => view('business::balance-sheets.datas', compact('asset_datas', 'total_asset', 'filter_from_date', 'filter_to_date', 'duration'))->render(),
'total_asset' => currency_format($total_asset, currency: business_currency()),
]);
}
return view('business::balance-sheets.index', compact('asset_datas', 'total_asset', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function exportExcel()
{
return Excel::download(new ExportBalanceSheet, 'balance-sheet.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportBalanceSheet, 'balance-sheet.csv');
}
public function exportPdf(Request $request)
{
$businessId = auth()->user()->business_id;
// 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);
$products = $productQuery->get()
->map(function ($item) {
$item->source = 'product';
return $item;
});
// BANK
$bankQuery = PaymentType::where('business_id', $businessId);
$banks = $bankQuery->get()
->map(function ($item) {
$item->source = 'bank';
return $item;
});
$asset_datas = $products->merge($banks);
// TOTAL STOCK VALUE
$total_stock_value = 0;
foreach ($products as $product) {
if (in_array($product->product_type, ['single', 'variant'])) {
foreach ($product->stocks as $stock) {
$total_stock_value += $stock->productStock * $stock->productPurchasePrice;
}
}
if ($product->product_type === 'combo') {
foreach ($product->combo_products as $combo) {
$childStock = $combo->stock;
if ($childStock) {
$total_stock_value += ($childStock->productStock / $combo->quantity) * $combo->purchase_price;
}
}
}
}
$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');
}
}

View File

@@ -0,0 +1,192 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\PaymentType;
use App\Models\Transaction;
class AcnooBankController extends Controller
{
public function index(Request $request)
{
$payment_types = PaymentType::with('branch:id,name')->where('business_id', auth()->user()->business_id)
->when(request('search'), function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->where('balance', 'like', '%' . $request->search . '%')
->orWhere('name', 'like', '%' . $request->search . '%')
->orWhere('meta->account_number', 'like', '%' . $request->search . '%')
->orWhere('meta->bank_name', 'like', '%' . $request->search . '%')
->orWhere('meta->branch_name', 'like', '%' . $request->search . '%')
->orWhere('meta->account_holder', 'like', '%' . $request->search . '%');
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::banks.datas', compact('payment_types'))->render()
]);
}
return view('business::banks.index', compact('payment_types'));
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'branch_id' => 'nullable|exists:branches,id',
'opening_balance' => 'nullable|numeric|min:0',
'opening_date' => 'nullable|date',
'show_in_invoice' => 'boolean',
'meta' => 'nullable|array',
'meta.account_number' => 'nullable|string|max:255',
'meta.routing_number' => 'nullable|string|max:255',
'meta.upi_id' => 'nullable|string|max:255',
'meta.bank_name' => 'nullable|string|max:255',
'meta.branch' => 'nullable|string|max:255',
'meta.account_holder' => 'nullable|string|max:255',
]);
$forbidden = ['cash', 'cheque'];
if (in_array(strtolower($request->name), $forbidden)) {
return response()->json([
'message' => __('You cannot create a bank account with this name.'),
], 422);
}
PaymentType::create($request->except('business_id', 'show_in_invoice', 'balance', 'opening_balance') + [
'business_id' => auth()->user()->business_id,
'opening_balance' => $request->opening_balance ?? 0,
'balance' => $request->opening_balance ?? 0,
'show_in_invoice' => $request->show_in_invoice,
]);
return response()->json([
'message' => __('Bank created Successfully'),
'redirect' => route('business.banks.index'),
]);
}
public function update(Request $request, string $id)
{
$request->validate([
'name' => 'required|string|max:255',
'branch_id' => 'nullable|exists:branches,id',
'opening_balance' => 'nullable|numeric|min:0',
'opening_date' => 'nullable|date',
'show_in_invoice' => 'boolean',
'meta' => 'nullable|array',
'meta.account_number' => 'nullable|string|max:255',
'meta.routing_number' => 'nullable|string|max:255',
'meta.upi_id' => 'nullable|string|max:255',
'meta.bank_name' => 'nullable|string|max:255',
'meta.branch' => 'nullable|string|max:255',
'meta.account_holder' => 'nullable|string|max:255',
]);
$payment_type = PaymentType::findOrFail($id);
$forbidden = ['cash', 'cheque'];
if (in_array(strtolower($request->name), $forbidden)) {
return response()->json([
'message' => __('You cannot create a bank account with this name.'),
], 422);
}
$hasTransactions = $payment_type->transactions()->exists();
if ($hasTransactions) {
return response()->json([
'message' => __("You can't Change opening balance because this bank already has transactions.")
], 422);
}
$updateData = $request->except('business_id');
// If transactions exist, do not update balance
if ($hasTransactions) {
unset($updateData['opening_balance'], $updateData['balance']);
} else {
// update balance
$updateData['balance'] = $request->opening_balance ?? $payment_type->balance;
}
$payment_type->update($updateData);
return response()->json([
'message' => __('Bank Updated Successfully'),
'redirect' => route('business.banks.index'),
]);
}
public function destroy($id)
{
$paymentType = PaymentType::findOrFail($id);
// Check if this payment type is used in any transaction
$hasTransactions = $paymentType->transactions()->exists();
$balance = $paymentType->balance ?? 0;
if ($hasTransactions && $balance != 0) {
return response()->json([
'message' => __('Bank cant be deleted. It has transactions or balance.')
], 400);
}
$paymentType->delete();
return response()->json([
'message' => __('Bank Deleted Successfully'),
'redirect' => route('business.banks.index'),
]);
}
public function deleteAll(Request $request)
{
$hasTransactions = Transaction::where(function ($query) use ($request) {
$query->whereIn('payment_type_id', $request->ids)
->orWhereIn('from_bank', $request->ids)
->orWhereIn('to_bank', $request->ids);
})
->whereHas('paymentType', function ($q) {
$q->where('balance', '!=', 0);
})
->pluck('payment_type_id')
->toArray();
PaymentType::whereIn('id', $request->ids)
->where(function ($q) use ($hasTransactions) {
$q->whereNotIn('id', $hasTransactions)
->orWhere(function ($q) use ($hasTransactions) {
$q->whereIn('id', $hasTransactions)
->where('balance', 0);
});
})
->delete();
if (count($hasTransactions) > 0) {
return response()->json([
'message' => 'Some banks were not deleted because they have transactions.',
'redirect' => route('business.banks.index')
], 422);
}
return response()->json([
'message' => 'Selected Banks Deleted Successfully.',
'redirect' => route('business.banks.index'),
]);
}
public function getBanks()
{
$banks = PaymentType::select('id', 'name')
->where('business_id', auth()->user()->business_id)
->get();
return response()->json($banks);
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Sale;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Party;
use App\Services\PdfService;
use App\Traits\DateFilterTrait;
use App\Traits\DateRangeTrait;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportBillWisePofit;
class AcnooBillWiseProfitController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function index(Request $request)
{
$parties = Party::where('business_id', auth()->user()->business_id)->where('type', '!=', 'Supplier')->latest()->get();
$salesQuery = Sale::with('party:id,name')
->where('business_id', auth()->user()->business_id);
$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()->paginate($request->per_page ?? 20)->appends($request->query());
$loss = (clone $salesQuery)->where('lossProfit', '<=', 0)->get()
->sum(function ($sale) {
return abs($sale->lossProfit);
});
$profit = (clone $salesQuery)->where('lossProfit', '>', 0)->sum('lossProfit');
$total_sale_amount = (clone $salesQuery)->sum('totalAmount');
if ($request->ajax()) {
return response()->json([
'data' => view('business::bill-wise-profits.datas', compact('profits', 'filter_from_date', 'filter_to_date', 'duration'))->render(),
'total_loss' => currency_format($loss, currency: business_currency()),
'total_profit' => currency_format($profit, currency: business_currency()),
'total_sale_amount' => currency_format($total_sale_amount, currency: business_currency()),
]);
}
return view('business::bill-wise-profits.index', compact('profits', 'loss', 'profit', 'total_sale_amount', 'filter_from_date', 'filter_to_date', 'duration', 'parties'));
}
public function show(string $id)
{
$lossProfit = Sale::select('id', 'invoiceNumber', 'party_id')
->where('business_id', auth()->user()->business_id)
->with('party:id,name', 'details', 'details.product:id,productName', 'details.stock:id,batch_no')
->findOrFail($id);
return response()->json($lossProfit);
}
public function exportExcel()
{
return Excel::download(new ExportBillWisePofit, 'bill-wise-profit.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportBillWisePofit, 'bill-wise-profit.csv');
}
public function exportPdf()
{
$profits = Sale::with('party:id,name')
->where('business_id', auth()->user()->business_id)
->latest()
->get();
return PdfService::render('business::bill-wise-profits.pdf', compact('profits'),'bill-wise-profit-report.pdf');
}
}

View File

@@ -0,0 +1,132 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Brand;
use Illuminate\Http\Request;
use App\Helpers\HasUploader;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
class AcnooBrandController extends Controller
{
use HasUploader;
public function __construct()
{
$this->middleware('check.permission:brands.read')->only(['index']);
$this->middleware('check.permission:brands.create')->only(['store']);
$this->middleware('check.permission:brands.update')->only(['update', 'status']);
$this->middleware('check.permission:brands.delete')->only(['destroy', 'deleteAll']);
}
public function index(Request $request)
{
$brands = Brand::where('business_id', auth()->user()->business_id)
->when(request('search'), function($q) use($request) {
$q->where(function($q) use($request) {
$q->where('brandName', 'like', '%'.$request->search.'%')
->orWhere('description', 'like', '%'.$request->search.'%');
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if($request->ajax()){
return response()->json([
'data' => view('business::brands.datas',compact('brands'))->render()
]);
}
return view('business::brands.index', compact('brands'));
}
public function store(Request $request)
{
$request->validate([
'brandName' => 'required|string|max:255',
'description' => 'nullable|string',
'icon' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg',
]);
Brand::create($request->except('business_id','icon') + [
'business_id' => auth()->user()->business_id,
'icon' => $request->icon ? $this->upload($request, 'icon') : NULL,
]);
return response()->json([
'message' => __('Brand created cuccessfully'),
'redirect' => route('business.brands.index'),
]);
}
public function update(Request $request, $id)
{
$brand = Brand::findOrFail($id);
$request->validate([
'brandName' => 'required|string|max:255',
'description' => 'nullable|string',
'icon' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg',
]);
$brand->update([
'brandName' => $request->brandName,
'icon' => $request->icon ? $this->upload($request, 'icon', $brand->icon) : $brand->icon
]);
return response()->json([
'message' => __('Brand updated successfully'),
'redirect' => route('business.brands.index'),
]);
}
public function destroy($id)
{
$brand = Brand::findOrFail($id);
if (file_exists($brand->icon)) {
Storage::delete($brand->icon);
}
$brand->delete();
return response()->json([
'message' => __('Brand deleted successfully'),
'redirect' => route('business.brands.index')
]);
}
public function status(Request $request, $id)
{
$brand = Brand::findOrFail($id);
$brand->update(['status' => $request->status]);
return response()->json(['message' => __('Brand')]);
}
public function deleteAll(Request $request)
{
$idsToDelete = $request->input('ids');
DB::beginTransaction();
try {
$brands = Brand::whereIn('id', $idsToDelete)->get();
foreach ($brands as $brand) {
if (file_exists($brand->icon)) {
Storage::delete($brand->icon);
}
}
Brand::whereIn('id', $idsToDelete)->delete();
DB::commit();
return response()->json([
'message' => __('Selected Brands deleted successfully'),
'redirect' => route('business.brands.index')
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json(__('Something went wrong.'), 400);
}
}
}

View File

@@ -0,0 +1,286 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Helpers\HasUploader;
use App\Models\PaymentType;
use App\Models\Transaction;
use App\Http\Controllers\Controller;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class AcnooCashController extends Controller
{
use HasUploader;
public function index(Request $request)
{
$business_id = auth()->user()->business_id;
$banks = PaymentType::where('business_id', $business_id)->latest()->get();
$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']);
// Default to today
$startDate = Carbon::today()->format('Y-m-d');
$endDate = Carbon::today()->format('Y-m-d');
if ($request->custom_days === 'yesterday') {
$startDate = Carbon::yesterday()->format('Y-m-d');
$endDate = Carbon::yesterday()->format('Y-m-d');
} elseif ($request->custom_days === 'last_seven_days') {
$startDate = Carbon::today()->subDays(6)->format('Y-m-d');
} elseif ($request->custom_days === 'last_thirty_days') {
$startDate = Carbon::today()->subDays(29)->format('Y-m-d');
} elseif ($request->custom_days === 'current_month') {
$startDate = Carbon::now()->startOfMonth()->format('Y-m-d');
$endDate = Carbon::now()->endOfMonth()->format('Y-m-d');
} elseif ($request->custom_days === 'last_month') {
$startDate = Carbon::now()->subMonth()->startOfMonth()->format('Y-m-d');
$endDate = Carbon::now()->subMonth()->endOfMonth()->format('Y-m-d');
} elseif ($request->custom_days === 'current_year') {
$startDate = Carbon::now()->startOfYear()->format('Y-m-d');
$endDate = Carbon::now()->endOfYear()->format('Y-m-d');
} elseif ($request->custom_days === 'custom_date' && $request->from_date && $request->to_date) {
$startDate = Carbon::parse($request->from_date)->format('Y-m-d');
$endDate = Carbon::parse($request->to_date)->format('Y-m-d');
}
$cash->whereDate('date', '>=', $startDate)
->whereDate('date', '<=', $endDate);
$cashes = $cash->when(request('search'), function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->where('date', '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()
->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::cashes.datas', compact('cashes'))->render()
]);
}
return view('business::cashes.index', compact('cashes', 'banks'));
}
public function store(Request $request)
{
$request->validate([
'to' => 'nullable|exists:payment_types,id',
'type' => 'nullable|in:credit,debit',
'transaction_type' => 'required|in:cash_to_bank,adjust_cash',
'amount' => 'required|numeric|min:0.01',
'date' => 'nullable|date',
'image' => 'nullable|image|mimes:jpg,png,jpeg,svg',
'note' => 'nullable|string',
]);
$business_id = auth()->user()->business_id;
$amount = $request->amount ?? 0;
$type = 'transfer';
DB::beginTransaction();
try {
// Cash to Bank
if ($request->transaction_type === 'cash_to_bank') {
$toBank = PaymentType::findOrFail($request->to);
// increase target bank balance
$toBank->increment('balance', $amount);
// Adjust Cash
} elseif ($request->transaction_type === 'adjust_cash') {
$type = $request->type;
}
// Store transaction record
Transaction::create([
'business_id' => $business_id,
'user_id' => auth()->id(),
'type' => $type,
'platform' => 'cash',
'transaction_type' => $request->transaction_type,
'amount' => $amount,
'from_bank' => null,
'to_bank' => ($request->transaction_type === 'cash_to_bank') ? $request->to : null,
'date' => $request->date ?? now(),
'image' => $request->image ? $this->upload($request, 'image') : NULL,
'note' => $request->note,
]);
DB::commit();
return response()->json([
'message' => __('Transaction completed successfully.'),
'redirect' => route('business.cashes.index'),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => 'Error: ' . $e->getMessage(),
], 500);
}
}
public function update(Request $request, string $id)
{
$request->validate([
'to' => 'nullable|exists:payment_types,id',
'type' => 'nullable|in:credit,debit',
'transaction_type' => 'required|in:cash_to_bank,adjust_cash',
'amount' => 'required|numeric|min:0.01',
'date' => 'nullable|date',
'image' => 'nullable|image|mimes:jpg,png,jpeg,svg',
'note' => 'nullable|string',
]);
$transaction = Transaction::findOrFail($id);
$newAmount = $request->amount ?? 0;
$newTransactionType = $request->transaction_type;
$type = 'transfer';
DB::beginTransaction();
try {
// Transaction type is the same
if ($transaction->transaction_type === $newTransactionType) {
if ($newTransactionType === 'cash_to_bank') {
$toBank = PaymentType::findOrFail($request->to);
// Adjust balance difference
$diff = $newAmount - $transaction->amount;
// Prevent negative balance
if ($toBank->balance + $diff < 0) {
return response()->json([
'message' => 'Cannot update: updated bank balance would be negative.'
], 400);
}
$toBank->increment('balance', $diff);
} elseif ($newTransactionType === 'adjust_cash') {
$type = $request->type;
}
} // Transaction type changed
else {
// Reverse old transaction effect
if ($transaction->transaction_type === 'cash_to_bank' && $transaction->to_bank) {
$prevBank = PaymentType::find($transaction->to_bank);
if ($prevBank) {
if ($prevBank->balance < $transaction->amount) {
return response()->json([
'message' => 'Cannot update: insufficient balance in ' . $prevBank->name . ' to reverse previous transaction.'
], 400);
}
$prevBank->decrement('balance', $transaction->amount);
}
}
// Apply new transaction effect
if ($newTransactionType === 'cash_to_bank') {
$toBank = PaymentType::findOrFail($request->to);
$toBank->increment('balance', $newAmount);
$type = 'transfer';
} elseif ($newTransactionType === 'adjust_cash') {
$type = $request->type;
}
}
// Update transaction record
$transaction->update([
'type' => $type,
'transaction_type' => $newTransactionType,
'amount' => $newAmount,
'to_bank' => ($newTransactionType === 'cash_to_bank') ? $request->to : null,
'date' => $request->date ?? now(),
'image' => $request->image ? $this->upload($request, 'image') : $transaction->image,
'note' => $request->note,
]);
DB::commit();
return response()->json([
'message' => __('Transaction updated successfully.'),
'redirect' => route('business.cashes.index'),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => 'Error: ' . $e->getMessage(),
], 500);
}
}
public function destroy(string $id)
{
$transaction = Transaction::findOrFail($id);
DB::beginTransaction();
try {
// Allow only "cash" platform transactions to be deleted
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;
// Reverse balance changes based on transaction type
switch ($transaction->transaction_type) {
case 'cash_to_bank':
if ($toBank) {
// Ensure bank has enough balance to reverse
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 (file_exists($transaction->image)) {
Storage::delete($transaction->image);
}
$transaction->delete();
DB::commit();
return response()->json([
'message' => __('Cash transaction reversed and deleted successfully.'),
'redirect' => route('business.cashes.index'),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => 'Error: ' . $e->getMessage(),
], 500);
}
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Transaction;
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\ExportCashFlowReport;
class AcnooCashFlowReportController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function index(Request $request)
{
$query = Transaction::with([
'paymentType:id,name',
'sale:id,party_id',
'sale.party:id,name',
'saleReturn:id,sale_id',
'purchase:id,party_id',
'purchase.party:id,name',
'purchaseReturn:id,purchase_id',
'dueCollect:id,party_id',
'dueCollect.party:id,name',
])
->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);
$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
$perPage = $request->input('per_page', 20);
$cash_flows = $query->paginate($perPage)->appends($request->query());
$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;
}
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()
]);
}
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'));
}
public function exportExcel()
{
return Excel::download(new ExportCashFlowReport, 'cash-flow.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportCashFlowReport, 'cash-flow.csv');
}
public function exportPdf()
{
$cash_flows = Transaction::with([
'paymentType:id,name',
'sale:id,party_id',
'sale.party:id,name',
'saleReturn:id,sale_id',
'purchase:id,party_id',
'purchase.party:id,name',
'purchaseReturn:id,purchase_id',
'dueCollect:id,party_id',
'dueCollect.party:id,name',
])
->where('business_id', auth()->user()->business_id)
->whereIn('type', ['debit', 'credit'])
->latest()
->get();
$opening_balance = 0;
return PdfService::render('business::cash-flow.pdf', compact('cash_flows', 'opening_balance'),'cash-flow-report.pdf');
}
}

View File

@@ -0,0 +1,123 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Category;
use App\Helpers\HasUploader;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
class AcnooCategoryController extends Controller
{
use HasUploader;
public function __construct()
{
$this->middleware('check.permission:categories.read')->only(['index']);
$this->middleware('check.permission:categories.create')->only(['store']);
$this->middleware('check.permission:categories.update')->only(['update', 'status']);
$this->middleware('check.permission:categories.delete')->only(['destroy', 'deleteAll']);
}
public function index(Request $request)
{
$categories = Category::where('business_id', auth()->user()->business_id)
->when(request('search'), function($q) use($request) {
$q->where(function($q) use($request) {
$q->where('categoryName', 'like', '%'.$request->search.'%');
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if($request->ajax()){
return response()->json([
'data' => view('business::categories.datas',compact('categories'))->render()
]);
}
return view('business::categories.index', compact('categories'));
}
public function store(Request $request)
{
$business_id = auth()->user()->business_id;
$request->validate([
'categoryName' => 'required|unique:categories,categoryName,NULL,id,business_id,' . $business_id,
'icon' => 'nullable|image|mimes:jpg,png,jpeg,gif',
]);
Category::create($request->except('business_id','icon') + [
'business_id' => auth()->user()->business_id,
'icon' => $request->icon ? $this->upload($request, 'icon') : NULL,
]);
return response()->json([
'message' => __('Category created successfully'),
'redirect' => route('business.categories.index'),
]);
}
public function update(Request $request, Category $category)
{
$request->validate([
'categoryName' => [
'required',
'unique:categories,categoryName,' . $category->id . ',id,business_id,' . auth()->user()->business_id,
],
'icon' => 'nullable|image|mimes:jpg,png,jpeg,gif',
]);
$category->update([
'business_id' => auth()->user()->business_id,
'categoryName' => $request->categoryName,
'icon' => $request->icon ? $this->upload($request, 'icon', $category->icon) : $category->icon,
]);
return response()->json([
'message' => __('Category updated successfully'),
'redirect' => route('business.categories.index'),
]);
}
public function destroy(Category $category)
{
if (file_exists($category->icon)) {
Storage::delete($category->icon);
}
$category->delete();
return response()->json([
'message' => __('Category deleted successfully'),
'redirect' => route('business.categories.index'),
]);
}
public function status(Request $request, $id)
{
$categoryStatus = Category::findOrFail($id);
$categoryStatus->update(['status' => $request->status]);
return response()->json(['message' => __('Category')]);
}
public function deleteAll(Request $request)
{
$categories = Category::whereIn('id', $request->ids)->get();
foreach ($categories as $category) {
if (file_exists($category->icon)) {
Storage::delete($category->icon);
}
$category->delete();
}
return response()->json([
'message' => __('Selected Category deleted successfully'),
'redirect' => route('business.categories.index')
]);
}
}

View File

@@ -0,0 +1,353 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\DueCollect;
use App\Models\Income;
use App\Models\Party;
use App\Models\PaymentType;
use App\Models\Sale;
use App\Models\Transaction;
use App\Http\Controllers\Controller;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class AcnooChequeController extends Controller
{
public function index(Request $request)
{
$business_id = auth()->user()->business_id;
$banks = PaymentType::where('business_id', $business_id)->latest()->get();
$cheque = Transaction::with('user:id,name')
->where('business_id', auth()->user()->business_id)
->whereIn('transaction_type', ['cheque_payment', 'cheque_reopen']);
// Default to today
$startDate = Carbon::today()->format('Y-m-d');
$endDate = Carbon::today()->format('Y-m-d');
if ($request->custom_days === 'yesterday') {
$startDate = Carbon::yesterday()->format('Y-m-d');
$endDate = Carbon::yesterday()->format('Y-m-d');
} elseif ($request->custom_days === 'last_seven_days') {
$startDate = Carbon::today()->subDays(6)->format('Y-m-d');
} elseif ($request->custom_days === 'last_thirty_days') {
$startDate = Carbon::today()->subDays(29)->format('Y-m-d');
} elseif ($request->custom_days === 'current_month') {
$startDate = Carbon::now()->startOfMonth()->format('Y-m-d');
$endDate = Carbon::now()->endOfMonth()->format('Y-m-d');
} elseif ($request->custom_days === 'last_month') {
$startDate = Carbon::now()->subMonth()->startOfMonth()->format('Y-m-d');
$endDate = Carbon::now()->subMonth()->endOfMonth()->format('Y-m-d');
} elseif ($request->custom_days === 'current_year') {
$startDate = Carbon::now()->startOfYear()->format('Y-m-d');
$endDate = Carbon::now()->endOfYear()->format('Y-m-d');
} elseif ($request->custom_days === 'custom_date' && $request->from_date && $request->to_date) {
$startDate = Carbon::parse($request->from_date)->format('Y-m-d');
$endDate = Carbon::parse($request->to_date)->format('Y-m-d');
}
$cheque->whereDate('date', '>=', $startDate)
->whereDate('date', '<=', $endDate);
$cheques = $cheque->when(request('search'), function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->where('date', 'like', '%' . $request->search . '%')
->orWhere('transaction_type', 'like', '%' . $request->search . '%')
->orWhere('amount', 'like', '%' . $request->search . '%')
->orWhere('meta->cheque_number', 'like', '%' . $request->search . '%')
->orWhereHas('user', function ($q) use ($request) {
$q->where('name', 'like', '%' . $request->search . '%');
});
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::cheques.datas', compact('cheques'))->render()
]);
}
return view('business::cheques.index', compact('cheques', 'banks'));
}
// Deposit cheque
public function store(Request $request)
{
$request->validate([
'transaction_id' => 'required|exists:transactions,id',
'date' => 'nullable|date',
'note' => 'nullable|string',
'payment_type' => [
'required',
function ($attribute, $value, $fail) {
if ($value !== 'cash' && !PaymentType::where('id', $value)->exists()) {
$fail(__('The selected payment type is invalid.'));
}
},
],
]);
$business_id = auth()->user()->business_id;
DB::beginTransaction();
try {
$transaction = Transaction::findOrFail($request->transaction_id);
$amount = $transaction->amount;
$platform = strtolower($transaction->platform);
$transaction->update([
'type' => 'deposit',
]);
$newTransactionData = [
'business_id' => $business_id,
'user_id' => auth()->id(),
'type' => 'credit',
'platform' => 'cheque',
'amount' => $amount,
'date' => $request->date ?? now(),
'note' => $request->note,
'meta' => [
'source_transaction_id' => $request->transaction_id ?? null,
],
];
// Cheque deposited into Cash
if ($request->payment_type === 'cash') {
$newTransactionData['transaction_type'] = 'cheque_to_cash';
$newTransactionData['payment_type_id'] = null;
} else {
// Cheque deposited into Bank
$toBank = PaymentType::findOrFail($request->payment_type);
$toBank->increment('balance', $amount);
$newTransactionData['transaction_type'] = 'cheque_to_bank';
$newTransactionData['payment_type_id'] = $toBank->id;
}
Transaction::create($newTransactionData);
// update related platform
if ($platform == 'sale') {
$sale = Sale::find($transaction->reference_id);
updateBalance($amount, 'increment');
$newPaid = $sale->paidAmount + $amount;
$expectedTotal = $sale->paidAmount + $sale->dueAmount;
$newChange = max($newPaid - $expectedTotal, 0);
$newDue = max($sale->dueAmount - $amount, 0);
$sale->update([
'paidAmount' => $newPaid,
'change_amount' => $sale->change_amount + $newChange,
'dueAmount' => $newDue,
'isPaid' => $newDue > 0 ? 0 : 1,
]);
// update party due
if ($sale->party_id) {
$party = Party::find($sale->party_id);
if ($party) {
$party->decrement('due', min($amount, $party->due));
}
}
}
elseif ($transaction->platform == 'income') {
$income = Income::find($transaction->reference_id);
$income->increment('amount', $transaction->amount);
}
elseif ($transaction->platform == 'due_collect'){
$due = DueCollect::find($transaction->reference_id);
if ($due) {
if ($due->sale_id) {
$invoice = Sale::find($due->sale_id);
if ($invoice) {
$invoice->update([
'dueAmount' => max($invoice->dueAmount - $amount, 0),
]);
}
}
// Update DueCollect
$due->update([
'dueAmountAfterPay' => max($due->dueAmountAfterPay - $amount, 0),
]);
if ($due->party_id) {
$party = Party::find($due->party_id);
if ($party) {
// Reduce customer's due
$party->decrement('due', min($amount, $party->due));
// Opening balance reduce (if due type)
if ($party->opening_balance_type == 'due') {
$party->update([
'opening_balance' => max(0, $party->opening_balance - $amount),
]);
}
}
}
updateBalance($amount, 'increment');
}
}
DB::commit();
return response()->json([
'message' => __('Cheque data saved successfully'),
'redirect' => route('business.cheques.index'),
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json([
'message' => $e->getMessage()
], 406);
}
}
// Reopen cheque
public function reopen(Request $request)
{
$request->validate([
'transaction_id' => 'required|exists:transactions,id',
]);
DB::beginTransaction();
try {
// original cheque payment
$originalTxn = Transaction::findOrFail($request->transaction_id);
// Must be cheque_payment
if ($originalTxn->transaction_type !== 'cheque_payment') {
return response()->json(['message' => __('This transaction cannot be reopened.')], 400);
}
$amount = $originalTxn->amount;
$business_id = auth()->user()->business_id;
$depositTxn = Transaction::where('platform', 'cheque')
->where('meta->source_transaction_id', $originalTxn->id)
->first();
if (!$depositTxn) {
return response()->json(['message' => __('Deposited cheque record not found.')], 400);
}
// Reverse bank balance only if cheque_to_bank
if ($depositTxn->transaction_type === 'cheque_to_bank' && $depositTxn->payment_type_id) {
$bank = PaymentType::find($depositTxn->payment_type_id);
if ($bank) {
$bank->decrement('balance', $amount);
}
}
$platform = strtolower($originalTxn->platform);
// Reverse Sale or Income
if ($platform == 'sale') {
$sale = Sale::find($originalTxn->reference_id);
if ($sale) {
// Reverse paid, change, and due amounts
$newPaid = $sale->paidAmount - $amount;
$newDue = $sale->dueAmount + $amount;
$newChange = max($sale->change_amount - max($sale->paidAmount - ($sale->paidAmount + $sale->dueAmount - $amount), 0), 0);
$sale->update([
'paidAmount' => max($newPaid, 0),
'change_amount' => max($newChange, 0),
'dueAmount' => $newDue,
'isPaid' => $newDue > 0 ? 0 : 1,
]);
// Reverse party due if exists
if ($sale->party_id) {
$party = Party::find($sale->party_id);
if ($party) {
$party->increment('due', $amount);
}
}
}
} elseif ($platform == 'income') {
$income = Income::find($originalTxn->reference_id);
if ($income) {
$income->decrement('amount', $amount);
}
} elseif ($platform == 'due_collect') {
$due = DueCollect::find($originalTxn->reference_id);
if ($due) {
if ($due->sale_id) {
$invoice = Sale::find($due->sale_id);
if ($invoice) {
$invoice->update([
'dueAmount' => $invoice->dueAmount + $amount,
]);
}
}
$due->update([
'dueAmountAfterPay' => $due->dueAmountAfterPay + $amount,
]);
if ($due->party_id) {
$party = Party::find($due->party_id);
if ($party) {
$party->increment('due', $amount);
if ($party->opening_balance_type == 'due') {
$party->update([
'opening_balance' => $party->opening_balance + $amount,
]);
}
}
}
updateBalance($amount, 'decrement');
}
}
$reverseType = $depositTxn->transaction_type === 'cheque_to_cash' ? 'cash_to_cheque' : 'bank_to_cheque';
// Create reverse debit transaction
Transaction::create([
'business_id' => $business_id,
'user_id' => auth()->id(),
'type' => 'debit',
'platform' => 'cheque',
'amount' => $amount,
'date' => now(),
'note' => 'Cheque reopened',
'transaction_type' => $reverseType,
'payment_type_id' => $depositTxn->payment_type_id,
'meta' => [
'reverted_transaction_id' => $depositTxn->id
]
]);
$originalTxn->update([
'type' => 'pending',
]);
DB::commit();
return response()->json([
'message' => __('Cheque reopened successfully'),
'redirect' => route('business.cheques.index'),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => $e->getMessage()
], 406);
}
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportComboProduct;
class AcnooComboProductController extends Controller
{
public function index(Request $request)
{
$user = auth()->user();
$search = $request->input('search');
$combo_products = Product::with(['combo_products', '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 . '%');
});
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
$combo_products->getCollection()->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);
});
$product->combo_items = $product->combo_products->map(function ($combo) {
return [
'name' => $combo->stock?->product?->productName ?? 'N/A',
'quantity' => $combo->quantity ?? 0,
'purchase_price' => currency_format($combo->purchase_price * $combo->quantity ?? 0, currency: business_currency()),
'stock' => $combo->stock?->productStock ?? 0,
];
});
return $product;
});
if ($request->ajax()) {
return response()->json([
'data' => view('business::products.combo-products.datas', compact('combo_products'))->render()
]);
}
return view('business::products.combo-products.index', compact('combo_products'));
}
public function exportExcel()
{
return Excel::download(new ExportComboProduct, 'combo-products.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportComboProduct, 'combo-products.csv');
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Product;
use App\Services\PdfService;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportComboProductReport;
class AcnooComboProductReportController 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.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()
->paginate($request->per_page ?? 20)->appends($request->query());
$combo_products->getCollection()->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);
});
$product->combo_items = $product->combo_products->map(function ($combo) {
return [
'name' => $combo->stock?->product?->productName ?? 'N/A',
'quantity' => $combo->quantity ?? 0,
'purchase_price' => currency_format($combo->purchase_price * $combo->quantity ?? 0, currency: business_currency()),
'stock' => $combo->stock?->productStock ?? 0,
];
});
return $product;
});
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.combo-products.datas', compact('combo_products'))->render()
]);
}
return view('business::reports.combo-products.index', compact('combo_products'));
}
public function exportExcel()
{
return Excel::download(new ExportComboProductReport, 'combo-product-report.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportComboProductReport, 'combo-product-report.csv');
}
public function exportPdf()
{
$combo_products = Product::with(['combo_products', 'unit:id,unitName', 'category:id,categoryName'])
->where('business_id', auth()->user()->business_id)
->where('product_type', 'combo')
->withCount('combo_products as total_combo_products')
->latest()
->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::reports.combo-products.pdf', compact('combo_products'),'combo-product-report.pdf');
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
class AcnooCommissionController extends Controller
{
public function index()
{
return view('business::commissions.index');
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Currency;
use App\Models\UserCurrency;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
class AcnooCurrencyController extends Controller
{
public function index(Request $request)
{
$currencies = Currency::whereStatus(1)->orderBy('is_default', 'desc')
->when(request('search'), function ($q) {
$q->where(function ($q) {
$q->where('name', 'like', '%' . request('search') . '%')
->orWhere('country_name', 'like', '%' . request('search') . '%')
->orWhere('code', 'like', '%' . request('search') . '%')
->orWhere('symbol', 'like', '%' . request('search') . '%');
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
$user_currency = UserCurrency::where('business_id', auth()->user()->business_id)->first();
if ($request->ajax()) {
return response()->json([
'data' => view('business::currencies.datas', compact('currencies', 'user_currency'))->render()
]);
}
return view('business::currencies.index', compact('currencies','user_currency'));
}
public function default($id)
{
$currency = Currency::findOrFail($id);
DB::beginTransaction();
try {
$user_currency = UserCurrency::where('business_id', auth()->user()->business_id)->first();
if ($user_currency) {
$user_currency->update([
'name' => $currency->name,
'currency_id' => $currency->id,
'country_name' => $currency->country_name,
'code' => $currency->code,
'rate' => $currency->rate,
'symbol' => $currency->symbol,
'position' => $currency->position,
]);
} else {
UserCurrency::create([
'currency_id' => $currency->id,
'business_id' => auth()->user()->business_id,
'name' => $currency->name,
'country_name' => $currency->country_name,
'code' => $currency->code,
'rate' => $currency->rate,
'symbol' => $currency->symbol,
'position' => $currency->position,
]);
}
// Clear update
cache()->forget("business_currency_" . auth()->user()->business_id);
DB::commit();
return redirect()->route('business.currencies.index')->with('message', __('Default currency activated successfully'));
} catch (\Exception $e) {
DB::rollBack();
return redirect()->route('business.currencies.index')->with('error', __('Failed to set default currency. Please try again.'));
}
}
}

View File

@@ -0,0 +1,123 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Party;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Services\PartyLedgerService;
use App\Services\PdfService;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportCustomerLedger;
use Modules\Business\App\Exports\ExportSingleCustomerLedger;
class AcnooCustomerLedgerController extends Controller
{
public function index(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()
->paginate($request->per_page ?? 20)->appends($request->query());
$totalAmount = $customers->sum(function ($customer) {
return $customer->sales?->sum('totalAmount') ?? 0;
});
$totalPaid = $customers->sum(function ($customer) {
return $customer->sales?->sum('paidAmount') ?? 0;
});
$totalDue = $customers->sum(function ($customer) {
return $customer->sales?->sum('dueAmount') ?? 0;
});
if ($request->ajax()) {
return response()->json([
'data' => view('business::party-reports.customer-ledger.datas', compact('customers', 'totalAmount', 'totalPaid', 'totalDue'))->render()
]);
}
return view('business::party-reports.customer-ledger.index', compact('customers', 'totalAmount', 'totalPaid', 'totalDue'));
}
public function show(Request $request, $partyId, PartyLedgerService $service)
{
$party = Party::find($partyId);
$ledger = $service->list($request, $partyId);
if ($request->ajax()) {
return response()->json([
'data' => view('business::party-reports.customer-ledger.show-details.datas', compact('ledger'))->render()
]);
}
return view('business::party-reports.customer-ledger.show-details.index', compact('ledger', 'party'));
}
public function exportExcel()
{
return Excel::download(new ExportCustomerLedger, 'customer-ledger.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportCustomerLedger, 'customer-ledger.csv');
}
public function exportPdf()
{
$customers = Party::with('sales')
->where('business_id', auth()->user()->business_id)
->where('type', '!=', 'Supplier')
->latest()
->get();
return PdfService::render('business::party-reports.customer-ledger.pdf', compact('customers'),'customer-ledger-report.pdf');
}
public function exportLedgerExcel(Request $request, $partyId, PartyLedgerService $service)
{
$party = Party::findOrFail($partyId);
$ledger = $service->list($request, $partyId);
return Excel::download(new ExportSingleCustomerLedger($ledger, $party), 'single-customer-ledger.xlsx');
}
public function exportLedgerCsv(Request $request, $partyId, PartyLedgerService $service)
{
$party = Party::findOrFail($partyId);
$ledger = $service->list($request, $partyId);
return Excel::download(new ExportSingleCustomerLedger($ledger, $party), 'single-customer-ledger.csv');
}
public function exportLedgerPdf(Request $request, $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'),
strtolower($party->name).'-ledger.pdf'
);
}
}

View File

@@ -0,0 +1,136 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Transaction;
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\ExportDayBookReport;
class AcnooDayBookReportController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function index(Request $request)
{
$query = Transaction::with([
'paymentType:id,name',
'sale:id,user_id,party_id,totalAmount',
'sale.party:id,name',
'sale.user:id,name',
'saleReturn:id,sale_id',
'purchase:id,user_id,party_id,totalAmount',
'purchase.party:id,name',
'purchase.user:id,name',
'purchaseReturn:id,purchase_id',
'dueCollect:id,user_id,party_id,totalDue',
'dueCollect.party:id,name',
'dueCollect.user:id,name',
])
->where('business_id', auth()->user()->business_id)
->whereIn('type', ['debit', 'credit']);
// 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);
}
// 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());
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()
]);
}
return view('business::day-book.index', compact('day_books', 'total_amount', 'total_money_in', 'total_money_out', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function exportExcel()
{
return Excel::download(new ExportDayBookReport, 'day-book.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportDayBookReport, 'day-book.csv');
}
public function exportPdf()
{
$day_books = Transaction::with([
'paymentType:id,name',
'sale:id,user_id,party_id,totalAmount',
'sale.party:id,name',
'sale.user:id,name',
'saleReturn:id,sale_id',
'purchase:id,user_id,party_id,totalAmount',
'purchase.party:id,name',
'purchase.user:id,name',
'purchaseReturn:id,purchase_id',
'dueCollect:id,user_id,party_id,totalDue',
'dueCollect.party:id,name',
'dueCollect.user:id,name',
])
->where('business_id', auth()->user()->business_id)
->whereIn('type', ['debit', 'credit'])
->latest()
->get();
return PdfService::render('business::day-book.pdf', compact('day_books'), 'day-book-report.pdf');
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\SaleDetails;
use App\Services\PdfService;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportDiscountProduct;
class AcnooDiscountProductReportController extends Controller
{
public function index(Request $request)
{
$user = auth()->user();
$branchId = null;
if (moduleCheck('MultiBranchAddon')) {
$branchId = $user->branch_id ?? $user->active_branch_id;
}
$discount_products = SaleDetails::with('product:id,productName,productCode')
->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)
->paginate($request->per_page ?? 20)
->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.discount-products.datas', compact('discount_products'))->render()
]);
}
return view('business::reports.discount-products.index', compact('discount_products'));
}
public function exportExcel()
{
return Excel::download(new ExportDiscountProduct, 'discount-products.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportDiscountProduct, 'discount-products.csv');
}
public function exportPdf()
{
$discount_products = SaleDetails::with('product:id,productName,productCode')
->whereHas('product', function ($q) {
$q->where('business_id', auth()->user()->business_id);
})
->where('discount', '>', 0)
->get();
return PdfService::render('business::reports.discount-products.pdf', compact('discount_products'),'discount-product-report.pdf');
}
}

View File

@@ -0,0 +1,663 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Sale;
use App\Models\Party;
use App\Models\Purchase;
use App\Models\DueCollect;
use App\Models\PaymentType;
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;
class AcnooDueController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:dues.read')->only(['index', 'collectDue']);
}
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']);
if ($request->filled('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()->paginate($request->per_page ?? 20)->appends($request->query());
if ($activeBranch) {
$filtered = $parties->getCollection()
->map(function ($party) {
$party->due = $party->type === 'Supplier'
? $party->purchases_dues->sum('dueAmount')
: $party->sales_dues->sum('dueAmount');
return $party;
})
->filter(fn($p) => $p->due > 0)
->values();
$parties->setCollection($filtered);
} else {
$parties->setCollection(
$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()
]);
}
return view('business::dues.index', compact(
'parties',
'total_supplier_due',
'total_customer_due'
));
}
public function collectDue($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();
$due_amount = 0;
if ($party->type == 'Supplier') {
foreach ($party->purchases_dues as $sales_due) {
$due_amount += $sales_due->dueAmount;
}
} else {
foreach ($party->sales_dues as $sales_due) {
$due_amount += $sales_due->dueAmount;
}
}
if (auth()->user()->active_branch) {
$party_opening_due = 0;
} else {
$party_opening_due = $party->due - $due_amount;
}
return view('business::dues.collect-due', compact('party', 'party_opening_due', 'payment_types'));
}
public function collectDueStore(Request $request)
{
$party = Party::find($request->party_id);
$request->validate([
'paymentDate' => 'required|string',
'payDueAmount' => 'required|numeric',
'party_id' => 'required|exists:parties,id',
'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);
}
DB::beginTransaction();
try {
$branch_id = null;
$invoice = null;
$payments = $request->payments ?? [];
if (isset($payments['main'])) {
$mainPayment = $payments['main'];
$mainPayment['amount'] = $request->payDueAmount ?? 0;
$payments = [$mainPayment];
}
$payDueAmount = collect($payments)
->reject(fn($p) => strtolower($p['type']) === 'cheque')
->sum('amount');
// Get related invoice if exists
if ($request->invoiceNumber) {
if ($party->type == 'Supplier') {
$invoice = Purchase::where('invoiceNumber', $request->invoiceNumber)->where('party_id', $request->party_id)->first();
} else {
$invoice = Sale::where('invoiceNumber', $request->invoiceNumber)->where('party_id', $request->party_id)->first();
}
if (!isset($invoice)) {
return response()->json([
'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.'
], 400);
}
}
// 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),
]);
}
}
$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()
]);
// Update invoice due
if (isset($invoice)) {
$invoice->update([
'dueAmount' => $invoice->dueAmount - $payDueAmount,
'paidAmount' => $invoice->paidAmount + $payDueAmount
]);
}
// Update party due
$party->update([
'due' => $party->due - $payDueAmount
]);
// update balance & adjust platform
if ($party->type == 'Supplier') {
updateBalance($payDueAmount, 'decrement', $branch_id);
$platform = 'due_pay';
} else {
updateBalance($payDueAmount, 'increment', $branch_id);
$platform = 'due_collect';
}
// MultiPaymentProcessed event
event(new MultiPaymentProcessed(
$payments,
$data->id,
$platform,
$payDueAmount,
$party->id,
));
// Notify
event(new DuePaymentReceived($data));
sendNotifyToUser($data->id, route('business.dues.index', ['id' => $data->id]), __('Due Collection has been created.'), $business_id);
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),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json(['message' => $e->getMessage()], 404);
}
}
public function getInvoice($id)
{
$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'));
}
public function generatePDF($id)
{
$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',
'transactions.paymentType:id,name'
)
->where('business_id', auth()->user()->business_id)
->where('party_id', $id)
->latest()
->firstOrFail();
$party = Party::with('dueCollect.business')->findOrFail($id);
$bank_detail = PaymentType::where('business_id', auth()->user()->business_id)->where('show_in_invoice', 1)->latest()->first();
return PdfService::render(
'business::dues.pdf',
[
'due_collect' => $due_collect,
'party' => $party,
'transactionTypes' => transaction_types($due_collect->transactions),
'bank_detail' => $bank_detail,
],
'dues.pdf'
);
}
public function sendMail(Request $request, $id)
{
$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();
$pdf = Pdf::loadView('business::dues.pdf', compact('due_collect', 'party', 'transactionTypes', 'bank_detail'));
// Send email with PDF attachment
Mail::raw('Please find attached your Due Collext invoice.', function ($message) use ($pdf) {
$message->to(auth()->user()->email)
->subject('Sales Invoice')
->attachData($pdf->output(), 'collect-due.pdf', [
'mime' => 'application/pdf',
]);
});
return response()->json([
'message' => __('Email Sent Successfully.'),
'redirect' => route('business.dues.index'),
]);
}
public function partyDue(Request $request)
{
$user = auth()->user();
$business_id = $user->business_id;
$activeBranch = $user->active_branch;
$party_type = $request->type;
$query = Party::where('business_id', $business_id);
// Filter by party type
if (in_array($party_type, ['Retailer', 'Dealer', 'Wholesaler', 'Supplier'])) {
$query->where('type', $party_type);
}
// 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 . '%');
});
});
// Paginate parties
$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');
return $party;
})
->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)
->values()
);
}
if ($request->ajax()) {
return response()->json([
'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)
->with('dueCollect')
->whereNull('party_id')
->where('dueAmount', '>', 0)
->when($request->search, function ($query) use ($request) {
$query->where(function ($q) use ($request) {
$q->where('invoiceNumber', 'like', '%' . $request->search . '%')
->orwhere('dueAmount', 'like', '%' . $request->search . '%');
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::walk-dues.datas', compact('walk_in_customers'))->render()
]);
}
return view('business::walk-dues.index', compact('walk_in_customers'));
}
public function walk_dues_filter(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) use ($request) {
$query->where(function ($q) use ($request) {
$q->where('invoiceNumber', 'like', '%' . $request->search . '%')
->orwhere('dueAmount', 'like', '%' . $request->search . '%');
});
})
->latest()
->paginate($request->per_page ?? 20);
if ($request->ajax()) {
return response()->json([
'data' => view('business::walk-dues.datas', compact('walk_in_customers'))->render()
]);
}
return redirect(url()->previous());
}
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();
$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(', ');
$walk_in_customer = Sale::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::walk-dues.invoice', compact('due_collect', 'walk_in_customer', 'transactionTypes', 'bank_detail'));
}
public function collectWalkDue(string $id)
{
$business_id = auth()->user()->business_id;
$payment_types = PaymentType::where('business_id', $business_id)->whereStatus(1)->latest()->get();
$walk_due = Sale::with('dueCollect')->where('business_id', $business_id)->whereNull('party_id')->where('dueAmount', '>', 0)->findOrFail($id);
$is_walk_in = true;
return view('business::walk-dues.collect-due', compact('walk_due', 'payment_types', 'is_walk_in'));
}
public function collectWalkDueStore(Request $request)
{
$business_id = auth()->user()->business_id;
$request->validate([
'paymentDate' => 'required|string',
'payDueAmount' => 'required|numeric',
'invoiceNumber' => 'required|string|exists:sales,invoiceNumber', // Only for walk-in customers (Sales invoices)
]);
DB::beginTransaction();
try {
$branch_id = null;
$invoice = Sale::where('invoiceNumber', $request->invoiceNumber)->whereNull('party_id')->first();
if (!$invoice) {
return response()->json([
'message' => 'Invoice Not Found.'
], 404);
}
if ($invoice->dueAmount < $request->payDueAmount) {
return response()->json([
'message' => 'Invoice due is ' . $invoice->dueAmount . '. You cannot pay more than the invoice due amount.'
], 400);
}
if (!auth()->user()->active_branch) {
if (isset($invoice) && isset($invoice->branch_id)) {
$branch_id = $invoice->branch_id;
}
}
$payments = $request->payments ?? [];
// Normalize main payment
if (isset($payments['main'])) {
$main = $payments['main'];
$main['amount'] = $request->payDueAmount;
$payments = [$main];
}
$payDueAmount = collect($payments)
->reject(fn($p) => strtolower($p['type']) === 'cheque')
->sum('amount');
$data = DueCollect::create([
'user_id' => auth()->id(),
'business_id' => $business_id,
'sale_id' => $invoice->id,
'invoiceNumber' => $request->invoiceNumber,
'totalDue' => $invoice->dueAmount,
'dueAmountAfterPay' => $invoice->dueAmount - $request->payDueAmount,
'payDueAmount' => $request->payDueAmount,
'paymentDate' => $request->paymentDate,
]);
$invoice->update([
'dueAmount' => $invoice->dueAmount - $request->payDueAmount
]);
updateBalance($request->payDueAmount, 'increment', $branch_id);
// MultiPaymentProcessed event
event(new MultiPaymentProcessed(
$request->payments ?? [],
$data->id,
'due_collect',
$request->payDueAmount,
));
sendNotifyToUser($data->id, route('business.dues.index', ['id' => $data->id]), __('Due Collection has been created.'), $business_id);
DB::commit();
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),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json(['message' => 'Something went wrong!'], 404);
}
}
public function viewDuePayment($id)
{
$businessId = auth()->user()->business_id;
$partyExists = Party::where('business_id', $businessId)
->where('id', $id)
->exists();
if ($partyExists) {
$transactions = Transaction::with('paymentType:id,name')
->where('business_id', $businessId)
->whereIn('platform', ['due_collect', 'due_pay'])
->whereHas('dueCollect', function ($q) use ($id) {
$q->where('party_id', $id);
})
->latest()
->get()
->map(function ($transaction) {
return [
'id' => $transaction->id,
'date' => formatted_date($transaction->date),
'receipt_no' => $transaction->invoice_no ?? '-',
'amount' => currency_format($transaction->amount, currency: business_currency()),
'payment_type' => $transaction->transaction_type === 'bank_payment' ? ($transaction->paymentType->name ?? '') : ucfirst(explode('_', $transaction->transaction_type)[0] ?? ''),
];
});
return response()->json([
'data' => $transactions,
]);
}
$sale = Sale::where('business_id', $businessId)
->whereNull('party_id')
->where('id', $id)
->first();
if ($sale) {
$transactions = Transaction::with('paymentType:id,name')
->where('business_id', $businessId)
->whereIn('platform', ['due_collect', 'due_pay'])
->whereHas('dueCollect', function ($q) use ($sale) {
$q->where('sale_id', $sale->id);
})
->latest()
->get()
->map(function ($transaction) {
return [
'id' => $transaction->id,
'date' => formatted_date($transaction->date),
'receipt_no' => $transaction->invoice_no ?? '-',
'amount' => currency_format($transaction->amount, currency: business_currency()),
'payment_type' => $transaction->transaction_type === 'bank_payment' ? ($transaction->paymentType->name ?? '') : ucfirst(explode('_', $transaction->transaction_type)[0] ?? ''),
];
});
return response()->json([
'data' => $transactions,
]);
}
}
}

View File

@@ -0,0 +1,153 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Party;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Services\PdfService;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportDue;
class AcnooDueReportController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:due-reports.read')->only(['index']);
}
public function index(Request $request)
{
$user = auth()->user();
$activeBranch = $user->active_branch;
$business_id = $user->business_id;
$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(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);
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()
);
}
$total_due = $parties->sum(fn($customer) => $customer->due);
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.due.datas', compact('parties', 'total_due'))->render()
]);
}
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'));
}
public function exportExcel()
{
return Excel::download(new ExportDue, 'customer-due.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportDue, 'customer-due.csv');
}
public function exportPdf()
{
$user = auth()->user();
$businessId = $user->business_id;
$activeBranch = $user->active_branch;
$query = Party::where('business_id', $businessId)
->where('type', '!=', 'Supplier')
->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);
}
return PdfService::render('business::reports.due.pdf', compact('parties'),'customer-due-report.pdf');
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\ExpenseCategory;
use App\Http\Controllers\Controller;
class AcnooExpenseCategoryController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:expense-categories.read')->only(['index']);
$this->middleware('check.permission:expense-categories.create')->only(['store']);
$this->middleware('check.permission:expense-categories.update')->only(['update', 'status']);
$this->middleware('check.permission:expense-categories.delete')->only(['destroy', 'deleteAll']);
}
public function index(Request $request)
{
$expense_categories = ExpenseCategory::where('business_id', auth()->user()->business_id)
->when(request('search'), function($q) use($request) {
$q->where(function($q) use($request) {
$q->where('categoryName', 'like', '%'.$request->search.'%')
->orWhere('categoryDescription', 'like', '%'.$request->search.'%');
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if($request->ajax()){
return response()->json([
'data' => view('business::expense-categories.datas',compact('expense_categories'))->render()
]);
}
return view('business::expense-categories.index', compact('expense_categories'));
}
public function store(Request $request)
{
$request->validate([
'categoryName' => 'required|unique:expense_categories,categoryName,NULL,id,business_id,' . auth()->user()->business_id,
]);
ExpenseCategory::create($request->except('business_id') + [
'business_id' => auth()->user()->business_id,
]);
return response()->json([
'message' => __('Expense Category saved successfully.'),
'redirect' => route('business.expense-categories.index')
]);
}
public function update(Request $request, $id)
{
$category = ExpenseCategory::findOrFail($id);
$request->validate([
'categoryName' => [
'required',
'unique:expense_categories,categoryName,' . $category->id . ',id,business_id,' . auth()->user()->business_id,
],
]);
$category->update($request->except('business_id') + [
'business_id' => auth()->user()->business_id,
]);
return response()->json([
'message' => __('Expense Category updated successfully.'),
'redirect' => route('business.expense-categories.index')
]);
}
public function destroy($id)
{
$expense_category = ExpenseCategory::findOrFail($id);
$expense_category->delete();
return response()->json([
'message' => __('Expense Category deleted successfully'),
'redirect' => route('business.expense-categories.index')
]);
}
public function status(Request $request, $id)
{
$expense_category = ExpenseCategory::findOrFail($id);
$expense_category->update(['status' => $request->status]);
return response()->json(['message' => __('Expense Category')]);
}
public function deleteAll(Request $request)
{
ExpenseCategory::whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Selected item deleted successfully.'),
'redirect' => route('business.expense-categories.index'),
]);
}
}

View File

@@ -0,0 +1,238 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Branch;
use App\Models\Expense;
use App\Models\PaymentType;
use App\Models\Transaction;
use Illuminate\Http\Request;
use App\Traits\DateRangeTrait;
use App\Models\ExpenseCategory;
use App\Traits\DateFilterTrait;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use App\Events\MultiPaymentProcessed;
class AcnooExpenseController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function __construct()
{
$this->middleware('check.permission:expenses.read')->only(['index']);
$this->middleware('check.permission:expenses.create')->only(['store']);
$this->middleware('check.permission:expenses.update')->only(['update']);
$this->middleware('check.permission:expenses.delete')->only(['destroy', 'deleteAll']);
}
public function index(Request $request)
{
$expense_categories = ExpenseCategory::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();
$branches = Branch::withTrashed()->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) 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 . '%');
});
});
}
$perPage = $request->input('per_page', 20);
$expenses = $expenseQuery->latest()->paginate($perPage)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::expenses.datas', compact('expenses', 'filter_from_date', 'filter_to_date', 'duration'))->render(),
]);
}
return view('business::expenses.index', compact('expenses', 'expense_categories', 'payment_types', 'branches', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function store(Request $request)
{
$request->validate([
'amount' => 'required|numeric',
'expenseFor' => 'nullable|string',
'referenceNo' => 'nullable|string',
'expenseDate' => 'nullable|string',
'note' => 'nullable|string',
'expense_category_id' => 'required|exists:expense_categories,id',
]);
DB::beginTransaction();
try {
$business_id = auth()->user()->business_id;
updateBalance($request->amount, 'decrement');
$expense = Expense::create($request->except('status') + [
'user_id' => auth()->id(),
'business_id' => auth()->user()->business_id,
]);
// MultiPaymentProcessed Event
event(new MultiPaymentProcessed(
$request->payments ?? [],
$expense->id,
'expense',
$request->amount ?? 0,
));
sendNotifyToUser($expense->id, route('business.expenses.index', ['id' => $expense->id]), __('Expense has been created.'), $business_id);
DB::commit();
return response()->json([
'message' => __('Expense saved successfully.'),
'redirect' => route('business.expenses.index')
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => __('Somethings went wrong!')], 404);
}
}
public function update(Request $request, $id)
{
$request->validate([
'amount' => 'required|numeric',
'expenseFor' => 'nullable|string',
'referenceNo' => 'nullable|string',
'expenseDate' => 'nullable|string',
'note' => 'nullable|string',
'expense_category_id' => 'required|exists:expense_categories,id',
]);
DB::beginTransaction();
try {
$business_id = auth()->user()->business_id;
$expense = Expense::findOrFail($id);
updateBalance($request->amount - $expense->amount, 'decrement');
$expense->update($request->except('status') + [
'user_id' => auth()->id(),
'business_id' => auth()->user()->business_id,
]);
// Multiple Payment Process
$oldTransactions = Transaction::where('business_id', $business_id)
->where('reference_id', $expense->id)
->where('platform', 'expense')
->get();
// Revert old transactions
foreach ($oldTransactions as $old) {
if ($old->transaction_type === 'bank_payment' && $old->payment_type_id) {
$paymentType = PaymentType::find($old->payment_type_id);
if ($paymentType) {
$paymentType->increment('balance', $old->amount);
}
}
}
// Delete old transactions
Transaction::where('business_id', $business_id)
->where('reference_id', $expense->id)
->where('platform', 'expense')
->delete();
// Process new payments
event(new MultiPaymentProcessed(
$request->payments ?? [],
$expense->id,
'expense',
$request->amount ?? 0,
));
DB::commit();
sendNotifyToUser($expense->id, route('business.expenses.index', ['id' => $expense->id]), __('Expense has been updated.'), $business_id);
return response()->json([
'message' => __('Expense updated successfully.'),
'redirect' => route('business.expenses.index')
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => __('Somethings went wrong!')], 404);
}
}
public function destroy($id)
{
DB::beginTransaction();
try {
$expense = Expense::findOrFail($id);
updateBalance($expense->amount, 'increment');
sendNotifyToUser($expense->id, route('business.expenses.index', ['id' => $expense->id]), __('Expense has been deleted.'), $expense->business_id);
$expense->delete();
DB::commit();
return response()->json([
'message' => __('Expense deleted successfully'),
'redirect' => route('business.expenses.index'),
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => __('Somethings went wrong!')], 404);
}
}
public function deleteAll(Request $request)
{
DB::beginTransaction();
try {
$expenses = Expense::whereIn('id', $request->ids)->get();
$totalAmount = $expenses->sum('amount');
updateBalance($totalAmount, 'increment');
Expense::whereIn('id', $request->ids)->delete();
foreach ($expenses as $expense) {
sendNotifyToUser($expense->id, route('business.expenses.index', ['id' => $expense->id]), __('Expense has been deleted.'), $expense->business_id);
}
DB::commit();
return response()->json([
'message' => __('Selected items deleted successfully.'),
'redirect' => route('business.expenses.index'),
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => __('Something went wrong!')], 404);
}
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Branch;
use App\Models\Expense;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use App\Http\Controllers\Controller;
use App\Services\PdfService;
use App\Traits\DateFilterTrait;
use App\Traits\DateRangeTrait;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportExpense;
class AcnooExpenseReportController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function __construct()
{
$this->middleware('check.permission:expense-reports.read')->only(['index']);
}
public function index(Request $request)
{
$businessId = auth()->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);
$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 . '%');
});
});
}
$perPage = $request->input('per_page', 20);
$expense_reports = $expenseQuery->latest()->paginate($perPage)->appends($request->query());
$total_expense = $expenseQuery->sum('amount');
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.expense.datas', compact('expense_reports', 'filter_from_date', 'filter_to_date', 'duration'))->render(),
'total_expense' => currency_format($total_expense, currency: business_currency())
]);
}
$branches = Branch::withTrashed()->where('business_id', auth()->user()->business_id)->latest()->get();
return view('business::reports.expense.expense-reports', compact('expense_reports', 'total_expense', 'branches', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function exportExcel()
{
return Excel::download(new ExportExpense, 'expense.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportExpense, 'expense.csv');
}
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();
return PdfService::render('business::reports.expense.pdf', compact('expense_reports'),'expenses-report.pdf');
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportExpiredProduct;
class AcnooExpireProductController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:expired-products.read')->only(['index']);
}
public function index(Request $request)
{
$search = $request->input('search');
$expired_products = Product::with('stocks', 'unit:id,unitName', 'brand:id,brandName', 'category:id,categoryName')
->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($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 . '%')
->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 . '%');
});
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::expired-products.datas', compact('expired_products'))->render()
]);
}
return view('business::expired-products.index', compact('expired_products'));
}
public function exportExcel()
{
return Excel::download(new ExportExpiredProduct, 'expired-products.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportExpiredProduct, 'expired-products.csv');
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Services\PdfService;
use App\Traits\DateFilterTrait;
use App\Traits\DateRangeTrait;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportExpiredProductReport;
class AcnooExpireProductReportController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function __construct()
{
$this->middleware('check.permission:expired-product-reports.read')->only(['index']);
}
public function index(Request $request)
{
$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';
[$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 . '%');
});
});
}
$perPage = $request->input('per_page', 20);
$expired_products = $expiredProductsQuery->latest()->paginate($perPage)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.expired-products.datas', compact('expired_products', 'filter_from_date', 'filter_to_date', 'duration'))->render()
]);
}
return view('business::reports.expired-products.index', compact('expired_products', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function exportExcel()
{
return Excel::download(new ExportExpiredProductReport, 'expired-product-reports.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportExpiredProductReport, 'expired-product-reports.csv');
}
public function exportPdf()
{
$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();
return PdfService::render('business::reports.expired-products.pdf', compact('expired_products'),'expired-product-report.pdf');
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\IncomeCategory;
use App\Http\Controllers\Controller;
class AcnooIncomeCategoryController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:income-categories.read')->only(['index']);
$this->middleware('check.permission:income-categories.create')->only(['store']);
$this->middleware('check.permission:income-categories.update')->only(['update', 'status']);
$this->middleware('check.permission:income-categories.delete')->only(['destroy', 'deleteAll']);
}
public function index(Request $request)
{
$income_categories = IncomeCategory::where('business_id', auth()->user()->business_id)
->when(request('search'), function($q) use($request) {
$q->where(function($q) use($request) {
$q->where('categoryName', 'like', '%'.$request->search.'%')
->orWhere('categoryDescription', 'like', '%'.$request->search.'%');
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if($request->ajax()){
return response()->json([
'data' => view('business::income-categories.datas',compact('income_categories'))->render()
]);
}
return view('business::income-categories.index', compact('income_categories'));
}
public function store(Request $request)
{
$request->validate([
'categoryName' => 'required|unique:income_categories,categoryName,NULL,id,business_id,' . auth()->user()->business_id,
]);
IncomeCategory::create($request->except('business_id') + [
'business_id' => auth()->user()->business_id,
]);
return response()->json([
'message' => __('Income Category saved successfully.'),
'redirect' => route('business.income-categories.index')
]);
}
public function update(Request $request, $id)
{
$category = IncomeCategory::findOrFail($id);
$request->validate([
'categoryName' => [
'required',
'unique:income_categories,categoryName,' . $category->id . ',id,business_id,' . auth()->user()->business_id,
],
]);
$category->update($request->except('business_id') + [
'business_id' => auth()->user()->business_id,
]);
return response()->json([
'message' => __('Income Category updated successfully.'),
'redirect' => route('business.income-categories.index')
]);
}
public function destroy($id)
{
$income_category = IncomeCategory::findOrFail($id);
$income_category->delete();
return response()->json([
'message' => __('Income Category deleted successfully'),
'redirect' => route('business.income-categories.index')
]);
}
public function status(Request $request, $id)
{
$income_category = IncomeCategory::findOrFail($id);
$income_category->update(['status' => $request->status]);
return response()->json(['message' => __('Income Category')]);
}
public function deleteAll(Request $request)
{
IncomeCategory::whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Selected item deleted successfully.'),
'redirect' => route('business.income-categories.index'),
]);
}
}

View File

@@ -0,0 +1,289 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Events\MultiPaymentProcessed;
use App\Models\Branch;
use App\Models\Income;
use App\Models\PaymentType;
use App\Models\Transaction;
use Illuminate\Http\Request;
use App\Models\IncomeCategory;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use App\Traits\DateFilterTrait;
use App\Traits\DateRangeTrait;
class AcnooIncomeController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function __construct()
{
$this->middleware('check.permission:incomes.read')->only(['index']);
$this->middleware('check.permission:incomes.create')->only(['store']);
$this->middleware('check.permission:incomes.update')->only(['update']);
$this->middleware('check.permission:incomes.delete')->only(['destroy', 'deleteAll']);
}
public function index(Request $request)
{
$income_categories = IncomeCategory::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();
$branches = Branch::withTrashed()->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';
[$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 . '%'));
});
}
$perPage = $request->input('per_page', 20);
$incomes = $incomeQuery->latest()->paginate($perPage)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::incomes.datas', compact('incomes', 'filter_from_date', 'filter_to_date', 'duration'))->render(),
]);
}
return view('business::incomes.index', compact('incomes', 'income_categories', 'payment_types', 'branches', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function store(Request $request)
{
$request->validate([
'income_category_id' => 'required|exists:income_categories,id',
'incomeFor' => 'nullable|string',
'referenceNo' => 'nullable|string',
'incomeDate' => 'nullable|string',
'note' => 'nullable|string',
'payments' => 'required|array|min:1',
'payments.*.type' => 'required|string',
'payments.*.amount' => 'nullable|numeric|min:0',
'payments.*.cheque_number' => 'nullable|string',
], [
'payments.required' => 'At least one payment method is required.',
'payments.*.type.required' => 'Each payment must have a type.',
'payments.*.amount.numeric' => 'Each payment amount must be numeric.',
]);
DB::beginTransaction();
try {
$business_id = auth()->user()->business_id;
$payments = $request->payments;
if (isset($payments['main'])) {
$mainPayment = $payments['main'];
$mainPayment['amount'] = $request->amount ?? 0;
$payments = [$mainPayment];
}
$total_amount = collect($payments)
->reject(fn($p) => strtolower($p['type'] ?? '') === 'cheque')
->sum(fn($p) => $p['amount'] ?? 0);
updateBalance($total_amount, 'increment');
$income = Income::create($request->except('status', 'amount', 'paymentType') + [
'user_id' => auth()->id(),
'business_id' => auth()->user()->business_id,
'amount' => $total_amount,
]);
// MultiPaymentProcessed Event
event(new MultiPaymentProcessed(
$payments ?? [],
$income->id,
'income',
$total_amount ?? 0,
));
DB::commit();
sendNotifyToUser($income->id, route('business.incomes.index', ['id' => $income->id]), __('Income has been created.'), $business_id);
return response()->json([
'message' => __('Income saved successfully.'),
'redirect' => route('business.incomes.index')
]);
} catch (\Exception $e) {
return $e->getMessage();
DB::rollback();
return response()->json(['message' => __('Somethings went wrong!')], 404);
}
}
public function update(Request $request, $id)
{
$request->validate([
'income_category_id' => 'required|exists:income_categories,id',
'incomeFor' => 'nullable|string',
'referenceNo' => 'nullable|string',
'incomeDate' => 'nullable|string',
'note' => 'nullable|string',
'payments' => 'required|array|min:1',
'payments.*.type' => 'required|string',
'payments.*.amount' => 'nullable|numeric|min:0',
'payments.*.cheque_number' => 'nullable|string',
], [
'payments.required' => 'At least one payment method is required.',
'payments.*.type.required' => 'Each payment must have a type.',
'payments.*.amount.numeric' => 'Each payment amount must be numeric.',
]);
DB::beginTransaction();
try {
$business_id = auth()->user()->business_id;
$income = Income::findOrFail($id);
// Normalize payments
$payments = $request->payments ?? [];
if (isset($payments['main'])) {
$mainPayment = $payments['main'];
$mainPayment['amount'] = $request->amount ?? 0;
$payments = [$mainPayment];
}
// Check if any old transaction type = deposit
$hasDeposit = Transaction::where('business_id', $business_id)
->where('reference_id', $income->id)
->where('platform', 'income')
->where('type', 'deposit')
->exists();
// Calculate total amount
$total_amount = collect($payments)
->reject(function ($p) use ($hasDeposit) {
// exclude cheque only if not deposit
return !$hasDeposit && strtolower($p['type'] ?? '') === 'cheque';
})
->sum(fn($p) => $p['amount'] ?? 0);
// Adjust balance difference
updateBalance($total_amount - $income->amount, 'increment');
// Update income
$income->update($request->except('status', 'amount', 'payments') + [
'user_id' => auth()->id(),
'business_id' => $business_id,
'amount' => $total_amount,
]);
// Revert old transactions
$oldTransactions = Transaction::where('business_id', $business_id)
->where('reference_id', $income->id)
->where('platform', 'income')
->get();
foreach ($oldTransactions as $old) {
if ($old->transaction_type === 'bank_payment' && $old->payment_type_id) {
$paymentType = PaymentType::find($old->payment_type_id);
if ($paymentType) {
$paymentType->decrement('balance', $old->amount);
}
}
}
// Delete old transactions
Transaction::where('business_id', $business_id)
->where('reference_id', $income->id)
->where('platform', 'income')
->delete();
// Process new payments
event(new MultiPaymentProcessed(
$payments ?? [],
$income->id,
'income',
$total_amount ?? 0,
));
sendNotifyToUser($income->id, route('business.incomes.index', ['id' => $income->id]), __('Income has been updated.'), $business_id);
DB::commit();
return response()->json([
'message' => __('Income updated successfully.'),
'redirect' => route('business.incomes.index')
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => __('Somethings went wrong!')], 404);
}
}
public function destroy($id)
{
DB::beginTransaction();
try {
$income = Income::findOrFail($id);
updateBalance($income->amount, 'decrement');
sendNotifyToUser($income->id, route('business.incomes.index', ['id' => $income->id]), __('Income has been deleted.'), $income->business_id);
$income->delete();
DB::commit();
return response()->json([
'message' => __('Income deleted successfully'),
'redirect' => route('business.incomes.index'),
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => __('Something went wrong!')], 404);
}
}
public function deleteAll(Request $request)
{
DB::beginTransaction();
try {
$incomes = Income::whereIn('id', $request->ids)->get();
$totalAmount = $incomes->sum('amount');
updateBalance($totalAmount, 'decrement');
foreach ($incomes as $income) {
sendNotifyToUser($income->id, route('business.incomes.index', ['id' => $income->id]), __('Income has been deleted.'), $income->business_id);
}
Income::whereIn('id', $request->ids)->delete();
DB::commit();
return response()->json([
'message' => __('Selected Items deleted successfully.'),
'redirect' => route('business.incomes.index'),
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => __('Something went wrong!')], 404);
}
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Branch;
use App\Models\Income;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Services\PdfService;
use App\Traits\DateFilterTrait;
use App\Traits\DateRangeTrait;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportIncome;
class AcnooIncomeReportController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function __construct()
{
$this->middleware('check.permission:income-reports.read')->only(['index']);
}
public function index(Request $request)
{
$businessId = auth()->user()->business_id;
$branches = Branch::withTrashed()
->where('business_id', $businessId)
->latest()
->get();
$incomeQuery = Income::with('category:id,categoryName', 'payment_type:id,name', 'branch:id,name', 'transactions')->where('business_id', $businessId);
// 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 . '%'));
});
}
$perPage = $request->input('per_page', 20);
$income_reports = $incomeQuery->latest()->paginate($perPage)->appends($request->query());
$total_income = $incomeQuery->sum('amount');
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.income.datas', compact('income_reports', 'filter_from_date', 'filter_to_date', 'duration'))->render(),
'total_income' => currency_format($total_income, currency: business_currency())
]);
}
return view('business::reports.income.income-reports', compact('income_reports', 'total_income', 'branches', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function exportExcel()
{
return Excel::download(new ExportIncome, 'income.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportIncome, 'income.csv');
}
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();
return PdfService::render('business::reports.income.pdf', compact('income_reports'),'incomes-report.pdf');
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class AcnooLanguageSettingController extends Controller
{
public function index()
{
return view('business::language-settings.index');
}
}

View File

@@ -0,0 +1,202 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use Carbon\Carbon;
use App\Models\Sale;
use App\Models\Stock;
use App\Models\Product;
use App\Models\Purchase;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
class AcnooLossProfitDetailReportController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:loss-profit-reports.read')->only(['index']);
}
public function lossProfitDetails()
{
$businessId = auth()->user()->business_id;
$today = Carbon::today()->format('Y-m-d');
$salesQuery = Sale::where('business_id', $businessId)->whereDate('created_at', $today);
$purchaseQuery = Purchase::where('business_id', $businessId)->whereDate('created_at', $today);
$productQuery = Product::where('business_id', $businessId);
// Opening stock (before today) from stocks table
$opening_stock_by_purchase = Stock::whereHas('product', function ($query) use ($businessId) {
$query->where('business_id', $businessId);
})
->whereDate('created_at', '<', $today)
->sum(DB::raw('productPurchasePrice * productStock'));
// Closing stock (up to today) from stocks table
$closing_stock_by_purchase = Stock::whereHas('product', function ($query) use ($businessId) {
$query->where('business_id', $businessId);
})
->whereDate('created_at', '<=', $today)
->sum(DB::raw('productPurchasePrice * productStock'));
$total_purchase_price = (clone $purchaseQuery)->sum('totalAmount');
$total_purchase_shipping_charge = (clone $purchaseQuery)->sum('shipping_charge');
$total_purchase_discount = (clone $purchaseQuery)->sum('discountAmount');
$all_purchase_return = (clone $purchaseQuery)->with([
'purchaseReturns' => function ($query) {
$query->withSum('details as total_return_amount', 'return_amount');
}
])
->get()
->flatMap
->purchaseReturns
->sum('total_return_amount');
$opening_stock_by_sale = Stock::whereHas('product', function ($query) use ($businessId) {
$query->where('business_id', $businessId);
})
->whereDate('created_at', '<', $today)
->sum(DB::raw('productSalePrice * productStock'));
$closing_stock_by_sale = Stock::whereHas('product', function ($query) use ($businessId) {
$query->where('business_id', $businessId);
})
->whereDate('created_at', '<=', $today)
->sum(DB::raw('productSalePrice * productStock'));
$total_sale_price = (clone $salesQuery)->sum('totalAmount');
$total_sale_shipping_charge = (clone $salesQuery)->sum('shipping_charge');
$total_sale_discount = (clone $salesQuery)->sum('discountAmount');
$total_sale_rounding_off = (clone $salesQuery)->sum('rounding_amount');
$all_sale_return = (clone $salesQuery)->with([
'saleReturns' => function ($query) {
$query->withSum('details as total_return_amount', 'return_amount');
}
])
->get()
->flatMap
->saleReturns
->sum('total_return_amount');
return view('business::reports.loss-profits-details.index', compact('opening_stock_by_purchase', 'closing_stock_by_purchase', 'total_purchase_price', 'total_purchase_shipping_charge', 'total_purchase_discount', 'all_purchase_return', 'all_sale_return', 'opening_stock_by_sale', 'closing_stock_by_sale', 'total_sale_price', 'total_sale_shipping_charge', 'total_sale_discount', 'total_sale_rounding_off'));
}
public function lossProfitFilter(Request $request)
{
$businessId = auth()->user()->business_id;
$startDate = Carbon::today();
$endDate = Carbon::today();
switch ($request->custom_days) {
case 'yesterday':
$startDate = $endDate = Carbon::yesterday();
break;
case 'last_seven_days':
$startDate = Carbon::today()->subDays(6);
break;
case 'last_thirty_days':
$startDate = Carbon::today()->subDays(29);
break;
case 'current_month':
$startDate = Carbon::now()->startOfMonth();
$endDate = Carbon::now()->endOfMonth();
break;
case 'last_month':
$startDate = Carbon::now()->subMonth()->startOfMonth();
$endDate = Carbon::now()->subMonth()->endOfMonth();
break;
case 'current_year':
$startDate = Carbon::now()->startOfYear();
$endDate = Carbon::now()->endOfYear();
break;
case 'custom_date':
if ($request->from_date && $request->to_date) {
$startDate = Carbon::parse($request->from_date);
$endDate = Carbon::parse($request->to_date);
}
break;
}
$salesQuery = Sale::where('business_id', $businessId)
->whereBetween('created_at', [$startDate, $endDate]);
$purchaseQuery = Purchase::where('business_id', $businessId)
->whereBetween('created_at', [$startDate, $endDate]);
$productQuery = Product::where('business_id', $businessId);
// Opening stock by purchase (before start date)
$opening_stock_by_purchase = Stock::whereHas('product', function ($q) use ($businessId) {
$q->where('business_id', $businessId);
})
->whereDate('created_at', '<', $startDate)
->sum(DB::raw('productPurchasePrice * productStock'));
// Closing stock by purchase (up to end date)
$closing_stock_by_purchase = Stock::whereHas('product', function ($q) use ($businessId) {
$q->where('business_id', $businessId);
})
->whereDate('created_at', '<=', $endDate)
->sum(DB::raw('productPurchasePrice * productStock'));
$total_purchase_price = (clone $purchaseQuery)->sum('totalAmount');
$total_purchase_shipping_charge = (clone $purchaseQuery)->sum('shipping_charge');
$total_purchase_discount = (clone $purchaseQuery)->sum('discountAmount');
$all_purchase_return = (clone $purchaseQuery)->with([
'purchaseReturns' => function ($query) {
$query->withSum('details as total_return_amount', 'return_amount');
}
])->get()->flatMap->purchaseReturns->sum('total_return_amount');
// Opening stock by sale
$opening_stock_by_sale = Stock::whereHas('product', function ($q) use ($businessId) {
$q->where('business_id', $businessId);
})
->whereDate('created_at', '<', $startDate)
->sum(DB::raw('productSalePrice * productStock'));
// Closing stock by sale
$closing_stock_by_sale = Stock::whereHas('product', function ($q) use ($businessId) {
$q->where('business_id', $businessId);
})
->whereDate('created_at', '<=', $endDate)
->sum(DB::raw('productSalePrice * productStock'));
$total_sale_price = (clone $salesQuery)->sum('totalAmount');
$total_sale_shipping_charge = (clone $salesQuery)->sum('shipping_charge');
$total_sale_discount = (clone $salesQuery)->sum('discountAmount');
$total_sale_rounding_off = (clone $salesQuery)->sum('rounding_amount') ?? 5;
$total_sale_rounding_off = 5;
$all_sale_return = (clone $salesQuery)->with([
'saleReturns' => function ($query) {
$query->withSum('details as total_return_amount', 'return_amount');
}
])->get()->flatMap->saleReturns->sum('total_return_amount');
if ($request->ajax()) {
return response()->json([
'opening_stock_by_purchase' => currency_format($opening_stock_by_purchase, currency: business_currency()),
'closing_stock_by_purchase' => currency_format($closing_stock_by_purchase, currency: business_currency()),
'total_purchase_price' => currency_format($total_purchase_price, currency: business_currency()),
'total_purchase_shipping_charge' => currency_format($total_purchase_shipping_charge, currency: business_currency()),
'total_purchase_discount' => currency_format($total_purchase_discount, currency: business_currency()),
'all_purchase_return' => currency_format($all_purchase_return, currency: business_currency()),
'all_sale_return' => currency_format($all_sale_return, currency: business_currency()),
'opening_stock_by_sale' => currency_format($opening_stock_by_sale, currency: business_currency()),
'closing_stock_by_sale' => currency_format($closing_stock_by_sale, currency: business_currency()),
'total_sale_price' => currency_format($total_sale_price, currency: business_currency()),
'total_sale_shipping_charge' => currency_format($total_sale_shipping_charge, currency: business_currency()),
'total_sale_discount' => currency_format($total_sale_discount, currency: business_currency()),
'total_sale_rounding_off' => currency_format($total_sale_rounding_off, currency: business_currency()),
]);
}
}
}

View File

@@ -0,0 +1,324 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use Illuminate\Http\Request;
use App\Traits\DateFilterTrait;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use App\Services\PdfService;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportLossProfitHistory;
class AcnooLossProfitHistoryController extends Controller
{
use DateFilterTrait;
public function index(Request $request)
{
$user = auth()->user();
$businessId = $user->business_id;
$branchId = null;
if (moduleCheck('MultiBranchAddon')) {
$branchId = $user->branch_id ?? $user->active_branch_id;
}
$duration = $request->custom_days ?: 'today';
$salesQuery = DB::table('sales')
->select(
DB::raw('DATE(saleDate) as date'),
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('DATE(saleDate)'));
$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,
'total_sales' => $sale->total_sales,
'total_incomes' => $sale->total_sale_income,
]);
$incomeQuery = DB::table('incomes')
->select(
DB::raw('DATE(incomeDate) 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('DATE(incomeDate)'));
$this->applyDateFilter($incomeQuery, $duration, 'incomeDate', $request->from_date, $request->to_date);
$dailyIncomes = $incomeQuery->get();
$income_datas = $dailyIncomes->map(fn ($income) => (object)[
'type' => 'Income',
'date' => $income->date,
'total_incomes' => $income->total_incomes,
]);
$mergedIncomeSaleData = collect();
$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)) {
$mergedIncomeSaleData->push($sale);
}
}
$dailyPayrolls = collect();
if (moduleCheck('HrmAddon')) {
$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)'));
$this->applyDateFilter($payrollQuery, $duration, 'date', $request->from_date, $request->to_date);
$dailyPayrolls = $payrollQuery->get();
}
$expenseQuery = DB::table('expenses')
->select(
DB::raw('DATE(expenseDate) 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('DATE(expenseDate)'));
$this->applyDateFilter($expenseQuery, $duration, 'expenseDate', $request->from_date, $request->to_date);
$dailyExpenses = $expenseQuery->get();
$mergedExpenseData = collect();
$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,
'total_expenses' => $expense->total_expenses_only,
]);
}
if ($payroll = $dailyPayrolls->firstWhere('date', $date)) {
$mergedExpenseData->push((object)[
'type' => 'Payroll',
'date' => $date,
'total_expenses' => $payroll->total_payrolls,
]);
}
}
$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;
$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',
'grossSaleProfit',
'grossIncomeProfit',
'totalExpenses',
'netProfit',
'cardGrossProfit',
'totalCardExpenses',
'cardNetProfit'
));
}
public function exportExcel()
{
return Excel::download(new ExportLossProfitHistory, 'loss-profit-history.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportLossProfitHistory, 'loss-profit-history.csv');
}
public function exportPdf()
{
$user = auth()->user();
$businessId = $user->business_id;
$branchId = null;
if (moduleCheck('MultiBranchAddon')) {
$branchId = $user->branch_id ?? $user->active_branch_id;
}
// SALES
$dailySales = DB::table('sales')
->select(
DB::raw('DATE(saleDate) as date'),
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('DATE(saleDate)'))
->get();
$sale_datas = $dailySales->map(fn ($sale) => (object)[
'type' => 'Sale',
'date' => $sale->date,
'total_sales' => $sale->total_sales,
'total_incomes' => $sale->total_sale_income,
]);
// INCOME
$dailyIncomes = DB::table('incomes')
->select(
DB::raw('DATE(incomeDate) 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('DATE(incomeDate)'))
->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();
foreach ($allDates as $date) {
if ($income = $income_datas->firstWhere('date', $date)) {
$mergedIncomeSaleData->push($income);
}
if ($sale = $sale_datas->firstWhere('date', $date)) {
$mergedIncomeSaleData->push($sale);
}
}
// PAYROLL
$dailyPayrolls = collect();
if (moduleCheck('HrmAddon')) {
$dailyPayrolls = 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();
}
// EXPENSES
$dailyExpenses = DB::table('expenses')
->select(
DB::raw('DATE(expenseDate) 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('DATE(expenseDate)'))
->get();
$mergedExpenseData = collect();
$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,
'total_expenses' => $expense->total_expenses_only,
]);
}
if ($payroll = $dailyPayrolls->firstWhere('date', $date)) {
$mergedExpenseData->push((object)[
'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;
// RENDER PDF
return PdfService::render(
'business::loss-profit-histories.pdf',
compact(
'mergedIncomeSaleData',
'mergedExpenseData',
'grossSaleProfit',
'grossIncomeProfit',
'totalExpenses',
'netProfit'
),
'loss-profit-history.pdf'
);
}
}

View File

@@ -0,0 +1,189 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use Illuminate\Http\Request;
use App\Traits\DateFilterTrait;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
class AcnooLossProfitHistoryReportController extends Controller
{
use DateFilterTrait;
public function index(Request $request)
{
$user = auth()->user();
$businessId = $user->business_id;
$branchId = null;
if (moduleCheck('MultiBranchAddon')) {
$branchId = $user->branch_id ?? $user->active_branch_id;
}
$duration = $request->custom_days ?: 'today';
$salesQuery = DB::table('sales')
->select(
DB::raw('DATE(saleDate) as date'),
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('DATE(saleDate)'));
$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,
'total_sales' => $sale->total_sales,
'total_incomes' => $sale->total_sale_income,
]);
$incomeQuery = DB::table('incomes')
->select(
DB::raw('DATE(incomeDate) 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('DATE(incomeDate)'));
$this->applyDateFilter($incomeQuery, $duration, 'incomeDate', $request->from_date, $request->to_date);
$dailyIncomes = $incomeQuery->get();
$income_datas = $dailyIncomes->map(fn ($income) => (object)[
'type' => 'Income',
'date' => $income->date,
'total_incomes' => $income->total_incomes,
]);
$mergedIncomeSaleData = collect();
$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)) {
$mergedIncomeSaleData->push($sale);
}
}
$dailyPayrolls = collect();
if (moduleCheck('HrmAddon')) {
$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)'));
$this->applyDateFilter($payrollQuery, $duration, 'date', $request->from_date, $request->to_date);
$dailyPayrolls = $payrollQuery->get();
}
$expenseQuery = DB::table('expenses')
->select(
DB::raw('DATE(expenseDate) 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('DATE(expenseDate)'));
$this->applyDateFilter($expenseQuery, $duration, 'expenseDate', $request->from_date, $request->to_date);
$dailyExpenses = $expenseQuery->get();
$mergedExpenseData = collect();
$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,
'total_expenses' => $expense->total_expenses_only,
]);
}
if ($payroll = $dailyPayrolls->firstWhere('date', $date)) {
$mergedExpenseData->push((object)[
'type' => 'Payroll',
'date' => $date,
'total_expenses' => $payroll->total_payrolls,
]);
}
}
$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;
$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',
'grossSaleProfit',
'grossIncomeProfit',
'totalExpenses',
'netProfit',
'cardGrossProfit',
'totalCardExpenses',
'cardNetProfit'
));
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Notification;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class AcnooNotificationController extends Controller
{
public function mtIndex()
{
$notifications = auth()->user()->notifications()
->whereDate('created_at', today())
->latest()
->get();
return view('business::notifications.index', compact('notifications'));
}
public function maanFilter(Request $request)
{
$notifications = Notification::when(request('days') == 'daily', function ($q) {
$q->whereDate('created_at', now()->format('Y-m-d'));
})
->when(request('days') == 'weekly', function ($q) {
$q->whereBetween('created_at', [now()->startOfWeek()->format('Y-m-d'), now()->endOfWeek()->format('Y-m-d')] );
})
->when(request('days') == '15_days', function ($q) {
$q->whereDate('created_at', '>=', now()->subDays(15)->format('Y-m-d'));
})
->when(request('days') == 'monthly', function ($q) {
$q->whereMonth('created_at', now()->format('m'));
})
->when(request('days') == 'yearly', function ($q) {
$q->whereYear('created_at', now()->format('Y'));
})
->latest()
->get();
return response()->json([
'data' => view('business::notifications.datas', compact('notifications'))->render()
]);
}
public function mtView($id)
{
$notify = Notification::find($id);
if ($notify) {
$notify->read_at = now();
$notify->save();
return redirect($notify->data['url'] ?? '/');
}
return back()->with('error', __('Premission denied.'));
}
public function mtReadAll()
{
auth()->user()->unreadNotifications()->update(['read_at' => now()]);
return back();
}
}

View File

@@ -0,0 +1,269 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Party;
use App\Helpers\HasUploader;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
class AcnooPartyController extends Controller
{
use HasUploader;
public function __construct()
{
$this->middleware('check.permission:parties.read')->only(['index']);
$this->middleware('check.permission:parties.create')->only(['create', 'store']);
$this->middleware('check.permission:parties.update')->only(['edit', 'update']);
$this->middleware('check.permission:parties.delete')->only(['destroy', 'deleteAll']);
}
public function index(Request $request)
{
$user = auth()->user();
$business_id = $user->business_id;
$activeBranch = $user->active_branch;
$search = $request->input('search');
$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 . '%');
});
});
// Filter by party type
if ($party_type === 'Customer') {
$query->whereIn('type', ['Retailer', 'Dealer', 'Wholesaler']);
} elseif ($party_type === 'Supplier') {
$query->where('type', 'Supplier');
}
$parties = $query->latest()->paginate($request->per_page ?? 20)->appends($request->query());
if ($activeBranch) {
$parties->setCollection(
$parties->getCollection()
->transform(function ($party) use ($activeBranch) {
$party_due = $party->type === 'Supplier'
? $party->purchases_dues->sum('dueAmount')
: $party->sales_dues->sum('dueAmount');
$party->due = $party->branch_id === $activeBranch->id ? $party_due + $party->due : $party_due;
return $party;
})
);
}
if ($request->ajax()) {
return response()->json([
'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');
}
public function store(Request $request)
{
$request->validate([
'phone' => 'nullable|max:20|' . Rule::unique('parties')->where('business_id', auth()->user()->business_id),
'name' => 'required|string|max:255',
'type' => 'required|string|in:Retailer,Dealer,Wholesaler,Supplier',
'email' => 'nullable|email',
'image' => 'nullable|image|mimes:jpeg,png,jpg,svg',
'address' => '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',
'shipping_address.state' => 'nullable|string|max:255',
'shipping_address.zip_code' => 'nullable|string|max:20',
'shipping_address.country' => 'nullable|string|max:255',
'credit_limit' => 'nullable|numeric|min:0|max:999999999999.99',
'opening_balance' => 'nullable|numeric|min:-999999999999.99|max:999999999999.99',
'opening_balance_type' => 'required|in:due,advance',
'meta' => 'nullable|array',
]);
Party::create($request->except('image', 'due', 'wallet', 'opening_balance', 'credit_limit','business_id') + [
'due' => ($request->opening_balance_type == 'due') ? ($request->opening_balance ?? 0) : 0,
'wallet' => ($request->opening_balance_type == 'advance') ? ($request->opening_balance ?? 0) : 0,
'opening_balance' => $request->opening_balance ?? 0,
'credit_limit' => $request->credit_limit ?? 0,
'image' => $request->image ? $this->upload($request, 'image') : NULL,
'business_id' => auth()->user()->business_id
]);
$type = in_array($request->type, ['Retailer', 'Dealer', 'Wholesaler']) ? 'Customer' : ($request->type === 'Supplier' ? 'Supplier' : '');
return response()->json([
'message' => __(ucfirst($type) . ' created successfully'),
'redirect' => route('business.parties.index', ['type' => $type])
]);
}
public function edit($id)
{
$party = Party::where('business_id', auth()->user()->business_id)->findOrFail($id);
return view('business::parties.edit', compact('party'));
}
public function update(Request $request, $id)
{
$party = Party::findOrFail($id);
$request->validate([
'phone' => 'nullable|max:20|unique:parties,phone,' . $party->id . ',id,business_id,' . auth()->user()->business_id,
'name' => 'required|string|max:255',
'type' => 'required|string|in:Retailer,Dealer,Wholesaler,Supplier',
'email' => 'nullable|email',
'image' => 'nullable|image|mimes:jpeg,png,jpg,svg',
'address' => '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',
'shipping_address.state' => 'nullable|string|max:255',
'shipping_address.zip_code' => 'nullable|string|max:20',
'shipping_address.country' => 'nullable|string|max:255',
'credit_limit' => 'nullable|numeric|min:0|max:999999999999.99',
'opening_balance' => 'nullable|numeric|min:-999999999999.99|max:999999999999.99',
'opening_balance_type' => 'required|in:due,advance',
'meta' => 'nullable|array',
]);
$branch_logic = $party->branch_id == auth()->user()->active_branch?->id;
// Previous
$prevOpening = $party->opening_balance ?? 0;
$prevType = $party->opening_balance_type;
// Current
$currentOpening = $request->opening_balance ?? 0;
$currentType = $request->opening_balance_type;
// Start with existing balance
$due = $party->due;
$wallet = $party->wallet;
if ($prevType === $currentType) {
// Same type then adjust by difference
if ($currentType === 'due') {
$due += ($currentOpening - $prevOpening);
} else {
$wallet += ($currentOpening - $prevOpening);
}
} else {
// Type changed then shift balances
if ($prevType === 'due' && $currentType === 'advance') {
$due -= $prevOpening;
$wallet += $currentOpening;
} elseif ($prevType === 'advance' && $currentType === 'due') {
$wallet -= $prevOpening;
$due += $currentOpening;
}
}
$party->update($request->except('image', 'due', 'wallet', 'opening_balance', 'credit_limit', 'opening_balance_type','business_id') + [
'due' => $branch_logic ? $due : $party->due,
'wallet' => $branch_logic ? $wallet : $party->wallet,
'opening_balance' => $currentOpening,
'opening_balance_type' => $currentType,
'credit_limit' => $request->credit_limit ?? $party->credit_limit,
'image' => $request->image ? $this->upload($request, 'image', $party->image) : $party->image,
]
);
$type = in_array($party->type, ['Retailer', 'Dealer', 'Wholesaler']) ? 'Customer' : ($party->type === 'Supplier' ? 'Supplier' : '');
return response()->json([
'message' => __(ucfirst($type) . ' updated successfully'),
'redirect' => route('business.parties.index', ['type' => $type])
]);
}
public function destroy($id)
{
$party = Party::findOrFail($id);
if (!$party->canBeDeleted()) {
return response()->json([
'message' => __('This party cannot be deleted.'),
], 400);
}
if (file_exists($party->image)) {
Storage::delete($party->image);
}
$party->delete();
$type = in_array($party->type, ['Retailer', 'Dealer', 'Wholesaler']) ? 'Customer' : ($party->type === 'Supplier' ? 'Supplier' : '');
return response()->json([
'message' => ucfirst($party->type) . ' deleted successfully',
'redirect' => route('business.parties.index', ['type' => $type]),
]);
}
public function deleteAll(Request $request)
{
$parties = Party::whereIn('id', $request->ids)->get();
$partyType = null;
$undeletable = [];
foreach ($parties as $party) {
if ($partyType === null) {
$partyType = in_array($party->type, ['Retailer', 'Dealer', 'Wholesaler']) ? 'Customer' : ($party->type === 'Supplier' ? 'Supplier' : 'Customer');
}
if (!$party->canBeDeleted()) {
$undeletable[] = $party->name;
} else {
if (file_exists($party->image)) {
Storage::delete($party->image);
}
$party->delete();
}
}
$message = __('Selected parties deleted successfully');
if (!empty($undeletable)) {
$message .= ' (Some parties were skipped: ' . implode(', ', $undeletable) . ')';
}
return response()->json([
'message' => $message,
'redirect' => route('business.parties.index', ['type' => $partyType]),
]);
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Modules\Business\App\Http\Controllers;
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;
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')
->when($request->search, function ($query) use ($request) {
$query->where(function ($q) use ($request) {
$q->where('name', 'like', '%' . $request->search . '%');
});
})
->latest()
->paginate($request->per_page ?? 20)
->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::party-reports.loss-profit.datas', compact('parties'))->render()
]);
}
return view('business::party-reports.loss-profit.index', compact('parties', 'totalAmount', 'totalProfit', 'totalLoss'));
}
public function exportExcel()
{
return Excel::download(new ExportPartyLossProfit, 'party-loss-profit.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportPartyLossProfit, 'party-loss-profit.csv');
}
public function exportPdf()
{
$parties = Party::with('sales')
->where('business_id', auth()->user()->business_id)
->where('type', '!=', 'Supplier')
->latest()
->get();
return PdfService::render('business::party-reports.loss-profit.pdf', compact('parties'),'party-loss-profit-report.pdf');
}
public function view($id)
{
$party = Party::with('sales.details', 'sales.details.product')
->where('id', $id)
->where('business_id', auth()->user()->business_id)
->firstOrFail();
return response()->json([
'sales' => $party->sales
]);
}
}

View File

@@ -0,0 +1,701 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Vat;
use App\Models\Rack;
use App\Models\Unit;
use App\Models\Brand;
use App\Models\Shelf;
use App\Models\Stock;
use App\Models\Option;
use App\Models\Product;
use App\Models\Category;
use App\Models\Variation;
use App\Models\Warehouse;
use App\Helpers\HasUploader;
use App\Models\ComboProduct;
use App\Models\ProductModel;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use App\Services\PdfService;
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
{
use HasUploader;
public function __construct()
{
$this->middleware('check.permission:products.read')->only(['index', 'show', 'expiredProduct']);
$this->middleware('check.permission:products.create')->only(['create', 'store']);
$this->middleware('check.permission:products.update')->only(['edit', 'update', 'CreateStock']);
$this->middleware('check.permission:products.delete')->only(['destroy', 'deleteAll']);
}
public function index(Request $request)
{
$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('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()
->paginate($request->per_page ?? 20)
->appends($request->query());
$products->getCollection()->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)
);
$product->combo_items = $product->combo_products->map(function ($combo) {
return [
'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,
];
});
}
return $product;
});
if ($request->ajax()) {
return response()->json([
'data' => view('business::products.datas', compact('products'))->render()
]);
}
return view('business::products.index', compact('products'));
}
public function create()
{
$business_id = auth()->user()->business_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();
$product_id = (Product::where('business_id', $business_id)->count() ?? 0) + 1;
$vats = Vat::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();
$variations = Variation::where('business_id', auth()->user()->business_id)->where('status', 1)->get();
$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 . '%')
->get()
->firstWhere('value.business_id', $business_id)['product_profit_option'] ?? '';
return view('business::products.create', compact('categories', 'brands', 'units', 'code', 'vats', 'product_models', 'warehouses', 'racks', 'shelves', 'profit_option', 'variations'));
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$business_id = auth()->user()->business_id;
$request->validate([
'vat_id' => 'nullable|exists:vats,id',
'unit_id' => 'nullable|exists:units,id',
'brand_id' => 'nullable|exists:brands,id',
'category_id' => 'nullable|exists:categories,id',
'model_id' => 'nullable|exists:product_models,id',
'vat_type' => 'nullable|in:inclusive,exclusive',
'productName' => 'required|string|max:255',
'productPicture' => 'nullable|image|mimes:jpg,png,jpeg,svg',
'productCode' => [
'nullable',
Rule::unique('products')->where(function ($query) use ($business_id) {
return $query->where('business_id', $business_id);
}),
],
'alert_qty' => 'nullable|numeric|min:0',
'size' => 'nullable|string|max:255',
'type' => 'nullable|string|max:255',
'color' => 'nullable|string|max:255',
'weight' => 'nullable|string|max:255',
'capacity' => 'nullable|string|max:255',
'productManufacturer' => 'nullable|string|max:255',
'product_type' => 'required|in:single,variant,combo',
'variation_ids' => 'nullable|array|exists:variations,id',
'stocks.*.warehouse_id' => 'nullable|exists:warehouses,id',
'stocks.*.productStock' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.exclusive_price' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.inclusive_price' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.profit_percent' => 'nullable|numeric|max:99999999.99',
'stocks.*.productSalePrice' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.productWholeSalePrice' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.productDealerPrice' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.mfg_date' => 'nullable|date',
'stocks.*.expire_date' => 'nullable|date|after_or_equal:stocks.*.mfg_date',
'stocks' => 'nullable|array',
'stocks.*.batch_no' => [
'nullable',
function ($attribute, $value, $fail) use ($request) {
$batchNos = collect($request->stocks)->pluck('batch_no')->filter()->toArray();
if (count($batchNos) !== count(array_unique($batchNos))) {
$fail('Duplicate batch number found in the request.');
}
},
],
// Combo validation
'combo_products' => 'nullable|array',
'combo_products.*.stock_id' => [
'required_if:product_type,combo',
Rule::exists('stocks', 'id')->where('business_id', $business_id),
],
'combo_products.*.quantity' => 'required_if:product_type,combo|numeric|min:1',
]);
DB::beginTransaction();
try {
// vat calculation
$vat = Vat::find($request->vat_id);
$vat_rate = $vat->rate ?? 0;
// 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') + [
'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->product_type == 'combo' ? $request->profit_percent ?? 0 : 0,
'productSalePrice' => $request->product_type == 'combo' ? $request->productSalePrice ?? 0 : 0,
'productPicture' => $request->productPicture ? $this->upload($request, 'productPicture') : NULL,
]);
// Single or Variant Product
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,
'batch_no' => $stock['batch_no'] ?? null,
'warehouse_id' => $stock['warehouse_id'] ?? null,
'productStock' => $stock['productStock'] ?? 0,
'productPurchasePrice' => $purchasePrice,
'profit_percent' => $stock['profit_percent'] ?? 0,
'productSalePrice' => $stock['productSalePrice'] ?? 0,
'productWholeSalePrice' => $stock['productWholeSalePrice'] ?? 0,
'productDealerPrice' => $stock['productDealerPrice'] ?? 0,
'mfg_date' => $stock['mfg_date'] ?? null,
'expire_date' => $stock['expire_date'] ?? null,
'variation_data' => $stock['variation_data'] ?? null,
'variant_name' => $stock['variant_name'] ?? null,
'serial_numbers' => $request->has_serial ? json_encode($stock['serial_numbers'] ?? []) : null,
'branch_id' => auth()->user()->branch_id ?? auth()->user()->active_branch_id,
'created_at' => now(),
'updated_at' => now(),
];
}
Stock::insert($stockData);
}
// Combo Product
if ($request->product_type === 'combo' && !empty($request->combo_products)) {
foreach ($request->combo_products as $item) {
ComboProduct::create([
'product_id' => $product->id,
'stock_id' => $item['stock_id'],
'quantity' => $item['quantity'],
'purchase_price' => $item['purchase_price'],
]);
}
}
DB::commit();
return response()->json([
'message' => __('Product saved successfully.'),
'redirect' => route('business.products.index')
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json([
'message' => $e->getMessage(),
], 406);
}
}
public function edit($id)
{
$business_id = auth()->user()->business_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();
$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 . '%')
->get()
->firstWhere('value.business_id', $business_id)['product_profit_option'] ?? '';
$product = Product::with([
'stocks' => function ($query) {
$query->orderBy('variant_name', 'asc')
->orderBy('id', 'asc');
},
'combo_products.stock:id,batch_no,product_id',
'combo_products.stock.product:id,productName,productCode,unit_id',
'combo_products.stock.product.unit:id,unitName'
])
->where('business_id', $business_id)
->findOrFail($id);
return view('business::products.edit', compact('categories', 'brands', 'units', 'product', 'vats', 'product_models', 'warehouses', 'racks', 'shelves', 'profit_option', 'variations'));
}
public function update(Request $request, $id)
{
$product = Product::findOrFail($id);
$business_id = auth()->user()->business_id;
if ($product->product_type != $request->product_type) {
return response()->json([
'message' => __('Product type can not be changed.'),
], 406);
}
$request->validate([
'vat_id' => 'nullable|exists:vats,id',
'unit_id' => 'nullable|exists:units,id',
'brand_id' => 'nullable|exists:brands,id',
'category_id' => 'nullable|exists:categories,id',
'model_id' => 'nullable|exists:product_models,id',
'vat_type' => 'nullable|in:inclusive,exclusive',
'productName' => 'required|string|max:255',
'productPicture' => 'nullable|image|mimes:jpg,png,jpeg,svg',
'productCode' => [
'nullable',
Rule::unique('products', 'productCode')->ignore($product->id)->where(function ($query) use ($business_id) {
return $query->where('business_id', $business_id);
}),
],
'alert_qty' => 'nullable|numeric|min:0',
'size' => 'nullable|string|max:255',
'type' => 'nullable|string|max:255',
'color' => 'nullable|string|max:255',
'weight' => 'nullable|string|max:255',
'capacity' => 'nullable|string|max:255',
'productManufacturer' => 'nullable|string|max:255',
'product_type' => 'required|in:single,variant,combo',
'variation_ids' => 'nullable|array|exists:variations,id',
'stocks.*.warehouse_id' => 'nullable|exists:warehouses,id',
'stocks.*.productStock' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.exclusive_price' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.inclusive_price' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.profit_percent' => 'nullable|numeric|max:99999999.99',
'stocks.*.productSalePrice' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.productWholeSalePrice' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.productDealerPrice' => 'nullable|numeric|min:0|max:99999999.99',
'stocks.*.mfg_date' => 'nullable|date',
'stocks.*.expire_date' => 'nullable|date|after_or_equal:stocks.*.mfg_date',
'stocks' => 'nullable|array',
'stocks.*.batch_no' => [
'nullable',
function ($attribute, $value, $fail) use ($request) {
$batchNos = collect($request->stocks)->pluck('batch_no')->filter()->toArray();
if (count($batchNos) !== count(array_unique($batchNos))) {
$fail('Duplicate batch number found in the request.');
}
},
],
// Combo validation
'combo_products' => 'nullable|array',
'combo_products.*.stock_id' => [
'required_if:product_type,combo',
Rule::exists('stocks', 'id')->where('business_id', $business_id),
],
'combo_products.*.quantity' => 'required_if:product_type,combo|numeric|min:1',
]);
DB::beginTransaction();
try {
// VAT calculation
$vat = Vat::find($request->vat_id);
$vat_rate = $vat->rate ?? 0;
// Update product
$product->update($request->except(['productPicture', 'productPurchasePrice', 'productDealerPrice', 'productWholeSalePrice', 'alert_qty', 'stocks', 'vat_amount', 'profit_percent', 'is_displayed_in_pos']) + [
'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,
'productPicture' => $request->productPicture ? $this->upload($request, 'productPicture', $product->productPicture) : $product->productPicture,
]);
// Delete previous stocks and combos
if ($product->product_type === 'combo') {
ComboProduct::where('product_id', $product->id)->delete();
}
// Handle Single/Variant Product Stocks
if (in_array($request->product_type, ['single', 'variant']) && !empty($request->stocks)) {
$existingStockIds = $product->stocks()->pluck('id')->toArray();
$incomingStockIds = collect($request->stocks)->pluck('stock_id')->filter()->toArray();
// Delete removed stocks
$stocksToDelete = array_diff($existingStockIds, $incomingStockIds);
Stock::whereIn('id', $stocksToDelete)->delete();
// 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,
'profit_percent' => $stock['profit_percent'] ?? 0,
'productSalePrice' => $stock['productSalePrice'] ?? 0,
'productWholeSalePrice' => $stock['productWholeSalePrice'] ?? 0,
'productDealerPrice' => $stock['productDealerPrice'] ?? 0,
'mfg_date' => $stock['mfg_date'] ?? null,
'expire_date' => $stock['expire_date'] ?? null,
'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,
];
if ($stockId) {
Stock::where('id', $stockId)->update($payload);
} else {
Stock::create($payload);
}
}
}
// Handle Combo Product
if ($request->product_type === 'combo' && !empty($request->combo_products)) {
foreach ($request->combo_products as $item) {
ComboProduct::create([
'product_id' => $product->id,
'stock_id' => $item['stock_id'],
'quantity' => $item['quantity'],
'purchase_price' => $item['purchase_price'] ?? 0,
]);
}
}
DB::commit();
return response()->json([
'message' => __('Product updated successfully.'),
'redirect' => route('business.products.index')
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json([
'message' => __('Something went wrong.'),
'error' => $e->getMessage(),
], 406);
}
}
public function destroy($id)
{
$product = Product::findOrFail($id);
if (file_exists($product->productPicture)) {
Storage::delete($product->productPicture);
}
$product->delete();
return response()->json([
'message' => __('Product deleted successfully'),
'redirect' => route('business.products.index')
]);
}
public function deleteAll(Request $request)
{
$products = Product::whereIn('id', $request->ids)->get();
foreach ($products as $product) {
if (file_exists($product->productPicture)) {
Storage::delete($product->productPicture);
}
}
Product::whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Selected product deleted successfully'),
'redirect' => route('business.products.index')
]);
}
public function getAllProduct()
{
$products = Product::with([
'stocks' => function ($query) {
$query->where('productStock', '>', 0);
},
'category:id,categoryName',
'unit:id,unitName',
'stocks.warehouse:id,name',
'vat:id,rate'
])
->where('business_id', auth()->user()->business_id)
->withSum('stocks as total_stock', 'productStock')
->latest()
->get()
->where('total_stock', '>', 0)
->values();
return response()->json($products);
}
public function getByCategory($category_id)
{
$products = Product::where('business_id', auth()->user()->business_id)->where('category_id', $category_id)->get();
return response()->json($products);
}
public function exportExcel()
{
return Excel::download(new ExportProduct, 'product.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportProduct, 'product.csv');
}
public function exportPdf()
{
$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();
return PdfService::render('business::products.pdf', compact('products'), 'product-list.pdf');
}
public function expiredProduct(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('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()
->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::expired-products.datas', compact('expired_products'))->render()
]);
}
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)
{
$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();
$profit_option = Option::where('key', 'business-settings')
->where('value', 'LIKE', '%"business_id":%' . $business_id . '%')
->get()
->firstWhere('value.business_id', $business_id)['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);
return response()->json($rack ? $rack->shelves : []);
}
public function getProductVariants($product_id)
{
$variant_stocks = Stock::select('id', 'variant_name', 'batch_no', 'expire_date')
->where('business_id', auth()->user()->business_id)
->where('product_id', $product_id)
->get();
return response()->json($variant_stocks);
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\SaleDetails;
use App\Services\PdfService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportProductLossProfit;
class AcnooProductLossProfitReportController extends Controller
{
public function index(Request $request)
{
$user = auth()->user();
$branchId = $user->branch_id ?? $user->active_branch_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($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}%");
});
});
$profit = (clone $baseQuery)
->where('lossProfit', '>', 0)
->sum('lossProfit');
$loss = (clone $baseQuery)
->where('lossProfit', '<', 0)
->sum('lossProfit');
$product_lossProfits = $baseQuery
->with('product:id,productName,productCode')
->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')
->paginate($request->per_page ?? 20)
->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view(
'business::reports.product-loss-profit.datas',
compact('product_lossProfits')
)->render()
]);
}
return view(
'business::reports.product-loss-profit.index',
compact('product_lossProfits', 'profit', 'loss')
);
}
public function exportExcel()
{
return Excel::download(new ExportProductLossProfit, 'product-lossProfit.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportProductLossProfit, 'product-lossProfit.csv');
}
public function exportPdf()
{
$branchId = moduleCheck('MultiBranchAddon') ? auth()->user()->branch_id ?? auth()->user()->active_branch_id : null;
$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();
return PdfService::render('business::reports.product-loss-profit.pdf', compact('product_lossProfits'), 'product-loss-profit-report.pdf');
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\ProductModel;
use Illuminate\Http\Request;
class AcnooProductModelController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:product-models.read')->only(['index']);
$this->middleware('check.permission:product-models.create')->only(['store']);
$this->middleware('check.permission:product-models.update')->only(['update', 'status']);
$this->middleware('check.permission:product-models.delete')->only(['destroy', 'deleteAll']);
}
public function index(Request $request)
{
$models = ProductModel::where('business_id', auth()->user()->business_id)->when(request('search'), function ($q) {
$q->where(function ($q) {
$q->where('name', 'like', '%' . request('search') . '%');
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::product-models.datas', compact('models'))->render()
]);
}
return view('business::product-models.index', compact('models'));
}
public function store(Request $request)
{
$request->validate([
'status' => 'required|boolean',
'name' => 'required|string|max:255',
]);
ProductModel::create($request->except('business_id') + [
'business_id' => auth()->user()->business_id,
]);
return response()->json([
'message' => __('Model saved successfully'),
'redirect' => route('business.product-models.index')
]);
}
public function update(Request $request, $id)
{
$request->validate([
'status' => 'required|boolean',
'name' => 'required|string|max:255,' . $id,
]);
$model = ProductModel::where('business_id', auth()->user()->business_id)->findOrFail($id);
$model->update($request->except('business_id'));
return response()->json([
'message' => __('Model updated successfully'),
'redirect' => route('business.product-models.index')
]);
}
public function destroy($id)
{
ProductModel::where('business_id', auth()->user()->business_id)->where('id', $id)->delete();
return response()->json([
'message' => __('Model deleted successfully'),
'redirect' => route('business.product-models.index')
]);
}
public function deleteAll(Request $request)
{
ProductModel::where('business_id', auth()->user()->business_id)->whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Selected model deleted successfully'),
'redirect' => route('business.product-models.index')
]);
}
public function status(Request $request, $id)
{
$model = ProductModel::findOrFail($id);
$model->update(['status' => $request->status]);
return response()->json(['message' => __('Model')]);
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Services\PdfService;
use App\Traits\DateFilterTrait;
use App\Traits\DateRangeTrait;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportProductPurchaseHistoryDetailReport;
use Modules\Business\App\Exports\ExportProductPurchaseHistoryReport;
class AcnooProductPurchaseHistoryReportController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function index(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', '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->paginate($request->per_page ?? 20)->appends($request->query());
$total_purchase_qty = $products->sum(function ($product) {
return $product->purchaseDetails->sum('quantities');
});
$total_sale_qty = $products->sum(function ($product) {
return $product->saleDetails->sum('quantities');
});
if ($request->ajax()) {
return response()->json([
'data' => view('business::product-purchase-history-report.datas', compact('products', 'total_purchase_qty', 'total_sale_qty', 'filter_from_date', 'filter_to_date', 'duration'))->render(),
]);
}
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)
{
$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', $businessId)
->findOrFail($id);
$purchaseDetailsQuery = $product->purchaseDetails()
->whereHas('purchase', function ($purchase) use ($duration, $request) {
$this->applyDateFilter($purchase, $duration, 'purchaseDate', $request->from_date, $request->to_date);
})
->with('purchase:id,invoiceNumber,purchaseDate')
->select('id', 'purchase_id', 'product_id', 'quantities', 'productPurchasePrice');
$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}%");
});
});
}
);
$purchaseDetails = $purchaseDetailsQuery->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::product-purchase-history-report.details-list', compact('product', 'purchaseDetails', 'filter_from_date', 'filter_to_date', 'duration'))->render(),
]);
}
return view(
'business::product-purchase-history-report.details',
compact('product', 'purchaseDetails', 'filter_from_date', 'filter_to_date', 'duration')
);
}
public function exportExcel()
{
return Excel::download(new ExportProductPurchaseHistoryReport, 'product-purchase-history.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportProductPurchaseHistoryReport, 'product-purchase-history.csv');
}
public function exportPdf(Request $request)
{
$businessId = auth()->user()->business_id;
$productQuery = Product::with('saleDetails', 'purchaseDetails', 'stocks', 'combo_products')->where('business_id', $businessId);
$products = $productQuery->get();
$total_purchase_qty = $products->sum(function ($product) {
return $product->purchaseDetails->sum('quantities');
});
$total_sale_qty = $products->sum(function ($product) {
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');
}
public function exportDetailExcel($id)
{
return Excel::download(new ExportProductPurchaseHistoryDetailReport($id), 'product-purchase-history-details.xlsx');
}
public function exportDetailCsv($id)
{
return Excel::download(new ExportProductPurchaseHistoryDetailReport($id), 'product-purchase-history-details.csv');
}
public function exportDetailPdf(Request $request, $id)
{
$product = Product::select('id', 'business_id', 'productName')
->where('business_id', auth()->user()->business_id)
->findOrFail($id);
$purchaseDetailsQuery = $product->purchaseDetails()
->with('purchase:id,invoiceNumber,purchaseDate')
->select('id', 'purchase_id', 'product_id', 'quantities', 'productPurchasePrice');
$purchaseDetails = $purchaseDetailsQuery->get();
return PdfService::render('business::product-purchase-history-report.pdf-detail', compact('product', 'purchaseDetails'), 'product-purchase-history-details.pdf');
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\PurchaseDetails;
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\ExportProductPurchaseReport;
class AcnooProductPurchaseReportController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function index(Request $request)
{
$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);
});
$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);
$query->whereHas('purchase', function ($q) use ($duration, $request) {
$this->applyDateFilter($q, $duration, 'purchaseDate', $request->from_date, $request->to_date);
});
$product_purchases = $query->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.product-purchase.datas', compact('product_purchases', 'filter_from_date', 'filter_to_date', 'duration'))->render()
]);
}
return view('business::reports.product-purchase.index', compact('product_purchases', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function exportExcel()
{
return Excel::download(new ExportProductPurchaseReport, 'product-purchase.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportProductPurchaseReport, 'product-purchase.csv');
}
public function exportPdf()
{
$product_purchases = 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');
}
}

View File

@@ -0,0 +1,159 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Services\PdfService;
use App\Traits\DateFilterTrait;
use App\Traits\DateRangeTrait;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportProductSaleHistoryDetailReport;
use Modules\Business\App\Exports\ExportProductSaleHistoryReport;
class AcnooProductSaleHistoryReportController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function index(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', '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->paginate($request->per_page ?? 20)->appends($request->query());
$total_single_sale_price = $products->sum(function ($product) {
return $product->saleDetails->sum('price');
});
$total_combo_sale_price = $products->sum('productSalePrice');
$total_sale_price = $total_single_sale_price + $total_combo_sale_price;
$total_purchase_qty = $products->sum(function ($product) {
return $product->purchaseDetails->sum('quantities');
});
$total_sale_qty = $products->sum(function ($product) {
return $product->saleDetails->sum('quantities');
});
if ($request->ajax()) {
return response()->json([
'data' => view('business::product-sale-history-report.datas', compact('products', 'total_purchase_qty', 'total_sale_qty', 'total_sale_price', 'filter_from_date', 'filter_to_date', 'duration'))->render(),
]);
}
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)
{
$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', $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');
$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}%");
});
});
}
);
$saleDetails = $saleDetailsQuery->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::product-sale-history-report.details-list', compact('product', 'saleDetails', 'filter_from_date', 'filter_to_date', 'duration'))->render(),
]);
}
return view('business::product-sale-history-report.details', compact('product', 'saleDetails', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function exportExcel()
{
return Excel::download(new ExportProductSaleHistoryReport, 'product-sale-history.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportProductSaleHistoryReport, 'product-sale-history.csv');
}
public function exportPdf(Request $request)
{
$businessId = auth()->user()->business_id;
$productQuery = Product::with('saleDetails', 'purchaseDetails', 'stocks', 'combo_products')->where('business_id', $businessId);
$products = $productQuery->get();
$total_single_sale_price = $products->sum(function ($product) {
return $product->saleDetails->sum('price');
});
$total_combo_sale_price = $products->sum('productSalePrice');
$total_sale_price = $total_single_sale_price + $total_combo_sale_price;
$total_purchase_qty = $products->sum(function ($product) {
return $product->purchaseDetails->sum('quantities');
});
$total_sale_qty = $products->sum(function ($product) {
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');
}
public function exportDetailExcel($id)
{
return Excel::download(new ExportProductSaleHistoryDetailReport($id), 'product-sale-history-details.xlsx');
}
public function exportDetailCsv($id)
{
return Excel::download(new ExportProductSaleHistoryDetailReport($id), 'product-sale-history-details.csv');
}
public function exportDetailPdf(Request $request, $id)
{
$product = Product::select('id', 'business_id', 'productName')
->where('business_id', auth()->user()->business_id)
->findOrFail($id);
$saleDetailsQuery = $product->saleDetails()
->with('sale:id,party_id,invoiceNumber,saleDate', 'sale.party:id,name')
->select('id', 'sale_id', 'product_id', 'quantities', 'lossprofit', 'price', 'productPurchasePrice');
$saleDetails = $saleDetailsQuery->get();
return PdfService::render('business::product-sale-history-report.pdf-detail', compact('product', 'saleDetails'), 'product-sale-history-details.pdf');
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Sale;
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\ExportProductSaleReport;
class AcnooProductSaleReportController extends Controller
{
use DateFilterTrait, DateRangeTrait;
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 . '%');
});
});
$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);
$product_sales = $query->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.product-sale.datas', compact('product_sales', 'filter_from_date', 'filter_to_date', 'duration'))->render()
]);
}
return view('business::reports.product-sale.index', compact('product_sales', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function exportExcel()
{
return Excel::download(new ExportProductSaleReport, 'product-sales.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportProductSaleReport, 'product-sales.csv');
}
public function exportPdf()
{
$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();
return PdfService::render('business::reports.product-sale.pdf', compact('product_sales'),'product-sale-report.pdf');
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,99 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Branch;
use App\Models\Purchase;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use App\Http\Controllers\Controller;
use App\Services\PdfService;
use App\Traits\DateFilterTrait;
use App\Traits\DateRangeTrait;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\PurchaseExport;
class AcnooPurchaseReportController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function __construct()
{
$this->middleware('check.permission:purchase-reports.read')->only(['index']);
}
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);
$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 . '%');
});
});
}
$perPage = $request->input('per_page', 20);
$purchases = $purchasesQuery->latest()->paginate($perPage)->appends($request->query());
$total_purchase = $purchasesQuery->sum('totalAmount');
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.purchase.datas', compact('purchases', 'filter_from_date', 'filter_to_date', 'duration'))->render(),
'total_purchase' => currency_format($total_purchase, currency: business_currency())
]);
}
$branches = Branch::withTrashed()->where('business_id', auth()->user()->business_id)->latest()->get();
return view('business::reports.purchase.purchase-reports', compact('purchases', 'total_purchase', 'branches', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function exportExcel()
{
return Excel::download(new PurchaseExport, 'purchase.xlsx');
}
public function exportCsv()
{
return Excel::download(new PurchaseExport, 'purchase.csv');
}
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();
return PdfService::render('business::reports.purchase.pdf', compact('purchases'), 'purchases-report.pdf');
}
}

View File

@@ -0,0 +1,126 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Branch;
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;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportPurchaseReturn;
class AcnooPurchaseReturnReportController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function __construct()
{
$this->middleware('check.permission:purchase-return-reports.read')->only(['index']);
}
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',
'party:id,name,email,phone,type',
'details:id,purchase_id,product_id,productPurchasePrice,quantities',
'details.product:id,productName,category_id',
'details.product.category:id,categoryName',
'purchaseReturns' => function ($query) {
$query->withSum('details as total_return_amount', 'return_amount');
}
])
->where('business_id', $businessId)
->whereHas('purchaseReturns');
$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('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 . '%');
});
});
}
// 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());
// Handle AJAX Request
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.purchase-return.datas', compact('purchases', 'filter_from_date', 'filter_to_date', 'duration'))->render(),
'total_purchase_return' => currency_format($total_purchase_return, currency: business_currency())
]);
}
$branches = Branch::withTrashed()->where('business_id', auth()->user()->business_id)->latest()->get();
return view('business::reports.purchase-return.purchase-reports', compact('purchases', 'total_purchase_return', 'branches', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function exportExcel()
{
return Excel::download(new ExportPurchaseReturn, 'purchase-return.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportPurchaseReturn, 'purchase-return.csv');
}
public function exportPdf(Request $request)
{
$purchases = Purchase::with([
'user:id,name',
'branch:id,name',
'party:id,name,email,phone,type',
'details:id,purchase_id,product_id,productPurchasePrice,quantities',
'details.product:id,productName,category_id',
'details.product.category:id,categoryName',
'purchaseReturns' => function ($query) {
$query->withSum('details as total_return_amount', 'return_amount');
}
])
->where('business_id', auth()->user()->business_id)
->whereHas('purchaseReturns')
->latest()
->get();
return PdfService::render('business::reports.purchase-return.pdf', compact('purchases'), 'purchase-return-report.pdf');
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Rack;
use App\Models\Shelf;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class AcnooRackController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:racks.create')->only(['store']);
$this->middleware('check.permission:racks.read')->only(['index']);
$this->middleware('check.permission:racks.update')->only(['update', 'status']);
$this->middleware('check.permission:racks.delete')->only(['destroy', 'deleteAll']);
}
public function index(Request $request)
{
$shelves = Shelf::whereStatus(1)->where('business_id', auth()->user()->business_id)->latest()->get();
$search = $request->search;
$racks = Rack::with('shelves:id,name')
->where('business_id', auth()->user()->business_id)
->when($search, function ($q) use ($search) {
$q->where(function ($q) use ($search) {
$q->where('name', 'like', '%' . $search . '%')
->orWhereHas('shelves', function ($q3) use ($search) {
$q3->where('name', 'like', '%' . $search . '%');
});
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::racks.datas', compact('racks','shelves'))->render()
]);
}
return view('business::racks.index', compact('racks', 'shelves'));
}
public function store(Request $request)
{
$request->validate([
'shelf_id' => 'required|array',
'shelf_id.*' => 'exists:shelves,id',
'name' => 'required|string|max:255',
'status' => 'nullable|boolean',
]);
$rack = Rack::create($request->except('business_id') + [
'business_id' => auth()->user()->business_id
]);
$rack->shelves()->sync($request->shelf_id);
return response()->json([
'message' => 'Rack created cuccessfully.',
'redirect' => route('business.racks.index'),
]);
}
public function update(Request $request, Rack $rack)
{
$request->validate([
'shelf_id' => 'required|array',
'shelf_id.*' => 'exists:shelves,id',
'name' => 'required|string|max:255',
'status' => 'nullable|boolean',
]);
$rack->update($request->except('business_id') + [
'business_id' => auth()->user()->business_id
]);
$rack->shelves()->sync($request->shelf_id);
return response()->json([
'message' => 'Rack updated successfully.',
'redirect' => route('business.racks.index'),
]);
}
public function destroy(Rack $rack)
{
$rack->delete();
return response()->json([
'message' => 'Rack deleted successfully',
'redirect' => route('business.racks.index')
]);
}
public function status(Request $request, $id)
{
$rack = Rack::findOrFail($id);
$rack->update(['status' => $request->status]);
return response()->json(['message' => 'Rack']);
}
public function deleteAll(Request $request)
{
$rack = Rack::whereIn('id', $request->input('ids'));
$rack->delete();
return response()->json([
'message' => __('Rack deleted successfully.'),
'redirect' => route('business.racks.index')
]);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
class AcnooSaleCommissionController extends Controller
{
public function index()
{
return view('business::sale-commissions.index');
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,97 @@
<?php
namespace Modules\Business\App\Http\Controllers;
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;
use App\Traits\DateRangeTrait;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportSaleReport;
class AcnooSaleReportController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function __construct()
{
$this->middleware('check.permission:sale-reports.read')->only(['index']);
}
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);
});
// 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 . '%');
});
});
}
$perPage = $request->input('per_page', 20);
$sales = $salesQuery->latest()->paginate($perPage)->appends($request->query());
$total_sale = $salesQuery->sum('totalAmount');
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.sales.datas', compact('sales', 'filter_from_date', 'filter_to_date', 'duration'))->render(),
'total_sale' => currency_format($total_sale, currency: business_currency())
]);
}
$branches = Branch::withTrashed()->where('business_id', auth()->user()->business_id)->latest()->get();
return view('business::reports.sales.sale-reports', compact('sales', 'total_sale', 'branches', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function exportExcel()
{
return Excel::download(new ExportSaleReport, 'sale.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportSaleReport, 'sale.csv');
}
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();
return PdfService::render('business::reports.sales.pdf', compact('sales'),'sales-report.pdf');
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace Modules\Business\App\Http\Controllers;
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;
use App\Traits\DateRangeTrait;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportSalesReturn;
class AcnooSaleReturnReportController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function __construct()
{
$this->middleware('check.permission:sale-return-reports.read')->only(['index']);
}
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([
'user:id,name',
'party:id,name',
'branch:id,name',
'details',
'details.product:id,productName,category_id',
'details.product.category:id,categoryName',
'saleReturns' => function ($query) {
$query->withSum('details as total_return_amount', 'return_amount')
->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');
// 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('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 . '%');
});
});
}
$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());
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.sales-return.datas', compact('sales', 'filter_from_date', 'filter_to_date', 'duration'))->render(),
'total_sale_return' => currency_format($total_sale_return, currency: business_currency())
]);
}
$branches = Branch::withTrashed()->where('business_id', auth()->user()->business_id)->latest()->get();
return view('business::reports.sales-return.sale-reports', compact('sales', 'total_sale_return', 'branches', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function exportExcel()
{
return Excel::download(new ExportSalesReturn, 'sales-return.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportSalesReturn, 'sales-return.csv');
}
public function exportPdf(Request $request)
{
$sales = Sale::with([
'user:id,name',
'party:id,name',
'branch:id,name',
'details',
'details.product:id,productName,category_id',
'details.product.category:id,categoryName',
'saleReturns' => function ($query) {
$query->withSum('details as total_return_amount', 'return_amount')
->with('branch:id,name');
}
])
->where('business_id', auth()->user()->business_id)
->whereHas('saleReturns')
->latest()
->get();
return PdfService::render('business::reports.sales-return.pdf', compact('sales'), 'sales-return-report.pdf');
}
}

View File

@@ -0,0 +1,186 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Option;
use Illuminate\Http\Request;
use App\Models\ProductSetting;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
use Illuminate\Validation\ValidationException;
class AcnooSettingsManagerController extends Controller
{
public function index()
{
$businessId = auth()->user()->business_id;
$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;
$currency_setting = Option::where('key', $currencySettingKey)->first();
return view('business::manage-settings.index', compact('invoice_setting', 'product_setting', 'currency_setting'));
}
public function updateInvoice(Request $request)
{
$request->validate([
'invoice_size' => 'required|string|max:100|in:a4,3_inch_80mm',
]);
$key = 'invoice_setting_' . auth()->user()->business_id;
Option::updateOrCreate(
['key' => $key],
['value' => $request->invoice_size]
);
Cache::forget($key);
return response()->json(__('Invoice setting updated successfully.'));
}
public function updateProductSetting(Request $request)
{
$request->validate([
'show_product_price' => 'nullable|boolean',
'show_product_code' => 'nullable|boolean',
'show_product_stock' => 'nullable|boolean',
'show_product_sale_price' => 'nullable|boolean',
'show_product_dealer_price' => 'nullable|boolean',
'show_product_wholesale_price' => 'nullable|boolean',
'show_product_unit' => 'nullable|boolean',
'show_product_brand' => 'nullable|boolean',
'show_product_category' => 'nullable|boolean',
'show_product_manufacturer' => 'nullable|boolean',
'show_product_image' => 'nullable|boolean',
'show_expire_date' => 'nullable|boolean',
'show_alert_qty' => 'nullable|boolean',
'show_vat_id' => 'nullable|boolean',
'show_vat_type' => 'nullable|boolean',
'show_exclusive_price' => 'nullable|boolean',
'show_inclusive_price' => 'nullable|boolean',
'show_profit_percent' => 'nullable|boolean',
'show_capacity' => 'nullable|boolean',
'show_weight' => 'nullable|boolean',
'show_color' => 'nullable|boolean',
'show_size' => 'nullable|boolean',
'show_type' => 'nullable|boolean',
'show_batch_no' => 'nullable|boolean',
'show_mfg_date' => 'nullable|boolean',
'show_model_no' => 'nullable|boolean',
'show_product_batch_no' => 'nullable|boolean',
'show_product_expire_date' => 'nullable|boolean',
'default_batch_no' => 'nullable|string|max:255',
'default_expired_date' => 'nullable|date',
'default_mfg_date' => 'nullable|date',
'default_sale_price' => 'nullable|numeric|min:0',
'default_wholesale_price' => 'nullable|numeric|min:0',
'default_dealer_price' => 'nullable|numeric|min:0',
'expire_date_type' => 'nullable|in:dmy,my',
'mfg_date_type' => 'nullable|in:dmy,my',
'show_product_type_single' => 'nullable|boolean',
'show_product_type_variant' => 'nullable|boolean',
'show_product_type_combo' => 'nullable|boolean',
'show_warehouse' => 'nullable|boolean',
'show_action' => 'nullable|boolean',
'show_rack' => 'nullable|boolean',
'show_shelf' => 'nullable|boolean',
'show_guarantee' => 'nullable|boolean',
'show_warranty' => 'nullable|boolean',
'show_serial' => 'nullable|boolean',
]);
if (
!$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.'],
]);
}
$modules = $request->except([
'_token',
'_method',
'default_expired_date_dmy',
'default_expired_date_my',
'default_mfg_date_dmy',
'default_mfg_date_my',
]);
// Set default_expired_date based on date type
$modules['default_expired_date'] = $request->expire_date_type === 'dmy'
? $request->default_expired_date_dmy
: ($request->expire_date_type === 'my'
? $request->default_expired_date_my
: null);
// Set default_mfg_date based on date type
$modules['default_mfg_date'] = $request->mfg_date_type === 'dmy'
? $request->default_mfg_date_dmy
: ($request->mfg_date_type === 'my'
? $request->default_mfg_date_my
: null);
$businessId = auth()->user()->business_id;
ProductSetting::updateOrCreate(
['business_id' => $businessId],
['modules' => $modules]
);
Cache::forget('product_setting_' . $businessId);
return response()->json(__('Product setting updated successfully.'));
}
public function updateCurrency(Request $request)
{
$request->validate([
'currency' => 'required|string|max:100|in:us,european',
]);
$key = 'currency_setting_' . auth()->user()->business_id;
Option::updateOrCreate(
['key' => $key],
['value' => $request->currency]
);
Cache::forget($key);
return response()->json(__('Currency setting updated successfully.'));
}
public function updateSerial(Request $request)
{
$request->validate([
'show_serial' => 'nullable|boolean',
]);
$businessId = auth()->user()->business_id;
$setting = ProductSetting::firstOrCreate(
['business_id' => $businessId],
['modules' => []]
);
$modules = $setting->modules ?? [];
$modules['show_serial'] = $request->boolean('show_serial') ? '1' : '0';
$setting->update([
'modules' => $modules
]);
Cache::forget('product_setting_' . $businessId);
return response()->json([
'message' => 'Serial setting updated successfully.',
]);
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Shelf;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class AcnooShelfController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:shelfs.read')->only(['index']);
$this->middleware('check.permission:shelfs.create')->only(['store']);
$this->middleware('check.permission:shelfs.update')->only(['update', 'status']);
$this->middleware('check.permission:shelfs.delete')->only(['destroy', 'deleteAll']);
}
public function index(Request $request)
{
$search = $request->search;
$shelves = Shelf::where('business_id', auth()->user()->business_id)
->when($search, function ($q) use ($search) {
$q->where(function ($q) use ($search) {
$q->where('name', 'like', '%' . $search . '%');
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::shelves.datas', compact('shelves'))->render()
]);
}
return view('business::shelves.index', compact('shelves'));
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
]);
Shelf::create($request->except('business_id') + [
'business_id' => auth()->user()->business_id
]);
return response()->json([
'message' => 'Shelf created cuccessfully.',
'redirect' => route('business.shelfs.index'),
]);
}
public function update(Request $request, Shelf $shelf)
{
$request->validate([
'name' => 'required|string|max:255',
]);
$shelf->update($request->except('business_id') + [
'business_id' => auth()->user()->business_id
]);
return response()->json([
'message' => 'Shelf updated successfully.',
'redirect' => route('business.shelfs.index'),
]);
}
public function destroy(Shelf $shelf)
{
$shelf->delete();
return response()->json([
'message' => 'Shelf deleted successfully',
'redirect' => route('business.shelfs.index')
]);
}
public function status(Request $request, $id)
{
$shelf = Shelf::findOrFail($id);
$shelf->update(['status' => $request->status]);
return response()->json(['message' => 'Shelf']);
}
public function deleteAll(Request $request)
{
$shelf = Shelf::whereIn('id', $request->input('ids'));
$shelf->delete();
return response()->json([
'message' => __('Shelf deleted successfully.'),
'redirect' => route('business.shelfs.index')
]);
}
}

View File

@@ -0,0 +1,105 @@
<?php
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 Maatwebsite\Excel\Facades\Excel;
use Illuminate\Pagination\LengthAwarePaginator;
use Modules\Business\App\Exports\ExportCurrentStock;
class AcnooStockController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:stocks.read')->only(['index']);
}
public function index()
{
$businessId = auth()->user()->business_id;
$alert_qty_filter = request('alert_qty');
$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);
// 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;
});
// 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()]
);
// Calculate totals for low stock view
$total_stock_value = $filteredProducts->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());
// 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_qty = Stock::whereHas('product', function ($q) use ($businessId) {
$q->where('business_id', $businessId);
})->sum('productStock');
}
// Handle AJAX request
if (request()->ajax()) {
return response()->json([
'data' => view('business::stocks.datas', compact('products', 'total_stock_value', 'total_qty'))->render()
]);
}
return view('business::stocks.index', [
'products' => $products,
'total_stock_value' => $total_stock_value,
'total_qty' => $total_qty,
]);
}
public function exportExcel()
{
return Excel::download(new ExportCurrentStock, 'current-stock.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportCurrentStock, 'current-stock.csv');
}
}

View File

@@ -0,0 +1,83 @@
<?php
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 Modules\Business\App\Exports\ExportCurrentStockReport;
class AcnooStockReportController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:stock-reports.read')->only(['index']);
}
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'));
$total_qty = Stock::whereHas('product', function ($q) use ($businessId) {
$q->where('business_id', $businessId);
})->sum('productStock');
$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')
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.stocks.datas', compact('stock_reports'))->render()
]);
}
return view('business::reports.stocks.stock-reports', compact('stock_reports', 'total_stock_value', 'total_qty'));
}
public function exportExcel()
{
return Excel::download(new ExportCurrentStockReport, 'current-stock-report.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportCurrentStockReport, 'current-stock-report.csv');
}
public function exportPdf()
{
$query = Product::with('stocks')
->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;
});
} else {
$stock_reports = $query->latest()->get();
}
return PdfService::render('business::reports.stocks.pdf', compact('stock_reports'), 'stock-report.pdf');
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Plan;
use App\Http\Controllers\Controller;
class AcnooSubscriptionController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:subscriptions.read')->only(['index']);
}
public function index()
{
$plans = Plan::where('status', 1)->latest()->get();
return view('business::subscriptions.index', compact('plans'));
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\PlanSubscribe;
use App\Http\Controllers\Controller;
use App\Services\PdfService;
use App\Traits\DateFilterTrait;
use App\Traits\DateRangeTrait;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportSubscription;
class AcnooSubscriptionReportController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function __construct()
{
$this->middleware('check.permission:subscription-reports.read')->only(['index']);
}
public function index(Request $request)
{
$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';
[$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 . '%');
});
});
});
}
$perPage = $request->input('per_page', 20);
$subscribers = $subscriberQuery->latest()->paginate($perPage)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.subscription-reports.datas', compact('subscribers', 'filter_from_date', 'filter_to_date', 'duration'))->render()
]);
}
return view('business::reports.subscription-reports.subscription-reports', compact('subscribers', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function exportExcel()
{
return Excel::download(new ExportSubscription, 'subscribers.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportSubscription, 'subscribers.csv');
}
public function exportPdf()
{
$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();
return PdfService::render('business::reports.subscription-reports.pdf', compact('subscribers'),'subscription-reports.pdf');
}
public function getInvoice($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'));
}
}

View File

@@ -0,0 +1,159 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Party;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Services\PdfService;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportSupplierDue;
class AcnooSupplierDueReportController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:supplier-due-reports.read')->only(['index']);
}
public function index(Request $request)
{
$user = auth()->user();
$businessId = $user->business_id;
$activeBranch = $user->active_branch;
$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();
// 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);
// 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()
);
}
// Calculate total_due
$total_due = $parties->sum(function ($supplier) {
return $supplier->due;
});
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.supplier-due.datas', compact('parties', 'total_due'))->render()
]);
}
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'));
}
public function exportExcel()
{
return Excel::download(new ExportSupplierDue, 'supplier-due.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportSupplierDue, 'supplier-due.csv');
}
public function exportPdf()
{
$user = auth()->user();
$businessId = $user->business_id;
$activeBranch = $user->active_branch;
$query = Party::where('business_id', $businessId)
->where('type', 'Supplier')
->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);
}
return PdfService::render('business::reports.supplier-due.pdf', compact('parties'),'supplier-due-report.pdf');
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Party;
use App\Services\PdfService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Services\PartyLedgerService;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportSingleSupplierLedger;
use Modules\Business\App\Exports\ExportSupplierLedger;
class AcnooSupplierLedgerController extends Controller
{
public function index(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()
->paginate($request->per_page ?? 20)->appends($request->query());
$totalAmount = $suppliers->sum(function ($customer) {
return $customer->purchases?->sum('totalAmount') ?? 0;
});
$totalPaid = $suppliers->sum(function ($customer) {
return $customer->purchases?->sum('paidAmount') ?? 0;
});
$totalDue = $suppliers->sum(function ($customer) {
return $customer->purchases?->sum('dueAmount') ?? 0;
});
if ($request->ajax()) {
return response()->json([
'data' => view('business::party-reports.supplier-ledger.datas', compact('suppliers', 'totalAmount', 'totalPaid', 'totalDue'))->render()
]);
}
return view('business::party-reports.supplier-ledger.index', compact('suppliers', 'totalAmount', 'totalPaid', 'totalDue'));
}
public function show(Request $request, $partyId, PartyLedgerService $service)
{
$party = Party::find($partyId);
$ledger = $service->list($request, $partyId);
if ($request->ajax()) {
return response()->json([
'data' => view('business::party-reports.supplier-ledger.show-details.datas', compact('ledger'))->render()
]);
}
return view('business::party-reports.supplier-ledger.show-details.index', compact('ledger', 'party'));
}
public function exportExcel()
{
return Excel::download(new ExportSupplierLedger, 'supplier-ledger.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportSupplierLedger, 'supplier-ledger.csv');
}
public function exportPdf()
{
$suppliers = Party::with('purchases')
->where('business_id', auth()->user()->business_id)
->where('type', '=', 'Supplier')
->latest()
->get();
return PdfService::render('business::party-reports.supplier-ledger.pdf', compact('suppliers'),'supplier-ledger-report.pdf');
}
public function exportLedgerExcel(Request $request, $partyId, PartyLedgerService $service)
{
$party = Party::findOrFail($partyId);
$ledger = $service->list($request, $partyId);
return Excel::download(new ExportSingleSupplierLedger($ledger, $party), 'single-supplier-ledger.xlsx');
}
public function exportLedgerCsv(Request $request, $partyId, PartyLedgerService $service)
{
$party = Party::findOrFail($partyId);
$ledger = $service->list($request, $partyId);
return Excel::download(new ExportSingleSupplierLedger($ledger, $party), 'single-supplier-ledger.csv');
}
public function exportLedgerPdf(Request $request, $partyId, PartyLedgerService $service)
{
$party = Party::find($partyId);
$ledger = $service->list($request, $partyId);
return PdfService::render(
'business::party-reports.supplier-ledger.show-details.pdf',
compact('ledger', 'party'),
strtolower($party->name).'-supplier.pdf'
);
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Party;
use App\Services\PdfService;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportTopCustomer;
class AcnooTopCustomerController extends Controller
{
public function index(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();
if ($request->ajax()) {
return response()->json([
'data' => view('business::party-reports.top-customers.datas', compact('customers'))->render()
]);
}
return view('business::party-reports.top-customers.index', compact('customers'));
}
public function exportExcel()
{
return Excel::download(new ExportTopCustomer, 'top-customers.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportTopCustomer, 'top-customers.csv');
}
public function exportPdf()
{
$customers = Party::with('sales')
->where('business_id', auth()->user()->business_id)
->where('type', '!=', 'Supplier')
->whereHas('sales')
->withCount('sales')
->withSum('sales', 'totalAmount')
->orderByDesc('sales_count')
->orderByDesc('sales_sum_total_amount')
->take(5)
->get();
return PdfService::render('business::party-reports.top-customers.pdf', compact('customers'),'top-customer-report.pdf');
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Party;
use Illuminate\Http\Request;
class AcnooTopCustomerReportController extends Controller
{
public function index(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();
if ($request->ajax()) {
return response()->json([
'data' => view('business::party-reports.top-customers.datas', compact('customers'))->render()
]);
}
return view('business::party-reports.top-customers.index', compact('customers'));
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\SaleDetails;
use App\Services\PdfService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportTopProduct;
class AcnooTopProductReportController extends Controller
{
public function index(Request $request)
{
$user = auth()->user();
$businessId = $user->business_id;
$branchId = null;
if (moduleCheck('MultiBranchAddon')) {
$branchId = $user->branch_id ?? $user->active_branch_id;
}
$top_products = SaleDetails::query()
->with('product:id,productName,productCode')
->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();
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.top-products.datas', compact('top_products'))->render()
]);
}
return view('business::reports.top-products.index', compact('top_products'));
}
public function exportExcel()
{
return Excel::download(new ExportTopProduct, 'top-products.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportTopProduct, 'top-products.csv');
}
public function exportPdf()
{
$branchId = moduleCheck('MultiBranchAddon') ? auth()->user()->branch_id ?? auth()->user()->active_branch_id : null;
$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();
return PdfService::render('business::reports.top-products.pdf', compact('top_products'),'top-product-report.pdf');
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Party;
use App\Services\PdfService;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportTopSupplier;
class AcnooTopSupplierController extends Controller
{
public function index(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')
->orderByDesc('purchases_sum_total_amount')
->take(5)
->get();
if ($request->ajax()) {
return response()->json([
'data' => view('business::party-reports.top-suppliers.datas', compact('suppliers'))->render()
]);
}
return view('business::party-reports.top-suppliers.index', compact('suppliers'));
}
public function exportExcel()
{
return Excel::download(new ExportTopSupplier, 'top-suppliers.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportTopSupplier, 'top-suppliers.csv');
}
public function exportPdf()
{
$suppliers = Party::with('purchases')
->where('business_id', auth()->user()->business_id)
->where('type', '=', 'Supplier')
->whereHas('purchases')
->withCount('purchases')
->withSum('purchases', 'totalAmount')
->orderByDesc('purchases_count')
->orderByDesc('purchases_sum_total_amount')
->take(5)
->get();
return PdfService::render('business::party-reports.top-suppliers.pdf', compact('suppliers'),'top-supplier-report.pdf');
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Party;
use Illuminate\Http\Request;
class AcnooTopSupplierReportController extends Controller
{
public function index(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')
->orderByDesc('purchases_sum_total_amount')
->take(5)
->get();
if ($request->ajax()) {
return response()->json([
'data' => view('business::party-reports.top-suppliers.datas', compact('suppliers'))->render()
]);
}
return view('business::party-reports.top-suppliers.index', compact('suppliers'));
}
}

View File

@@ -0,0 +1,136 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Party;
use App\Models\Transaction;
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\ExportTransactionReport;
class AcnooTransactionController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function index(Request $request)
{
$businessId = auth()->user()->business_id;
$parties = Party::where('business_id', $businessId)->latest()->get();
$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);
});
});
});
}
// Calculate summary data
$total_tr_amount = (clone $transactionsQuery)->sum('amount');
$total_tr_money_in = (clone $transactionsQuery)
->where('type', 'credit')
->sum('amount');
$total_tr_money_out = (clone $transactionsQuery)
->where('type', 'debit')
->sum('amount');
// Paginate data
$perPage = $request->input('per_page', 20);
$transactions = $transactionsQuery->latest()->paginate($perPage)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::transactions.datas', compact('transactions', 'filter_from_date', 'filter_to_date', 'duration'))->render(),
'total_tr_amount' => currency_format($total_tr_amount, currency: business_currency()),
'total_tr_money_in' => currency_format($total_tr_money_in, currency: business_currency()),
'total_tr_money_out' => currency_format($total_tr_money_out, currency: business_currency()),
]);
}
return view('business::transactions.index', compact('transactions', 'total_tr_amount', 'total_tr_money_in', 'total_tr_money_out', 'parties', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function exportExcel()
{
return Excel::download(new ExportTransactionReport, 'bill-wise-profit.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportTransactionReport, 'bill-wise-profit.csv');
}
public function exportPdf()
{
$transactions = Transaction::with('paymentType')
->where('business_id', auth()->user()->business_id)
->latest()
->get();
return PdfService::render('business::transactions.pdf', compact('transactions'),'transactions-report.pdf');
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\DueCollect;
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;
class AcnooTransactionHistoryReportController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function __construct()
{
$this->middleware('check.permission:transaction-history-reports.read')->only(['index']);
}
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);
// 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);
});
});
}
$perPage = $request->input('per_page', 20);
$transactions = $transactionsQuery->latest()->paginate($perPage)->appends($request->query());
$total_due = $transactionsQuery->sum('totalDue');
$total_paid = $transactionsQuery->sum('payDueAmount');
if ($request->ajax()) {
return response()->json([
'data' => view('business::reports.transaction-history.datas', compact('transactions','total_due', 'total_paid', 'filter_from_date', 'filter_to_date', 'duration'))->render(),
'total_due' => currency_format($total_due, currency: business_currency()),
'total_paid' => currency_format($total_paid, currency: business_currency()),
]);
}
return view('business::reports.transaction-history.transaction-reports', compact('transactions', 'total_due', 'total_paid', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function exportExcel()
{
return Excel::download(new ExportTransaction, 'transaction-history.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportTransaction, 'transaction-history.csv');
}
public function exportPdf()
{
$transactions = DueCollect::with('party:id,name,type', 'payment_type:id,name', 'transactions')->where('business_id', auth()->user()->business_id)
->latest()
->get();
return PdfService::render('business::reports.transaction-history.pdf', compact('transactions'),'due-transactions-report.pdf');
}
}

View File

@@ -0,0 +1,651 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Branch;
use App\Models\Stock;
use App\Models\Transfer;
use App\Models\TransferProduct;
use App\Models\Warehouse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Business\App\Exports\ExportTransfer;
class AcnooTransferController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:transfers.read')->only(['index']);
$this->middleware('check.permission:transfers.create')->only(['create', 'store']);
$this->middleware('check.permission:transfers.update')->only(['edit', 'update']);
$this->middleware('check.permission:transfers.delete')->only(['destroy', 'deleteAll']);
}
public function index(Request $request)
{
$branches = Branch::withTrashed()->where('business_id', auth()->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) {
$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()
->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::transfers.datas', compact('transfers','branches'))->render()
]);
}
return view('business::transfers.index', compact('transfers', 'branches'));
}
public function create()
{
$business_id = auth()->user()->business_id;
$warehouses = Warehouse::where('business_id', $business_id)->latest()->get();
$branches = Branch::withTrashed()->where('business_id', auth()->user()->business_id)->latest()->get();
return view('business::transfers.create', compact('warehouses', 'branches'));
}
public function store(Request $request)
{
$request->validate([
'from_branch_id' => 'nullable|exists:branches,id',
'to_branch_id' => 'nullable|exists:branches,id|required_with:from_branch_id',
'from_warehouse_id' => 'nullable|exists:warehouses,id|required_with:to_warehouse_id',
'to_warehouse_id' => 'nullable|exists:warehouses,id|required_with:from_warehouse_id',
'transfer_date' => 'required|date',
'note' => 'nullable|string',
'status' => 'required|in:pending,completed,cancelled',
'shipping_charge' => 'nullable|numeric|min:0',
'products' => 'required|array|min:1',
'products.*.quantity' => 'required|integer|min:1',
'products.*.unit_price' => 'required|numeric|min:0',
'products.*.discount' => 'nullable|numeric|min:0',
'products.*.tax' => 'nullable|numeric|min:0',
]);
$user = auth()->user();
$fromBranch = $request->from_branch_id ?? $user->branch_id ?? $user->active_branch_id;
$toBranch = $request->to_branch_id;
$fromWh = $request->from_warehouse_id;
$toWh = $request->to_warehouse_id;
// Transfer validation logic
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) {
// 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);
}
DB::beginTransaction();
try {
$subTotal = 0;
$totalDiscount = 0;
$totalTax = 0;
foreach ($request->products as $item) {
$qty = $item['quantity'];
$price = $item['unit_price'];
$discount = $item['discount'] ?? 0;
$tax = $item['tax'] ?? 0;
$subTotal += ($qty * $price);
$totalTax += $tax;
$totalDiscount += $discount;
}
$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,
'from_branch_id' => $fromBranch,
'from_warehouse_id' => $fromWh,
'to_branch_id' => $toBranch,
'to_warehouse_id' => $toWh,
'shipping_charge' => $shipping,
'sub_total' => $subTotal,
'total_discount' => $totalDiscount,
'total_tax' => $totalTax,
'grand_total' => $grandTotal,
]);
$transferProductData = [];
foreach ($request->products as $stockId => $item) {
// Find product_id from stock_id
$stock = Stock::find($stockId);
if (!$stock) {
return response()->json([
'message' => "Invalid stock ID: {$stockId}"
], 400);
}
$transferProductData[] = [
'transfer_id' => $transfer->id,
'stock_id' => $stockId,
'product_id' => $stock->product_id,
'quantity' => $item['quantity'] ?? 0,
'unit_price' => $item['unit_price'] ?? 0,
'discount' => $item['discount'] ?? 0,
'tax' => $item['tax'] ?? 0,
'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();
if (!$fromStock) {
return response()->json([
'message' => "Stock not found in source (branch/warehouse) for stock ID: {$stockId}"
], 400);
}
if ($fromStock->productStock < $item['quantity']) {
return response()->json([
'message' => "Insufficient stock in source for product ID: {$fromStock->product_id}, available: {$fromStock->productStock}"
], 400);
}
// Decrease FROM stock
$fromStock->decrement('productStock', $item['quantity']);
// 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();
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 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'
]);
}
// Skip booted from model
Stock::withoutEvents(function () use ($toStock) {
$toStock->save();
});
}
// Increase TO stock safely
$toStock->increment('productStock', $item['quantity']);
}
}
}
TransferProduct::insert($transferProductData);
DB::commit();
return response()->json([
'message' => __('Transfer saved successfully.'),
'redirect' => route('business.transfers.index'),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => 'Error: ' . $e->getMessage(),
], 500);
}
}
public function edit($id)
{
$user = auth()->user();
$business_id = $user->business_id;
$transfer = Transfer::with(['transferProducts.product', 'transferProducts.stock'])
->where('business_id', $business_id)
->findOrFail($id);
// Determine the branch to filter warehouses
$branchId = $user->branch_id ?? $user->active_branch_id;
$warehousesQuery = Warehouse::where('business_id', $business_id);
if ($branchId) {
$warehousesQuery->where('branch_id', $branchId);
}
$warehouses = $warehousesQuery->latest()->get();
$branches = Branch::withTrashed()
->where('business_id', $business_id)
->latest()
->get();
return view('business::transfers.edit', compact('transfer', 'warehouses', 'branches'));
}
public function update(Request $request, $id)
{
$request->validate([
'from_branch_id' => 'nullable|exists:branches,id',
'to_branch_id' => 'nullable|exists:branches,id|required_with:from_branch_id',
'from_warehouse_id' => 'nullable|exists:warehouses,id|required_with:to_warehouse_id',
'to_warehouse_id' => 'nullable|exists:warehouses,id|required_with:from_warehouse_id',
'transfer_date' => 'required|date',
'note' => 'nullable|string',
'status' => 'required|in:pending,completed,cancelled',
'shipping_charge' => 'nullable|numeric|min:0',
'products' => 'required|array|min:1',
'products.*.quantity' => 'required|integer|min:1',
'products.*.unit_price' => 'required|numeric|min:0',
'products.*.discount' => 'nullable|numeric|min:0',
'products.*.tax' => 'nullable|numeric|min:0',
]);
$transfer = Transfer::findOrFail($id);
if ($request->status == 'cancelled') {
$transfer->update(['status' => 'cancelled']);
return response()->json([
'message' => __('Transfer cancelled successfully.'),
'redirect' => route('business.transfers.index')
]);
}
$user = auth()->user();
$fromBranch = $request->from_branch_id ?? $user->branch_id ?? $user->active_branch_id;
$toBranch = $request->to_branch_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;
foreach ($request->products as $item) {
$subTotal += $item['quantity'] * $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([
'from_branch_id' => $fromBranch,
'to_branch_id' => $toBranch,
'from_warehouse_id' => $fromWh,
'to_warehouse_id' => $toWh,
'transfer_date' => $request->transfer_date,
'note' => $request->note,
'status' => $request->status,
'shipping_charge' => $shipping,
'sub_total' => $subTotal,
'total_discount' => $totalDiscount,
'total_tax' => $totalTax,
'grand_total' => $grandTotal,
]);
// Update TransferProduct
TransferProduct::where('transfer_id', $transfer->id)->delete();
$transferProductData = [];
foreach ($request->products as $stockId => $item) {
$transferProductData[] = [
'transfer_id' => $transfer->id,
'stock_id' => $stockId,
'quantity' => $item['quantity'] ?? 0,
'unit_price' => $item['unit_price'] ?? 0,
'discount' => $item['discount'] ?? 0,
'tax' => $item['tax'] ?? 0,
'created_at' => now(),
'updated_at' => now(),
];
}
TransferProduct::insert($transferProductData);
// 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))
->first();
if (!$fromStock) {
return response()->json([
'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))
->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 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'
]);
}
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']);
}
}
}
}
DB::commit();
return response()->json([
'message' => __('Transfer updated successfully.'),
'redirect' => route('business.transfers.index'),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => 'Error: ' . $e->getMessage(),
], 500);
}
}
public function destroy($id)
{
$business_id = auth()->user()->business_id;
$transfer = Transfer::with('transferProducts')->where('business_id', $business_id)->findOrFail($id);
DB::beginTransaction();
try {
if ($transfer->status == 'completed') {
foreach ($transfer->transferProducts as $tp) {
$stock = Stock::find($tp->stock_id);
// check if destination has enough stock to rollback
$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)
->first();
if (!$toStock || $toStock->quantity < $tp->quantity) {
return response()->json([
'message' => __('Cannot delete. Destination stock not enough to reverse this transfer'),
], 422);
}
}
foreach ($transfer->transferProducts as $tp) {
$stock = Stock::find($tp->stock_id);
// decrease destination stock
$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)
->first();
if ($toStock) {
$toStock->decrement('quantity', $tp->quantity);
}
// increase source stock
$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)
->first();
if ($fromStock) {
$fromStock->increment('quantity', $tp->quantity);
}
}
}
$transfer->delete();
DB::commit();
return response()->json([
'message' => __('Transfer deleted successfully'),
'redirect' => route('business.transfers.index'),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => __('Error while deleting transfer: ') . $e->getMessage(),
], 500);
}
}
public function deleteAll(Request $request)
{
$business_id = auth()->user()->business_id;
DB::beginTransaction();
try {
$transfers = Transfer::with('transferProducts')
->where('business_id', $business_id)
->whereIn('id', $request->ids)
->get();
foreach ($transfers as $transfer) {
if ($transfer->status === 'completed') {
foreach ($transfer->transferProducts as $tp) {
$stock = Stock::find($tp->stock_id);
// check if destination has enough stock to rollback
$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)
->first();
if (!$toStock || $toStock->quantity < $tp->quantity) {
return response()->json([
'message' => __('Cannot delete. Destination stock not enough to reverse transfer ID: ') . $transfer->id,
], 422);
}
}
}
}
foreach ($transfers as $transfer) {
if ($transfer->status === 'completed') {
foreach ($transfer->transferProducts as $tp) {
$stock = Stock::find($tp->stock_id);
// decrease destination stock
$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)
->first();
if ($toStock) {
$toStock->decrement('quantity', $tp->quantity);
}
// increase source stock
$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)
->first();
if ($fromStock) {
$fromStock->increment('quantity', $tp->quantity);
}
}
}
$transfer->delete();
}
DB::commit();
return response()->json([
'message' => __('Selected transfers deleted successfully.'),
'redirect' => route('business.transfers.index'),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => __('Error while deleting transfers: ') . $e->getMessage(),
], 500);
}
}
public function exportExcel()
{
return Excel::download(new ExportTransfer, 'transfer.xlsx');
}
public function exportCsv()
{
return Excel::download(new ExportTransfer, 'transfer.csv');
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Unit;
use Illuminate\Http\Request;
class AcnooUnitController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:units.read')->only(['index']);
$this->middleware('check.permission:units.create')->only(['store']);
$this->middleware('check.permission:units.update')->only(['update', 'status']);
$this->middleware('check.permission:units.delete')->only(['destroy', 'deleteAll']);
}
public function index(Request $request)
{
$units = Unit::where('business_id', auth()->user()->business_id)->when(request('search'), function ($q) {
$q->where(function ($q) {
$q->where('unitName', 'like', '%' . request('search') . '%');
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::units.datas', compact('units'))->render()
]);
}
return view('business::units.index', compact('units'));
}
public function store(Request $request)
{
$request->validate([
'status' => 'required|boolean',
'unitName' => 'required|string|max:255',
]);
Unit::create($request->except('status', 'business_id') + [
'business_id' => auth()->user()->business_id,
'status' => $request->status,
]);
return response()->json([
'message' => __('Unit saved successfully'),
'redirect' => route('business.units.index')
]);
}
public function update(Request $request, $id)
{
$request->validate([
'status' => 'required|boolean',
'unitName' => 'required|string|max:255,' . $id,
]);
$units = Unit::find($id);
$units->update($request->except('status', 'business_id') + [
'business_id' => auth()->user()->business_id,
'status' => $request->status,
]);
return response()->json([
'message' => __('Unit updated successfully'),
'redirect' => route('business.units.index')
]);
}
public function destroy($id)
{
$unit = Unit::findOrFail($id);
$unit->delete();
return response()->json([
'message' => __('Units deleted successfully'),
'redirect' => route('business.units.index')
]);
}
public function deleteAll(Request $request)
{
Unit::whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Unit deleted successfully'),
'redirect' => route('business.units.index')
]);
}
public function status(Request $request, $id)
{
$unit = Unit::findOrFail($id);
$unit->update(['status' => $request->status]);
return response()->json(['message' => __('Unit')]);
}
}

View File

@@ -0,0 +1,113 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Variation;
class AcnooVariationController extends Controller
{
public function index(Request $request)
{
$search = $request->input('search');
$variations = Variation::where('business_id', auth()->user()->business_id)->when($search, function ($q) use ($search) {
$q->where(function ($q) use ($search) {
$q->where('name', 'like', '%' . $search . '%')
->orWhere('values', 'like', '%'.$search.'%');
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::variations.datas', compact('variations'))->render()
]);
}
return view('business::variations.index', compact('variations'));
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'values' => 'required|string',
]);
$values = json_decode($request->values, true);
if (!is_array($values) || !collect($values)->every(fn($v) => is_string($v))) {
return response()->json(['message' => 'The values must be an array of strings.'], 422);
}
Variation::create([
'business_id' => auth()->user()->business_id,
'name' => $request->name,
'values' => $values,
]);
return response()->json([
'message' => __('Variation saved successfully.'),
'redirect' => route('business.variations.index')
]);
}
public function update(Request $request, $id)
{
$variation = Variation::findOrFail($id);
$request->validate([
'name' => 'required|string|max:255',
'values' => 'required|string',
]);
$values = json_decode($request->values, true);
if (!is_array($values) || !collect($values)->every(fn($v) => is_string($v))) {
return response()->json(['message' => 'The values must be an array of strings.'], 422);
}
$variation->update([
'business_id' => auth()->user()->business_id,
'name' => $request->name,
'values' => $values,
]);
return response()->json([
'message' => __('Variation updated successfully.'),
'redirect' => route('business.variations.index')
]);
}
public function destroy($id)
{
Variation::findOrFail($id)->delete();
return response()->json([
'message' => __('Variation deleted successfully'),
'redirect' => route('business.variations.index')
]);
}
public function deleteAll(Request $request)
{
$variations = Variation::whereIn('id', $request->ids)->get();
Variation::whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Selected variations deleted successfully'),
'redirect' => route('business.variations.index')
]);
}
public function status(Request $request, $id)
{
$variation = Variation::findOrFail($id);
$variation->update(['status' => $request->status]);
return response()->json(['message' => __('Variation')]);
}
}

View File

@@ -0,0 +1,266 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Vat;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class AcnooVatController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:vats.read')->only(['index']);
$this->middleware('check.permission:vats.create')->only(['create', 'store']);
$this->middleware('check.permission:vats.update')->only(['edit', 'update', 'status']);
$this->middleware('check.permission:vats.delete')->only(['destroy', 'deleteAll']);
}
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);
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);
if ($request->ajax()) {
return response()->json([
'data' => view('business::vats.datas', compact('vats'))->render()
]);
}
return redirect(url()->previous());
}
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);
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();
return view('business::vat-groups.create',compact('vats'));
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'vat_ids' => 'required_if:rate,null',
'rate' => 'required_if:rate,null|numeric',
]);
// single vat
if ($request->rate && !$request->vat_ids) {
Vat::create($request->all() + [
'business_id' => auth()->user()->business_id,
]);
}
// group vat
elseif (!$request->rate && $request->vat_ids) {
$vats = Vat::whereIn('id', $request->vat_ids)->select('id', 'name', 'rate')->get();
$tax_rate = 0;
$sub_vats = [];
foreach ($vats as $vat) {
$sub_vats[] = [
'id' => $vat->id,
'name' => $vat->name,
'rate' => $vat->rate,
];
$tax_rate += $vat->rate;
}
Vat::create([
'rate' => $tax_rate,
'sub_vat' => $sub_vats,
'name' => $request->name,
'business_id' => auth()->user()->business_id,
]);
} else {
return response()->json([
'message' => 'Invalid data format.',
], 406);
}
return response()->json([
'message' => 'Vat created successfully',
'redirect' => route('business.vats.index'),
]);
}
// 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);
return view('business::vat-groups.edit', compact('vat', 'vats'));
}
public function update(Request $request, Vat $vat)
{
$request->validate([
'name' => 'required|string|max:255',
'vat_ids' => 'required_if:rate,null',
'rate' => 'required_if:rate,null|numeric',
]);
DB::beginTransaction();
try {
// Single VAT update
if ($request->rate && !$request->vat_ids) {
$vat->update($request->only(['name', 'rate', 'status']));
$vatGroupExist = Vat::where('sub_vat', 'LIKE', '%"id":' . $vat->id . '%')->get();
foreach ($vatGroupExist as $group) {
$subVats = collect($group->sub_vat)->map(function ($subVat) use ($vat) {
if ($subVat['id'] == $vat->id) {
$subVat['rate'] = $vat->rate;
$subVat['name'] = $vat->name;
}
return $subVat;
});
$group->update([
'rate' => $subVats->sum('rate'),
'sub_vat' => $subVats->toArray(),
]);
}
}
// Group VAT update
elseif (!$request->rate && $request->vat_ids) {
$vats = Vat::whereIn('id', $request->vat_ids)->select('id', 'name', 'rate')->get();
$tax_rate = 0;
$sub_vats = [];
foreach ($vats as $single_tax) {
$sub_vats[] = [
'id' => $single_tax->id,
'name' => $single_tax->name,
'rate' => $single_tax->rate,
];
$tax_rate += $single_tax->rate;
}
$vat->update([
'rate' => $tax_rate,
'sub_vat' => $sub_vats,
'name' => $request->name,
'status' => $request->status ?? $vat->status,
]);
} else {
DB::rollBack();
return response()->json([
'message' => 'Invalid data format.',
], 406);
}
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);
}
}
public function destroy(Vat $vat)
{
// When sub_vat is null
if (is_null($vat->sub_vat)) {
// Check if this VAT exists in any other VAT's sub_vat
$vatGroupExist = Vat::where('sub_vat', 'LIKE', '%"id":' . $vat->id . '%')->exists();
if ($vatGroupExist) {
return response()->json([
'message' => 'Cannot delete. This VAT is part of a VAT group.',
], 404);
}
}
$vat->delete();
return response()->json([
'message' => 'VAT Deleted Successfully',
'redirect' => route('business.vats.index'),
]);
}
public function status(Request $request, $id)
{
$status = Vat::findOrFail($id);
$status->update(['status' => $request->status]);
return response()->json(['message' => 'Vat']);
}
public function deleteAll(Request $request)
{
$vats = Vat::whereIn('id', $request->ids)->get();
// Filter out VATs that are part of a VAT group when sub_vat is null
$restrictedVats = $vats->filter(function ($vat) {
return is_null($vat->sub_vat) &&
Vat::where('sub_vat', 'LIKE', '%"id":' . $vat->id . '%')->exists();
});
// If there are restricted VATs
if ($restrictedVats->isNotEmpty()) {
return response()->json([
'message' => 'Some VATs cannot be deleted as they are part of a VAT group.',
], 404);
}
Vat::whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Selected items deleted successfully.'),
'redirect' => route('business.vats.index'),
]);
}
}

View File

@@ -0,0 +1,158 @@
<?php
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\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;
class AcnooVatReportController extends Controller
{
use DateFilterTrait, DateRangeTrait;
public function __construct()
{
$this->middleware('check.permission:vat-reports.read')->only(['index']);
}
public function index(Request $request)
{
$businessId = auth()->user()->business_id;
$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);
// Date Filter
$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');
}
$purchasesQuery = Purchase::with('details', 'party', 'details.product', 'details.product.category', 'payment_type:id,name', 'transactions')
->where('business_id', $businessId)
->where('vat_amount', '>', 0);
// Date Filter
$this->applyDateFilter($purchasesQuery, $duration, 'purchaseDate', $request->from_date, $request->to_date);
$purchases = $purchasesQuery->latest()->paginate(20, ['*'], 'purchases');
$purchasesVatTotals = [];
foreach ($vats as $vat) {
$purchasesVatTotals[$vat->id] = (clone $purchasesQuery)->where('vat_id', $vat->id)->sum('vat_amount');
}
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(),
]);
}
return view('business::reports.vats.index', compact('sales', 'salesVatTotals', 'purchases', 'purchasesVatTotals', 'vats', 'filter_from_date', 'filter_to_date', 'duration'));
}
public function exportExcel($type = 'all')
{
return $this->exportFile($type, 'vat-report.xlsx');
}
public function exportCsv($type = 'all')
{
return $this->exportFile($type, 'vat-report.csv');
}
private function exportFile($type, $filename, $format = null)
{
$businessId = auth()->user()->business_id;
$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();
}
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();
}
$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');
}
}

View File

@@ -0,0 +1,399 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Helpers\HasUploader;
use App\Http\Controllers\Controller;
use App\Models\PaymentType;
use App\Models\Transaction;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class BankTransactionController extends Controller
{
use HasUploader;
public function index()
{
$business_id = auth()->user()->business_id;
$payment_type_id = request('payment_type_id');
$payment_type = PaymentType::where('business_id', $business_id)->findOrFail($payment_type_id);
$transactions = Transaction::with('user:id,name', 'branch:id,name')
->where('business_id', $business_id)
->whereDate('date', Carbon::today())
->where(function ($query) use ($payment_type_id) {
$query->where('payment_type_id', $payment_type_id)
->orWhere('from_bank', $payment_type_id)
->orWhere('to_bank', $payment_type_id);
})
->latest()
->paginate(20);
return view('business::banks.transactions.index', compact('transactions', 'payment_type'));
}
public function acnooFilter(Request $request)
{
$payment_type_id = request('payment_type_id');
$transaction = Transaction::with('user:id,name')
->where('business_id', auth()->user()->business_id)
->where(function ($q) use ($payment_type_id) {
$q->where('payment_type_id', $payment_type_id)
->orWhere('from_bank', $payment_type_id)
->orWhere('to_bank', $payment_type_id);
});
// Default to today
$startDate = Carbon::today()->format('Y-m-d');
$endDate = Carbon::today()->format('Y-m-d');
if ($request->custom_days === 'yesterday') {
$startDate = Carbon::yesterday()->format('Y-m-d');
$endDate = Carbon::yesterday()->format('Y-m-d');
} elseif ($request->custom_days === 'last_seven_days') {
$startDate = Carbon::today()->subDays(6)->format('Y-m-d');
} elseif ($request->custom_days === 'last_thirty_days') {
$startDate = Carbon::today()->subDays(29)->format('Y-m-d');
} elseif ($request->custom_days === 'current_month') {
$startDate = Carbon::now()->startOfMonth()->format('Y-m-d');
$endDate = Carbon::now()->endOfMonth()->format('Y-m-d');
} elseif ($request->custom_days === 'last_month') {
$startDate = Carbon::now()->subMonth()->startOfMonth()->format('Y-m-d');
$endDate = Carbon::now()->subMonth()->endOfMonth()->format('Y-m-d');
} elseif ($request->custom_days === 'current_year') {
$startDate = Carbon::now()->startOfYear()->format('Y-m-d');
$endDate = Carbon::now()->endOfYear()->format('Y-m-d');
} elseif ($request->custom_days === 'custom_date' && $request->from_date && $request->to_date) {
$startDate = Carbon::parse($request->from_date)->format('Y-m-d');
$endDate = Carbon::parse($request->to_date)->format('Y-m-d');
}
$transaction->whereDate('date', '>=', $startDate)
->whereDate('date', '<=', $endDate);
$transactions = $transaction->when(request('search'), function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->where('date', '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()
->paginate($request->per_page ?? 20);
if ($request->ajax()) {
return response()->json([
'data' => view('business::banks.transactions.datas', compact('transactions'))->render()
]);
}
return redirect(url()->previous());
}
public function store(Request $request)
{
$request->validate([
'from' => 'required|exists:payment_types,id',
'type' => 'nullable|in:credit,debit',
'transaction_type' => 'required|in:bank_to_bank,bank_to_cash,adjust_bank',
'amount' => 'required|numeric|min:0.01',
'date' => 'nullable|date',
'image' => 'nullable|image|mimes:jpg,png,jpeg,svg',
'note' => 'nullable|string',
]);
$business_id = auth()->user()->business_id;
$amount = $request->amount ?? 0;
$type = 'transfer';
DB::beginTransaction();
try {
$fromBank = PaymentType::find($request->from);
// Prevent transferring to the same bank
if ($request->transaction_type == 'bank_to_bank' && $request->from == $request->to) {
return response()->json([
'message' => 'Cannot transfer between the same bank account.'
], 400);
}
// Update balances based on transaction type
if ($request->transaction_type == 'bank_to_bank') {
$toBank = PaymentType::find($request->to);
if ($fromBank->balance < $amount) {
return response()->json([
'message' => 'Insufficient balance in source bank account.'
], 400);
}
$fromBank->decrement('balance', $amount);
$toBank->increment('balance', $amount);
} elseif ($request->transaction_type == 'bank_to_cash') {
if ($fromBank->balance < $amount) {
return response()->json([
'message' => 'Insufficient balance in selected bank.'
], 400);
}
$fromBank->decrement('balance', $amount);
} elseif ($request->transaction_type == 'adjust_bank') {
$type = $request->type;
if ($type == 'credit' && $fromBank->balance < $amount) {
return response()->json([
'message' => 'Cannot decrease below zero balance.'
], 400);
}
if ($type == 'credit') {
$fromBank->increment('balance', $amount);
} else {
$fromBank->decrement('balance', $amount);
}
}
Transaction::create([
'business_id' => $business_id,
'user_id' => auth()->id(),
'type' => $type,
'platform' => 'bank',
'transaction_type' => $request->transaction_type,
'amount' => $amount,
'from_bank' => $request->from,
'to_bank' => ($request->transaction_type == 'bank_to_bank') ? $request->to : null,
'date' => $request->date ?? now(),
'image' => $request->image ? $this->upload($request, 'image') : NULL,
'note' => $request->note,
]);
DB::commit();
return response()->json([
'message' => __('Transaction completed successfully.'),
'redirect' => route('business.bank-transactions.index', ['payment_type_id' => $request->from]),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => 'Error: ' . $e->getMessage(),
], 500);
}
}
public function update(Request $request, $id)
{
$request->validate([
'from' => 'required|exists:payment_types,id',
'type' => 'nullable|in:credit,debit',
'transaction_type' => 'required|in:bank_to_bank,bank_to_cash,adjust_bank',
'amount' => 'required|numeric|min:0.01',
'date' => 'nullable|date',
'image' => 'nullable|image|mimes:jpg,png,jpeg,svg',
'note' => 'nullable|string',
]);
$transaction = Transaction::findOrFail($id);
$fromBank = PaymentType::find($request->from);
$newAmount = $request->amount;
$newType = $request->transaction_type;
DB::beginTransaction();
try {
// Transaction type Same
if ($transaction->transaction_type === $request->transaction_type) {
if ($newType === 'bank_to_bank') {
$toBank = PaymentType::find($request->to);
// Adjust balance difference
$diff = $newAmount - $transaction->amount;
if ($fromBank->balance < $diff && $diff > 0) {
return response()->json(['message' => 'Insufficient balance.'], 400);
}
$fromBank->decrement('balance', $diff);
$toBank->increment('balance', $diff);
} elseif ($newType === 'bank_to_cash') {
$diff = $newAmount - $transaction->amount;
if ($fromBank->balance < $diff && $diff > 0) {
return response()->json(['message' => 'Insufficient balance.'], 400);
}
$fromBank->decrement('balance', $diff);
} elseif ($newType === 'adjust_bank') {
$bankType = $request->type;
$oldType = $transaction->type;
$oldAmount = $transaction->amount;
if ($bankType === $oldType) {
// Same type: adjust by difference
$diff = $newAmount - $oldAmount;
if ($bankType == 'credit') {
$fromBank->increment('balance', $diff);
} else {
$fromBank->decrement('balance', $diff);
}
} else {
// Different type: reverse old and apply new
if ($oldType == 'credit') $fromBank->decrement('balance', $oldAmount);
else $fromBank->increment('balance', $oldAmount);
if ($bankType == 'credit') $fromBank->increment('balance', $newAmount);
else $fromBank->decrement('balance', $newAmount);
}
}
} // Transaction type changed
else {
// Reverse old transaction completely
if ($transaction->transaction_type === 'bank_to_bank') {
$oldFrom = PaymentType::find($transaction->from_bank);
$oldTo = PaymentType::find($transaction->to_bank);
$oldFrom->increment('balance', $transaction->amount);
$oldTo->decrement('balance', $transaction->amount);
} elseif ($transaction->transaction_type === 'bank_to_cash') {
$oldFrom = PaymentType::find($transaction->from_bank);
$oldFrom->increment('balance', $transaction->amount);
} elseif ($transaction->transaction_type === 'adjust_bank') {
$oldFrom = PaymentType::find($transaction->from_bank);
$transaction->type === 'credit'
? $oldFrom->decrement('balance', $transaction->amount)
: $oldFrom->increment('balance', $transaction->amount);
}
// Apply new transaction
if ($newType === 'bank_to_bank') {
$toBank = PaymentType::find($request->to);
if ($fromBank->balance < $newAmount) {
return response()->json(['message' => 'Insufficient balance.'], 400);
}
$fromBank->decrement('balance', $newAmount);
$toBank->increment('balance', $newAmount);
} elseif ($newType === 'bank_to_cash') {
if ($fromBank->balance < $newAmount) {
return response()->json(['message' => 'Insufficient balance.'], 400);
}
$fromBank->decrement('balance', $newAmount);
} elseif ($newType === 'adjust_bank') {
if ($request->type == 'credit') $fromBank->increment('balance', $newAmount);
else $fromBank->decrement('balance', $newAmount);
}
}
// Update transaction record
$transaction->update([
'type' => $newType === 'adjust_bank' ? $request->type : 'transfer',
'transaction_type' => $newType,
'amount' => $newAmount,
'from_bank' => $request->from,
'to_bank' => $newType === 'bank_to_bank' ? $request->to : null,
'date' => $request->date ?? now(),
'image' => $request->image ? $this->upload($request, 'image', $transaction->image) : $transaction->image,
'note' => $request->note,
]);
DB::commit();
return response()->json([
'message' => __('Transaction updated successfully.'),
'redirect' => route('business.bank-transactions.index', ['payment_type_id' => $request->from]),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json(['message' => 'Error: ' . $e->getMessage()], 500);
}
}
public function destroy(string $id)
{
$transaction = Transaction::findOrFail($id);
DB::beginTransaction();
try {
$fromBank = PaymentType::find($transaction->from_bank);
$toBank = PaymentType::find($transaction->to_bank);
$amount = $transaction->amount;
// Allow only "bank" platform transactions to be deleted
if ($transaction->platform !== 'bank') {
return response()->json([
'message' => 'Cannot delete here, please delete from ' . ucfirst($transaction->platform) . ' section.',
], 400);
}
// Reverse balance changes based on transaction type
switch ($transaction->transaction_type) {
case 'bank_to_bank':
if ($toBank && $fromBank) {
// Ensure receiver bank has enough balance before reversing
if ($toBank->balance < $amount) {
return response()->json([
'message' => 'Insufficient balance in ' . $toBank->name . ' to reverse this transaction.',
], 400);
}
$fromBank->increment('balance', $amount);
$toBank->decrement('balance', $amount);
}
break;
case 'bank_to_cash':
if ($fromBank) {
$fromBank->increment('balance', $amount);
}
break;
case 'adjust_bank':
if ($fromBank) {
if ($transaction->type === 'credit') {
// Previously increased, so now decrease
if ($fromBank->balance < $amount) {
return response()->json([
'message' => 'Insufficient balance in ' . $fromBank->name . ' to reverse this transaction.',
], 400);
}
$fromBank->decrement('balance', $amount);
} else {
// Previously decreased, so now increase
$fromBank->increment('balance', $amount);
}
}
break;
}
if (file_exists($transaction->image)) {
Storage::delete($transaction->image);
}
$transaction->delete();
DB::commit();
return response()->json([
'message' => __('Transaction reversed and deleted successfully.'),
'redirect' => url()->previous(),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => 'Error: ' . $e->getMessage(),
], 500);
}
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use AgeekDev\Barcode\Facades\Barcode;
use AgeekDev\Barcode\Enums\Type;
use App\Http\Controllers\Controller;
use App\Models\Product;
use App\Models\Stock;
use Illuminate\Http\Request;
class BarcodeGeneratorController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:barcodes.read')->only(['index']);
$this->middleware('check.permission:barcodes.create')->only(['store']);
}
public function index()
{
$barcode_types = array_map(
fn($case) => ['value' => $case->value],
Type::cases()
);
$products = Product::where('business_id', auth()->user()->business_id)->latest()->get();
return view('business::barcode-generators.index', compact('products', 'barcode_types'));
}
public function fetchProducts(Request $request)
{
$products = Product::with('stocks')->where('business_id', auth()->user()->business_id)
->when(!empty($request->search), function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->where('productName', 'like', '%' . $request->search . '%')
->orWhere('productCode', 'like', '%' . $request->search . '%');
});
})
->withSum('stocks', 'productStock')
->limit(5)
->get();
return response()->json($products);
}
public function store(Request $request)
{
$request->validate([
'barcode_setting' => 'required|in:1,2,3',
]);
$barcodeType = $request->input('barcode_type', Type::TYPE_CODE_128->value);
$stockIds = $request->input('stock_ids', []);
$quantities = $request->input('qty', []);
$previewDates = $request->input('preview_date', []);
$selectedVatType = $request->vat_type;
if (empty($stockIds)) {
return response()->json(['message' => __('Please select at least one product.')], 400);
}
$stocks = Stock::with('product.vat')->whereIn('id', $stockIds)->get();
$generatedBarcodes = [];
foreach ($stockIds as $index => $stockId) {
$stock = $stocks->firstWhere('id', $stockId);
if (!$stock || !$stock->product) {
continue; // Skip if stock or product not found
}
$product = $stock->product;
$qty = $quantities[$index] ?? 1;
$previewDate = $previewDates[$index] ?? null;
// VAT Logic
$basePrice = $stock->productSalePrice ?? 0;
$currentVatType = $product->vat_type;
if ($request->product_price && $selectedVatType !== $currentVatType) {
$vatRate = optional($product->vat)->rate ?? 0;
if ($selectedVatType === 'inclusive') {
// Convert from exclusive to inclusive
$basePrice = $basePrice + (($vatRate / 100) * $basePrice);
} elseif ($selectedVatType === 'exclusive') {
// Convert from inclusive to exclusive
$basePrice = $basePrice / (1 + ($vatRate / 100));
}
}
// if product code missing then take product name
$code = $product->productCode ?: $product->productName;
$code = trim($code) !== '' ? $code : ('UNKNOWN-' . $product->id);
$barcodeSvg = Barcode::imageType("png")
->type(Type::from($barcodeType))
->generate($code);
// Generate copies
for ($copyIndex = 0; $copyIndex < $qty; $copyIndex++) {
$generatedBarcodes[] = [
'barcode_svg' => $barcodeSvg,
'packing_date' => $previewDate,
'product_name' => $product->productName,
'business_name' => $product->business->companyName ?? '',
'product_code' => $code,
'product_price' => $basePrice,
'product_stock' => $stock->productStock ?? 0,
'show_product_name' => $request->product_name,
'product_name_size' => $request->product_name_size,
'show_business_name' => $request->business_name,
'business_name_size' => $request->business_name_size,
'show_product_price' => $request->product_price,
'product_price_size' => $request->product_price_size,
'show_product_code' => $request->product_code,
'product_code_size' => $request->product_code_size,
'show_pack_date' => $request->pack_date,
'pack_date_size' => $request->pack_date_size,
];
}
}
session(['generatedBarcodes' => $generatedBarcodes]);
session()->put('printer', $request->barcode_setting);
return response()->json([
'redirect' => route('business.barcodes.index'),
'secondary_redirect_url' => route('business.barcodes.preview'),
]);
}
public function preview()
{
$printer = session('printer');
$generatedBarcodes = session('generatedBarcodes');
session()->forget('printer');
session()->forget('generatedBarcodes');
return view('business::barcode-generators.print', compact('generatedBarcodes', 'printer'));
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use Illuminate\Http\Request;
use App\Imports\ProductImport;
use App\Http\Controllers\Controller;
use Maatwebsite\Excel\Facades\Excel;
class BulkUploadController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:bulk-uploads.read')->only(['index']);
$this->middleware('check.permission:bulk-uploads.create')->only(['store']);
}
public function index()
{
return view('business::bulk-uploads.index');
}
public function store(Request $request)
{
$request->validate([
'file' => 'required|file|mimes:xlsx,xls,csv'
]);
$businessId = auth()->user()->business_id;
Excel::import(new ProductImport($businessId), $request->file('file'));
return response()->json([
'message' => __('Bulk upload successfully.'),
'redirect' => route('business.products.index')
]);
}
}

View File

@@ -0,0 +1,180 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Stock;
use Gloudemans\Shoppingcart\Exceptions\InvalidRowIDException;
use Gloudemans\Shoppingcart\Facades\Cart;
use Illuminate\Http\Request;
class CartController extends Controller
{
public function index()
{
$cart_contents = Cart::content()->filter(fn($item) => $item->options->type == 'sale');
$stockIds = $cart_contents->pluck('options.stock_id')->filter()->unique();
$batchNos = Stock::whereIn('id', $stockIds)->pluck('batch_no', 'id');
foreach ($cart_contents as $cartItem) {
$stockId = $cartItem->options->stock_id ?? null;
if ($stockId && isset($batchNos[$stockId])) {
$newOptions = $cartItem->options->merge([
'batch_no' => $batchNos[$stockId],
]);
Cart::update($cartItem->rowId, [
'qty' => $cartItem->qty,
'options' => $newOptions,
]);
}
}
$modules = product_setting()->modules ?? [];
return view('business::sales.cart-list', compact('cart_contents', 'modules'));
}
public function store(Request $request)
{
$this->validate($request, [
'stock_id' => 'nullable|exists:stocks,id',
'warehouse_id' => 'nullable|exists:warehouses,id',
'type' => 'nullable|string|in:sale,purchase',
'id' => 'required|integer',
'name' => 'required|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',
'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',
]);
// 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,
}
)->first();
if ($existingCartItem) {
$newQty = $existingCartItem->qty + $request->quantity;
Cart::update($existingCartItem->rowId, ['qty' => $newQty]);
} else {
// Add new item to cart
$mainItemData = [
'id' => $request->id,
'name' => $request->name,
'qty' => $request->quantity,
'price' => $request->price,
'options' => [
'type' => $request->type,
'product_code' => $request->product_code,
'product_unit_id' => $request->product_unit_id,
'product_unit_name' => $request->product_unit_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,
'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,
]
];
Cart::add($mainItemData);
}
return response()->json([
'success' => true,
'message' => 'Item added to cart successfully.'
]);
}
public function update(Request $request, $id)
{
$cart = Cart::get($id);
if (!$cart) {
return response()->json(['success' => false, 'message' => __('Item not found in cart')]);
}
$qty = $request->qty ?? $cart->qty;
if ($qty < 0) {
return response()->json(['success' => false, 'message' => __('Enter a valid quantity')]);
}
Cart::update($id, [
'qty' => $qty,
'price' => $request->price ?? $cart->price,
'options' => [
'type' => $cart->options->type,
'expire_date' => $request->expire_date ?? $cart->options->expire_date,
'stock_id' => $request->stock_id ?? $cart->options->stock_id,
'batch_no' => $request->batch_no ?? $cart->options->batch_no,
'product_code' => $cart->options->product_code,
'product_unit_id' => $cart->options->product_unit_id,
'product_unit_name' => $cart->options->product_unit_name,
'product_image' => $cart->options->product_image,
'sales_price' => $cart->options->sales_price,
'discount' => $request->discount ?? $cart->options->discount,
'whole_sale_price' => $cart->options->whole_sale_price,
'dealer_price' => $cart->options->dealer_price,
'purchase_price' => $cart->options->purchase_price,
'product_type' => $cart->options->product_type,
'warehouse_id' => $cart->options->warehouse_id,
'variant_name' => $cart->options->variant_name,
'vat_percent' => $cart->options->vat_percent,
]
]);
return response()->json([
'success' => true,
'message' => __('Cart updated successfully')
]);
}
public function destroy($id)
{
try {
Cart::remove($id);
return response()->json(['success' => true, 'message' => __('Item removed from cart')]);
} catch (InvalidRowIDException $e) {
return response()->json(['success' => false, 'message' => __('The cart does not contain this item')]);
}
}
public function removeAllCart(Request $request)
{
$carts = Cart::content();
if ($carts->count() < 1) {
return response()->json(['message' => __('Cart is empty. Add items first!')]);
}
Cart::destroy();
$response = [
'success' => true,
'message' => __('All cart removed successfully!'),
];
return response()->json($response);
}
}

View File

@@ -0,0 +1,244 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\Business;
use Carbon\Carbon;
use App\Models\Sale;
use App\Models\Party;
use App\Models\Income;
use App\Models\Expense;
use App\Models\Product;
use App\Models\Purchase;
use App\Models\SaleReturn;
use App\Models\PurchaseReturn;
use App\Models\SaleReturnDetails;
use App\Http\Controllers\Controller;
use App\Models\PurchaseReturnDetail;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class DashboardController extends Controller
{
public function index()
{
$businessId = auth()->user()->business_id;
$allProducts = Product::with('stocks')
->withSum('stocks', 'productStock')
->where('business_id', $businessId)
->latest()
->get();
$stocks = $allProducts->filter(function ($product) {
return $product->stocks->sum('productStock') <= $product->alert_qty;
})->take(5);
// Latest sales
$sales = Sale::with('party:id,name', 'details')
->where('business_id', $businessId)
->latest()
->limit(5)
->get();
// Latest purchases
$purchases = Purchase::with('details', 'party:id,name')
->where('business_id', $businessId)
->latest()
->limit(5)
->get();
return view('business::dashboard.index', compact('stocks', 'purchases', 'sales'));
}
public function getDashboardData()
{
$businessId = auth()->user()->business_id;
$data['total_sales'] = currency_format(Sale::where('business_id', $businessId)->sum('totalAmount'), currency: business_currency(), abbreviate: true);
$data['this_month_total_sales'] = currency_format(Sale::where('business_id', $businessId)
->whereMonth('created_at', Carbon::now()->month)
->whereYear('created_at', Carbon::now()->year)
->sum('totalAmount'), currency: business_currency(), abbreviate: true);
$data['total_purchase'] = currency_format(Purchase::where('business_id', $businessId)->sum('totalAmount'), currency: business_currency(), abbreviate: true);
$data['this_month_total_purchase'] = currency_format(Purchase::where('business_id', $businessId)
->whereMonth('created_at', Carbon::now()->month)
->whereYear('created_at', Carbon::now()->year)
->sum('totalAmount'), currency: business_currency(), abbreviate: true);
// Get total and monthly lossProfit
$sale_loss_profit = Sale::where('business_id', auth()->user()->business_id)->sum('lossProfit');
$this_month_loss_profit = Sale::where('business_id', auth()->user()->business_id)
->whereMonth('created_at', Carbon::now()->month)
->whereYear('created_at', Carbon::now()->year)
->sum('lossProfit');
// Get total income and expense
$total_income = Income::where('business_id', $businessId)->sum('amount');
$this_month_total_income = Income::where('business_id', $businessId)
->whereMonth('created_at', Carbon::now()->month)
->whereYear('created_at', Carbon::now()->year)
->sum('amount');
$total_expense = Expense::where('business_id', $businessId)->sum('amount');
$this_month_total_expense = Expense::where('business_id', $businessId)
->whereMonth('created_at', Carbon::now()->month)
->whereYear('created_at', Carbon::now()->year)
->sum('amount');
// Update income and expense based on lossProfit value
$total_income += $sale_loss_profit > 0 ? $sale_loss_profit : 0;
$total_expense += $sale_loss_profit < 0 ? abs($sale_loss_profit) : 0;
$this_month_total_income += $this_month_loss_profit > 0 ? $this_month_loss_profit : 0;
$this_month_total_expense += $this_month_loss_profit < 0 ? abs($this_month_loss_profit) : 0;
// Format data for display
$data['total_income'] = currency_format($total_income, currency: business_currency(), abbreviate: true);
$data['this_month_total_income'] = currency_format($this_month_total_income, currency: business_currency(), abbreviate: true);
$data['total_expense'] = currency_format($total_expense, currency: business_currency(), abbreviate: true);
$data['this_month_total_expense'] = currency_format($this_month_total_expense, currency: business_currency(), abbreviate: true);
$data['total_customer'] = Party::where('business_id', $businessId)->where('type', '!=', 'Supplier')->count();
$data['this_month_total_customer'] = Party::where('business_id', $businessId)
->where('type', '!=', 'Supplier')
->whereMonth('created_at', Carbon::now()->month)
->whereYear('created_at', Carbon::now()->year)
->count();
$data['total_supplier'] = Party::where('business_id', $businessId)->whereType('Supplier')->count();
$data['this_month_total_supplier'] = Party::where('business_id', $businessId)
->whereType('Supplier')
->whereMonth('created_at', Carbon::now()->month)
->whereYear('created_at', Carbon::now()->year)
->count();
$sale_return_id = SaleReturn::where('business_id', $businessId)
->pluck('id');
$data['total_sales_return'] = currency_format(SaleReturnDetails::whereIn('sale_return_id', $sale_return_id)
->sum('return_amount'), currency: business_currency(), abbreviate: true);
$saleReturns = SaleReturn::where('business_id', $businessId)
->whereYear('return_date', now()->year)
->whereMonth('return_date', now()->month)
->pluck('id');
$data['this_month_total_sale_return'] = currency_format(SaleReturnDetails::whereIn('sale_return_id', $saleReturns)
->sum('return_amount'), currency: business_currency(), abbreviate: true);
$purchase_return_id = PurchaseReturn::where('business_id', $businessId)
->pluck('id');
$data['total_purchase_return'] = currency_format(PurchaseReturnDetail::whereIn('purchase_return_id', $purchase_return_id)
->sum('return_amount'), currency: business_currency(), abbreviate: true);
$purchaseReturns = PurchaseReturn::where('business_id', $businessId)
->whereYear('return_date', now()->year)
->whereMonth('return_date', now()->month)
->pluck('id');
$data['this_month_total_purchase_return'] = currency_format(PurchaseReturnDetail::whereIn('purchase_return_id', $purchaseReturns)
->sum('return_amount'), currency: business_currency(), abbreviate: true);
return response()->json($data);
}
public function overall_report()
{
$businessId = auth()->user()->business_id;
// Calculate overall values
$overall_purchase = Purchase::where('business_id', $businessId)
->whereYear('created_at', request('year') ?? date('Y'))
->sum('totalAmount');
$overall_sale = Sale::where('business_id', $businessId)
->whereYear('created_at', request('year') ?? date('Y'))
->sum('totalAmount');
$overall_income = Income::where('business_id', $businessId)
->whereYear('created_at', request('year') ?? date('Y'))
->sum('amount');
$overall_expense = Expense::where('business_id', $businessId)
->whereYear('created_at', request('year') ?? date('Y'))
->sum('amount');
// Get the total loss/profit for the month
$sale_loss_profit = Sale::where('business_id', $businessId)
->whereYear('created_at', request('year') ?? date('Y'))
->sum('lossProfit');
// Update income and expense based on lossProfit value
$overall_income += $sale_loss_profit > 0 ? $sale_loss_profit : 0;
$overall_expense += $sale_loss_profit < 0 ? abs($sale_loss_profit) : 0;
$data = [
'overall_purchase' => $overall_purchase,
'overall_sale' => $overall_sale,
'overall_income' => $overall_income,
'overall_expense' => $overall_expense,
];
return response()->json($data);
}
public function revenue()
{
$data['loss'] = Sale::where('business_id', auth()->user()->business_id)
->whereYear('created_at', request('year') ?? date('Y'))
->where('lossProfit', '<', 0)
->selectRaw('MONTHNAME(created_at) as month, SUM(ABS(lossProfit)) as total')
->orderBy('created_at')
->groupBy('created_at')
->get();
$data['profit'] = Sale::where('business_id', auth()->user()->business_id)
->whereYear('created_at', request('year') ?? date('Y'))
->where('lossProfit', '>=', 0)
->selectRaw('MONTHNAME(created_at) as month, SUM(ABS(lossProfit)) as total')
->orderBy('created_at')
->groupBy('created_at')
->get();
return response()->json($data);
}
public function updateExpireDate(Request $request)
{
$days = $request->query('days', 0);
$operation = $request->query('operation');
$business = Business::where('id', auth()->user()->business_id)->first();
if (!$business) {
return response()->json([
'message' => 'Business not found.',
], 404);
}
if ($operation === 'add') {
$business->will_expire = now()->addDays($days);
} elseif ($operation === 'sub') {
$business->will_expire = now()->subDays($days);
} else {
return response()->json([
'message' => 'Invalid operation. Use "add" or "sub".',
], 400);
}
$business->save();
Cache::forget("plan-data-{$business->id}");
return response()->json([
'message' => 'Expiry date updated successfully.',
'will_expire' => $business->will_expire,
]);
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Helpers\HasUploader;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
class ProfileController extends Controller
{
use HasUploader;
public function index()
{
$user = User::where('business_id', auth()->user()->business_id)->with('business:id,remainingShopBalance,shopOpeningBalance')->first();
return view('business::profile.index', compact('user'));
}
public function update(Request $request, string $id)
{
$user = User::findOrFail($id);
$request->validate([
'companyName' => [
$user->hasRole('shop-owner') ? 'required' : 'nullable',
'string',
'max:255'
],
'email' => 'required|email',
'image' => 'nullable|image',
]);
if ($request->password || $request->current_password) {
if (Hash::check($request->current_password, $user->password)) {
$request->validate([
'current_password' => 'required|string',
'password' => 'required|string|confirmed',
]);
} else {
return response()->json(__('Current Password does not match with old password'), 404);
}
}
$user->update($request->except('image', 'password') + [
'image' => $request->image ? $this->upload($request, 'image', $user->image) : $user->image,
] + ($request->password ? ['password' => Hash::make($request->password)] : []) // If password is provided, hash it and update
);
if ($request->companyName){
$user->business->update($request->only('companyName'));
}
return response()->json([
'message' => __('Profile updated successfully'),
'redirect' => route('business.profiles.index'),
]);
}
}

View File

@@ -0,0 +1,236 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Events\MultiPaymentProcessed;
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;
class PurchaseReturnController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:purchase-returns.create')->only(['create', 'store']);
$this->middleware('check.permission:purchase-returns.read')->only(['index']);
}
public function index(Request $request)
{
$purchases = Purchase::with([
'user:id,name',
'branch:id,name',
'party:id,name,email,phone,type',
'details:id,purchase_id,product_id,productPurchasePrice,quantities',
'details.product:id,productName,category_id',
'details.product.category:id,categoryName',
'purchaseReturns' => function ($query) {
$query->withSum('details as total_return_amount', 'return_amount')->with('branch:id,name');
}
])
->where('business_id', auth()->user()->business_id)
->whereHas('purchaseReturns')
->when($request->branch_id, function ($q) use ($request) {
$q->where('branch_id', $request->branch_id);
})
->when(request('search'), function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->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 . '%');
});
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::purchase-returns.datas', compact('purchases'))->render()
]);
}
$branches = Branch::withTrashed()->where('business_id', auth()->user()->business_id)->latest()->get();
return view('business::purchase-returns.index', compact('purchases', 'branches'));
}
public function create(Request $request)
{
$business_id = auth()->user()->business_id;
$purchase = Purchase::with('details', 'party', 'details.product', 'details.product.unit:id,unitName')
->where('business_id', $business_id)
->findOrFail($request->purchase_id);
// Calculate the discount per unit factor
$total_purchase_price = $purchase->details->sum(fn($detail) => $detail->productPurchasePrice * $detail->quantities);
$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)
{
$request->validate([
'purchase_id' => 'required|exists:purchases,id',
'return_qty' => 'required|array',
]);
DB::beginTransaction();
try {
$purchase = Purchase::with('details')
->where('business_id', auth()->user()->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);
$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,
'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];
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}.",
], 400);
}
// Calculate per-unit discount and return amounts
$unit_discount = $detail->productPurchasePrice * $discount_per_unit_factor;
$return_discount = $unit_discount * $requested_qty;
$return_amount = ($detail->productPurchasePrice - $unit_discount) * $requested_qty;
$total_return_amount += $return_amount;
$total_return_discount += $return_discount;
// Update stock & purchase details
Stock::where('id', $detail->stock_id)->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,
];
}
// Insert purchase return details
if (!empty($purchase_return_detail_data)) {
PurchaseReturnDetail::insert($purchase_return_detail_data);
}
if ($total_return_amount <= 0) {
return response()->json("You cannot return an empty product.", 400);
}
// Update party dues (if applicable)
$party = Party::find($purchase->party_id);
if ($party) {
$refund_amount = $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]);
}
}
// Any remaining amount should be deducted from wallet
if ($refund_amount > 0 && $party->wallet > 0) {
$deduct = min($party->wallet, $refund_amount);
$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,
'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();
event(new MultiPaymentProcessed(
$payments,
$purchase_return->id,
'purchase_return',
$total_return_amount ?? 0,
));
DB::commit();
return response()->json([
'message' => __('Purchase returned successfully.'),
'redirect' => route('business.purchase-returns.index'),
'secondary_redirect_url' => route('business.purchases.invoice', $purchase->id),
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => __('Something went wrong!')], 500);
}
}
}

View File

@@ -0,0 +1,305 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Events\MultiPaymentProcessed;
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 Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
class SaleReturnController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:sale-returns.create')->only(['create', 'store']);
$this->middleware('check.permission:sale-returns.read')->only(['index']);
}
public function index(Request $request)
{
$sales = Sale::with([
'user:id,name',
'party:id,name',
'details',
'branch:id,name',
'details.product:id,productName,category_id',
'details.product.category:id,categoryName',
'saleReturns' => function ($query) {
$query->withSum('details as total_return_amount', 'return_amount')
->with('branch:id,name');
}
])
->where('business_id', auth()->user()->business_id)
->whereHas('saleReturns')
->when($request->branch_id, function ($q) use ($request) {
$q->where('branch_id', $request->branch_id);
})
->when(request('search'), function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->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 . '%');
});
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::sale-returns.datas', compact('sales'))->render()
]);
}
$branches = Branch::withTrashed()->where('business_id', auth()->user()->business_id)->latest()->get();
return view('business::sale-returns.index', compact('sales', 'branches'));
}
public function create(Request $request)
{
$business_id = auth()->user()->business_id;
$sale = Sale::with('details', 'party', 'details.product', 'details.stock', 'details.product.unit:id,unitName')
->where('business_id', $business_id)
->findOrFail($request->sale_id);
// Calculate the discount per unit factor
$total_sale_price = $sale->details->sum(fn($detail) => $detail->price * $detail->quantities);
$discount_per_unit_factor = $total_sale_price > 0 ? $sale->discountAmount / $total_sale_price : 0;
$avg_rounding_amount = $sale->details->sum('quantities') > 0 ? $sale->rounding_amount / $sale->details->sum('quantities') : 0;
$payment_types = PaymentType::where('business_id', $business_id)->whereStatus(1)->latest()->get();
return view('business::sale-returns.create', compact('sale', 'discount_per_unit_factor', 'avg_rounding_amount', 'payment_types'));
}
public function store(Request $request)
{
$request->validate([
'sale_id' => 'required|exists:sales,id',
'return_qty' => 'required|array',
]);
$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);
// Calculate total discount factor with itemwise discount
$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;
$sale_return = SaleReturn::create([
'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];
if ($requested_qty <= 0) {
continue;
}
if ($requested_qty > $detail->quantities) {
return response()->json([
'message' => "You can't return more than ordered quantity of {$detail->quantities}.",
], 400);
}
$product = $detail->product;
// 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;
$return_discount = $total_discount_per_unit * $requested_qty;
$return_amount = ($detail->price - $total_discount_per_unit + $rounding_amount_per_unit) * $requested_qty;
$total_return_amount += $return_amount;
$total_return_discount += $return_discount;
if ($product && $product->product_type === 'combo') {
$combo_total_purchase = 0;
$combo_total_sale = $detail->price * $requested_qty;
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;
}
else {
$stock = Stock::where('id', $detail->stock_id)->first();
if (!$stock) {
return response()->json(['error' => 'Stock not found.'], 404);
}
$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
$detail->quantities -= $requested_qty;
$detail->lossProfit -= $loss_profit_adjustment;
$detail->timestamps = false;
$detail->save();
$sale_return_detail_data[] = [
'business_id' => $business_id,
'sale_detail_id' => $detail->id,
'sale_return_id' => $sale_return->id,
'return_qty' => $requested_qty,
'return_amount' => $return_amount,
];
}
if (!empty($sale_return_detail_data)) {
SaleReturnDetails::insert($sale_return_detail_data);
}
if ($total_return_amount <= 0) {
return response()->json("You cannot return an empty product.", 400);
}
$remaining_refund = $total_return_amount;
// Adjust Due
$new_due = $sale->dueAmount;
if ($remaining_refund > 0) {
if ($remaining_refund >= $sale->dueAmount) {
$remaining_refund -= $sale->dueAmount;
$new_due = 0;
} else {
$new_due = $sale->dueAmount - $remaining_refund;
$remaining_refund = 0;
}
}
// Adjust Paid (refund part)
$new_paid = $sale->paidAmount;
if ($remaining_refund > 0) {
if ($remaining_refund >= $sale->paidAmount) {
$remaining_refund -= $sale->paidAmount;
$new_paid = 0;
} else {
$new_paid = $sale->paidAmount - $remaining_refund;
$remaining_refund = 0;
}
}
// total sale amount
$new_total_amount = max(0, $sale->totalAmount - $total_return_amount);
$sale->update([
'change_amount' => 0,
'dueAmount' => $new_due,
'paidAmount' => $new_paid,
'totalAmount' => $new_total_amount,
'actual_total_amount' => $new_total_amount,
'discountAmount' => max(0, $sale->discountAmount - $total_return_discount),
'lossProfit' => $sale->lossProfit - $total_loss_profit_adjustment,
]);
// Party Refund Logic
$party = Party::find($sale->party_id);
if ($party) {
// use leftover refund after due/paid adjustments
$refund_amount = $remaining_refund;
// Reduce party due
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]);
}
}
// Add leftover refund to wallet
if ($refund_amount > 0) {
$party->increment('wallet', $refund_amount);
}
}
$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();
event(new MultiPaymentProcessed(
$payments,
$sale_return->id,
'sale_return',
$total_return_amount ?? 0,
));
DB::commit();
return response()->json([
'message' => __('Sale returned successfully.'),
'redirect' => route('business.sale-returns.index'),
'secondary_redirect_url' => route('business.sales.invoice', $sale->id),
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => __('Something went wrong!')], 500);
}
}
}

View File

@@ -0,0 +1,164 @@
<?php
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 Illuminate\Support\Facades\Cache;
class SettingController extends Controller
{
use HasUploader;
public function index()
{
$setting = Option::where('key', 'business-settings')
->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);
return view('business::settings.general', compact('setting', 'business_categories', 'business'));
}
public function update(Request $request, $id)
{
$request->validate([
'address' => 'nullable|max:250',
'email' => 'nullable|email|max:255',
'companyName' => 'required|max:250',
'business_category_id' => 'required|exists:business_categories,id',
'phoneNumber' => 'nullable|min:5|max:15',
'vat_name' => 'nullable|max:250',
'vat_no' => 'nullable|max:250|required_with:vat_name',
'a4_invoice_logo' => 'nullable|image',
'thermal_invoice_logo' => 'nullable|image',
'invoice_scanner_logo' => 'nullable|image',
'sale_rounding_option' => 'nullable|in:none,round_up,nearest_whole_number,nearest_0.05,nearest_0.1,nearest_0.5',
'product_profit_option' => 'nullable|in:markup,margin',
'note' => 'nullable|string|max:250',
'note_label' => 'nullable|string|max:250',
'gratitude_message' => 'nullable|string|max:250',
'show_company_name' => 'nullable|boolean',
'show_phone_number' => 'nullable|boolean',
'show_address' => 'nullable|boolean',
'show_email' => 'nullable|boolean',
'show_vat' => 'nullable|boolean',
'show_note' => 'nullable|boolean',
'show_gratitude_msg' => 'nullable|boolean',
'show_invoice_scanner_logo' => 'nullable|boolean',
'show_a4_invoice_logo' => 'nullable|boolean',
'show_thermal_invoice_logo' => 'nullable|boolean',
]);
DB::beginTransaction();
try {
$business = Business::findOrFail(auth()->user()->business_id);
$business->update([
'address' => $request->address,
'companyName' => $request->companyName,
'business_category_id' => $request->business_category_id,
'phoneNumber' => $request->phoneNumber,
'email' => $request->email,
'vat_name' => $request->vat_name,
'vat_no' => $request->vat_no,
]);
$moduleKeys = [
'show_company_name',
'show_phone_number',
'show_address',
'show_email',
'show_vat',
];
$modules = [];
foreach ($moduleKeys as $key) {
$modules[$key] = $request->has($key) ? 1 : 0;
}
$business->update([
'meta' => $modules
]);
$data = $request->except('_token', '_method', 'logo', 'favicon', 'a4_invoice_logo', 'thermal_invoice_logo', 'invoice_scanner_logo', 'address', 'companyName', 'business_category_id', 'phoneNumber');
$setting = Option::find($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') + [
'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),
'invoice_scanner_logo' => $request->invoice_scanner_logo ? $this->upload($request, 'invoice_scanner_logo', $setting->value['invoice_scanner_logo'] ?? null) : ($setting->value['invoice_scanner_logo'] ?? null),
'sale_rounding_option' => $request->sale_rounding_option ?? 'none',
'product_profit_option' => $request->product_profit_option,
'note' => $request->note,
'note_label' => $request->note_label,
'gratitude_message' => $request->gratitude_message,
'vat_name' => $request->vat_name,
'vat_no' => $request->vat_no,
'show_note' => $request->has('show_note') ? 1 : 0,
'show_gratitude_msg' => $request->has('show_gratitude_msg') ? 1 : 0,
'show_invoice_scanner_logo' => $request->has('show_invoice_scanner_logo') ? 1 : 0,
'show_a4_invoice_logo' => $request->has('show_a4_invoice_logo') ? 1 : 0,
'show_thermal_invoice_logo' => $request->has('show_thermal_invoice_logo') ? 1 : 0,
'warranty_void_label' => $request->warranty_void_label,
'show_warranty' => $request->has('show_warranty') ? 1 : 0,
'warranty_void' => $request->warranty_void,
],
]);
} else {
Option::insert([
'key' => 'business-settings',
'value' => json_encode([
'business_id' => $business->id,
'a4_invoice_logo' => $request->a4_invoice_logo ? $this->upload($request, 'a4_invoice_logo') : null,
'thermal_invoice_logo' => $request->thermal_invoice_logo ? $this->upload($request, 'thermal_invoice_logo') : null,
'invoice_scanner_logo' => $request->invoice_scanner_logo ? $this->upload($request, 'invoice_scanner_logo') : null,
'sale_rounding_option' => $request->sale_rounding_option ?? 'none',
'product_profit_option' => $request->product_profit_option,
'note' => $request->note,
'note_label' => $request->note_label,
'gratitude_message' => $request->gratitude_message,
'vat_name' => $request->vat_name,
'vat_no' => $request->vat_no,
'warranty_void_label' => $request->warranty_void_label,
'warranty_void' => $request->warranty_void,
'show_note' => 1,
'show_gratitude_msg' => 1,
'show_invoice_scanner_logo' => 1,
'show_a4_invoice_logo' => 1,
'show_thermal_invoice_logo' => 1,
'show_warranty' => 1,
]),
'created_at' => now(),
'updated_at' => now(),
]);
}
Cache::forget("business_setting_{$business->id}");
Cache::forget("business_sale_rounding_{$business->id}");
DB::commit();
return response()->json([
'message' => __('Business General Setting updated successfully'),
'redirect' => route('business.settings.index'),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json(__('Something went wrong.'), 400);
}
}
}

View File

@@ -0,0 +1,144 @@
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Models\User;
use App\Models\Branch;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Hash;
class UserRoleController extends Controller
{
public function __construct()
{
$this->middleware('check.permission:roles.read')->only(['index']);
$this->middleware('check.permission:roles.create')->only(['create', 'store']);
$this->middleware('check.permission:roles.update')->only(['edit', 'update']);
$this->middleware('check.permission:roles.delete')->only(['destroy', 'deleteAll']);
}
public function index(Request $request)
{
$user = auth()->user();
$search = $request->input('search');
$users = User::with('branch:id,name')
->where('business_id', $user->business_id)
->when($user->role == 'staff', function ($q) use ($user) {
$q->where('branch_id', $user->branch_id);
})
->where('id', '!=', auth()->id())
->where('role', 'staff')
->when($request->branch_id, function ($q) use ($request) {
$q->where('branch_id', $request->branch_id);
})
->when($user->branch_id || $user->active_branch_id, function ($q) use ($user) {
$q->where('branch_id', $user->branch_id ?? $user->active_branch_id);
})
->when($search, function ($q) use ($search) {
$q->where(function ($q) use ($search) {
$q->where('name', 'like', '%' . $search . '%')
->orWhereHas('branch', function ($q) use ($search) {
$q->where('name', 'like', '%' . $search . '%');
});
});
})
->latest()
->paginate($request->per_page ?? 20)->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('business::roles.datas', compact('users'))->render()
]);
}
$branches = Branch::where('business_id', $user->business_id)->latest()->get();
return view('business::roles.index', compact('users', 'branches'));
}
public function create()
{
$branches = Branch::withTrashed()->where('business_id', auth()->user()->business_id)->latest()->get();
return view('business::roles.create', compact('branches'));
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|max:30',
'password' => 'required|min:4|max:15',
'email' => 'required|email|unique:users,email',
]);
User::create([
'role' => 'staff',
'name' => $request->name,
'email' => $request->email,
'visibility' => $request->permissions,
'password' => Hash::make($request->password),
'business_id' => auth()->user()->business_id,
'branch_id' => $request->branch_id ?? auth()->user()->branch_id ?? auth()->user()->active_branch_id,
]);
return response()->json([
'message' => __('User role created successfully'),
'redirect' => route('business.roles.index')
]);
}
public function edit($id)
{
$user = User::where('business_id', auth()->user()->business_id)->findOrFail($id);
$branches = Branch::withTrashed()->where('business_id', auth()->user()->business_id)->latest()->get();
return view('business::roles.edit', compact('user', 'branches'));
}
public function update(Request $request, $id)
{
$request->validate([
'name' => 'required|max:30',
'password' => 'nullable|min:4|max:15',
'email' => 'required|email|unique:users,email,' . $id,
]);
$user = User::where('business_id', auth()->user()->business_id)->findOrFail($id);
$user->update([
'name' => $request->name,
'email' => $request->email,
'visibility' => $request->permissions,
'business_id' => auth()->user()->business_id,
'password' => $request->password ? Hash::make($request->password) : $user->password,
'branch_id' => $request->branch_id ?? auth()->user()->branch_id ?? auth()->user()->active_branch_id,
]);
return response()->json([
'message' => __('User role updated successfully'),
'redirect' => route('business.roles.index')
]);
}
public function destroy($id)
{
$user = User::findOrFail($id);
$user->delete();
return response()->json([
'message' => __('User role deleted successfully'),
'redirect' => route('business.roles.index')
]);
}
public function deleteAll(Request $request)
{
User::whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Selected role deleted successfully'),
'redirect' => route('business.roles.index')
]);
}
}