Status Updated
This commit is contained in:
@@ -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,8 +149,8 @@ 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.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
|
|||||||
@@ -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,28 +536,24 @@ 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();
|
if ($snap) {
|
||||||
|
$invoice->customer_name = $snap['customer_name'] ?? null;
|
||||||
|
$invoice->company_name = $snap['company_name'] ?? null;
|
||||||
|
$invoice->customer_mobile = $snap['mobile_no'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
$invoice->container_id = $container->id;
|
// ✅ User model वरून email, address, pincode घ्या
|
||||||
$invoice->customer_id = $customerUser->id ?? null; // ✅ integer id
|
if ($customerUser) {
|
||||||
$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 घ्या
|
|
||||||
$invoice->customer_email = $customerUser->email ?? null;
|
$invoice->customer_email = $customerUser->email ?? null;
|
||||||
$invoice->customer_address = $customerUser->address ?? null;
|
$invoice->customer_address = $customerUser->address ?? null;
|
||||||
$invoice->pincode = $customerUser->pincode ?? 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'));
|
$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
|
||||||
@@ -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,73 +889,71 @@ 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 वापरू
|
||||||
$container->load('rows');
|
$container->load('rows');
|
||||||
|
|
||||||
// summary आधीपासून index() मध्ये जसा काढतोस तसाच logic reuse
|
// summary आधीपासून index() मध्ये जसा काढतोस तसाच logic reuse
|
||||||
$rows = $container->rows ?? collect();
|
$rows = $container->rows ?? collect();
|
||||||
|
|
||||||
$totalCtn = 0;
|
$totalCtn = 0;
|
||||||
$totalQty = 0;
|
$totalQty = 0;
|
||||||
$totalCbm = 0;
|
$totalCbm = 0;
|
||||||
$totalKg = 0;
|
$totalKg = 0;
|
||||||
|
|
||||||
$ctnKeys = ['CTN', 'CTNS'];
|
$ctnKeys = ['CTN', 'CTNS'];
|
||||||
$qtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY', 'QTY', 'PCS', 'PIECES'];
|
$qtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY', 'QTY', 'PCS', 'PIECES'];
|
||||||
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
|
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
|
||||||
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
|
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
|
||||||
|
|
||||||
$getFirstNumeric = function (array $data, array $possibleKeys) {
|
$getFirstNumeric = function (array $data, array $possibleKeys) {
|
||||||
$normalizedMap = [];
|
$normalizedMap = [];
|
||||||
foreach ($data as $key => $value) {
|
foreach ($data as $key => $value) {
|
||||||
if ($key === null) continue;
|
if ($key === null) continue;
|
||||||
$normKey = strtoupper((string)$key);
|
$normKey = strtoupper((string)$key);
|
||||||
$normKey = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normKey);
|
$normKey = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normKey);
|
||||||
$normalizedMap[$normKey] = $value;
|
$normalizedMap[$normKey] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($possibleKeys as $search) {
|
foreach ($possibleKeys as $search) {
|
||||||
$normSearch = strtoupper($search);
|
$normSearch = strtoupper($search);
|
||||||
$normSearch = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normSearch);
|
$normSearch = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normSearch);
|
||||||
|
|
||||||
foreach ($normalizedMap as $nKey => $value) {
|
foreach ($normalizedMap as $nKey => $value) {
|
||||||
if (strpos($nKey, $normSearch) !== false) {
|
if (strpos($nKey, $normSearch) !== false) {
|
||||||
if (is_numeric($value)) {
|
if (is_numeric($value)) {
|
||||||
return (float)$value;
|
return (float)$value;
|
||||||
}
|
}
|
||||||
if (is_string($value) && is_numeric(trim($value))) {
|
if (is_string($value) && is_numeric(trim($value))) {
|
||||||
return (float)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) {
|
$summary = [
|
||||||
$data = $row->data ?? [];
|
'total_ctn' => round($totalCtn, 2),
|
||||||
if (!is_array($data)) continue;
|
'total_qty' => round($totalQty, 2),
|
||||||
|
'total_cbm' => round($totalCbm, 3),
|
||||||
|
'total_kg' => round($totalKg, 2),
|
||||||
|
];
|
||||||
|
|
||||||
$totalCtn += $getFirstNumeric($data, $ctnKeys);
|
return view('admin.partials.container_popup_readonly', [
|
||||||
$totalQty += $getFirstNumeric($data, $qtyKeys);
|
'container' => $container,
|
||||||
$totalCbm += $getFirstNumeric($data, $cbmKeys);
|
'summary' => $summary,
|
||||||
$totalKg += $getFirstNumeric($data, $kgKeys);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$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,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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'
|
||||||
|
");
|
||||||
|
}
|
||||||
|
};
|
||||||
BIN
public/invoices/invoice-INV-2026-000202.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000202.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000205.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000205.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000206.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000206.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000209.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000209.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000213.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000213.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000214.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000214.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000217.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000217.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000218.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000218.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000221.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000221.pdf
Normal file
Binary file not shown.
@@ -672,10 +672,15 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
|
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
|
||||||
|
// 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
|
||||||
|
|
||||||
// जर container pending नसेल तर सर्व fields readonly
|
|
||||||
$isReadOnly = $isTotalColumn || $container->status !== 'pending';
|
|
||||||
@endphp
|
|
||||||
<td>
|
<td>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@@ -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%);
|
||||||
@@ -1277,16 +1295,19 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<span class="badge badge-{{ $invoice->status }}">
|
<span class="badge badge-{{ $invoice->status }}">
|
||||||
@if($invoice->status == 'paid')
|
@if($invoice->status == 'paid')
|
||||||
<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 == 'overdue')
|
@elseif($invoice->status == 'paying')
|
||||||
<i class="bi bi-exclamation-triangle-fill status-icon"></i>
|
<i class="bi bi-arrow-repeat status-icon"></i> {{-- processing icon --}}
|
||||||
@endif
|
@elseif($invoice->status == 'overdue')
|
||||||
{{ ucfirst($invoice->status) }}
|
<i class="bi bi-exclamation-triangle-fill status-icon"></i>
|
||||||
</span>
|
@endif
|
||||||
|
{{ ucfirst($invoice->status) }}
|
||||||
|
</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>
|
||||||
|
|||||||
@@ -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 --}}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -781,89 +799,125 @@
|
|||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<i class="fas fa-list"></i> Invoice Items
|
<i class="fas fa-list"></i> Invoice Items
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="invoice-table items-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center" style="width:44px;">
|
||||||
|
<input type="checkbox" id="selectAllItems">
|
||||||
|
</th>
|
||||||
|
<th class="text-center" style="min-width:50px;">#</th>
|
||||||
|
<th style="min-width:200px;">Description</th>
|
||||||
|
<th class="text-center" style="min-width:75px;">CTN</th>
|
||||||
|
<th class="text-center" style="min-width:75px;">QTY</th>
|
||||||
|
<th class="text-center" style="min-width:95px;">TTL/QTY</th>
|
||||||
|
<th class="text-center" style="min-width:75px;">Unit</th>
|
||||||
|
<th class="text-center" style="min-width:110px;">Price</th>
|
||||||
|
<th class="text-center" style="min-width:125px;">TTL Amount</th>
|
||||||
|
<th class="text-center" style="min-width:85px;">CBM</th>
|
||||||
|
<th class="text-center" style="min-width:95px;">TTL CBM</th>
|
||||||
|
<th class="text-center" style="min-width:80px;">KG</th>
|
||||||
|
<th class="text-center" style="min-width:95px;">TTL KG</th>
|
||||||
|
<th class="text-center" style="min-width:95px;">Shop No</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="itemsTableBody">
|
||||||
|
@foreach($invoice->items as $i => $item)
|
||||||
|
@php
|
||||||
|
$alreadyGrouped = in_array($item->id, $groupedItemIds ?? []);
|
||||||
|
@endphp
|
||||||
|
<tr class="{{ $alreadyGrouped ? 'grouped-item-row' : '' }}">
|
||||||
|
<td class="text-center">
|
||||||
|
<input type="checkbox"
|
||||||
|
class="item-select-checkbox"
|
||||||
|
name="item_ids[]"
|
||||||
|
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">{{ $item->ctn }}</td>
|
||||||
|
<td class="text-center">{{ $item->qty }}</td>
|
||||||
|
<td class="text-center" style="font-weight:700;">{{ $item->ttl_qty }}</td>
|
||||||
|
<td class="text-center">{{ $item->unit }}</td>
|
||||||
|
|
||||||
|
@if($isEmbedded)
|
||||||
|
<td class="text-center" style="min-width:120px;">
|
||||||
|
<input type="number" step="0.01" min="0"
|
||||||
|
name="items[{{ $item->id }}][price]"
|
||||||
|
value="{{ old('items.' . $item->id . '.price', $item->price) }}"
|
||||||
|
class="form-control form-control-sm text-end">
|
||||||
|
</td>
|
||||||
|
<td class="text-center" style="min-width:140px;">
|
||||||
|
<input type="number" step="0.01" min="0"
|
||||||
|
name="items[{{ $item->id }}][ttl_amount]"
|
||||||
|
value="{{ old('items.' . $item->id . '.ttl_amount', $item->ttl_amount) }}"
|
||||||
|
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>
|
||||||
|
@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>
|
||||||
|
</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>
|
||||||
|
</tr>
|
||||||
|
@endif
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-responsive">
|
{{-- Items pagination bar --}}
|
||||||
<table class="invoice-table items-table">
|
<div class="d-flex justify-content-between align-items-center mt-2"
|
||||||
<thead>
|
id="itemsPaginationBar"
|
||||||
<tr>
|
style="font-size:0.8rem;">
|
||||||
<th class="text-center" style="width:44px;">
|
<div>
|
||||||
<input type="checkbox" id="selectAllItems">
|
Showing <span id="itemsStart">0</span> to
|
||||||
</th>
|
<span id="itemsEnd">0</span> of
|
||||||
<th class="text-center" style="min-width:50px;">#</th>
|
<span id="itemsTotal">0</span> items
|
||||||
<th style="min-width:200px;">Description</th>
|
</div>
|
||||||
<th class="text-center" style="min-width:75px;">CTN</th>
|
|
||||||
<th class="text-center" style="min-width:75px;">QTY</th>
|
|
||||||
<th class="text-center" style="min-width:95px;">TTL/QTY</th>
|
|
||||||
<th class="text-center" style="min-width:75px;">Unit</th>
|
|
||||||
<th class="text-center" style="min-width:110px;">Price</th>
|
|
||||||
<th class="text-center" style="min-width:125px;">TTL Amount</th>
|
|
||||||
<th class="text-center" style="min-width:85px;">CBM</th>
|
|
||||||
<th class="text-center" style="min-width:95px;">TTL CBM</th>
|
|
||||||
<th class="text-center" style="min-width:80px;">KG</th>
|
|
||||||
<th class="text-center" style="min-width:95px;">TTL KG</th>
|
|
||||||
<th class="text-center" style="min-width:95px;">Shop No</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach($invoice->items as $i => $item)
|
|
||||||
@php
|
|
||||||
$alreadyGrouped = in_array($item->id, $groupedItemIds ?? []);
|
|
||||||
@endphp
|
|
||||||
<tr class="{{ $alreadyGrouped ? 'grouped-item-row' : '' }}">
|
|
||||||
<td class="text-center">
|
|
||||||
<input type="checkbox"
|
|
||||||
class="item-select-checkbox"
|
|
||||||
name="item_ids[]"
|
|
||||||
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">{{ $item->ctn }}</td>
|
|
||||||
<td class="text-center">{{ $item->qty }}</td>
|
|
||||||
<td class="text-center" style="font-weight:700;">{{ $item->ttl_qty }}</td>
|
|
||||||
<td class="text-center">{{ $item->unit }}</td>
|
|
||||||
|
|
||||||
@if($isEmbedded)
|
<nav aria-label="Items pagination">
|
||||||
<td class="text-center" style="min-width:120px;">
|
<ul class="pagination mb-0 pagination-sm" id="itemsPages">
|
||||||
<input type="number" step="0.01" min="0"
|
{{-- JS will render buttons here --}}
|
||||||
name="items[{{ $item->id }}][price]"
|
</ul>
|
||||||
value="{{ old('items.' . $item->id . '.price', $item->price) }}"
|
</nav>
|
||||||
class="form-control form-control-sm text-end">
|
</div>
|
||||||
</td>
|
|
||||||
<td class="text-center" style="min-width:140px;">
|
|
||||||
<input type="number" step="0.01" min="0"
|
|
||||||
name="items[{{ $item->id }}][ttl_amount]"
|
|
||||||
value="{{ old('items.' . $item->id . '.ttl_amount', $item->ttl_amount) }}"
|
|
||||||
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>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<td class="text-center">{{ $item->cbm }}</td>
|
</div>
|
||||||
<td class="text-center">{{ $item->ttl_cbm }}</td>
|
</div>
|
||||||
<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>
|
|
||||||
</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>
|
|
||||||
</tr>
|
|
||||||
@endif
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</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>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user