diff --git a/app/Http/Controllers/Admin/AdminInvoiceController.php b/app/Http/Controllers/Admin/AdminInvoiceController.php new file mode 100644 index 0000000..94ac645 --- /dev/null +++ b/app/Http/Controllers/Admin/AdminInvoiceController.php @@ -0,0 +1,122 @@ +get(); + return view('admin.invoice', compact('invoices')); + } + + // ------------------------------------------------------------- + // POPUP VIEW (AJAX) + // ------------------------------------------------------------- + public function popup($id) + { + $invoice = Invoice::with('items')->findOrFail($id); + return view('admin.popup_invoice', compact('invoice')); + } + + // ------------------------------------------------------------- + // EDIT INVOICE PAGE + // ------------------------------------------------------------- + public function edit($id) + { + $invoice = Invoice::findOrFail($id); + return view('admin.invoice_edit', compact('invoice')); + } + + // ------------------------------------------------------------- + // UPDATE INVOICE + // ------------------------------------------------------------- + public function update(Request $request, $id) + { + $invoice = Invoice::findOrFail($id); + + // Validate editable fields + $data = $request->validate([ + 'invoice_date' => 'required|date', + 'due_date' => 'required|date|after_or_equal:invoice_date', + 'payment_method' => 'nullable|string', + 'reference_no' => 'nullable|string|max:255', + 'final_amount' => 'required|numeric|min:0', + 'gst_percent' => 'required|numeric|min:0|max:28', + 'status' => 'required|in:pending,paid,overdue', + 'notes' => 'nullable|string', + ]); + + // Auto-calc + $gst_amount = ($data['final_amount'] * $data['gst_percent']) / 100; + $final_amount_with_gst = $data['final_amount'] + $gst_amount; + + $data['gst_amount'] = $gst_amount; + $data['final_amount_with_gst'] = $final_amount_with_gst; + + // Update DB + $invoice->update($data); + + // Generate PDF + $this->generateInvoicePDF($invoice); + + return redirect() + ->route('admin.invoices.index') + ->with('success', 'Invoice updated & PDF generated successfully.'); + } + + // ------------------------------------------------------------- + // PDF GENERATION USING mPDF + // ------------------------------------------------------------- + public function generateInvoicePDF($invoice) + { + // PDF Name + $fileName = 'invoice-' . $invoice->invoice_number . '.pdf'; + + // Save directly in /public/invoices + $folder = public_path('invoices/'); + + // Create folder if not exists + if (!file_exists($folder)) { + mkdir($folder, 0777, true); + } + + // Full path + $filePath = $folder . $fileName; + + // Delete old file + if (file_exists($filePath)) { + unlink($filePath); + } + + // Initialize mPDF + $mpdf = new Mpdf([ + 'mode' => 'utf-8', + 'format' => 'A4', + 'default_font' => 'sans-serif' + ]); + + // Load HTML view + $html = view('admin.pdf.invoice', compact('invoice'))->render(); + + // Generate + $mpdf->WriteHTML($html); + + // Save to public/invoices + $mpdf->Output($filePath, 'F'); + + // Save path in DB + $invoice->update([ + 'pdf_path' => 'invoices/' . $fileName + ]); + } +} diff --git a/app/Http/Controllers/Admin/AdminOrderController.php b/app/Http/Controllers/Admin/AdminOrderController.php index 05a2446..6dadb27 100644 --- a/app/Http/Controllers/Admin/AdminOrderController.php +++ b/app/Http/Controllers/Admin/AdminOrderController.php @@ -99,82 +99,163 @@ class AdminOrderController extends Controller // ------------------------------------------------------------------------- public function finishOrder(Request $request) - { - $request->validate([ - 'mark_no' => 'required', - 'origin' => 'required', - 'destination' => 'required', - ]); +{ + $request->validate([ + 'mark_no' => 'required', + 'origin' => 'required', + 'destination' => 'required', + ]); - $items = session('temp_order_items', []); + $items = session('temp_order_items', []); - if (empty($items)) { - return redirect()->to(route('admin.orders.index') . '#createOrderForm') - ->with('error', 'Add at least one item before finishing.'); - } - - // Generate Order ID - $year = date('y'); - $prefix = "KNT-$year-"; - - $lastOrder = Order::latest('id')->first(); - $nextNumber = $lastOrder ? intval(substr($lastOrder->order_id, -8)) + 1 : 1; - - $orderId = $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT); - - // TOTAL SUMS - $total_ctn = array_sum(array_column($items, 'ctn')); - $total_qty = array_sum(array_column($items, 'qty')); - $total_ttl_qty = array_sum(array_column($items, 'ttl_qty')); - $total_amount = array_sum(array_column($items, 'ttl_amount')); - $total_cbm = array_sum(array_column($items, 'cbm')); - $total_ttl_cbm = array_sum(array_column($items, 'ttl_cbm')); - $total_kg = array_sum(array_column($items, 'kg')); - $total_ttl_kg = array_sum(array_column($items, 'ttl_kg')); - - // CREATE ORDER - $order = Order::create([ - 'order_id' => $orderId, - 'mark_no' => $request->mark_no, - 'origin' => $request->origin, - 'destination' => $request->destination, - 'ctn' => $total_ctn, - 'qty' => $total_qty, - 'ttl_qty' => $total_ttl_qty, - 'ttl_amount' => $total_amount, - 'cbm' => $total_cbm, - 'ttl_cbm' => $total_ttl_cbm, - 'kg' => $total_kg, - 'ttl_kg' => $total_ttl_kg, - 'status' => 'pending', - ]); - - // SAVE ALL SUB-ITEMS - foreach ($items as $item) { - OrderItem::create([ - 'order_id' => $order->id, - 'description' => $item['description'], - 'ctn' => $item['ctn'], - 'qty' => $item['qty'], - 'ttl_qty' => $item['ttl_qty'], - 'unit' => $item['unit'], - 'price' => $item['price'], - 'ttl_amount' => $item['ttl_amount'], - 'cbm' => $item['cbm'], - 'ttl_cbm' => $item['ttl_cbm'], - 'kg' => $item['kg'], - 'ttl_kg' => $item['ttl_kg'], - 'shop_no' => $item['shop_no'], - ]); - } - - // CLEAR TEMP DATA - session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']); - - return redirect()->route('admin.orders.index') - ->with('success', 'Order saved successfully.'); + if (empty($items)) { + return redirect()->to(route('admin.orders.index') . '#createOrderForm') + ->with('error', 'Add at least one item before finishing.'); } + // ======================= + // GENERATE ORDER ID + // ======================= + $year = date('y'); + $prefix = "KNT-$year-"; + + $lastOrder = Order::latest('id')->first(); + $nextNumber = $lastOrder ? intval(substr($lastOrder->order_id, -8)) + 1 : 1; + + $orderId = $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT); + + // ======================= + // TOTAL SUMS + // ======================= + $total_ctn = array_sum(array_column($items, 'ctn')); + $total_qty = array_sum(array_column($items, 'qty')); + $total_ttl_qty = array_sum(array_column($items, 'ttl_qty')); + $total_amount = array_sum(array_column($items, 'ttl_amount')); + $total_cbm = array_sum(array_column($items, 'cbm')); + $total_ttl_cbm = array_sum(array_column($items, 'ttl_cbm')); + $total_kg = array_sum(array_column($items, 'kg')); + $total_ttl_kg = array_sum(array_column($items, 'ttl_kg')); + + // ======================= + // CREATE ORDER + // ======================= + $order = Order::create([ + 'order_id' => $orderId, + 'mark_no' => $request->mark_no, + 'origin' => $request->origin, + 'destination' => $request->destination, + 'ctn' => $total_ctn, + 'qty' => $total_qty, + 'ttl_qty' => $total_ttl_qty, + 'ttl_amount' => $total_amount, + 'cbm' => $total_cbm, + 'ttl_cbm' => $total_ttl_cbm, + 'kg' => $total_kg, + 'ttl_kg' => $total_ttl_kg, + 'status' => 'pending', + ]); + + // SAVE ORDER ITEMS + foreach ($items as $item) { + OrderItem::create([ + 'order_id' => $order->id, + 'description' => $item['description'], + 'ctn' => $item['ctn'], + 'qty' => $item['qty'], + 'ttl_qty' => $item['ttl_qty'], + 'unit' => $item['unit'], + 'price' => $item['price'], + 'ttl_amount' => $item['ttl_amount'], + 'cbm' => $item['cbm'], + 'ttl_cbm' => $item['ttl_cbm'], + 'kg' => $item['kg'], + 'ttl_kg' => $item['ttl_kg'], + 'shop_no' => $item['shop_no'], + ]); + } + + // ======================= + // INVOICE CREATION START + // ======================= + + // 1. Auto-generate invoice number + $lastInvoice = \App\Models\Invoice::latest()->first(); + $nextInvoice = $lastInvoice ? $lastInvoice->id + 1 : 1; + $invoiceNumber = 'INV-' . date('Y') . '-' . str_pad($nextInvoice, 6, '0', STR_PAD_LEFT); + + // 2. Fetch customer (using mark list → customer_id) + $markList = MarkList::where('mark_no', $order->mark_no)->first(); + $customer = null; + + if ($markList && $markList->customer_id) { + $customer = \App\Models\User::where('customer_id', $markList->customer_id)->first(); + } + + // 3. Create Invoice Record + $invoice = \App\Models\Invoice::create([ + 'order_id' => $order->id, + 'customer_id' => $customer->id ?? null, + 'mark_no' => $order->mark_no, + + 'invoice_number' => $invoiceNumber, + 'invoice_date' => now(), + 'due_date' => now()->addDays(10), + + 'payment_method' => null, + 'reference_no' => null, + 'status' => 'pending', + + 'final_amount' => $total_amount, + 'gst_percent' => 0, + 'gst_amount' => 0, + 'final_amount_with_gst' => $total_amount, + + // snapshot customer fields + 'customer_name' => $customer->customer_name ?? null, + 'company_name' => $customer->company_name ?? null, + 'customer_email' => $customer->email ?? null, + 'customer_mobile' => $customer->mobile_no ?? null, + 'customer_address' => $customer->address ?? null, + 'pincode' => $customer->pincode ?? null, + + 'notes' => null, + ]); + + // 4. Clone order items into invoice_items + foreach ($order->items as $item) { + \App\Models\InvoiceItem::create([ + 'invoice_id' => $invoice->id, + 'description' => $item->description, + 'ctn' => $item->ctn, + 'qty' => $item->qty, + 'ttl_qty' => $item->ttl_qty, + 'unit' => $item->unit, + 'price' => $item->price, + 'ttl_amount' => $item->ttl_amount, + 'cbm' => $item->cbm, + 'ttl_cbm' => $item->ttl_cbm, + 'kg' => $item->kg, + 'ttl_kg' => $item->ttl_kg, + 'shop_no' => $item->shop_no, + ]); + } + + // 5. TODO: PDF generation (I will add this later) + $invoice->pdf_path = null; // placeholder for now + $invoice->save(); + + // ======================= + // END INVOICE CREATION + // ======================= + + // CLEAR TEMP DATA + session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']); + + return redirect()->route('admin.orders.index') + ->with('success', 'Order + Invoice created successfully.'); +} + + // ------------------------------------------------------------------------- // ORDER SHOW PAGE // ------------------------------------------------------------------------- diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php new file mode 100644 index 0000000..e8c5739 --- /dev/null +++ b/app/Models/Invoice.php @@ -0,0 +1,77 @@ +hasMany(InvoiceItem::class)->orderBy('id', 'ASC'); + } + + public function order() + { + return $this->belongsTo(Order::class); + } + + public function customer() + { + return $this->belongsTo(User::class, 'customer_id'); + } + + /**************************** + * Helper Functions + ****************************/ + + // Auto calculate GST fields (you can call this in controller before saving) + public function calculateTotals() + { + $gst = ($this->final_amount * $this->gst_percent) / 100; + $this->gst_amount = $gst; + $this->final_amount_with_gst = $this->final_amount + $gst; + } + + // Check overdue status condition + public function isOverdue() + { + return $this->status === 'pending' && now()->gt($this->due_date); + } +} diff --git a/app/Models/InvoiceItem.php b/app/Models/InvoiceItem.php new file mode 100644 index 0000000..9e3a6ff --- /dev/null +++ b/app/Models/InvoiceItem.php @@ -0,0 +1,40 @@ +belongsTo(Invoice::class); + } +} diff --git a/composer.json b/composer.json index a7e83bc..ebdc485 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "php": "^8.2", "laravel/framework": "^12.0", "laravel/tinker": "^2.10.1", + "mpdf/mpdf": "^8.2", "php-open-source-saver/jwt-auth": "2.8" }, "require-dev": { diff --git a/composer.lock b/composer.lock index b7a52c5..9785bd2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dc4cd5d2076c08406e2d0bd4db2cd741", + "content-hash": "fa680e1e8b3550d710849bbd820e3626", "packages": [ { "name": "brick/math", @@ -2184,6 +2184,239 @@ ], "time": "2025-03-24T10:02:05+00:00" }, + { + "name": "mpdf/mpdf", + "version": "v8.2.6", + "source": { + "type": "git", + "url": "https://github.com/mpdf/mpdf.git", + "reference": "dd30e3b01061cf8dfe65e7041ab4cc46d8ebdd44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/mpdf/zipball/dd30e3b01061cf8dfe65e7041ab4cc46d8ebdd44", + "reference": "dd30e3b01061cf8dfe65e7041ab4cc46d8ebdd44", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "ext-mbstring": "*", + "mpdf/psr-http-message-shim": "^1.0 || ^2.0", + "mpdf/psr-log-aware-trait": "^2.0 || ^3.0", + "myclabs/deep-copy": "^1.7", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": "^5.6 || ^7.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "setasign/fpdi": "^2.1" + }, + "require-dev": { + "mockery/mockery": "^1.3.0", + "mpdf/qrcode": "^1.1.0", + "squizlabs/php_codesniffer": "^3.5.0", + "tracy/tracy": "~2.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-bcmath": "Needed for generation of some types of barcodes", + "ext-xml": "Needed mainly for SVG manipulation", + "ext-zlib": "Needed for compression of embedded resources, such as fonts" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Mpdf\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-only" + ], + "authors": [ + { + "name": "Matěj Humpál", + "role": "Developer, maintainer" + }, + { + "name": "Ian Back", + "role": "Developer (retired)" + } + ], + "description": "PHP library generating PDF files from UTF-8 encoded HTML", + "homepage": "https://mpdf.github.io", + "keywords": [ + "pdf", + "php", + "utf-8" + ], + "support": { + "docs": "https://mpdf.github.io", + "issues": "https://github.com/mpdf/mpdf/issues", + "source": "https://github.com/mpdf/mpdf" + }, + "funding": [ + { + "url": "https://www.paypal.me/mpdf", + "type": "custom" + } + ], + "time": "2025-08-18T08:51:51+00:00" + }, + { + "name": "mpdf/psr-http-message-shim", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/mpdf/psr-http-message-shim.git", + "reference": "f25a0153d645e234f9db42e5433b16d9b113920f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/psr-http-message-shim/zipball/f25a0153d645e234f9db42e5433b16d9b113920f", + "reference": "f25a0153d645e234f9db42e5433b16d9b113920f", + "shasum": "" + }, + "require": { + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mpdf\\PsrHttpMessageShim\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Dorison", + "email": "mark@chromatichq.com" + }, + { + "name": "Kristofer Widholm", + "email": "kristofer@chromatichq.com" + }, + { + "name": "Nigel Cunningham", + "email": "nigel.cunningham@technocrat.com.au" + } + ], + "description": "Shim to allow support of different psr/message versions.", + "support": { + "issues": "https://github.com/mpdf/psr-http-message-shim/issues", + "source": "https://github.com/mpdf/psr-http-message-shim/tree/v2.0.1" + }, + "time": "2023-10-02T14:34:03+00:00" + }, + { + "name": "mpdf/psr-log-aware-trait", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/mpdf/psr-log-aware-trait.git", + "reference": "a633da6065e946cc491e1c962850344bb0bf3e78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/psr-log-aware-trait/zipball/a633da6065e946cc491e1c962850344bb0bf3e78", + "reference": "a633da6065e946cc491e1c962850344bb0bf3e78", + "shasum": "" + }, + "require": { + "psr/log": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mpdf\\PsrLogAwareTrait\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Dorison", + "email": "mark@chromatichq.com" + }, + { + "name": "Kristofer Widholm", + "email": "kristofer@chromatichq.com" + } + ], + "description": "Trait to allow support of different psr/log versions.", + "support": { + "issues": "https://github.com/mpdf/psr-log-aware-trait/issues", + "source": "https://github.com/mpdf/psr-log-aware-trait/tree/v3.0.0" + }, + "time": "2023-05-03T06:19:36+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, { "name": "namshi/jose", "version": "7.2.3", @@ -2655,6 +2888,56 @@ ], "time": "2025-10-18T11:10:27+00:00" }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, { "name": "php-open-source-saver/jwt-auth", "version": "2.8.0", @@ -3515,6 +3798,78 @@ }, "time": "2025-09-04T20:59:21+00:00" }, + { + "name": "setasign/fpdi", + "version": "v2.6.4", + "source": { + "type": "git", + "url": "https://github.com/Setasign/FPDI.git", + "reference": "4b53852fde2734ec6a07e458a085db627c60eada" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Setasign/FPDI/zipball/4b53852fde2734ec6a07e458a085db627c60eada", + "reference": "4b53852fde2734ec6a07e458a085db627c60eada", + "shasum": "" + }, + "require": { + "ext-zlib": "*", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "setasign/tfpdf": "<1.31" + }, + "require-dev": { + "phpunit/phpunit": "^7", + "setasign/fpdf": "~1.8.6", + "setasign/tfpdf": "~1.33", + "squizlabs/php_codesniffer": "^3.5", + "tecnickcom/tcpdf": "^6.8" + }, + "suggest": { + "setasign/fpdf": "FPDI will extend this class but as it is also possible to use TCPDF or tFPDF as an alternative. There's no fixed dependency configured." + }, + "type": "library", + "autoload": { + "psr-4": { + "setasign\\Fpdi\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Slabon", + "email": "jan.slabon@setasign.com", + "homepage": "https://www.setasign.com" + }, + { + "name": "Maximilian Kresse", + "email": "maximilian.kresse@setasign.com", + "homepage": "https://www.setasign.com" + } + ], + "description": "FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF documents and use them as templates in FPDF. Because it is also possible to use FPDI with TCPDF, there are no fixed dependencies defined. Please see suggestions for packages which evaluates the dependencies automatically.", + "homepage": "https://www.setasign.com/fpdi", + "keywords": [ + "fpdf", + "fpdi", + "pdf" + ], + "support": { + "issues": "https://github.com/Setasign/FPDI/issues", + "source": "https://github.com/Setasign/FPDI/tree/v2.6.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/setasign/fpdi", + "type": "tidelift" + } + ], + "time": "2025-08-05T09:57:14+00:00" + }, { "name": "symfony/clock", "version": "v7.3.0", @@ -6751,66 +7106,6 @@ }, "time": "2024-05-16T03:13:13+00:00" }, - { - "name": "myclabs/deep-copy", - "version": "1.13.4", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2025-08-01T08:46:24+00:00" - }, { "name": "nunomaduro/collision", "version": "v8.8.2", diff --git a/database/migrations/2025_11_15_120418_create_invoices_table.php b/database/migrations/2025_11_15_120418_create_invoices_table.php new file mode 100644 index 0000000..163a9f1 --- /dev/null +++ b/database/migrations/2025_11_15_120418_create_invoices_table.php @@ -0,0 +1,70 @@ +id(); + + // Links + $table->unsignedBigInteger('order_id')->index(); + $table->unsignedBigInteger('customer_id')->nullable()->index(); // snapshot link if available + $table->string('mark_no')->nullable()->index(); + + // Invoice identity + $table->string('invoice_number')->unique(); + $table->date('invoice_date')->nullable(); + $table->date('due_date')->nullable(); + + // Payment / status + $table->string('payment_method')->nullable(); + $table->string('reference_no')->nullable(); + $table->enum('status', ['pending','paid','overdue'])->default('pending'); + + // Amounts + $table->decimal('final_amount', 14, 2)->default(0.00); // editable by user + $table->decimal('gst_percent', 5, 2)->default(0.00); // editable by user + $table->decimal('gst_amount', 14, 2)->default(0.00); // auto-calculated + $table->decimal('final_amount_with_gst', 14, 2)->default(0.00); // auto-calculated + + // Customer snapshot (immutable fields) + $table->string('customer_name')->nullable(); + $table->string('company_name')->nullable(); + $table->string('customer_email')->nullable(); + $table->string('customer_mobile')->nullable(); + $table->text('customer_address')->nullable(); + $table->string('pincode')->nullable(); + + // PDF / notes + $table->string('pdf_path')->nullable(); + $table->text('notes')->nullable(); + + $table->timestamps(); + + // Foreign keys (optional — adjust table names/namespaces if yours are different) + $table->foreign('order_id')->references('id')->on('orders')->onDelete('cascade'); + // customer_id may reference users table, keep nullable to avoid migration order issues + $table->foreign('customer_id')->references('id')->on('users')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('invoices', function (Blueprint $table) { + $table->dropForeign(['order_id']); + $table->dropForeign(['customer_id']); + }); + Schema::dropIfExists('invoices'); + } +} diff --git a/database/migrations/2025_11_15_120425_create_invoice_items_table.php b/database/migrations/2025_11_15_120425_create_invoice_items_table.php new file mode 100644 index 0000000..cc4da76 --- /dev/null +++ b/database/migrations/2025_11_15_120425_create_invoice_items_table.php @@ -0,0 +1,52 @@ +id(); + $table->unsignedBigInteger('invoice_id')->index(); + + // Snapshot of order item fields (not editable) + $table->text('description')->nullable(); + $table->integer('ctn')->default(0); + $table->integer('qty')->default(0); + $table->integer('ttl_qty')->default(0); + $table->string('unit')->nullable(); + $table->decimal('price', 14, 2)->default(0.00); + $table->decimal('ttl_amount', 14, 2)->default(0.00); + + $table->decimal('cbm', 12, 3)->default(0.000); + $table->decimal('ttl_cbm', 12, 3)->default(0.000); + + $table->decimal('kg', 12, 3)->default(0.000); + $table->decimal('ttl_kg', 12, 3)->default(0.000); + + $table->string('shop_no')->nullable(); + + $table->timestamps(); + + // FK + $table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('invoice_items', function (Blueprint $table) { + $table->dropForeign(['invoice_id']); + }); + Schema::dropIfExists('invoice_items'); + } +} diff --git a/public/invoices/invoice-INV-2025-000002.pdf b/public/invoices/invoice-INV-2025-000002.pdf new file mode 100644 index 0000000..841155b Binary files /dev/null and b/public/invoices/invoice-INV-2025-000002.pdf differ diff --git a/public/invoices/invoice-INV-2025-000003.pdf b/public/invoices/invoice-INV-2025-000003.pdf new file mode 100644 index 0000000..b54a6e8 Binary files /dev/null and b/public/invoices/invoice-INV-2025-000003.pdf differ diff --git a/resources/views/admin/invoice.blade.php b/resources/views/admin/invoice.blade.php index c12ff5a..5aec478 100644 --- a/resources/views/admin/invoice.blade.php +++ b/resources/views/admin/invoice.blade.php @@ -1,12 +1,110 @@ @extends('admin.layouts.app') -@section('page-title', 'Dashboard') +@section('page-title', 'Invoice List') @section('content') +
Here you can manage all system modules.
+| # | +Invoice Number | +Customer | +Final Amount (₹) | +GST % | +Total w/GST (₹) | +Status | +Invoice Date | +Due Date | +Action | +
|---|---|---|---|---|---|---|---|---|---|
| {{ $i + 1 }} | + ++ + {{ $invoice->invoice_number }} + + | + +{{ $invoice->customer_name }} | + +₹{{ number_format($invoice->final_amount, 2) }} | +{{ $invoice->gst_percent }}% | +₹{{ number_format($invoice->final_amount_with_gst, 2) }} | + ++ + {{ ucfirst($invoice->status) }} + + | + +{{ $invoice->invoice_date }} | +{{ $invoice->due_date }} | + ++ + Edit + + | +
+
+ | # | +Description | +CTN | +QTY | +TTL/QTY | +Unit | +Price | +TTL Amount | +CBM | +TTL CBM | +KG | +TTL KG | +Shop No | +
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {{ $i + 1 }} | +{{ $item->description }} | +{{ $item->ctn }} | +{{ $item->qty }} | +{{ $item->ttl_qty }} | +{{ $item->unit }} | +{{ number_format($item->price, 2) }} | +{{ number_format($item->ttl_amount, 2) }} | +{{ $item->cbm }} | +{{ $item->ttl_cbm }} | +{{ $item->kg }} | +{{ $item->ttl_kg }} | +{{ $item->shop_no }} | +
Invoice Date: {{ $invoice->invoice_date }}
+Due Date: {{ $invoice->due_date }}
+ +
+ {{ $invoice->customer_name }}
+ {{ $invoice->company_name }}
+ {{ $invoice->customer_mobile }}
+ {{ $invoice->customer_email }}
+ {{ $invoice->customer_address }}
+
| # | +Description | +CTN | +QTY | +TTL/QTY | +Unit | +Price | +TTL Amount | +CBM | +TTL CBM | +KG | +TTL KG | +Shop No | +
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {{ $i+1 }} | +{{ $item->description }} | +{{ $item->ctn }} | +{{ $item->qty }} | +{{ $item->ttl_qty }} | +{{ $item->unit }} | +{{ number_format($item->price,2) }} | +{{ number_format($item->ttl_amount,2) }} | +{{ $item->cbm }} | +{{ $item->ttl_cbm }} | +{{ $item->kg }} | +{{ $item->ttl_kg }} | +{{ $item->shop_no }} | +
Amount: ₹{{ number_format($invoice->final_amount,2) }}
+GST ({{ $invoice->gst_percent }}%): ₹{{ number_format($invoice->gst_amount,2) }}
+Total With GST: ₹{{ number_format($invoice->final_amount_with_gst,2) }}
+ +Status: {{ ucfirst($invoice->status) }}
+