Status Updated

This commit is contained in:
Utkarsh Khedkar
2026-03-12 18:11:43 +05:30
parent bb2a361a97
commit 5d8a746876
18 changed files with 421 additions and 176 deletions

View File

@@ -130,7 +130,7 @@ class AdminInvoiceController extends Controller
$data = $request->validate([ $data = $request->validate([
'invoice_date' => 'required|date', 'invoice_date' => 'required|date',
'due_date' => 'required|date|after_or_equal:invoice_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', 'notes' => 'nullable|string',
]); ]);
@@ -149,7 +149,7 @@ class AdminInvoiceController extends Controller
$this->generateInvoicePDF($invoice); $this->generateInvoicePDF($invoice);
return redirect() return redirect()
->route('admin.invoices.index') ->route('admin.invoices.edit', $invoice->id)
->with('success', 'Invoice updated & PDF generated successfully.'); ->with('success', 'Invoice updated & PDF generated successfully.');
} }

View File

@@ -11,7 +11,7 @@ use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel; use Maatwebsite\Excel\Facades\Excel;
use Carbon\Carbon; use Carbon\Carbon;
use Barryvdh\DomPDF\Facade\Pdf; use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Facades\Storage; // <-- added for Excel download use Illuminate\Support\Facades\Storage;
class ContainerController extends Controller class ContainerController extends Controller
{ {
@@ -518,9 +518,12 @@ class ContainerController extends Controller
$firstMark = $rowsForCustomer[0]['mark']; $firstMark = $rowsForCustomer[0]['mark'];
$snap = $markToSnapshot[$firstMark] ?? null; $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 = new Invoice();
$invoice->container_id = $container->id; $invoice->container_id = $container->id;
$invoice->customer_id = $customerId; $invoice->customer_id = $customerUser->id ?? null; // ✅ integer id store करतोय
$invoice->mark_no = $firstMark; $invoice->mark_no = $firstMark;
$invoice->invoice_number = $this->generateInvoiceNumber(); $invoice->invoice_number = $this->generateInvoiceNumber();
@@ -533,29 +536,25 @@ class ContainerController extends Controller
->addDays(10) ->addDays(10)
->format('Y-m-d'); ->format('Y-m-d');
// Customer User model वरून fetch करा (customer_id string आहे जसे CID-2025-000001) // ✅ Snapshot data from MarkList (backward compatibility)
$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) { if ($snap) {
$invoice->customer_name = $snap['customer_name'] ?? null; $invoice->customer_name = $snap['customer_name'] ?? null;
$invoice->company_name = $snap['company_name'] ?? null; $invoice->company_name = $snap['company_name'] ?? null;
$invoice->customer_mobile = $snap['mobile_no'] ?? 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->final_amount = 0;
$invoice->gst_percent = 0; $invoice->gst_percent = 0;
$invoice->gst_amount = 0; $invoice->gst_amount = 0;
$invoice->final_amount_with_gst = 0; $invoice->final_amount_with_gst = 0;
// ✅ User model वरून email, address, pincode घ्या
$invoice->customer_email = $customerUser->email ?? null;
$invoice->customer_address = $customerUser->address ?? null;
$invoice->pincode = $customerUser->pincode ?? null;
$uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark')); $uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark'));
$invoice->notes = 'Auto-created from Container ' . $container->container_number $invoice->notes = 'Auto-created from Container ' . $container->container_number
. ' for Mark(s): ' . implode(', ', $uniqueMarks); . ' for Mark(s): ' . implode(', ', $uniqueMarks);
@@ -621,7 +620,18 @@ class ContainerController extends Controller
public function show(Container $container) public function show(Container $container)
{ {
$container->load('rows'); $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) public function updateRows(Request $request, Container $container)
@@ -879,7 +889,6 @@ class ContainerController extends Controller
return Storage::download($path, $fileName); return Storage::download($path, $fileName);
} }
public function popupPopup(Container $container) public function popupPopup(Container $container)
{ {
// existing show सारखाच data वापरू // existing show सारखाच data वापरू
@@ -947,5 +956,4 @@ class ContainerController extends Controller
'summary' => $summary, 'summary' => $summary,
]); ]);
} }
} }

View File

@@ -119,4 +119,11 @@ class Invoice extends Model
return max(0, $grand - $paid); return max(0, $grand - $paid);
} }
public function isLockedForEdit(): bool
{
return $this->status === 'paid';
}
} }

View File

@@ -9,15 +9,29 @@ class AddChargeColumnsToInvoicesTable extends Migration
public function up() public function up()
{ {
Schema::table('invoices', function (Blueprint $table) { Schema::table('invoices', function (Blueprint $table) {
$table->decimal('charge_groups_total', 15, 2)->nullable()->after('final_amount_with_gst'); if (!Schema::hasColumn('invoices', 'charge_groups_total')) {
$table->decimal('grand_total_with_charges', 15, 2)->nullable()->after('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() public function down()
{ {
Schema::table('invoices', function (Blueprint $table) { 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');
}
}); });
} }
} }

View File

@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration {
public function up(): void
{
DB::statement("
ALTER TABLE `invoices`
MODIFY `status` ENUM('pending','paying','paid','overdue')
NOT NULL DEFAULT 'pending'
");
}
public function down(): void
{
DB::statement("
ALTER TABLE `invoices`
MODIFY `status` ENUM('pending','paid','overdue')
NOT NULL DEFAULT 'pending'
");
}
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -672,10 +672,15 @@
); );
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount; $isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
// row index = headerRowIndex + 1 + offset — ContainerRow मध्ये row_index save आहे
// जर container pending नसेल तर सर्व fields readonly $isLockedByInvoice = in_array($row->row_index, $lockedRowIndexes ?? []);
$isReadOnly = $isTotalColumn || $container->status !== 'pending'; $isReadOnly = $isTotalColumn || $container->status !== 'pending' || $isLockedByInvoice;
@endphp @endphp
@if($loop->first && $isLockedByInvoice)
{{-- पहिल्या cell मध्ये lock icon --}}
@endif
<td> <td>
<input <input
type="text" type="text"

View File

@@ -170,6 +170,13 @@
font-weight: 500; font-weight: 500;
} }
.filter-select option[value="paying"] {
background-color: #e0e7ff;
color: #4338ca;
font-weight: 500;
}
.filter-select option[value="all"] { .filter-select option[value="all"] {
background-color: white; background-color: white;
color: #1f2937; color: #1f2937;
@@ -534,6 +541,10 @@
background: url('/images/status-bg-overdue.png') !important; background: url('/images/status-bg-overdue.png') !important;
} }
.badge-paying {
background: url('/images/status-bg-paying.png') !important;
}
/* Fallback colors if images don't load - ALL WITH SAME SIZE */ /* Fallback colors if images don't load - ALL WITH SAME SIZE */
.badge.badge-paid { .badge.badge-paid {
background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important; background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important;
@@ -554,6 +565,13 @@
border-color: #8b5cf6 !important; border-color: #8b5cf6 !important;
} }
.badge.badge-paying {
background: linear-gradient(135deg, #e0e7ff, #c7d2fe) !important;
color: #4338ca !important;
border-color: #6366f1 !important;
}
/* Entry Button - Centered */ /* Entry Button - Centered */
.btn-entry { .btn-entry {
background: linear-gradient(135deg, #3492f8 55%, #1256cc 110%); background: linear-gradient(135deg, #3492f8 55%, #1256cc 110%);
@@ -1282,11 +1300,14 @@
<i class="bi bi-check-circle-fill status-icon"></i> <i class="bi bi-check-circle-fill status-icon"></i>
@elseif($invoice->status == 'pending') @elseif($invoice->status == 'pending')
<i class="bi bi-clock-fill status-icon"></i> <i class="bi bi-clock-fill status-icon"></i>
@elseif($invoice->status == 'paying')
<i class="bi bi-arrow-repeat status-icon"></i> {{-- processing icon --}}
@elseif($invoice->status == 'overdue') @elseif($invoice->status == 'overdue')
<i class="bi bi-exclamation-triangle-fill status-icon"></i> <i class="bi bi-exclamation-triangle-fill status-icon"></i>
@endif @endif
{{ ucfirst($invoice->status) }} {{ ucfirst($invoice->status) }}
</span> </span>
</td> </td>
<td class="date-cell"> <td class="date-cell">
@@ -1338,11 +1359,14 @@
<i class="bi bi-check-circle-fill status-icon"></i> <i class="bi bi-check-circle-fill status-icon"></i>
@elseif($invoice->status == 'pending') @elseif($invoice->status == 'pending')
<i class="bi bi-clock-fill status-icon"></i> <i class="bi bi-clock-fill status-icon"></i>
@elseif($invoice->status == 'paying')
<i class="bi bi-arrow-repeat status-icon"></i>
@elseif($invoice->status == 'overdue') @elseif($invoice->status == 'overdue')
<i class="bi bi-exclamation-triangle-fill status-icon"></i> <i class="bi bi-exclamation-triangle-fill status-icon"></i>
@endif @endif
{{ ucfirst($invoice->status) }} {{ ucfirst($invoice->status) }}
</span> </span>
</div> </div>
<div class="mobile-invoice-details"> <div class="mobile-invoice-details">
@@ -1677,6 +1701,7 @@ document.addEventListener('DOMContentLoaded', function() {
${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''} ${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''}
${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''} ${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''}
${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''} ${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''}
${invoice.status === 'paying' ? '<i class="bi bi-arrow-repeat status-icon"></i>' : ''}
${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)} ${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
</span> </span>
</td> </td>
@@ -1710,6 +1735,7 @@ document.addEventListener('DOMContentLoaded', function() {
${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''} ${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''}
${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''} ${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''}
${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''} ${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''}
${invoice.status === 'paying' ? '<i class="bi bi-arrow-repeat status-icon"></i>' : ''}
${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)} ${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
</span> </span>
</div> </div>

View File

@@ -381,9 +381,11 @@
</label> </label>
<select name="status" class="form-select-compact" required> <select name="status" class="form-select-compact" required>
<option value="pending" {{ old('status', $invoice->status) === 'pending' ? 'selected' : '' }}>Pending</option> <option value="pending" {{ old('status', $invoice->status) === 'pending' ? 'selected' : '' }}>Pending</option>
<option value="paying" {{ old('status', $invoice->status) === 'paying' ? 'selected' : '' }}>Paying</option>
<option value="paid" {{ old('status', $invoice->status) === 'paid' ? 'selected' : '' }}>Paid</option> <option value="paid" {{ old('status', $invoice->status) === 'paid' ? 'selected' : '' }}>Paid</option>
<option value="overdue" {{ old('status', $invoice->status) === 'overdue' ? 'selected' : '' }}>Overdue</option> <option value="overdue" {{ old('status', $invoice->status) === 'overdue' ? 'selected' : '' }}>Overdue</option>
</select> </select>
</div> </div>
{{-- Notes --}} {{-- Notes --}}

View File

@@ -640,6 +640,24 @@
/* ── DIVIDER ── */ /* ── DIVIDER ── */
.section-divider { height: 1px; background: var(--border); margin: 0 2.5rem; } .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;
}
</style> </style>
</head> </head>
<body> <body>
@@ -785,6 +803,7 @@
<i class="fas fa-list"></i> Invoice Items <i class="fas fa-list"></i> Invoice Items
</div> </div>
<div class="panel-body">
<div class="table-responsive"> <div class="table-responsive">
<table class="invoice-table items-table"> <table class="invoice-table items-table">
<thead> <thead>
@@ -807,7 +826,7 @@
<th class="text-center" style="min-width:95px;">Shop No</th> <th class="text-center" style="min-width:95px;">Shop No</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody id="itemsTableBody">
@foreach($invoice->items as $i => $item) @foreach($invoice->items as $i => $item)
@php @php
$alreadyGrouped = in_array($item->id, $groupedItemIds ?? []); $alreadyGrouped = in_array($item->id, $groupedItemIds ?? []);
@@ -820,8 +839,12 @@
value="{{ $item->id }}" value="{{ $item->id }}"
{{ $alreadyGrouped ? 'disabled' : '' }}> {{ $alreadyGrouped ? 'disabled' : '' }}>
</td> </td>
<td class="text-center" style="font-weight:600;color:var(--text-muted);">{{ $i + 1 }}</td> <td class="text-center" style="font-weight:600;color:var(--text-muted);">
<td class="desc-col" style="font-weight:600;color:var(--primary);">{{ $item->description }}</td> {{ $i + 1 }}
</td>
<td class="desc-col" style="font-weight:600;color:var(--primary);">
{{ $item->description }}
</td>
<td class="text-center">{{ $item->ctn }}</td> <td class="text-center">{{ $item->ctn }}</td>
<td class="text-center">{{ $item->qty }}</td> <td class="text-center">{{ $item->qty }}</td>
<td class="text-center" style="font-weight:700;">{{ $item->ttl_qty }}</td> <td class="text-center" style="font-weight:700;">{{ $item->ttl_qty }}</td>
@@ -841,22 +864,31 @@
class="form-control form-control-sm text-end"> class="form-control form-control-sm text-end">
</td> </td>
@else @else
<td class="text-center price-green">{{ number_format($item->price, 2) }}</td> <td class="text-center price-green">
<td class="text-center price-blue">{{ number_format($item->ttl_amount, 2) }}</td> {{ number_format($item->price, 2) }}
</td>
<td class="text-center price-blue">
{{ number_format($item->ttl_amount, 2) }}
</td>
@endif @endif
<td class="text-center">{{ $item->cbm }}</td> <td class="text-center">{{ $item->cbm }}</td>
<td class="text-center">{{ $item->ttl_cbm }}</td> <td class="text-center">{{ $item->ttl_cbm }}</td>
<td class="text-center">{{ $item->kg }}</td> <td class="text-center">{{ $item->kg }}</td>
<td class="text-center">{{ $item->ttl_kg }}</td> <td class="text-center">{{ $item->ttl_kg }}</td>
<td class="text-center"><span class="badge-shop">{{ $item->shop_no }}</span></td> <td class="text-center">
<span class="badge-shop">{{ $item->shop_no }}</span>
</td>
</tr> </tr>
@endforeach @endforeach
@if($invoice->items->isEmpty()) @if($invoice->items->isEmpty())
<tr> <tr>
<td colspan="15" class="text-center py-4" style="color:var(--text-muted);font-weight:600;"> <td colspan="15" class="text-center py-4"
<i class="fas fa-inbox me-2" style="font-size:1.3rem;opacity:.4;"></i><br>No invoice items found. style="color:var(--text-muted);font-weight:600;">
<i class="fas fa-inbox me-2"
style="font-size:1.3rem;opacity:.4;"></i><br>
No invoice items found.
</td> </td>
</tr> </tr>
@endif @endif
@@ -864,6 +896,28 @@
</table> </table>
</div> </div>
{{-- Items pagination bar --}}
<div class="d-flex justify-content-between align-items-center mt-2"
id="itemsPaginationBar"
style="font-size:0.8rem;">
<div>
Showing <span id="itemsStart">0</span> to
<span id="itemsEnd">0</span> of
<span id="itemsTotal">0</span> items
</div>
<nav aria-label="Items pagination">
<ul class="pagination mb-0 pagination-sm" id="itemsPages">
{{-- JS will render buttons here --}}
</ul>
</nav>
</div>
</div>
</div>
</div>
<!-- ACTION BAR --> <!-- ACTION BAR -->
<div class="action-bar"> <div class="action-bar">
<small>Selected items for charge group: <span id="selectedItemsCount">0</span></small> <small>Selected items for charge group: <span id="selectedItemsCount">0</span></small>
@@ -1671,8 +1725,7 @@ document.addEventListener('DOMContentLoaded', function () {
: '<i class="fas fa-eye-slash"></i> Hide'; : '<i class="fas fa-eye-slash"></i> Hide';
}); });
}); });
// "Select All" checkbox logic
// simple select all (duplicate राहिला तरी harmless)
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
const selectAll = document.getElementById('selectAllItems'); const selectAll = document.getElementById('selectAllItems');
const checkboxes = document.querySelectorAll('.item-select-checkbox'); 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 = '<button class="page-link" type="button" aria-label="Previous"></button>';
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 = '<button class="page-link" type="button" aria-label="Next"></button>';
if (itemsCurrentPage < totalPages) {
nextLi.querySelector('button').addEventListener('click', () => {
itemsCurrentPage++;
renderItemsPage();
});
}
itemsPages.appendChild(nextLi);
}
}
renderItemsPage();
} else if (itemsBar) {
itemsBar.style.display = 'none';
}
});
</script> </script>