Files
kulakpos_web/Modules/Business/App/Http/Controllers/AcnooPurchaseController.php
eko54r 05fd3230b8
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 5m14s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 17s
Build, Push and Deploy / deploy-staging (push) Successful in 41s
Build, Push and Deploy / deploy-production (push) Has been skipped
update marketing
2026-05-14 11:55:22 +07:00

1645 lines
69 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace Modules\Business\App\Http\Controllers;
use App\Events\MultiPaymentProcessed;
use App\Helpers\HasUploader;
use App\Http\Controllers\Controller;
use App\Imports\PurchaseProductImport;
use App\Models\Branch;
use App\Models\Brand;
use App\Models\Business;
use App\Models\Category;
use App\Models\Option;
use App\Models\Party;
use App\Models\PaymentType;
use App\Models\Product;
use App\Models\Purchase;
use App\Models\PurchaseDetails;
use App\Models\PurchaseReturn;
use App\Models\PurchaseReturnDetail;
use App\Models\Stock;
use App\Models\Transaction;
use App\Models\Vat;
use App\Models\VatTransaction;
use App\Models\Warehouse;
use App\Services\Invoice\PurchaseInvoiceService;
use App\Services\Invoice\PurchaseTaxInvoiceService;
use App\Services\PdfService;
use App\Services\PurchaseVatTransaction;
use Barryvdh\DomPDF\Facade\Pdf;
use Carbon\Carbon;
use Gloudemans\Shoppingcart\Facades\Cart;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Validation\Rule;
use Maatwebsite\Excel\Facades\Excel;
class AcnooPurchaseController extends Controller
{
use HasUploader;
public function __construct()
{
$this->middleware('check.permission:purchases.read')->only(['index']);
$this->middleware('check.permission:purchases.create')->only(['create', 'store']);
$this->middleware('check.permission:purchases.update')->only(['edit', 'update']);
$this->middleware('check.permission:purchases.delete')->only(['destroy', 'deleteAll']);
}
public function index(Request $request)
{
$business_id = auth()->user()->business_id;
$purchasesWithReturns = PurchaseReturn::where('business_id', $business_id)
->pluck('purchase_id')
->toArray();
$purchasesQuery = Purchase::with('user:id,name', 'party:id,name,email,phone,type', 'payment_type:id,name', 'branch:id,name')
->where('business_id', $business_id);
$purchasesQuery->when($request->branch_id, function ($q) use ($request) {
$q->where('branch_id', $request->branch_id);
});
// Default to today
$startDate = Carbon::today()->format('Y-m-d');
$endDate = Carbon::today()->format('Y-m-d');
if ($request->custom_days === 'yesterday') {
$startDate = Carbon::yesterday()->format('Y-m-d');
$endDate = Carbon::yesterday()->format('Y-m-d');
} elseif ($request->custom_days === 'last_seven_days') {
$startDate = Carbon::today()->subDays(6)->format('Y-m-d');
} elseif ($request->custom_days === 'last_thirty_days') {
$startDate = Carbon::today()->subDays(29)->format('Y-m-d');
} elseif ($request->custom_days === 'current_month') {
$startDate = Carbon::now()->startOfMonth()->format('Y-m-d');
$endDate = Carbon::now()->endOfMonth()->format('Y-m-d');
} elseif ($request->custom_days === 'last_month') {
$startDate = Carbon::now()->subMonth()->startOfMonth()->format('Y-m-d');
$endDate = Carbon::now()->subMonth()->endOfMonth()->format('Y-m-d');
} elseif ($request->custom_days === 'current_year') {
$startDate = Carbon::now()->startOfYear()->format('Y-m-d');
$endDate = Carbon::now()->endOfYear()->format('Y-m-d');
} elseif ($request->custom_days === 'custom_date' && $request->from_date && $request->to_date) {
$startDate = Carbon::parse($request->from_date)->format('Y-m-d');
$endDate = Carbon::parse($request->to_date)->format('Y-m-d');
}
$purchasesQuery->whereDate('purchaseDate', '>=', $startDate)
->whereDate('purchaseDate', '<=', $endDate);
// 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());
if ($request->ajax()) {
return response()->json([
'data' => view('business::purchases.datas', compact('purchases', 'purchasesWithReturns'))->render(),
]);
}
$branches = Branch::withTrashed()->where('business_id', $business_id)->latest()->get();
$payment_types = PaymentType::where('business_id', $business_id)->whereStatus(1)->latest()->get();
return view('business::purchases.index', compact('purchases', 'purchasesWithReturns', 'branches', 'payment_types'));
}
public function create()
{
$business_id = auth()->user()->business_id;
$activeBranchId = auth()->user()->active_branch?->id;
// Clears all cart items
Cart::destroy();
$suppliers = Party::where('type', 'Supplier')
->where('business_id', $business_id)
->latest()
->get()
->transform(function ($supplier) use ($activeBranchId) {
$supplier->due = $supplier->dueForBranch($activeBranchId);
return $supplier;
});
$products = Product::with(['category:id,categoryName', 'unit:id,unitName', 'brand:id,brandName'])
->withSum('stocks', 'productStock')
->where('business_id', $business_id)
->whereNot('product_type', 'combo')
->latest()
->get();
$cart_contents = Cart::content()->filter(fn ($item) => $item->options->type == 'purchase');
$categories = Category::where('business_id', $business_id)->latest()->get();
$brands = Brand::where('business_id', $business_id)->latest()->get();
$vats = Vat::where('business_id', $business_id)->whereStatus(1)->latest()->get();
$payment_types = PaymentType::where('business_id', $business_id)->whereStatus(1)->latest()->get();
$warehouses = Warehouse::select('id', 'name')->where('business_id', $business_id)->latest()->get();
$profit_option = Option::where('key', 'business-settings')
->where('value', 'LIKE', '%"business_id":' . $business_id . '%')
->get()
->firstWhere('value.business_id', $business_id)
->value['product_profit_option'] ?? '';
// Generate a unique invoice number
$purchase_id = (Purchase::max('id') ?? 0) + 1;
$invoice_no = 'P-'.str_pad($purchase_id, 5, '0', STR_PAD_LEFT);
return view('business::purchases.create', compact('suppliers', 'products', 'cart_contents', 'invoice_no', 'categories', 'brands', 'vats', 'payment_types', 'warehouses', 'profit_option'));
}
public function store(Request $request)
{
$request->validate([
'party_id' => 'required|exists:parties,id',
'invoiceNumber' => 'required|string',
'receive_amount' => 'nullable|numeric',
'vat_id' => 'nullable|exists:vats,id',
'discountAmount' => 'nullable|numeric',
'discount_type' => 'nullable|in:flat,percent',
'shipping_charge' => 'nullable|numeric',
'purchaseDate' => 'nullable|date',
]);
$user = auth()->user();
$business = Business::findOrFail($user->business_id);
$branch_id = $user->branch_id ?? $user->active_branch_id;
// Check each cart item for batch duplication in Stock table
$carts = Cart::content()->filter(fn ($item) => $item->options->type == 'purchase');
if ($carts->count() < 1) {
return response()->json(['message' => __('Cart is empty. Add items first!')], 400);
}
DB::beginTransaction();
try {
// Subtotal
$subtotal = $carts->sum(fn ($cartItem) => (float) $cartItem->subtotal);
// Discount (applied first on subtotal)
$discountAmount = $request->discountAmount ?? 0;
if ($request->discount_type === 'percent') {
$discountAmount = ($subtotal * $discountAmount) / 100;
}
if ($discountAmount > $subtotal) {
return response()->json(['message' => __('Discount cannot be more than subtotal!')], 400);
}
// Subtotal after discount (before VAT)
$subtotalAfterDiscount = $subtotal - $discountAmount;
// VAT (applied on subtotal after discount)
$vat = Vat::find($request->vat_id);
$vatAmount = $vat ? ($subtotalAfterDiscount * $vat->rate) / 100 : 0;
// Charges and totals
$shippingCharge = $request->shipping_charge ?? 0;
$totalAmount = $subtotalAfterDiscount + $vatAmount + $shippingCharge;
$receiveAmount = $request->receive_amount ?? 0;
$changeAmount = max($receiveAmount - $totalAmount, 0);
$dueAmount = max($totalAmount - $receiveAmount, 0);
$paidAmount = $receiveAmount - $changeAmount;
// Party due update
if ($dueAmount > 0) {
$party = Party::findOrFail($request->party_id);
// Check party credit limit
$newTotalDue = $party->due + $dueAmount;
if ($party->credit_limit > 0 && $newTotalDue > $party->credit_limit) {
return response()->json([
'message' => __('Cannot create purchase. Party due will exceed credit limit!'),
], 400);
}
$party->update([
'due' => $newTotalDue,
]);
}
// Business balance update
updateBalance($paidAmount, 'decrement');
// Create Purchase
$purchase = Purchase::create($request->except('discountAmount', 'discount_type', 'discount_percent', 'shipping_charge', 'payment_type_id') + [
'user_id' => $user->id,
'vat_id' => $request->vat_id,
'vat_amount' => $vatAmount,
'business_id' => $user->business_id,
'discountAmount' => $discountAmount,
'discount_type' => $request->discount_type ?? 'flat',
'discount_percent' => $request->discount_type == 'percent' ? $request->discountAmount : 0,
'totalAmount' => $totalAmount,
'paidAmount' => $paidAmount,
'change_amount' => $changeAmount,
'dueAmount' => $dueAmount,
'shipping_charge' => $shippingCharge,
'purchaseDate' => $request->purchaseDate ? Carbon::parse($request->purchaseDate)->setTimeFromTimeString(now()->format('H:i:s')) : now(),
'isPaid' => $dueAmount > 0 ? 0 : 1,
]);
// Insert Purchase Details and Create Stocks
foreach ($carts as $cartItem) {
$product = Product::findOrFail($cartItem->id);
$batchNo = $cartItem->options['batch_no'] ?? null;
$expireDate = $cartItem->options['expire_date'] ?? null;
$variantName = $cartItem->options['variant_name'] ?? null;
$existingStock = Stock::where([
'business_id' => $user->business_id,
'batch_no' => $batchNo,
'product_id' => $cartItem->id,
'branch_id' => $branch_id,
])
->when($variantName, fn ($q) => $q->where('variant_name', $variantName))
->first();
$warehouseId = $cartItem->options['warehouse_id'] ?? ($existingStock->warehouse_id ?? null);
$newSerials = $cartItem->options['serial_numbers'] ?? [];
$existingSerials = $existingStock?->serial_numbers ?? [];
// Only serials that don't already exist
$uniqueNewSerials = array_diff($newSerials, $existingSerials);
if (! empty($newSerials) && count($uniqueNewSerials) === 0) {
return response()->json([
'success' => false,
'message' => 'Serial already exists in stock',
'duplicates' => array_values(array_intersect($newSerials, $existingSerials)),
], 400);
}
$mergedSerials = array_values(
array_unique(array_merge($existingSerials, $uniqueNewSerials))
);
// qty depends on serial
$qtyToAdd = count($uniqueNewSerials) > 0 ? count($uniqueNewSerials) : $cartItem->qty;
// price calculation
$vatRate = 0;
if ($product->vat_id) {
$vatData = Vat::find($product->vat_id);
$vatRate = $vatData?->rate ?? 0;
}
$purchasePrice = $cartItem->price;
// convert inclusive to exclusive
$exclusive_price = $vatRate > 0
? round($purchasePrice / (1 + ($vatRate / 100)), 4)
: $purchasePrice;
$isSerialProduct = ! empty($newSerials);
if ($isSerialProduct) {
// For serial products, use cart sales_price directly
$salesPrice = $cartItem->options['sales_price'];
$profitPercent = $cartItem->options['profit_percent'];
} else {
$profitPercent = ($cartItem->options['profit_percent'] ?? 0);
$profitOption = Option::where('key', 'business-settings')
->where('value', 'LIKE', '%"business_id":' . $business->id . '%')
->get()
->firstWhere('value.business_id', $business->id)
->value['product_profit_option'] ?? 'markup';
if ($profitOption === 'margin') {
$salesPrice = $exclusive_price / (1 - ($profitPercent / 100));
} else {
$salesPrice = $exclusive_price + ($exclusive_price * $profitPercent / 100);
}
if ($product->vat_type === 'inclusive' && $vatRate > 0) {
$salesPrice = $salesPrice * (1 + ($vatRate / 100));
}
$salesPrice = round($salesPrice, 2);
}
$stock = Stock::updateOrCreate(
[
'batch_no' => $batchNo,
'product_id' => $cartItem->id,
'variant_name' => $variantName,
'branch_id' => $branch_id,
'business_id' => $user->business_id,
],
[
'expire_date' => $expireDate,
'product_id' => $cartItem->id,
'warehouse_id' => $warehouseId,
'exclusive_price' => $exclusive_price,
'productPurchasePrice' => $purchasePrice,
'productSalePrice' => $salesPrice,
'profit_percent' => $profitPercent,
'productDealerPrice' => $cartItem->options['dealer_price'],
'serial_numbers' => ! empty($newSerials) ? $mergedSerials : null,
'productWholeSalePrice' => $cartItem->options['whole_sale_price'],
'productStock' => $cartItem->qty + ($existingStock->productStock ?? 0),
]
);
// purchase detail
$detail = PurchaseDetails::create([
'stock_id' => $stock->id,
'purchase_id' => $purchase->id,
'product_id' => $cartItem->id,
'quantities' => $qtyToAdd,
'expire_date' => $expireDate,
'productPurchasePrice' => $purchasePrice,
'productSalePrice' => $salesPrice,
'profit_percent' => $profitPercent,
'productDealerPrice' => $cartItem->options['dealer_price'],
'productWholeSalePrice' => $cartItem->options['whole_sale_price'],
'serial_numbers' => ! empty($uniqueNewSerials) ? array_values($uniqueNewSerials) : null,
]);
$vatAmount = PurchaseVatTransaction::storeVatTransactions($business, $detail, $product, $party ?? null);
$detail->update([
'price_without_tax' => number_format($purchasePrice - ($vatAmount / $cartItem->qty), 3, '.', ''),
]);
}
// MultiPaymentProcessed Event
event(new MultiPaymentProcessed(
$request->payments ?? [],
$purchase->id,
'purchase',
$request->receive_amount ?? 0,
$request->party_id ?? null,
));
// Clear cart
foreach ($carts as $cartItem) {
Cart::remove($cartItem->rowId);
}
sendNotifyToUser($purchase->id, route('business.purchases.index', ['id' => $purchase->id]), __('New Purchase created.'), $user->business_id);
if (moduleCheck('MarketingAddon')) {
$this->purchaseMessage($business, $party ?? null, $purchase);
}
$tax_invoice = moduleCheck('TaxInvoiceAddon') && invoice_setting($purchase->business_id) == 'standard_a4';
$invoice_route = $tax_invoice ? route('business.purchases.tax.invoice', $purchase->id) : route('business.purchases.invoice', $purchase->id);
DB::commit();
return response()->json([
'message' => __('Purchase created successfully.'),
'redirect' => route('business.purchases.index'),
'secondary_redirect_url' => $invoice_route,
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => $e->getMessage()], 500);
}
}
public function edit($id)
{
// Clears all cart items
Cart::destroy();
$business_id = auth()->user()->business_id;
$activeBranchId = auth()->user()->active_branch?->id;
$purchase = Purchase::with('details', 'details.product', 'details.stock:id,batch_no', 'details.product.unit', 'payment_type:id,name', 'transactions')
->where('business_id', $business_id)
->findOrFail($id);
$suppliers = Party::where('type', 'Supplier')
->where('business_id', $business_id)
->latest()
->get()
->transform(function ($supplier) use ($activeBranchId) {
$supplier->due = $supplier->dueForBranch($activeBranchId);
return $supplier;
});
$products = Product::with(['category:id,categoryName', 'unit:id,unitName', 'brand:id,brandName'])
->withSum('stocks', 'productStock')
->where('business_id', $business_id)
->whereNot('product_type', 'combo')
->latest()
->get();
$categories = Category::where('business_id', $business_id)->latest()->get();
$brands = Brand::where('business_id', $business_id)->latest()->get();
$vats = Vat::where('business_id', $business_id)->whereStatus(1)->latest()->get();
$payment_types = PaymentType::where('business_id', $business_id)->whereStatus(1)->latest()->get();
$warehouses = Warehouse::select('id', 'name')->where('business_id', $business_id)->latest()->get();
$profit_option = Option::where('key', 'business-settings')
->where('value', 'LIKE', '%"business_id":' . $business_id . '%')
->get()
->firstWhere('value.business_id', $business_id)
->value['product_profit_option'] ?? '';
// Add purchase details to the cart
foreach ($purchase->details as $detail) {
$stock = Stock::where('id', $detail->stock_id)->first();
// Add to cart
Cart::add([
'id' => $detail->product_id,
'name' => $detail->product->productName ?? '',
'qty' => $detail->quantities,
'price' => $detail->productPurchasePrice,
'options' => [
'type' => 'purchase',
'product_code' => $detail->product->productCode ?? '',
'product_unit_id' => $detail->product->unit_id ?? null,
'product_unit_name' => $detail->product->unit->unitName ?? '',
'product_image' => $detail->product->productPicture ?? '',
'sales_price' => $detail->productSalePrice,
'profit_percent' => $detail->profit_percent,
'whole_sale_price' => $detail->productWholeSalePrice,
'dealer_price' => $detail->productDealerPrice,
'batch_no' => $detail->stock->batch_no ?? null,
'expire_date' => $detail->expire_date,
'variant_name' => $stock->variant_name ?? null,
'warehouse_id' => $stock->warehouse_id ?? null,
'price_without_tax' => $detail->price_without_tax ?? 0,
'serial_numbers' => $detail->serial_numbers ?? null,
'has_serial' => $detail->product->has_serial ?? null,
],
]);
}
$cart_contents = Cart::content()->filter(fn ($item) => $item->options->type == 'purchase');
return view('business::purchases.edit', compact('purchase', 'suppliers', 'products', 'cart_contents', 'categories', 'brands', 'vats', 'payment_types', 'warehouses', 'profit_option'));
}
public function update(Request $request, $id)
{
$request->validate([
'party_id' => 'required|exists:parties,id',
'invoiceNumber' => 'required|string',
'receive_amount' => 'nullable|numeric',
'vat_id' => 'nullable|exists:vats,id',
'discountAmount' => 'nullable|numeric',
'discount_type' => 'nullable|in:flat,percent',
'shipping_charge' => 'nullable|numeric',
'purchaseDate' => 'nullable|date',
]);
$user = auth()->user();
$business = Business::findOrFail($user->business_id);
$carts = Cart::content()->filter(fn ($item) => $item->options->type === 'purchase');
if ($carts->count() < 1) {
return response()->json(['message' => __('Cart is empty. Add items first!')], 400);
}
DB::beginTransaction();
try {
$purchase = Purchase::with('details')->findOrFail($id);
// Calculate amounts
$subtotal = $carts->sum(fn ($cartItem) => (float) $cartItem->subtotal);
// Discount (applied first on subtotal)
$discountAmount = $request->discountAmount ?? 0;
if ($request->discount_type == 'percent') {
$discountAmount = ($subtotal * $discountAmount) / 100;
}
if ($discountAmount > $subtotal) {
return response()->json([
'message' => __('Discount cannot be more than subtotal!'),
], 400);
}
// Subtotal after discount (before VAT)
$subtotalAfterDiscount = $subtotal - $discountAmount;
// VAT (applied on subtotal after discount)
$vat = Vat::find($request->vat_id);
$vatAmount = 0;
if ($vat) {
$vatAmount = ($subtotalAfterDiscount * $vat->rate) / 100;
}
// Shipping Charge
$shippingCharge = $request->shipping_charge ?? 0;
// Total Amount
$totalAmount = $subtotalAfterDiscount + $vatAmount + $shippingCharge;
// Receive, Change, Due Amount Calculation
$receiveAmount = $request->receive_amount ?? 0;
$changeAmount = $receiveAmount > $totalAmount ? $receiveAmount - $totalAmount : 0;
$dueAmount = max($totalAmount - $receiveAmount, 0);
$paidAmount = $receiveAmount - $changeAmount;
if ($purchase->dueAmount || $dueAmount) {
$party = Party::findOrFail($request->party_id);
// If same party, adjust old due and add new one
$newDue = $request->party_id == $purchase->party_id ? ($party->due - $purchase->dueAmount) + $dueAmount : $party->due + $dueAmount;
// Check credit limit
if ($party->credit_limit > 0 && $newDue > $party->credit_limit) {
return response()->json(['message' => __('Party credit limit exceeded!')], 400);
}
$party->update(['due' => $newDue]);
// If changed to a new party, reduce previous partys due
if ($request->party_id != $purchase->party_id) {
$prev_party = Party::findOrFail($purchase->party_id);
$prev_party->update(['due' => $prev_party->due - $purchase->dueAmount]);
}
}
// branch/business balance
updateBalance($purchase->paidAmount - $paidAmount, 'decrement');
// Update purchase details
$purchase->update($request->except('discountAmount', 'discount_type', 'discount_percent', 'shipping_charge') + [
'business_id' => $user->business_id,
'user_id' => $user->id,
'vat_id' => $request->vat_id,
'vat_amount' => $vatAmount,
'discountAmount' => $discountAmount,
'totalAmount' => $totalAmount,
'paidAmount' => $paidAmount,
'change_amount' => $changeAmount,
'dueAmount' => $dueAmount,
'isPaid' => $dueAmount > 0 ? 0 : 1,
'shipping_charge' => $shippingCharge,
'payment_type_id' => $request->payment_type_id,
'discount_type' => $request->discount_type ?? 'flat',
'discount_percent' => $request->discount_type == 'percent' ? $request->discountAmount : 0,
'purchaseDate' => $request->purchaseDate ? Carbon::parse($request->purchaseDate)->setTimeFromTimeString(now()->format('H:i:s')) : now(),
]);
// Revert previous stock changes
foreach ($purchase->details as $detail) {
$stock = Stock::find($detail->stock_id);
if ($stock) {
if (! empty($detail->serial_numbers)) {
$stock->serial_numbers = array_values(array_diff($stock->serial_numbers, $detail->serial_numbers));
$stock->productStock = count($stock->serial_numbers);
} else {
$stock->productStock -= $detail->quantities;
}
$stock->save();
}
}
// Store old purchase details before deleting
$oldDetails = $purchase->details->keyBy('product_id');
// Delete existing purchase details
$prev_details_ids = $oldDetails->pluck('id')->toArray();
PurchaseDetails::where('purchase_id', $purchase->id)->delete();
// remove previous vat transactions
VatTransaction::where('vatable_id', $prev_details_ids)
->where('vatable_type', PurchaseDetails::class)
->delete();
// Insert updated purchase details and adjust stock
foreach ($carts as $cartItem) {
$product = Product::findOrFail($cartItem->id);
$batch_no = $cartItem->options['batch_no'] ?? null;
$expire_date = $cartItem->options['expire_date'] ?? null;
$variantName = $cartItem->options['variant_name'] ?? null;
$dealer_price = $cartItem->options['dealer_price'] ?? 0.0;
$whole_sale_price = $cartItem->options['whole_sale_price'] ?? 0.0;
$existingStock = Stock::where(['batch_no' => $batch_no, 'product_id' => $cartItem->id])
->when($variantName, fn ($q) => $q->where('variant_name', $variantName))
->first();
$warehouseId = $cartItem->options['warehouse_id'] ?? ($existingStock->warehouse_id ?? null);
// serial logic
$newSerials = $cartItem->options['serial_numbers'] ?? [];
$purchaseSerials = ! empty($newSerials) ? array_values($newSerials) : null;
$existingSerials = $existingStock?->serial_numbers ?? [];
// get previous purchase serials
$oldPurchaseSerials = $oldDetails[$cartItem->id]->serial_numbers ?? [];
$remainingSerials = array_diff($existingSerials, $oldPurchaseSerials);
$mergedSerials = ! empty($newSerials)
? array_values(array_unique(array_merge($remainingSerials, $newSerials)))
: array_values($remainingSerials);
if (! empty($newSerials)) {
// Serial Product
$stockQty = count($mergedSerials);
$purchaseQty = count($newSerials);
} else {
// Non Serial Product
$stockQty = ($existingStock->productStock ?? 0) + $cartItem->qty;
$purchaseQty = $cartItem->qty;
}
// Price Calculation
$purchasePrice = $cartItem->price;
// Exclusive Price
$exclusive_price = $purchasePrice;
$vatRate = 0;
if ($product->vat_id) {
$vat = Vat::find($product->vat_id);
$vatRate = $vat?->rate ?? 0;
if ($vatRate > 0) {
$exclusive_price = round($purchasePrice / (1 + ($vatRate / 100)), 3);
}
}
$isSerialProduct = ! empty($newSerials);
if ($isSerialProduct) {
// For serial products, use cart sales_price directly
$salesPrice = $cartItem->options['sales_price'] ?? $purchasePrice;
$profitPercent = 0;
} else {
$profitPercent = $cartItem->options['profit_percent'] ?? 0;
$profitOption = Option::where('key', 'business-settings')
->where('value', 'LIKE', '%"business_id":' . $business->id . '%')
->get()
->firstWhere('value.business_id', $business->id)
->value['product_profit_option'] ?? 'markup';
if ($profitOption === 'margin') {
$salesPrice = $exclusive_price / (1 - ($profitPercent / 100));
} else {
$salesPrice = $exclusive_price + ($exclusive_price * $profitPercent / 100);
}
if ($product->vat_type === 'inclusive' && $vatRate > 0) {
$salesPrice = $salesPrice * (1 + ($vatRate / 100));
}
$salesPrice = round($salesPrice, 3);
}
// update or create stock
$stock = Stock::updateOrCreate(
[
'batch_no' => $batch_no,
'product_id' => $cartItem->id,
'variant_name' => $variantName,
'business_id' => $user->business_id,
],
[
'product_id' => $cartItem->id,
'expire_date' => $expire_date,
'productDealerPrice' => $dealer_price,
'productPurchasePrice' => $purchasePrice,
'exclusive_price' => $exclusive_price,
'profit_percent' => $profitPercent,
'productSalePrice' => $salesPrice,
'productWholeSalePrice' => $whole_sale_price,
'productStock' => $stockQty,
'warehouse_id' => $warehouseId,
'serial_numbers' => $mergedSerials,
]
);
$detail = PurchaseDetails::create([
'purchase_id' => $purchase->id,
'product_id' => $cartItem->id,
'quantities' => $purchaseQty,
'stock_id' => $stock->id,
'expire_date' => $expire_date,
'productPurchasePrice' => $purchasePrice,
'profit_percent' => $profitPercent,
'productSalePrice' => $salesPrice,
'serial_numbers' => $purchaseSerials,
'productDealerPrice' => $dealer_price,
'productWholeSalePrice' => $whole_sale_price,
]);
$vatAmount = PurchaseVatTransaction::storeVatTransactions($business, $detail, $product, $party ?? null);
$detail->update([
'price_without_tax' => number_format($purchasePrice - ($vatAmount / $purchaseQty), 3, '.', ''),
]);
}
// Multiple Payment Process
$oldTransactions = Transaction::where('business_id', $user->business_id)
->where('reference_id', $purchase->id)
->where('platform', 'purchase')
->get();
// Revert old transactions
foreach ($oldTransactions as $old) {
switch ($old->transaction_type) {
case 'bank_payment':
$paymentType = PaymentType::find($old->payment_type_id);
if ($paymentType) {
$paymentType->increment('balance', $old->amount);
}
break;
case 'wallet_payment':
if ($request->party_id) {
$party = Party::find($request->party_id);
if ($party) {
$party->decrement('wallet', $old->amount);
}
}
break;
case 'cash_payment':
case 'cheque_payment':
// nothing to revert
break;
}
}
// Delete old transactions
Transaction::where('business_id', $user->business_id)
->where('reference_id', $purchase->id)
->where('platform', 'purchase')
->delete();
event(new MultiPaymentProcessed(
$request->payments ?? [],
$purchase->id,
'purchase',
$request->receive_amount ?? 0,
$request->party_id ?? null,
));
// Clear the cart
foreach ($carts as $cartItem) {
Cart::remove($cartItem->rowId);
}
sendNotifyToUser($purchase->id, route('business.purchases.index', ['id' => $purchase->id]), __('Purchase has been updated.'), $user->business_id);
$tax_invoice = moduleCheck('TaxInvoiceAddon') && invoice_setting($purchase->business_id) == 'standard_a4';
$invoice_route = $tax_invoice ? route('business.purchases.tax.invoice', $purchase->id) : route('business.purchases.invoice', $purchase->id);
DB::commit();
return response()->json([
'message' => __('Purchase updated successfully.'),
'redirect' => route('business.purchases.index'),
'secondary_redirect_url' => $invoice_route,
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => $e->getMessage()], 500);
}
}
public function destroy($id)
{
DB::beginTransaction();
try {
$purchase = Purchase::with('details')->findOrFail($id);
foreach ($purchase->details as $detail) {
VatTransaction::where('vatable_id', $detail->id)
->where('vatable_type', get_class($detail))
->delete();
Stock::where('id', $detail->stock_id)->decrement('productStock', $detail->quantities);
}
if ($purchase->party_id) {
$party = Party::findOrFail($purchase->party_id);
$party->update([
'due' => $party->due - $purchase->dueAmount,
]);
}
updateBalance($purchase->paidAmount, 'increment');
sendNotifyToUser($purchase->id, route('business.purchases.index', ['id' => $purchase->id]), __('Purchase has been deleted.'), $purchase->business_id);
$purchase->delete();
// Clears all cart items
Cart::destroy();
DB::commit();
return response()->json([
'message' => __('Purchase deleted successfully.'),
'redirect' => route('business.purchases.index'),
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => $e->getMessage()], 404);
}
}
public function deleteAll(Request $request)
{
DB::beginTransaction();
try {
$purchases = Purchase::where('business_id', auth()->user()->business_id)->whereIn('id', $request->ids)->get();
foreach ($purchases as $purchase) {
foreach ($purchase->details as $detail) {
VatTransaction::where('vatable_id', $detail->id)
->where('vatable_type', get_class($detail))
->delete();
Stock::where('id', $detail->stock_id)->decrement('productStock', $detail->quantities);
}
if ($purchase->party_id) {
$party = Party::findOrFail($purchase->party_id);
$party->update([
'due' => $party->due - $purchase->dueAmount,
]);
}
updateBalance($purchase->paidAmount, 'increment');
sendNotifyToUser($purchase->id, route('business.purchases.index', ['id' => $purchase->id]), __('Purchases has been deleted.'), $purchase->business_id);
$purchase->delete();
}
// Clears all cart items
Cart::destroy();
DB::commit();
return response()->json([
'message' => __('Selected purchases deleted successfully.'),
'redirect' => route('business.purchases.index'),
]);
} catch (\Exception $e) {
DB::rollback();
return response()->json(['message' => $e->getMessage()], 404);
}
}
public function acnooFilter(Request $request)
{
$business_id = auth()->user()->business_id;
$purchasesWithReturns = PurchaseReturn::where('business_id', $business_id)
->pluck('purchase_id')
->toArray();
$purchasesQuery = Purchase::with('user:id,name', 'party:id,name,email,phone,type', 'payment_type:id,name', 'branch:id,name')
->where('business_id', $business_id);
$purchasesQuery->when($request->branch_id, function ($q) use ($request) {
$q->where('branch_id', $request->branch_id);
});
// Default to today
$startDate = Carbon::today()->format('Y-m-d');
$endDate = Carbon::today()->format('Y-m-d');
if ($request->custom_days === 'yesterday') {
$startDate = Carbon::yesterday()->format('Y-m-d');
$endDate = Carbon::yesterday()->format('Y-m-d');
} elseif ($request->custom_days === 'last_seven_days') {
$startDate = Carbon::today()->subDays(6)->format('Y-m-d');
} elseif ($request->custom_days === 'last_thirty_days') {
$startDate = Carbon::today()->subDays(29)->format('Y-m-d');
} elseif ($request->custom_days === 'current_month') {
$startDate = Carbon::now()->startOfMonth()->format('Y-m-d');
$endDate = Carbon::now()->endOfMonth()->format('Y-m-d');
} elseif ($request->custom_days === 'last_month') {
$startDate = Carbon::now()->subMonth()->startOfMonth()->format('Y-m-d');
$endDate = Carbon::now()->subMonth()->endOfMonth()->format('Y-m-d');
} elseif ($request->custom_days === 'current_year') {
$startDate = Carbon::now()->startOfYear()->format('Y-m-d');
$endDate = Carbon::now()->endOfYear()->format('Y-m-d');
} elseif ($request->custom_days === 'custom_date' && $request->from_date && $request->to_date) {
$startDate = Carbon::parse($request->from_date)->format('Y-m-d');
$endDate = Carbon::parse($request->to_date)->format('Y-m-d');
}
$purchasesQuery->whereDate('purchaseDate', '>=', $startDate)
->whereDate('purchaseDate', '<=', $endDate);
// 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', 10);
$purchases = $purchasesQuery->latest()->paginate($perPage);
if ($request->ajax()) {
return response()->json([
'data' => view('business::purchases.datas', compact('purchases', 'purchasesWithReturns'))->render(),
]);
}
return redirect(url()->previous());
}
public function productFilter(Request $request)
{
$business_id = auth()->user()->business_id;
$products = Product::where('business_id', $business_id)
->when($request->search, function ($query) use ($request) {
$query->where(function ($q) use ($request) {
$q->where('productName', 'like', '%'.$request->search.'%')
->orWhere('productCode', 'like', '%'.$request->search.'%');
});
})
->when($request->category_id, function ($query) use ($request) {
$query->where('category_id', $request->category_id);
})
->when($request->brand_id, function ($query) use ($request) {
$query->where('brand_id', $request->brand_id);
})
->withSum('stocks as total_stock', 'productStock')
->latest()
->get();
// Query categories for search options
$categories = Category::where('business_id', $business_id)
->when($request->search, function ($query) use ($request) {
$query->where('categoryName', 'like', '%'.$request->search.'%');
})
->get();
// Query brands for search options
$brands = Brand::where('business_id', $business_id)
->when($request->search, function ($query) use ($request) {
$query->where('brandName', 'like', '%'.$request->search.'%');
})
->get();
$total_products = $products->count();
if ($request->ajax()) {
return response()->json([
'total_products' => $total_products,
'product_id' => $total_products == 1 ? $products->first()->id : null,
'data' => view('business::purchases.product-list', compact('products'))->render(),
'categories' => view('business::purchases.category-list', compact('categories'))->render(),
'brands' => view('business::purchases.brand-list', compact('brands'))->render(),
]);
}
return redirect(url()->previous());
}
// Category search Filter
public function categoryFilter(Request $request)
{
$search = $request->search;
$categories = Category::where('business_id', auth()->user()->business_id)
->when($search, function ($query) use ($search) {
$query->where('categoryName', 'like', '%'.$search.'%');
})
->get();
return response()->json([
'categories' => view('business::purchases.category-list', compact('categories'))->render(),
]);
}
// Brand search Filter
public function brandFilter(Request $request)
{
$search = $request->search;
$brands = Brand::where('business_id', auth()->user()->business_id)
->when($search, function ($query) use ($search) {
$query->where('brandName', 'like', '%'.$search.'%');
})
->get();
return response()->json([
'brands' => view('business::purchases.brand-list', compact('brands'))->render(),
]);
}
public function getInvoice($purchase_id, PurchaseInvoiceService $service)
{
$data = $service->getInvoiceData($purchase_id);
return view('business::purchases.invoice', $data);
}
public function getTaxInvoice($purchase_id, PurchaseTaxInvoiceService $service)
{
$data = $service->getInvoiceData($purchase_id);
return view('taxinvoiceaddon::purchases.tax', $data);
}
public function showPurchaseCart()
{
$cart_contents = Cart::content()->filter(fn ($item) => $item->options->type == 'purchase');
return view('business::purchases.cart-list', compact('cart_contents'));
}
public function getCartData()
{
$cart_contents = Cart::content()->filter(fn ($item) => $item->options->type == 'purchase');
$data['sub_total'] = 0;
foreach ($cart_contents as $cart) {
$data['sub_total'] += $cart->price;
}
$data['sub_total'] = currency_format($data['sub_total'], currency: business_currency());
return response()->json($data);
}
public function generatePDF($purchase_id)
{
$purchase = Purchase::where('business_id', auth()->user()->business_id)
->with(
'user:id,name,role',
'party:id,name,phone,address',
'business:id,phoneNumber,companyName,vat_name,vat_no,address,email,meta',
'details:id,productPurchasePrice,quantities,product_id,purchase_id,stock_id',
'details.stock:id,batch_no',
'details.product:id,productName',
'payment_type:id,name',
'transactions:id,reference_id,transaction_type,payment_type_id',
'transactions.paymentType:id,name'
)
->findOrFail($purchase_id);
/**
* Transaction types (same helper used in Sale)
*/
$transactionTypes = transaction_types($purchase->transactions);
/**
* Purchase returns
*/
$purchase_returns = PurchaseReturn::with(
'purchase:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber',
'purchase.party:id,name',
'details',
'details.purchaseDetail:id,productPurchasePrice,product_id,stock_id',
'details.purchaseDetail.product:id,productName',
'details.purchaseDetail.stock:id,batch_no'
)
->where('business_id', auth()->user()->business_id)
->where('purchase_id', $purchase_id)
->latest()
->get();
/**
* Return transaction type (same logic as Sale)
*/
$returnTransaction = $purchase_returns->first()?->transactions->first();
$returnTransactionType = '';
if ($returnTransaction) {
$returnTransactionType =
$returnTransaction->transaction_type === 'bank_payment'
? ($returnTransaction->paymentType?->name)
: ucfirst(explode('_', $returnTransaction->transaction_type)[0]);
}
/**
* Merge return qty into purchase details (same as Sale)
*/
$purchase->details = $purchase->details->map(function ($detail) use ($purchase_returns) {
$return_qty_sum = $purchase_returns->flatMap(function ($return) use ($detail) {
return $return->details
->where('purchaseDetail.id', $detail->id)
->pluck('return_qty');
})->sum();
$detail->quantities += $return_qty_sum;
return $detail;
});
/**
* Discount calculation from returned items (same pattern as Sale)
*/
$total_discount = 0;
$product_discounts = [];
foreach ($purchase_returns as $return) {
foreach ($return->details as $detail) {
if (! isset($product_discounts[$detail->purchase_detail_id])) {
$product_discounts[$detail->purchase_detail_id] = [
'return_qty' => 0,
'return_amount' => 0,
'price' => $detail->purchaseDetail->productPurchasePrice,
];
}
$product_discounts[$detail->purchase_detail_id]['return_qty'] += $detail->return_qty;
$product_discounts[$detail->purchase_detail_id]['return_amount'] += $detail->return_amount;
}
}
foreach ($product_discounts as $data) {
$product_price = $data['price'] * $data['return_qty'];
$total_discount += ($product_price - $data['return_amount']);
}
/**
* Bank / Payment info
*/
$bank_detail = PaymentType::where('business_id', auth()->user()->business_id)
->latest()
->first();
/**
* CENTRALIZED PDF RENDER (same as Sale)
*/
return PdfService::render(
'business::purchases.pdf',
compact(
'purchase',
'purchase_returns',
'total_discount',
'transactionTypes',
'returnTransactionType',
'bank_detail'
),
'purchase-invoice.pdf'
);
}
public function generateTaxPDF($purchase_id)
{
$purchase = Purchase::where('business_id', auth()->user()->business_id)
->with(
'user:id,name,role', 'party:id,name,phone,address,email', 'business:id,phoneNumber,companyName,vat_name,vat_no,address,email,meta', 'details:id,price_without_tax,productPurchasePrice,quantities,product_id,purchase_id,serial_numbers', 'details.stock:id,batch_no', 'details.product:id,vat_id,productName,has_serial', 'payment_type:id,name'
)
->findOrFail($purchase_id);
$business_id = auth()->check() ? auth()->user()->business_id : null;
/**
* Transaction types (same helper used in Sale)
*/
$detail_ids = $purchase->details->pluck('id');
$tax_items = VatTransaction::selectRaw('vat_id, vat_rate, SUM(vat_amount) as vat_amount')
->with('vat:id,name')
->whereIn('vatable_id', $detail_ids)
->where('vatable_type', PurchaseDetails::class)
->groupBy('vat_id', 'vat_rate')
->get();
$vatRowMap = VatTransaction::whereIn('vatable_id', $detail_ids)
->where('vatable_type', PurchaseDetails::class)
->selectRaw('vatable_id, vat_id, SUM(vat_amount) as total')
->groupBy('vatable_id', 'vat_id')
->get()
->groupBy('vatable_id')
->map(function ($items) {
return $items->pluck('total', 'vat_id')->toArray();
})
->toArray();
$transactionTypes = transaction_types($purchase->transactions);
/**
* Purchase returns
*/
$purchase_returns = PurchaseReturn::with(
'purchase:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber', 'purchase.party:id,name,email', 'details', 'details.purchaseDetail.product:id,productName,has_serial', 'details.purchaseDetail.stock:id,batch_no'
)
->where('business_id', auth()->user()->business_id)
->where('purchase_id', $purchase_id)
->latest()
->get();
// collect return detail ids
$return_detail_ids = $purchase_returns->flatMap(function ($return) {
return $return->details->pluck('id');
});
// get VAT for purchase return details
$returnVatRowMap = VatTransaction::whereIn('vatable_id', $return_detail_ids)
->where('vatable_type', PurchaseReturnDetail::class)
->selectRaw('vatable_id, vat_id, SUM(vat_amount) as total')
->groupBy('vatable_id', 'vat_id')
->get()
->groupBy('vatable_id')
->map(function ($items) {
return $items->pluck('total', 'vat_id')->toArray();
})
->toArray();
/**
* Return transaction type (same logic as Sale)
*/
$returnTransaction = $purchase_returns->first()?->transactions->first();
$returnTransactionType = '';
if ($returnTransaction) {
$returnTransactionType =
$returnTransaction->transaction_type === 'bank_payment'
? ($returnTransaction->paymentType?->name)
: ucfirst(explode('_', $returnTransaction->transaction_type)[0]);
}
/**
* Merge return qty into purchase details (same as Sale)
*/
$purchase->details = $purchase->details->map(function ($detail) use ($purchase_returns) {
$return_qty_sum = $purchase_returns->flatMap(function ($return) use ($detail) {
return $return->details
->where('purchaseDetail.id', $detail->id)
->pluck('return_qty');
})->sum();
$detail->quantities += $return_qty_sum;
return $detail;
});
/**
* Discount calculation from returned items (same pattern as Sale)
*/
$total_discount = 0;
$product_discounts = [];
foreach ($purchase_returns as $return) {
foreach ($return->details as $detail) {
if (! isset($product_discounts[$detail->purchase_detail_id])) {
$product_discounts[$detail->purchase_detail_id] = [
'return_qty' => 0,
'return_amount' => 0,
'price' => $detail->purchaseDetail->productPurchasePrice,
];
}
$product_discounts[$detail->purchase_detail_id]['return_qty'] += $detail->return_qty;
$product_discounts[$detail->purchase_detail_id]['return_amount'] += $detail->return_amount;
}
}
foreach ($product_discounts as $data) {
$product_price = $data['price'] * $data['return_qty'];
$total_discount += ($product_price - $data['return_amount']);
}
/**
* Bank / Payment info
*/
$bank_detail = PaymentType::where('business_id', auth()->user()->business_id)
->latest()
->first();
/**
* CENTRALIZED PDF RENDER (same as Sale)
*/
return PdfService::render(
'taxinvoiceaddon::purchases.pdf',
compact(
'purchase',
'purchase_returns',
'total_discount',
'transactionTypes',
'returnTransactionType',
'bank_detail',
'tax_items',
'business_id',
'vatRowMap',
'returnVatRowMap'
),
'purchase-tax-invoice.pdf'
);
}
public function sendMail($purchase_id)
{
$purchase = Purchase::with('user:id,name,role', 'party:id,name,phone,address', 'business:id,phoneNumber,companyName,vat_name,vat_no,address,email,meta', 'details:id,productPurchasePrice,quantities,product_id,purchase_id', 'details.stock:id,batch_no', 'details.product:id,productName', 'payment_type:id,name')
->findOrFail($purchase_id);
$transactionTypes = $purchase->transactions
->map(function ($transaction) {
if ($transaction->transaction_type === 'bank_payment' && ! empty($transaction->paymentType?->name)) {
return $transaction->paymentType->name;
}
return $transaction->transaction_type ? ucfirst(explode('_', $transaction->transaction_type)[0]) : '';
})
->unique()
->implode(', ');
$purchase_returns = PurchaseReturn::with('purchase:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber', 'purchase.party:id,name', 'details', 'details.purchaseDetail.product:id,productName', 'details.purchaseDetail.stock:id,batch_no')
->where('business_id', auth()->user()->business_id)
->where('purchase_id', $purchase_id)
->latest()
->get();
$returnTransaction = $purchase_returns->first()?->transactions->first();
$returnTransactionType = '';
if ($returnTransaction) {
$returnTransactionType = $returnTransaction->transaction_type === 'bank_payment'
? ($returnTransaction->paymentType?->name)
: ucfirst(explode('_', $returnTransaction->transaction_type)[0]);
}
// sum of return_qty
$purchase->details = $purchase->details->map(function ($detail) use ($purchase_returns) {
$return_qty_sum = $purchase_returns->flatMap(function ($return) use ($detail) {
return $return->details->where('purchaseDetail.id', $detail->id)->pluck('return_qty');
})->sum();
$detail->quantities = $detail->quantities + $return_qty_sum;
return $detail;
});
// Calculate total discount based on return quantities and amounts
$total_discount = 0;
$product_discounts = [];
foreach ($purchase_returns as $return) {
foreach ($return->details as $detail) {
// Initialize discount tracking for the first occurrence of each purchase_detail_id
if (! isset($product_discounts[$detail->purchase_detail_id])) {
$product_discounts[$detail->purchase_detail_id] = [
'return_qty' => 0,
'return_amount' => 0,
'price' => $detail->purchaseDetail->productPurchasePrice,
];
}
// Accumulate return quantities and return amounts
$product_discounts[$detail->purchase_detail_id]['return_qty'] += $detail->return_qty;
$product_discounts[$detail->purchase_detail_id]['return_amount'] += $detail->return_amount;
}
}
// Calculate the total discount for each returned product
foreach ($product_discounts as $data) {
$product_price = $data['price'] * $data['return_qty'];
$discount = $product_price - $data['return_amount'];
$total_discount += $discount;
}
$bank_detail = PaymentType::where('business_id', auth()->user()->business_id)->where('show_in_invoice', 1)->latest()->first();
$pdf = Pdf::loadView('business::purchases.pdf', compact('purchase', 'purchase_returns', 'total_discount', 'transactionTypes', 'bank_detail', 'returnTransactionType'));
// Send email with PDF attachment
Mail::raw('Please find attached your Purchase invoice.', function ($message) use ($pdf) {
$message->to(auth()->user()->email)
->subject('Purchase Invoice')
->attachData($pdf->output(), 'purchase-invoice.pdf', [
'mime' => 'application/pdf',
]);
});
return response()->json([
'message' => __('Email Sent Successfully.'),
'redirect' => route('business.purchases.index'),
]);
}
public function sendTaxMail($purchase_id)
{
$purchase = Purchase::with('user:id,name,role', 'party:id,name,phone,address,email', 'business:id,phoneNumber,companyName,vat_name,vat_no,address,email,meta', 'details:id,price_without_tax,productPurchasePrice,quantities,product_id,purchase_id,serial_numbers', 'details.stock:id,batch_no', 'details.product:id,vat_id,productName,has_serial', 'payment_type:id,name')
->findOrFail($purchase_id);
$business_id = auth()->check() ? auth()->user()->business_id : null;
$detail_ids = $purchase->details->pluck('id');
$tax_items = VatTransaction::selectRaw('vat_id, vat_rate, SUM(vat_amount) as vat_amount')
->with('vat:id,name')
->whereIn('vatable_id', $detail_ids)
->where('vatable_type', PurchaseDetails::class)
->groupBy('vat_id', 'vat_rate')
->get();
$vatRowMap = VatTransaction::whereIn('vatable_id', $detail_ids)
->where('vatable_type', PurchaseDetails::class)
->selectRaw('vatable_id, vat_id, SUM(vat_amount) as total')
->groupBy('vatable_id', 'vat_id')
->get()
->groupBy('vatable_id')
->map(function ($items) {
return $items->pluck('total', 'vat_id')->toArray();
})
->toArray();
$transactionTypes = $purchase->transactions
->map(function ($transaction) {
if ($transaction->transaction_type === 'bank_payment' && ! empty($transaction->paymentType?->name)) {
return $transaction->paymentType->name;
}
return $transaction->transaction_type ? ucfirst(explode('_', $transaction->transaction_type)[0]) : '';
})
->unique()
->implode(', ');
$purchase_returns = PurchaseReturn::with('purchase:id,party_id,isPaid,totalAmount,dueAmount,paidAmount,invoiceNumber', 'purchase.party:id,name,email', 'details', 'details.purchaseDetail.product:id,productName,has_serial', 'details.purchaseDetail.stock:id,batch_no')
->where('business_id', auth()->user()->business_id)
->where('purchase_id', $purchase_id)
->latest()
->get();
$return_detail_ids = $purchase_returns->flatMap(function ($return) {
return $return->details->pluck('id');
});
// get VAT for purchase return details
$returnVatRowMap = VatTransaction::whereIn('vatable_id', $return_detail_ids)
->where('vatable_type', PurchaseReturnDetail::class)
->selectRaw('vatable_id, vat_id, SUM(vat_amount) as total')
->groupBy('vatable_id', 'vat_id')
->get()
->groupBy('vatable_id')
->map(function ($items) {
return $items->pluck('total', 'vat_id')->toArray();
})
->toArray();
$returnTransaction = $purchase_returns->first()?->transactions->first();
$returnTransactionType = '';
if ($returnTransaction) {
$returnTransactionType = $returnTransaction->transaction_type === 'bank_payment'
? ($returnTransaction->paymentType?->name)
: ucfirst(explode('_', $returnTransaction->transaction_type)[0]);
}
// sum of return_qty
$purchase->details = $purchase->details->map(function ($detail) use ($purchase_returns) {
$return_qty_sum = $purchase_returns->flatMap(function ($return) use ($detail) {
return $return->details->where('purchaseDetail.id', $detail->id)->pluck('return_qty');
})->sum();
$detail->quantities = $detail->quantities + $return_qty_sum;
return $detail;
});
// Calculate total discount based on return quantities and amounts
$total_discount = 0;
$product_discounts = [];
foreach ($purchase_returns as $return) {
foreach ($return->details as $detail) {
// Initialize discount tracking for the first occurrence of each purchase_detail_id
if (! isset($product_discounts[$detail->purchase_detail_id])) {
$product_discounts[$detail->purchase_detail_id] = [
'return_qty' => 0,
'return_amount' => 0,
'price' => $detail->purchaseDetail->productPurchasePrice,
];
}
// Accumulate return quantities and return amounts
$product_discounts[$detail->purchase_detail_id]['return_qty'] += $detail->return_qty;
$product_discounts[$detail->purchase_detail_id]['return_amount'] += $detail->return_amount;
}
}
// Calculate the total discount for each returned product
foreach ($product_discounts as $data) {
$product_price = $data['price'] * $data['return_qty'];
$discount = $product_price - $data['return_amount'];
$total_discount += $discount;
}
$bank_detail = PaymentType::where('business_id', auth()->user()->business_id)->where('show_in_invoice', 1)->latest()->first();
$pdf = Pdf::loadView('taxinvoiceaddon::purchases.pdf', compact('purchase', 'purchase_returns', 'total_discount', 'transactionTypes', 'bank_detail', 'returnTransactionType', 'tax_items', 'business_id', 'vatRowMap', 'returnVatRowMap'));
// Send email with PDF attachment
Mail::raw('Please find attached your Purchase invoice.', function ($message) use ($pdf) {
$message->to(auth()->user()->email)
->subject('Purchase Invoice')
->attachData($pdf->output(), 'purchase-tax-invoice.pdf', [
'mime' => 'application/pdf',
]);
});
return response()->json([
'message' => __('Email Sent Successfully.'),
'redirect' => route('business.purchases.index'),
]);
}
public function createSupplier(Request $request)
{
$request->validate([
'phone' => 'nullable|max:20|'.Rule::unique('parties')->where('business_id', auth()->user()->business_id),
'name' => 'required|string|max:255',
'type' => 'required|string|in:Retailer,Dealer,Wholesaler,Supplier',
'email' => 'nullable|email',
'image' => 'nullable|image',
'address' => 'nullable|string|max:255',
'due' => 'nullable|numeric|min:0',
]);
Party::create($request->except('image', 'due') + [
'due' => $request->due ?? 0,
'image' => $request->image ? $this->upload($request, 'image') : null,
'business_id' => auth()->user()->business_id,
]);
return response()->json([
'message' => __('Supplier created successfully'),
'redirect' => route('business.purchases.create'),
]);
}
public function bulkIndex()
{
return view('business::purchases.bulk-upload.index');
}
public function bulkCartStore(Request $request)
{
$request->validate([
'file' => 'required|file|mimes:xlsx,xls,csv',
]);
$businessId = auth()->user()->business_id;
$import = new PurchaseProductImport($businessId);
try {
Excel::import($import, $request->file('file'));
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => $e->getMessage(),
], 422);
}
return response()->json([
'success' => true,
'message' => __('Bulk upload successfully.'),
'redirect' => route('business.purchases.create'),
]);
}
public function viewPayment(string $id)
{
$purchase = Purchase::with('transactions', 'transactions.paymentType:id,name')
->where('business_id', auth()->user()->business_id)
->findOrFail($id);
$transactions = $purchase->transactions->map(function ($transaction) {
return [
'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' ? (ucwords($transaction->paymentType->name) ?? '') : (ucfirst(explode('_', $transaction->transaction_type)[0] ?? '')),
];
});
return response()->json(['data' => $transactions]);
}
}