$request->all()]);
if ($request->ajax()) {
$query = Purchase::with(['party','items']);
// Filtering
if ($request->date_range) {
[$start, $end] = explode(' to ', $request->date_range);
$query->whereBetween('invoice_date', [$start, $end]);
}
if ($request->party_id) {
$query->where('party_id', $request->party_id);
}
if ($request->invoice_no) {
$query->where('invoice_no', 'like', '%' . $request->invoice_no . '%');
}
if ($request->status) {
$query->where('status', $request->status);
}
$data = datatables()->of($query->latest())
->addColumn('party', fn($row) => $row->party->company)
->addColumn('items', fn($row) => $row->items()->count())
->addColumn('actions', function ($row) {
return '
';
})
->rawColumns(['actions'])
->make(true);
Log::info('PurchaseManagementController@index returning AJAX data');
return $data;
}
$parties = CustomerVendor::all();
$items = StockItem::all();
Log::info('PurchaseManagementController@index returning view', [
'parties_count' => $parties->count(),
'items_count' => $items->count()
]);
$purchaseOrders = PurchaseOrder::with(['quotation','quotation.items'])->where('status', 'approved')->get();
// dd($purchaseOrders);
$nextInvoiceNo = nextNumber('UEPL/P/INV', 'purchases', 'invoice_no');
$currentDate = date('Y-m-d');
return view('superadmin.purchasemanagement.index', compact('parties', 'items', 'purchaseOrders', 'nextInvoiceNo', 'currentDate'));
}
public function show($id)
{
Log::info('PurchaseManagementController@show called', ['id' => $id]);
$purchase = Purchase::with(['party', 'items.item', 'files'])->findOrFail($id);
Log::info('PurchaseManagementController@show loaded purchase', ['purchase_id' => $purchase->id]);
return response()->json($purchase);
}
public function edit($id)
{
Log::info('PurchaseManagementController@edit called', ['id' => $id]);
$purchase = Purchase::with(['items', 'files'])->findOrFail($id);
Log::info('PurchaseManagementController@edit loaded purchase', ['purchase_id' => $purchase->id]);
return response()->json($purchase);
}
// Update purchase
public function update(Request $request, $id)
{
// Step 1: Find the existing purchase
$purchase = Purchase::findOrFail($id);
// Step 2: Validate the incoming request
$validatedData = $request->validate([
'purchase_order_id' => 'required|integer',
'purchase_order_date' => 'required|date',
'purchase_no' => 'required|string|exists:purchases,purchase_id',
'vendor_email' => 'required|email',
'invoice_no' => 'required|string|exists:purchases,invoice_no',
'invoice_date' => 'required|date',
'purchase_id' => 'required|integer|exists:customer_vendors,id', // likely party/vendor ID
'vendor_reference_no' => 'nullable|string',
'payment_terms' => 'nullable|integer',
'company_gstn' => 'required|string',
'company_address' => 'required|string',
'company_phone' => 'required|string',
'description' => 'nullable|string',
'delivery_terms' => 'nullable|string',
'items' => 'required|array|min:1',
'items.*.sl_no' => 'required|integer',
'items.*.item_id' => 'required|integer',
'items.*.description' => 'nullable|string',
'items.*.quantity' => 'required|numeric|min:0.01',
'items.*.uom' => 'required|string',
'items.*.rate' => 'required|numeric|min:0',
'items.*.tds' => 'nullable|numeric|min:0',
'items.*.discount' => 'nullable|numeric|min:0',
'items.*.value' => 'nullable|numeric',
'items.*.sgst' => 'nullable|numeric',
'items.*.cgst' => 'nullable|numeric',
'items.*.igst' => 'nullable|numeric',
'items.*.amount' => 'nullable|numeric',
'additional_charges' => 'nullable|numeric|min:0',
'total_tds' => 'nullable|numeric|min:0',
'sub_total' => 'required|numeric|min:0',
'total_amount' => 'required|numeric|min:0',
'paid_amount' => 'nullable|numeric|min:0',
'balance_amount' => 'nullable|numeric|min:0',
'payment_mode' => 'nullable|string',
'payment_reference' => 'nullable|string',
'payment_reason' => 'nullable|string',
'note' => 'nullable|string',
]);
// Step 3: Update the purchase record
$purchase->update([
'purchase_id' => $validatedData['purchase_no'], // assuming purchase_no is stored as purchase_id
'purchase_date' => $validatedData['purchase_order_date'],
'invoice_no' => $validatedData['invoice_no'],
'invoice_date' => $validatedData['invoice_date'],
'party_id' => $validatedData['purchase_id'], // vendor/party ID
'sub_total' => $validatedData['sub_total'],
'additional_charges' => $validatedData['additional_charges'],
'tds' => $validatedData['total_tds'],
'total_amount' => $validatedData['total_amount'],
'paid_amount' => $validatedData['paid_amount'],
'balance_amount' => $validatedData['balance_amount'],
'status' => $validatedData['paid_amount'] >= $validatedData['total_amount'] ? 'paid' : 'unpaid',
'payment_mode' => $validatedData['payment_mode'],
'reference' => $validatedData['payment_reference'],
'notes' => $validatedData['note'],
]);
// Step 4: Handle items - Sync logic (update existing, add new, remove deleted)
$itemIdsFromRequest = collect($validatedData['items'])->map(function ($item) use ($purchase) {
return isset($item['id']) ? $item['id'] : null; // if item already exists in DB, it should have an 'id'
})->filter(); // only non-null IDs
// Delete items not present in the request
$purchase->items()
->whereNotIn('id', $itemIdsFromRequest)
->delete();
// Update or create items
foreach ($validatedData['items'] as $item) {
$itemData = [
'item_id' => $item['item_id'],
'uom' => $item['uom'],
'quantity' => $item['quantity'],
'rate' => $item['rate'],
'discount' => $item['discount'] ?? 0,
];
if (isset($item['id'])) {
// Existing item: update
$purchase->items()->where('id', $item['id'])->update($itemData);
} else {
// New item: create
$purchase->items()->create($itemData);
}
}
// Step 5: Handle file uploads (if any)
if ($request->hasFile('purchase_files')) {
// Example: Add new files, or replace old ones
// You may want to store them and attach to purchase
// Storage::put('purchases/'.$purchase->id, $request->file('purchase_files'));
}
// Step 6: Return success response
return redirect()->route('superadmin.purchase.index')->with('success', 'Purchase updated successfully');
}
public function store(Request $request)
{
// Step 1: Validate the request data
$validatedData = $request->validate([
'purchase_order_id' => 'required|integer',
'purchase_order_date' => 'required|date',
'purchase_no' => 'required|string',
'vendor_email' => 'required|email',
'invoice_no' => 'required|string',
'invoice_date' => 'required|date',
'purchase_id' => 'required|integer',
'vendor_reference_no' => 'nullable|string',
'payment_terms' => 'nullable|integer',
'company_gstn' => 'required|string',
'company_address' => 'required|string',
'company_phone' => 'required|string',
'description' => 'nullable|string',
'delivery_terms' => 'nullable|string',
'items' => 'required|array',
'items.*.sl_no' => 'required|integer',
'items.*.item_id' => 'required|integer',
'items.*.description' => 'required|string',
'items.*.quantity' => 'required|numeric',
'items.*.uom' => 'required|string',
'items.*.rate' => 'required|numeric',
'items.*.tds' => 'nullable|numeric',
'items.*.discount' => 'nullable|numeric',
'items.*.value' => 'nullable|numeric',
'items.*.sgst' => 'nullable|numeric',
'items.*.cgst' => 'nullable|numeric',
'items.*.igst' => 'nullable|numeric',
'items.*.amount' => 'nullable|numeric',
'additional_charges' => 'nullable|numeric',
'total_tds' => 'nullable|numeric',
'sub_total' => 'required|numeric',
'total_amount' => 'required|numeric',
'paid_amount' => 'nullable|numeric',
'balance_amount' => 'nullable|numeric',
'payment_mode' => 'nullable|string',
'payment_reference' => 'nullable|string',
'payment_reason' => 'nullable|string',
'note' => 'nullable|string',
]);
// Step 2: Create the purchase record
$purchase = Purchase::create([
'purchase_id' => $validatedData['purchase_no'], // Use purchase_no as purchase_id
'purchase_date' => $validatedData['purchase_order_date'],
'invoice_no' => $validatedData['invoice_no'],
'invoice_date' => $validatedData['invoice_date'],
'party_id' => $validatedData['purchase_id'], // Assuming purchase_id refers to party_id
'sub_total' => $validatedData['sub_total'],
'additional_charges' => $validatedData['additional_charges'],
'tds' => $validatedData['total_tds'],
'total_amount' => $validatedData['total_amount'],
'paid_amount' => $validatedData['paid_amount'],
'balance_amount' => $validatedData['balance_amount'],
'status' => 'unpaid', // Default status
'payment_mode' => $validatedData['payment_mode'],
'reference' => $validatedData['payment_reference'],
'notes' => $validatedData['note'],
]);
// Step 3: Create purchase items records
foreach ($validatedData['items'] as $item) {
PurchaseItem::create([
'purchase_id' => $purchase->id,
'item_id' => $item['item_id'],
'uom' => $item['uom'],
'quantity' => $item['quantity'],
'rate' => $item['rate'],
'discount' => $item['discount'],
]);
}
// Step 4: Handle files (if any)
if ($request->hasFile('purchase_files')) {
// Logic to handle file uploads
// Example: Store files in storage and save paths in database
}
// Step 5: Return a response
return redirect()->route('superadmin.purchase.index')->with('success', 'Purchase created successfully');
}
public function getNextInvoiceNumber()
{
Log::info('PurchaseManagementController@getNextInvoiceNumber called');
$latest = Purchase::orderBy('id', 'desc')->first();
if (!$latest || !$latest->invoice_no) {
$nextNumber = 1;
} else {
// Extract the last 4 digits and increment
$lastNumber = (int)substr($latest->invoice_no, -4);
$nextNumber = $lastNumber + 1;
}
$nextInvoiceNo = 'UEPL/P/INV/' . str_pad($nextNumber, 4, '0', STR_PAD_LEFT);
Log::info('PurchaseManagementController@getNextInvoiceNumber finished', ['invoice_no' => $nextInvoiceNo]);
return response()->json(['invoice_no' => $nextInvoiceNo]);
}
public function export(Request $request)
{
Log::info('PurchaseManagementController@export called', ['request' => $request->all()]);
// Same filter logic as your index()
$query = \App\Models\Purchase::with(['party', 'items.item']);
if ($request->date_range) {
[$start, $end] = explode(' to ', $request->date_range);
$query->whereBetween('invoice_date', [$start, $end]);
}
if ($request->party_id) {
$query->where('party_id', $request->party_id);
}
if ($request->invoice_no) {
$query->where('invoice_no', 'like', '%' . $request->invoice_no . '%');
}
if ($request->status) {
$query->where('status', $request->status);
}
$purchases = $query->orderBy('invoice_date', 'desc')->get();
// Create Spreadsheet
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// Set headers
$sheet->fromArray([
['Purchase Id', 'Purchase Date', 'Invoice No', 'Invoice Date', 'Party', 'Sub Total', 'Total Amount', 'Paid Amount', 'Balance', 'Status', 'Notes', 'Item Name', 'Quantity', 'UOM', 'Rate', 'Discount %']
], NULL, 'A1');
$row = 2;
foreach ($purchases as $purchase) {
$first = true;
foreach ($purchase->items as $item) {
$sheet->fromArray([
$first ? $purchase->purchase_id : '',
$first ? $purchase->purchase_date : '',
$first ? $purchase->invoice_no : '',
$first ? $purchase->invoice_date : '',
$first ? ($purchase->party ? $purchase->party->company : '') : '',
$first ? $purchase->sub_total : '',
$first ? $purchase->total_amount : '',
$first ? $purchase->paid_amount : '',
$first ? $purchase->balance_amount : '',
$first ? $purchase->status : '',
$first ? $purchase->notes : '',
$item->item ? $item->item->item_name : '',
$item->quantity,
$item->uom,
$item->rate,
$item->discount
], NULL, 'A' . $row);
$row++;
$first = false;
}
// If no items, still show the purchase
if ($first) {
$sheet->fromArray([
$purchase->purchase_id,
$purchase->purchase_date,
$purchase->invoice_no,
$purchase->invoice_date,
$purchase->party ? $purchase->party->company : '',
$purchase->sub_total,
$purchase->total_amount,
$purchase->paid_amount,
$purchase->balance_amount,
$purchase->status,
$purchase->notes,
'',
'',
'',
'',
''
], NULL, 'A' . $row);
$row++;
}
}
// Download response
$writer = new Xlsx($spreadsheet);
$filename = 'purchase_export_' . date('Ymd_His') . '.xlsx';
$temp_file = tempnam(sys_get_temp_dir(), $filename);
$writer->save($temp_file);
return response()->download($temp_file, $filename)->deleteFileAfterSend(true);
}
public function getNextCreditNoteNo()
{
$year = date('Y');
$latest = \App\Models\CreditNote::where('credit_note_no', 'like', "CN-{$year}-%")
->orderByDesc('id')->first();
if (!$latest) {
$nextNumber = 1;
} else {
$lastNumber = (int)substr($latest->credit_note_no, -4);
$nextNumber = $lastNumber + 1;
}
$nextCreditNo = 'CN-' . $year . '-' . str_pad($nextNumber, 4, '0', STR_PAD_LEFT);
return response()->json(['credit_note_no' => $nextCreditNo]);
}
public function creditNoteStore(Request $request)
{
Log::info('PurchaseManagementController@creditNoteStore called', ['request' => $request->all()]);
$data = $request->validate([
'credit_note_no' => 'required|unique:credit_notes,credit_note_no',
'date' => 'required|date',
'invoice_no' => 'required|string',
'party_id' => 'required|exists:customer_vendors,id',
'amount' => 'required|numeric|min:0',
'reason' => 'required|string',
'description' => 'nullable|string',
]);
$creditNote = \App\Models\CreditNote::create($data);
return response()->json(['success' => true, 'credit_note_id' => $creditNote->id]);
}
public function payments(Request $request)
{
$query = Purchase::with('party');
// Filters
if ($request->date_range) {
[$start, $end] = explode(' to ', $request->date_range);
$query->whereBetween('invoice_date', [$start, $end]);
}
if ($request->party_id) {
$query->where('party_id', $request->party_id);
}
if ($request->min_amount) {
$query->where('paid_amount', '>=', $request->min_amount);
}
if ($request->max_amount) {
$query->where('paid_amount', '<=', $request->max_amount);
}
return datatables()->of($query->latest())
->addColumn('party', fn($row) => $row->party ? $row->party->company : '')
->addColumn('purchase_id', fn($row) => $row->purchase_id)
->addColumn('purchase_date', fn($row) => $row->purchase_date)
->addColumn('invoice_no', fn($row) => $row->invoice_no)
->addColumn('invoice_date', fn($row) => $row->invoice_date)
->addColumn('paid_amount', fn($row) => $row->paid_amount)
->addColumn('payment_mode', fn($row) => $row->payment_mode)
->addColumn('reference', fn($row) => $row->reference)
->addColumn('actions', function ($row) {
return '';
})
->rawColumns(['actions'])
->make(true);
}
public function Vendorshow($id)
{
$purchase = Purchase::with(['party', 'items.item'])->findOrFail($id);
return response()->json($purchase);
}
public function creditNotesData(Request $request)
{
$query = CreditNote::with('party');
// Filters (optional)
if ($request->date_range) {
[$start, $end] = explode(' to ', $request->date_range);
$query->whereBetween('date', [$start, $end]);
}
//dd($start, $end);
if ($request->party_id) {
$query->where('party_id', $request->party_id);
}
if ($request->credit_note_no) {
$query->where('credit_note_no', 'like', "%{$request->credit_note_no}%");
}
// DataTables response
return datatables()->eloquent($query)->addColumn('party', function ($row) {
return $row->party ? $row->party->company : '';
})->addColumn('actions', function ($row) {
return '';
})->rawColumns(['actions'])
->make(true);
}
public function showCreditNote($id)
{
$creditNote = \App\Models\CreditNote::with('party')->findOrFail($id);
return response()->json($creditNote);
}
public function agingReport(Request $request)
{
// Validate and get filters
$asOfDate = $request->filled('as_of_date') ? $request->as_of_date : date('Y-m-d');
$partyId = $request->filled('party_id') ? $request->party_id : null;
// Query purchases with balance > 0
$query = \App\Models\Purchase::with('party')
->where('invoice_date', '<=', $asOfDate)
->where('balance_amount', '>', 0);
if ($partyId) {
$query->where('party_id', $partyId);
}
$purchases = $query->get();
// Group by party
$report = [];
foreach ($purchases as $purchase) {
$days = \Carbon\Carbon::parse($purchase->invoice_date)->diffInDays($asOfDate, false);
$party = $purchase->party ? $purchase->party->company : 'Unknown';
if (!isset($report[$party])) {
$report[$party] = [
'party' => $party,
'current' => 0,
'1_30' => 0,
'31_60' => 0,
'61_90' => 0,
'over_90' => 0,
'total_due' => 0,
];
}
if ($days <= 0) {
$report[$party]['current'] += $purchase->balance_amount;
} elseif ($days <= 30) {
$report[$party]['1_30'] += $purchase->balance_amount;
} elseif ($days <= 60) {
$report[$party]['31_60'] += $purchase->balance_amount;
} elseif ($days <= 90) {
$report[$party]['61_90'] += $purchase->balance_amount;
} else {
$report[$party]['over_90'] += $purchase->balance_amount;
}
$report[$party]['total_due'] += $purchase->balance_amount;
}
// Convert to datatable-friendly array
$report = array_values($report);
return response()->json([
'data' => $report
]);
}
public function destroy_old(Purchase $purchase)
{
$purchase->delete();
return response()->json(['success' => true, 'message' => 'Purchase deleted.']);
}
public function destroy(Purchase $purchase)
{
// Delete related items
$purchase->items()->delete();
// Delete related files from storage and DB
foreach ($purchase->files as $file) {
if ($file->file_path && Storage::disk('public')->exists($file->file_path)) {
Storage::disk('public')->delete($file->file_path);
}
$file->delete();
}
// Delete the purchase record
$purchase->delete();
return response()->json(['success' => true, 'message' => 'Purchase and related data deleted.']);
}
public function deleteFile(PurchaseFile $file)
{
if ($file->file_path && Storage::disk('public')->exists($file->file_path)) {
Storage::disk('public')->delete($file->file_path);
}
$file->delete();
return response()->json(['success' => true, 'message' => 'File deleted successfully']);
}
}