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([
'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,7 +149,7 @@ class AdminInvoiceController extends Controller
$this->generateInvoicePDF($invoice);
return redirect()
->route('admin.invoices.index')
->route('admin.invoices.edit', $invoice->id)
->with('success', 'Invoice updated & PDF generated successfully.');
}

View File

@@ -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,29 +536,25 @@ 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;
// ✅ 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;
// ✅ 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'));
$invoice->notes = 'Auto-created from Container ' . $container->container_number
. ' for Mark(s): ' . implode(', ', $uniqueMarks);
@@ -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,7 +889,6 @@ class ContainerController extends Controller
return Storage::download($path, $fileName);
}
public function popupPopup(Container $container)
{
// existing show सारखाच data वापरू
@@ -947,5 +956,4 @@ class ContainerController extends Controller
'summary' => $summary,
]);
}
}

View File

@@ -119,4 +119,11 @@ class Invoice extends Model
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()
{
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');
}
});
}
}

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

View File

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

View File

@@ -381,9 +381,11 @@
</label>
<select name="status" class="form-select-compact" required>
<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="overdue" {{ old('status', $invoice->status) === 'overdue' ? 'selected' : '' }}>Overdue</option>
</select>
</div>
{{-- Notes --}}

View File

@@ -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;
}
</style>
</head>
<body>
@@ -785,6 +803,7 @@
<i class="fas fa-list"></i> Invoice Items
</div>
<div class="panel-body">
<div class="table-responsive">
<table class="invoice-table items-table">
<thead>
@@ -807,7 +826,7 @@
<th class="text-center" style="min-width:95px;">Shop No</th>
</tr>
</thead>
<tbody>
<tbody id="itemsTableBody">
@foreach($invoice->items as $i => $item)
@php
$alreadyGrouped = in_array($item->id, $groupedItemIds ?? []);
@@ -820,8 +839,12 @@
value="{{ $item->id }}"
{{ $alreadyGrouped ? 'disabled' : '' }}>
</td>
<td class="text-center" style="font-weight:600;color:var(--text-muted);">{{ $i + 1 }}</td>
<td class="desc-col" style="font-weight:600;color:var(--primary);">{{ $item->description }}</td>
<td class="text-center" style="font-weight:600;color:var(--text-muted);">
{{ $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->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">
</td>
@else
<td class="text-center price-green">{{ number_format($item->price, 2) }}</td>
<td class="text-center price-blue">{{ number_format($item->ttl_amount, 2) }}</td>
<td class="text-center price-green">
{{ number_format($item->price, 2) }}
</td>
<td class="text-center price-blue">
{{ number_format($item->ttl_amount, 2) }}
</td>
@endif
<td class="text-center">{{ $item->cbm }}</td>
<td class="text-center">{{ $item->ttl_cbm }}</td>
<td class="text-center">{{ $item->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>
@endforeach
@if($invoice->items->isEmpty())
<tr>
<td colspan="15" class="text-center py-4" 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 colspan="15" class="text-center py-4"
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>
</tr>
@endif
@@ -864,6 +896,28 @@
</table>
</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 -->
<div class="action-bar">
<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';
});
});
// 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 = '<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>