diff --git a/app/Http/Controllers/Admin/AdminInvoiceController.php b/app/Http/Controllers/Admin/AdminInvoiceController.php index d2cc567..9a14f97 100644 --- a/app/Http/Controllers/Admin/AdminInvoiceController.php +++ b/app/Http/Controllers/Admin/AdminInvoiceController.php @@ -130,7 +130,7 @@ class AdminInvoiceController extends Controller $data = $request->validate([ 'invoice_date' => 'required|date', 'due_date' => 'required|date|after_or_equal:invoice_date', - 'status' => 'required|in:pending,paid,overdue', + 'status' => 'required|in:pending,paying,paid,overdue', 'notes' => 'nullable|string', ]); @@ -149,8 +149,8 @@ class AdminInvoiceController extends Controller $this->generateInvoicePDF($invoice); return redirect() - ->route('admin.invoices.index') - ->with('success', 'Invoice updated & PDF generated successfully.'); + ->route('admin.invoices.edit', $invoice->id) + ->with('success', 'Invoice updated & PDF generated successfully.'); } // ------------------------------------------------------------- diff --git a/app/Http/Controllers/ContainerController.php b/app/Http/Controllers/ContainerController.php index eb6d89a..b948702 100644 --- a/app/Http/Controllers/ContainerController.php +++ b/app/Http/Controllers/ContainerController.php @@ -11,7 +11,7 @@ use Illuminate\Http\Request; use Maatwebsite\Excel\Facades\Excel; use Carbon\Carbon; use Barryvdh\DomPDF\Facade\Pdf; -use Illuminate\Support\Facades\Storage; // <-- added for Excel download +use Illuminate\Support\Facades\Storage; class ContainerController extends Controller { @@ -518,9 +518,12 @@ class ContainerController extends Controller $firstMark = $rowsForCustomer[0]['mark']; $snap = $markToSnapshot[$firstMark] ?? null; + // ✅ Customer User model वरून fetch करा (customer_id string आहे जसे CID-2025-000001) + $customerUser = \App\Models\User::where('customer_id', $customerId)->first(); + $invoice = new Invoice(); $invoice->container_id = $container->id; - $invoice->customer_id = $customerId; + $invoice->customer_id = $customerUser->id ?? null; // ✅ integer id store करतोय $invoice->mark_no = $firstMark; $invoice->invoice_number = $this->generateInvoiceNumber(); @@ -533,28 +536,24 @@ class ContainerController extends Controller ->addDays(10) ->format('Y-m-d'); - // Customer User model वरून fetch करा (customer_id string आहे जसे CID-2025-000001) - $customerUser = \App\Models\User::where('customer_id', $customerId)->first(); - - $invoice->container_id = $container->id; - $invoice->customer_id = $customerUser->id ?? null; // ✅ integer id - $invoice->mark_no = $firstMark; - - if ($snap) { - $invoice->customer_name = $snap['customer_name'] ?? null; - $invoice->company_name = $snap['company_name'] ?? null; - $invoice->customer_mobile = $snap['mobile_no'] ?? null; - } - - $invoice->final_amount = 0; - $invoice->gst_percent = 0; - $invoice->gst_amount = 0; - $invoice->final_amount_with_gst = 0; - - // ✅ User model वरून email, address, pincode घ्या + // ✅ Snapshot data from MarkList (backward compatibility) + if ($snap) { + $invoice->customer_name = $snap['customer_name'] ?? null; + $invoice->company_name = $snap['company_name'] ?? null; + $invoice->customer_mobile = $snap['mobile_no'] ?? null; + } + + // ✅ User model वरून email, address, pincode घ्या + if ($customerUser) { $invoice->customer_email = $customerUser->email ?? null; $invoice->customer_address = $customerUser->address ?? null; $invoice->pincode = $customerUser->pincode ?? null; + } + + $invoice->final_amount = 0; + $invoice->gst_percent = 0; + $invoice->gst_amount = 0; + $invoice->final_amount_with_gst = 0; $uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark')); $invoice->notes = 'Auto-created from Container ' . $container->container_number @@ -621,7 +620,18 @@ class ContainerController extends Controller public function show(Container $container) { $container->load('rows'); - return view('admin.container_show', compact('container')); + + // paid invoices च्या row indexes collect करा + $lockedRowIndexes = \App\Models\Invoice::where('invoices.container_id', $container->id) + ->where('invoices.status', 'paid') + ->join('invoice_items', 'invoices.id', '=', 'invoice_items.invoice_id') + ->pluck('invoice_items.container_row_index') + ->filter() + ->unique() + ->values() + ->toArray(); + + return view('admin.container_show', compact('container', 'lockedRowIndexes')); } public function updateRows(Request $request, Container $container) @@ -879,73 +889,71 @@ class ContainerController extends Controller return Storage::download($path, $fileName); } - public function popupPopup(Container $container) -{ - // existing show सारखाच data वापरू - $container->load('rows'); + { + // existing show सारखाच data वापरू + $container->load('rows'); - // summary आधीपासून index() मध्ये जसा काढतोस तसाच logic reuse - $rows = $container->rows ?? collect(); + // summary आधीपासून index() मध्ये जसा काढतोस तसाच logic reuse + $rows = $container->rows ?? collect(); - $totalCtn = 0; - $totalQty = 0; - $totalCbm = 0; - $totalKg = 0; + $totalCtn = 0; + $totalQty = 0; + $totalCbm = 0; + $totalKg = 0; - $ctnKeys = ['CTN', 'CTNS']; - $qtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY', 'QTY', 'PCS', 'PIECES']; - $cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM']; - $kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT']; + $ctnKeys = ['CTN', 'CTNS']; + $qtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY', 'QTY', 'PCS', 'PIECES']; + $cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM']; + $kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT']; - $getFirstNumeric = function (array $data, array $possibleKeys) { - $normalizedMap = []; - foreach ($data as $key => $value) { - if ($key === null) continue; - $normKey = strtoupper((string)$key); - $normKey = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normKey); - $normalizedMap[$normKey] = $value; - } + $getFirstNumeric = function (array $data, array $possibleKeys) { + $normalizedMap = []; + foreach ($data as $key => $value) { + if ($key === null) continue; + $normKey = strtoupper((string)$key); + $normKey = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normKey); + $normalizedMap[$normKey] = $value; + } - foreach ($possibleKeys as $search) { - $normSearch = strtoupper($search); - $normSearch = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normSearch); + foreach ($possibleKeys as $search) { + $normSearch = strtoupper($search); + $normSearch = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normSearch); - foreach ($normalizedMap as $nKey => $value) { - if (strpos($nKey, $normSearch) !== false) { - if (is_numeric($value)) { - return (float)$value; - } - if (is_string($value) && is_numeric(trim($value))) { - return (float)trim($value); + foreach ($normalizedMap as $nKey => $value) { + if (strpos($nKey, $normSearch) !== false) { + if (is_numeric($value)) { + return (float)$value; + } + if (is_string($value) && is_numeric(trim($value))) { + return (float)trim($value); + } } } } + return 0; + }; + + foreach ($rows as $row) { + $data = $row->data ?? []; + if (!is_array($data)) continue; + + $totalCtn += $getFirstNumeric($data, $ctnKeys); + $totalQty += $getFirstNumeric($data, $qtyKeys); + $totalCbm += $getFirstNumeric($data, $cbmKeys); + $totalKg += $getFirstNumeric($data, $kgKeys); } - return 0; - }; - foreach ($rows as $row) { - $data = $row->data ?? []; - if (!is_array($data)) continue; + $summary = [ + 'total_ctn' => round($totalCtn, 2), + 'total_qty' => round($totalQty, 2), + 'total_cbm' => round($totalCbm, 3), + 'total_kg' => round($totalKg, 2), + ]; - $totalCtn += $getFirstNumeric($data, $ctnKeys); - $totalQty += $getFirstNumeric($data, $qtyKeys); - $totalCbm += $getFirstNumeric($data, $cbmKeys); - $totalKg += $getFirstNumeric($data, $kgKeys); + return view('admin.partials.container_popup_readonly', [ + 'container' => $container, + 'summary' => $summary, + ]); } - - $summary = [ - 'total_ctn' => round($totalCtn, 2), - 'total_qty' => round($totalQty, 2), - 'total_cbm' => round($totalCbm, 3), - 'total_kg' => round($totalKg, 2), - ]; - - return view('admin.partials.container_popup_readonly', [ - 'container' => $container, - 'summary' => $summary, - ]); -} - } diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index eaba94c..bf2e258 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -119,4 +119,11 @@ class Invoice extends Model return max(0, $grand - $paid); } + + public function isLockedForEdit(): bool + { + return $this->status === 'paid'; + } + + } diff --git a/database/migrations/2026_03_12_053830_add_charge_columns_to_invoices_table.php b/database/migrations/2026_03_12_053830_add_charge_columns_to_invoices_table.php index 567740f..79adc4a 100644 --- a/database/migrations/2026_03_12_053830_add_charge_columns_to_invoices_table.php +++ b/database/migrations/2026_03_12_053830_add_charge_columns_to_invoices_table.php @@ -9,15 +9,29 @@ class AddChargeColumnsToInvoicesTable extends Migration public function up() { Schema::table('invoices', function (Blueprint $table) { - $table->decimal('charge_groups_total', 15, 2)->nullable()->after('final_amount_with_gst'); - $table->decimal('grand_total_with_charges', 15, 2)->nullable()->after('charge_groups_total'); + if (!Schema::hasColumn('invoices', 'charge_groups_total')) { + $table->decimal('charge_groups_total', 15, 2) + ->nullable() + ->after('final_amount_with_gst'); + } + + if (!Schema::hasColumn('invoices', 'grand_total_with_charges')) { + $table->decimal('grand_total_with_charges', 15, 2) + ->nullable() + ->after('charge_groups_total'); + } }); } public function down() { Schema::table('invoices', function (Blueprint $table) { - $table->dropColumn(['charge_groups_total', 'grand_total_with_charges']); + if (Schema::hasColumn('invoices', 'charge_groups_total')) { + $table->dropColumn('charge_groups_total'); + } + if (Schema::hasColumn('invoices', 'grand_total_with_charges')) { + $table->dropColumn('grand_total_with_charges'); + } }); } -} \ No newline at end of file +} diff --git a/database/migrations/2026_03_12_091307_alter_invoices_status_add_paying.php b/database/migrations/2026_03_12_091307_alter_invoices_status_add_paying.php new file mode 100644 index 0000000..ac5ceec --- /dev/null +++ b/database/migrations/2026_03_12_091307_alter_invoices_status_add_paying.php @@ -0,0 +1,23 @@ +row_index, $lockedRowIndexes ?? []); + $isReadOnly = $isTotalColumn || $container->status !== 'pending' || $isLockedByInvoice; + @endphp - // जर container pending नसेल तर सर्व fields readonly - $isReadOnly = $isTotalColumn || $container->status !== 'pending'; - @endphp + @if($loop->first && $isLockedByInvoice) + {{-- पहिल्या cell मध्ये lock icon --}} + @endif + - - @if($invoice->status == 'paid') - - @elseif($invoice->status == 'pending') - - @elseif($invoice->status == 'overdue') - - @endif - {{ ucfirst($invoice->status) }} - + + @if($invoice->status == 'paid') + + @elseif($invoice->status == 'pending') + + @elseif($invoice->status == 'paying') + {{-- processing icon --}} + @elseif($invoice->status == 'overdue') + + @endif + {{ ucfirst($invoice->status) }} + + @@ -1338,11 +1359,14 @@ @elseif($invoice->status == 'pending') + @elseif($invoice->status == 'paying') + @elseif($invoice->status == 'overdue') @endif {{ ucfirst($invoice->status) }} +
@@ -1677,6 +1701,7 @@ document.addEventListener('DOMContentLoaded', function() { ${invoice.status === 'paid' ? '' : ''} ${invoice.status === 'pending' ? '' : ''} ${invoice.status === 'overdue' ? '' : ''} + ${invoice.status === 'paying' ? '' : ''} ${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)} @@ -1710,6 +1735,7 @@ document.addEventListener('DOMContentLoaded', function() { ${invoice.status === 'paid' ? '' : ''} ${invoice.status === 'pending' ? '' : ''} ${invoice.status === 'overdue' ? '' : ''} + ${invoice.status === 'paying' ? '' : ''} ${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
diff --git a/resources/views/admin/invoice_edit.blade.php b/resources/views/admin/invoice_edit.blade.php index a45efb0..5309b0b 100644 --- a/resources/views/admin/invoice_edit.blade.php +++ b/resources/views/admin/invoice_edit.blade.php @@ -381,9 +381,11 @@ + {{-- Notes --}} diff --git a/resources/views/admin/popup_invoice.blade.php b/resources/views/admin/popup_invoice.blade.php index 4146f1d..84f730f 100644 --- a/resources/views/admin/popup_invoice.blade.php +++ b/resources/views/admin/popup_invoice.blade.php @@ -640,6 +640,24 @@ /* ── DIVIDER ── */ .section-divider { height: 1px; background: var(--border); margin: 0 2.5rem; } + + .pagination .page-link { + border-radius: 6px; + border: 1px solid #e5e7eb; + padding: 4px 10px; + font-size: 0.78rem; + color: #111827; + } + .pagination .page-item.active .page-link { + background-color: #111827; + border-color: #111827; + color: #ffffff; + } + .pagination .page-item.disabled .page-link { + background-color: #f3f4f6; + color: #9ca3af; + border-color: #e5e7eb; + } @@ -781,89 +799,125 @@ @endphp
-
- Invoice Items +
+ Invoice Items +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + @foreach($invoice->items as $i => $item) + @php + $alreadyGrouped = in_array($item->id, $groupedItemIds ?? []); + @endphp + + + + + + + + + + @if($isEmbedded) + + + @else + + + @endif + + + + + + + + @endforeach + + @if($invoice->items->isEmpty()) + + + + @endif + +
+ + #DescriptionCTNQTYTTL/QTYUnitPriceTTL AmountCBMTTL CBMKGTTL KGShop 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 }} +
+
+ No invoice items found. +
-
- - - - - - - - - - - - - - - - - - - - - @foreach($invoice->items as $i => $item) - @php - $alreadyGrouped = in_array($item->id, $groupedItemIds ?? []); - @endphp - - - - - - - - + {{-- Items pagination bar --}} +
+
+ Showing 0 to + 0 of + 0 items +
- @if($isEmbedded) -
- - @else - - - @endif + + - - - - - - - @endforeach - - @if($invoice->items->isEmpty()) - - - - @endif - -
- - #DescriptionCTNQTYTTL/QTYUnitPriceTTL AmountCBMTTL CBMKGTTL KGShop 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 }}
-
No invoice items found. -
+
+
+
Selected items for charge group: 0 @@ -1671,8 +1725,7 @@ document.addEventListener('DOMContentLoaded', function () { : ' Hide'; }); }); - -// simple select all (duplicate राहिला तरी harmless) +// "Select All" checkbox logic document.addEventListener('DOMContentLoaded', function () { const selectAll = document.getElementById('selectAllItems'); const checkboxes = document.querySelectorAll('.item-select-checkbox'); @@ -1687,6 +1740,113 @@ document.addEventListener('DOMContentLoaded', function () { }); } }); + +// Items table pagination logic edit blade +document.addEventListener('DOMContentLoaded', function () { + // ====== ITEMS TABLE PAGINATION (50 per page, numbered) ====== + const itemsPerPage = 50; + const itemsTbody = document.getElementById('itemsTableBody'); + const allItemRows = itemsTbody ? Array.from(itemsTbody.querySelectorAll('tr')) : []; + const itemsStart = document.getElementById('itemsStart'); + const itemsEnd = document.getElementById('itemsEnd'); + const itemsTotal = document.getElementById('itemsTotal'); + const itemsPages = document.getElementById('itemsPages'); + const itemsBar = document.getElementById('itemsPaginationBar'); + + let itemsCurrentPage = 1; + + if (allItemRows.length) { + // data rows + const dataRows = allItemRows.filter(row => !row.querySelector('td[colspan]')); + const total = dataRows.length; + const totalPages = Math.max(1, Math.ceil(total / itemsPerPage)); + + if (itemsTotal) itemsTotal.textContent = total; + + function renderItemsPage() { + if (!total) { + allItemRows.forEach(r => r.style.display = ''); + if (itemsStart) itemsStart.textContent = 0; + if (itemsEnd) itemsEnd.textContent = 0; + if (itemsPages) itemsPages.innerHTML = ''; + return; + } + + const startIndex = (itemsCurrentPage - 1) * itemsPerPage; + const endIndex = Math.min(startIndex + itemsPerPage, total); + + dataRows.forEach((row, idx) => { + row.style.display = (idx >= startIndex && idx < endIndex) ? '' : 'none'; + }); + + // "no items" row hide + allItemRows.forEach(row => { + const td = row.querySelector('td[colspan]'); + if (td) row.style.display = 'none'; + }); + + if (itemsStart) itemsStart.textContent = startIndex + 1; + if (itemsEnd) itemsEnd.textContent = endIndex; + + // pagination buttons: ‹ 1 2 … › + if (itemsPages) { + itemsPages.innerHTML = ''; + + // prev + const prevLi = document.createElement('li'); + prevLi.className = 'page-item' + (itemsCurrentPage === 1 ? ' disabled' : ''); + prevLi.innerHTML = ''; + if (itemsCurrentPage > 1) { + prevLi.querySelector('button').addEventListener('click', () => { + itemsCurrentPage--; + renderItemsPage(); + }); + } + itemsPages.appendChild(prevLi); + + // page numbers + for (let p = 1; p <= totalPages; p++) { + const li = document.createElement('li'); + li.className = 'page-item' + (p === itemsCurrentPage ? ' active' : ''); + const btn = document.createElement('button'); + btn.type = 'button'; + btn.className = 'page-link'; + btn.textContent = p; + + if (p !== itemsCurrentPage) { + btn.addEventListener('click', () => { + itemsCurrentPage = p; + renderItemsPage(); + }); + } + + li.appendChild(btn); + itemsPages.appendChild(li); + } + + // next + const nextLi = document.createElement('li'); + nextLi.className = 'page-item' + (itemsCurrentPage === totalPages ? ' disabled' : ''); + nextLi.innerHTML = ''; + if (itemsCurrentPage < totalPages) { + nextLi.querySelector('button').addEventListener('click', () => { + itemsCurrentPage++; + renderItemsPage(); + }); + } + itemsPages.appendChild(nextLi); + } + } + + renderItemsPage(); + } else if (itemsBar) { + itemsBar.style.display = 'none'; + } + + +}); + +