Compare commits

...

9 Commits

Author SHA256 Message Date
Utkarsh Khedkar
19d7f423b3 pdf Updated, Invoice Updated, Report Updated 2026-03-17 19:14:47 +05:30
Utkarsh Khedkar
0257b68f16 Merge branch 'dev' of http://103.248.30.24:3000/kent-logistics/Kent-logistics-Laravel into dev 2026-03-13 23:06:51 +05:30
Utkarsh Khedkar
785f2564be changes 2026-03-13 23:06:19 +05:30
Abhishek Mali
9a6ca49ad7 user order controller update for show view data 2026-03-13 20:19:34 +05:30
Abhishek Mali
bf2689e62d container and invoice api done small api like view invoice traking in order section is remaning 2026-03-13 17:25:58 +05:30
Utkarsh Khedkar
c25b468c77 Status Updated paying 2026-03-12 18:20:09 +05:30
Utkarsh Khedkar
5d8a746876 Status Updated 2026-03-12 18:11:43 +05:30
Utkarsh Khedkar
bb2a361a97 Merge branch 'dev' of http://103.248.30.24:3000/kent-logistics/Kent-logistics-Laravel into dev 2026-03-12 12:34:49 +05:30
Utkarsh Khedkar
6b5876e08f Fetch Data For Customer Details 2026-03-12 12:34:27 +05:30
51 changed files with 3753 additions and 2416 deletions

View File

@@ -5,6 +5,10 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\Container;
use App\Models\Invoice;
use App\Models\User;
use App\Models\MarkList;
class AdminAuthController extends Controller
{
@@ -31,16 +35,15 @@ class AdminAuthController extends Controller
}
$credentials = [
$field => $loginInput,
$field => $loginInput,
'password' => $request->password,
];
// attempt login
if (Auth::guard('admin')->attempt($credentials)) {
$request->session()->regenerate();
$user = Auth::guard('admin')->user();
return redirect()->route('admin.dashboard')->with('success', 'Welcome back, ' . $user->name . '!');
return redirect()->route('admin.dashboard')
->with('success', 'Welcome back, ' . $user->name . '!');
}
return back()->withErrors(['login' => 'Invalid login credentials.']);
@@ -51,6 +54,25 @@ class AdminAuthController extends Controller
Auth::guard('admin')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('admin.login')->with('success', 'Logged out successfully.');
return redirect()->route('admin.login')
->with('success', 'Logged out successfully.');
}
}
public function profile()
{
$user = Auth::guard('admin')->user();
// ── Real Stats ──
$stats = [
'total_containers' => Container::count(),
'total_invoices' => Invoice::count(),
'paid_invoices' => Invoice::where('status', 'paid')->count(),
'pending_invoices' => Invoice::where('status', 'pending')->count(),
'total_customers' => User::count(),
'total_marklist' => MarkList::count(),
'active_marklist' => MarkList::where('status', 'active')->count(),
];
return view('admin.profile', compact('user', 'stats'));
}
}

View File

@@ -22,7 +22,8 @@ class AdminCustomerController extends Controller
$query = User::with([
'marks',
'orders',
'invoices.installments' // 🔥 IMPORTANT
'invoices.installments',
'invoices.chargeGroups', // 🔥 for order total calculation
])->orderBy('id', 'desc');
if (!empty($search)) {
@@ -159,4 +160,4 @@ class AdminCustomerController extends Controller
}
}
}

View File

@@ -2,30 +2,31 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Controller; // ⬅️ हे नक्की असू दे
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\InvoiceInstallment;
use App\Models\InvoiceChargeGroup;
use App\Models\InvoiceChargeGroupItem;
use App\Models\InvoiceInstallment;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Mpdf\Mpdf;
class AdminInvoiceController extends Controller
{
// -------------------------------------------------------------
// INVOICE LIST PAGE
// INDEX (LIST ALL INVOICES WITH FILTERS)
// -------------------------------------------------------------
public function index(Request $request)
{
$query = Invoice::with(['items', 'customer', 'container']);
$query = Invoice::query();
if ($request->filled('search')) {
$search = $request->search;
$query->where(function ($q) use ($search) {
$q->where('invoice_number', 'like', "%{$search}%")
->orWhere('customer_name', 'like', "%{$search}%");
->orWhere('customer_name', 'like', "%{$search}%");
});
}
@@ -56,13 +57,6 @@ class AdminInvoiceController extends Controller
'chargeGroups.items',
])->findOrFail($id);
// demo update असेल तर
$invoice->update([
'customer_email' => 'test@demo.com',
'customer_address' => 'TEST ADDRESS',
'pincode' => '999999',
]);
$shipment = null;
$groupedItemIds = $invoice->chargeGroups
@@ -87,6 +81,26 @@ class AdminInvoiceController extends Controller
'installments',
])->findOrFail($id);
// ✅ Customer details sync
if ($invoice->customer) {
$needsUpdate = [];
if (empty($invoice->customer_email) || $invoice->customer_email === 'test@demo.com') {
$needsUpdate['customer_email'] = $invoice->customer->email;
}
if (empty($invoice->customer_address) || $invoice->customer_address === 'TEST ADDRESS') {
$needsUpdate['customer_address'] = $invoice->customer->address;
}
if (empty($invoice->pincode) || $invoice->pincode === '999999') {
$needsUpdate['pincode'] = $invoice->customer->pincode;
}
if (!empty($needsUpdate)) {
$invoice->update($needsUpdate);
$invoice->refresh();
}
}
$shipment = null;
$groupedItemIds = $invoice->chargeGroups
@@ -107,16 +121,16 @@ class AdminInvoiceController extends Controller
{
Log::info('🟡 Invoice Update Request Received', [
'invoice_id' => $id,
'request' => $request->all(),
'request' => $request->all(),
]);
$invoice = Invoice::findOrFail($id);
$data = $request->validate([
'invoice_date' => 'required|date',
'due_date' => 'required|date|after_or_equal:invoice_date',
'status' => 'required|in:pending,paid,overdue',
'notes' => 'nullable|string',
'due_date' => 'required|date|after_or_equal:invoice_date',
'status' => 'required|in:pending,paying,paid,overdue',
'notes' => 'nullable|string',
]);
Log::info('✅ Validated Invoice Header Update Data', $data);
@@ -125,16 +139,16 @@ class AdminInvoiceController extends Controller
$invoice->refresh();
Log::info('🔍 Invoice AFTER HEADER UPDATE', [
'invoice_id' => $invoice->id,
'charge_groups_total' => $invoice->charge_groups_total,
'gst_amount' => $invoice->gst_amount,
'invoice_id' => $invoice->id,
'charge_groups_total' => $invoice->charge_groups_total,
'gst_amount' => $invoice->gst_amount,
'grand_total_with_charges'=> $invoice->grand_total_with_charges,
]);
$this->generateInvoicePDF($invoice);
return redirect()
->route('admin.invoices.index')
->route('admin.invoices.edit', $invoice->id)
->with('success', 'Invoice updated & PDF generated successfully.');
}
@@ -145,12 +159,12 @@ class AdminInvoiceController extends Controller
{
Log::info('🟡 Invoice Items Update Request', [
'invoice_id' => $invoice->id,
'payload' => $request->all(),
'payload' => $request->all(),
]);
$data = $request->validate([
'items' => ['required', 'array'],
'items.*.price' => ['required', 'numeric', 'min:0'],
'items' => ['required', 'array'],
'items.*.price' => ['required', 'numeric', 'min:0'],
'items.*.ttl_amount' => ['required', 'numeric', 'min:0'],
]);
@@ -162,12 +176,12 @@ class AdminInvoiceController extends Controller
if (!$item) {
Log::warning('Invoice item not found or mismatched invoice', [
'invoice_id' => $invoice->id,
'item_id' => $itemId,
'item_id' => $itemId,
]);
continue;
}
$item->price = $itemData['price'];
$item->price = $itemData['price'];
$item->ttl_amount = $itemData['ttl_amount'];
$item->save();
}
@@ -180,15 +194,16 @@ class AdminInvoiceController extends Controller
}
// -------------------------------------------------------------
// PDF GENERATION
// PDF GENERATION (EXISTING - केवळ chargeGroups load ला confirm कर)
// -------------------------------------------------------------
public function generateInvoicePDF($invoice)
{
$invoice->load(['items', 'customer', 'container']);
// ✅ यामध्ये chargeGroups आणि installments load कर
$invoice->load(['items', 'customer', 'container', 'chargeGroups.items', 'installments']);
$shipment = null;
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
$folder = public_path('invoices/');
$folder = public_path('invoices/');
if (!file_exists($folder)) {
mkdir($folder, 0777, true);
@@ -201,13 +216,13 @@ class AdminInvoiceController extends Controller
}
$mpdf = new Mpdf([
'mode' => 'utf-8',
'format' => 'A4',
'mode' => 'utf-8',
'format' => 'A4',
'default_font' => 'sans-serif',
]);
$html = view('admin.pdf.invoice', [
'invoice' => $invoice,
'invoice' => $invoice,
'shipment' => $shipment,
])->render();
@@ -224,9 +239,9 @@ class AdminInvoiceController extends Controller
{
$request->validate([
'installment_date' => 'required|date',
'payment_method' => 'required|string',
'reference_no' => 'nullable|string',
'amount' => 'required|numeric|min:1',
'payment_method' => 'required|string',
'reference_no' => 'nullable|string',
'amount' => 'required|numeric|min:1',
]);
$invoice = Invoice::findOrFail($invoice_id);
@@ -238,37 +253,54 @@ class AdminInvoiceController extends Controller
if ($request->amount > $remaining) {
return response()->json([
'status' => 'error',
'status' => 'error',
'message' => 'Installment amount exceeds remaining balance.',
], 422);
}
$installment = InvoiceInstallment::create([
'invoice_id' => $invoice_id,
'invoice_id' => $invoice_id,
'installment_date' => $request->installment_date,
'payment_method' => $request->payment_method,
'reference_no' => $request->reference_no,
'amount' => $request->amount,
'payment_method' => $request->payment_method,
'reference_no' => $request->reference_no,
'amount' => $request->amount,
]);
$newPaid = $paidTotal + $request->amount;
$newPaid = $paidTotal + $request->amount;
$remaining = max(0, $grandTotal - $newPaid);
if ($newPaid >= $grandTotal && $grandTotal > 0) {
$invoice->update(['status' => 'paid']);
$isOverdue = now()->startOfDay()->gt(\Carbon\Carbon::parse($invoice->due_date)->startOfDay());
if ($grandTotal > 0 && $newPaid >= $grandTotal) {
$newStatus = 'paid';
} elseif ($newPaid > 0 && $isOverdue) {
$newStatus = 'overdue';
} elseif ($newPaid > 0 && !$isOverdue) {
$newStatus = 'paying';
} elseif ($newPaid <= 0 && $isOverdue) {
$newStatus = 'overdue';
} else {
$newStatus = 'pending';
}
$invoice->update([
'payment_method' => $request->payment_method,
'reference_no' => $request->reference_no,
'status' => $newStatus,
]);
return response()->json([
'status' => 'success',
'message' => 'Installment added successfully.',
'installment' => $installment,
'status' => 'success',
'message' => 'Installment added successfully.',
'installment' => $installment,
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
'gstAmount' => $invoice->gst_amount ?? 0,
'grandTotal' => $grandTotal,
'totalPaid' => $newPaid,
'remaining' => $remaining,
'isCompleted' => $remaining <= 0,
'isZero' => $newPaid == 0,
'gstAmount' => $invoice->gst_amount ?? 0,
'grandTotal' => $grandTotal,
'totalPaid' => $newPaid,
'remaining' => $remaining,
'newStatus' => $newStatus,
'isCompleted' => $remaining <= 0,
'isZero' => $newPaid == 0,
]);
}
@@ -278,7 +310,7 @@ class AdminInvoiceController extends Controller
public function deleteInstallment($id)
{
$installment = InvoiceInstallment::findOrFail($id);
$invoice = $installment->invoice;
$invoice = $installment->invoice;
$installment->delete();
$invoice->refresh();
@@ -288,21 +320,32 @@ class AdminInvoiceController extends Controller
$paidTotal = $invoice->installments()->sum('amount');
$remaining = max(0, $grandTotal - $paidTotal);
if ($paidTotal <= 0 && $grandTotal > 0) {
$invoice->update(['status' => 'pending']);
} elseif ($paidTotal > 0 && $paidTotal < $grandTotal) {
$invoice->update(['status' => 'pending']);
$isOverdue = now()->startOfDay()->gt(\Carbon\Carbon::parse($invoice->due_date)->startOfDay());
if ($grandTotal > 0 && $paidTotal >= $grandTotal) {
$newStatus = 'paid';
} elseif ($paidTotal > 0 && $isOverdue) {
$newStatus = 'overdue';
} elseif ($paidTotal > 0 && !$isOverdue) {
$newStatus = 'paying';
} elseif ($paidTotal <= 0 && $isOverdue) {
$newStatus = 'overdue';
} else {
$newStatus = 'pending';
}
$invoice->update(['status' => $newStatus]);
return response()->json([
'status' => 'success',
'message' => 'Installment deleted.',
'status' => 'success',
'message' => 'Installment deleted.',
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
'gstAmount' => $invoice->gst_amount ?? 0,
'grandTotal' => $grandTotal,
'totalPaid' => $paidTotal,
'remaining' => $remaining,
'isZero' => $paidTotal == 0,
'gstAmount' => $invoice->gst_amount ?? 0,
'grandTotal' => $grandTotal,
'totalPaid' => $paidTotal,
'remaining' => $remaining,
'newStatus' => $newStatus,
'isZero' => $paidTotal == 0,
]);
}
@@ -313,126 +356,149 @@ class AdminInvoiceController extends Controller
{
Log::info('🟡 storeChargeGroup HIT', [
'invoice_id' => $invoiceId,
'payload' => $request->all(),
'payload' => $request->all(),
]);
$invoice = Invoice::with('items', 'chargeGroups')->findOrFail($invoiceId);
$data = $request->validate([
'groupname' => 'required|string|max:255',
'basistype' => 'required|in:ttl_qty,amount,ttl_cbm,ttl_kg',
'basisvalue' => 'required|numeric',
'rate' => 'required|numeric|min:0.0001',
'autototal' => 'required|numeric|min:0.01',
'itemids' => 'required|array',
'itemids.*' => 'integer|exists:invoice_items,id',
'tax_type' => 'nullable|in:none,gst,igst',
'gst_percent' => 'nullable|numeric|min:0|max:28',
'groupname' => 'required|string|max:255',
'basistype' => 'required|in:ttl_qty,amount,ttl_cbm,ttl_kg',
'basisvalue' => 'required|numeric',
'rate' => 'required|numeric|min:0.0001',
'autototal' => 'required|numeric|min:0.01',
'itemids' => 'required|array',
'itemids.*' => 'integer|exists:invoice_items,id',
'tax_type' => 'nullable|in:none,gst,igst',
'gst_percent' => 'nullable|numeric|min:0|max:28',
'total_with_gst' => 'nullable|numeric|min:0',
]);
Log::info('✅ storeChargeGroup VALIDATED', $data);
// duplicate name check
$exists = InvoiceChargeGroup::where('invoice_id', $invoice->id)
->where('group_name', $data['groupname'])
->exists();
if ($exists) {
return back()
->withErrors(['groupname' => 'This group name is already used for this invoice.'])
->withInput();
}
$taxType = $data['tax_type'] ?? 'gst';
$taxType = $data['tax_type'] ?? 'gst';
$gstPercent = $data['gst_percent'] ?? 0;
$baseTotal = $data['autototal'];
$baseTotal = $data['autototal'];
$totalWithGst = $data['total_with_gst'] ?? $baseTotal;
if ($totalWithGst == 0 && $gstPercent > 0) {
$gstAmount = ($baseTotal * $gstPercent) / 100;
$gstAmount = ($baseTotal * $gstPercent) / 100;
$totalWithGst = $baseTotal + $gstAmount;
}
// 1) Group create
$group = InvoiceChargeGroup::create([
'invoice_id' => $invoice->id,
'group_name' => $data['groupname'],
'basis_type' => $data['basistype'],
'basis_value' => $data['basisvalue'],
'rate' => $data['rate'],
'total_charge' => $baseTotal,
'tax_type' => $taxType,
'gst_percent' => $gstPercent,
'invoice_id' => $invoice->id,
'group_name' => $data['groupname'],
'basis_type' => $data['basistype'],
'basis_value' => $data['basisvalue'],
'rate' => $data['rate'],
'total_charge' => $baseTotal,
'tax_type' => $taxType,
'gst_percent' => $gstPercent,
'total_with_gst' => $totalWithGst,
]);
// 2) Items link
foreach ($data['itemids'] as $itemId) {
InvoiceChargeGroupItem::create([
'group_id' => $group->id,
'group_id' => $group->id,
'invoice_item_id' => $itemId,
]);
}
// 3) सर्व groups वरून invoice level totals
$invoice->load('chargeGroups');
$chargeGroupsBase = $invoice->chargeGroups->sum('total_charge'); // base
$chargeGroupsWithG = $invoice->chargeGroups->sum('total_with_gst'); // base + gst
$chargeGroupsGst = $chargeGroupsWithG - $chargeGroupsBase; // gst only
$chargeGroupsBase = $invoice->chargeGroups->sum('total_charge');
$chargeGroupsWithG = $invoice->chargeGroups->sum('total_with_gst');
$chargeGroupsGst = $chargeGroupsWithG - $chargeGroupsBase;
$invoiceGstPercent = $group->gst_percent ?? 0;
$invoiceTaxType = $group->tax_type ?? 'gst';
$invoiceTaxType = $group->tax_type ?? 'gst';
$cgstPercent = 0;
$sgstPercent = 0;
$igstPercent = 0;
if ($invoiceTaxType === 'gst') {
$cgstPercent = $invoiceGstPercent / 2;
$sgstPercent = $invoiceGstPercent / 2;
} elseif ($invoiceTaxType === 'igst') {
$igstPercent = $invoiceGstPercent;
}
// 🔴 इथे main fix:
// final_amount = base (total_charge sum)
// final_amount_with_gst = base + gst (total_with_gst sum)
// grand_total_with_charges = final_amount_with_gst (same)
$invoice->update([
'charge_groups_total' => $chargeGroupsBase,
'gst_amount' => $chargeGroupsGst,
'gst_percent' => $invoiceGstPercent,
'tax_type' => $invoiceTaxType,
'cgst_percent' => $cgstPercent,
'sgst_percent' => $sgstPercent,
'igst_percent' => $igstPercent,
'final_amount' => $chargeGroupsBase,
'final_amount_with_gst' => $chargeGroupsWithG,
'charge_groups_total' => $chargeGroupsBase,
'gst_amount' => $chargeGroupsGst,
'gst_percent' => $invoiceGstPercent,
'tax_type' => $invoiceTaxType,
'cgst_percent' => $cgstPercent,
'sgst_percent' => $sgstPercent,
'igst_percent' => $igstPercent,
'final_amount' => $chargeGroupsBase,
'final_amount_with_gst' => $chargeGroupsWithG,
'grand_total_with_charges' => $chargeGroupsWithG,
]);
Log::info('✅ Invoice updated from Charge Group (CG total + GST)', [
'invoice_id' => $invoice->id,
'charge_groups_total' => $chargeGroupsBase,
'gst_amount' => $chargeGroupsGst,
'gst_percent' => $invoiceGstPercent,
'tax_type' => $invoiceTaxType,
'cgst_percent' => $cgstPercent,
'sgst_percent' => $sgstPercent,
'igst_percent' => $igstPercent,
'final_amount' => $invoice->final_amount,
'final_amount_with_gst' => $invoice->final_amount_with_gst,
'invoice_id' => $invoice->id,
'charge_groups_total' => $chargeGroupsBase,
'gst_amount' => $chargeGroupsGst,
'grand_total_with_charges'=> $invoice->grand_total_with_charges,
]);
return response()->json([
return response()->json([
'success' => true,
'message' => 'Charge group saved successfully.',
'group_id' => $group->id,
]);
}
}
// ============================================
// 🆕 PDF DOWNLOAD (Direct browser download)
// ============================================
public function downloadPdf($id)
{
$invoice = Invoice::with(['items', 'customer', 'container', 'chargeGroups.items', 'installments'])
->findOrFail($id);
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
$folder = public_path('invoices/');
$filePath = $folder . $fileName;
// जर PDF exist नसेल तर generate कर
if (!file_exists($filePath)) {
$this->generateInvoicePDF($invoice);
}
return response()->download($filePath, $fileName);
}
// ============================================
// 🆕 EXCEL DOWNLOAD (CSV format - simple)
// ============================================
public function share($id)
{
$invoice = Invoice::findOrFail($id);
// इथे तुला जसं share करायचंय तसं logic टाक:
// उदा. public link generate करून redirect कर, किंवा WhatsApp deeplink, इ.
$url = route('admin.invoices.popup', $invoice->id); // example: popup link share
return redirect()->away('https://wa.me/?text=' . urlencode($url));
}
}

View File

@@ -28,11 +28,23 @@ class AdminOrderController extends Controller
* ---------------------------*/
public function dashboard()
{
$totalOrders = Order::count();
$pendingOrders = Order::where('status', 'pending')->count();
$totalShipments = Shipment::count();
$totalItems = OrderItem::count();
// ── Order counts (from Invoice Management / Orders table) ──
$totalOrders = Order::count();
// "Pending" म्हणजे delivered नसलेले सर्व orders
// Order तयार होतो तेव्हा status = 'order_placed' असतो, 'pending' नाही
$deliveredStatuses = ['delivered'];
$pendingOrders = Order::whereNotIn('status', $deliveredStatuses)->count();
// ── Invoice counts ──
$totalContainers = Container::count();
$totalInvoices = Invoice::count();
$paidInvoices = Invoice::where('status', 'paid')->count();
$pendingInvoices = Invoice::where('status', 'pending')->count();
$overdueInvoices = Invoice::where('status', 'overdue')->count();
$totalRevenue = Invoice::sum('final_amount_with_gst');
// ── User / Staff counts ──
$activeCustomers = User::where('status', 'active')->count();
$inactiveCustomers = User::where('status', 'inactive')->count();
$totalStaff = Admin::where('type', 'staff')->count();
@@ -43,8 +55,11 @@ class AdminOrderController extends Controller
return view('admin.dashboard', compact(
'totalOrders',
'pendingOrders',
'totalShipments',
'totalItems',
'totalContainers',
'totalInvoices',
'paidInvoices',
'pendingInvoices',
'overdueInvoices',
'totalRevenue',
'activeCustomers',
'inactiveCustomers',
@@ -90,11 +105,23 @@ class AdminOrderController extends Controller
->when($request->filled('status'), function ($q) use ($request) {
$q->where('invoices.status', $request->status);
})
->orderByDesc('invoices.invoice_date') // इथे बदल
->orderByDesc('invoices.id') // same-date साठी tie-breaker
->orderByDesc('invoices.invoice_date')
->orderByDesc('invoices.id')
->get();
return view('admin.orders', compact('invoices'));
// ── Real DB counts (filter-independent) ──
$totalInvoices = \App\Models\Invoice::count();
$paidInvoices = \App\Models\Invoice::where('status', 'paid')->count();
$pendingInvoices = \App\Models\Invoice::where('status', 'pending')->count();
$overdueInvoices = \App\Models\Invoice::where('status', 'overdue')->count();
return view('admin.orders', compact(
'invoices',
'totalInvoices',
'paidInvoices',
'pendingInvoices',
'overdueInvoices'
));
}
/* ---------------------------
@@ -759,4 +786,4 @@ class AdminOrderController extends Controller
], 500);
}
}
}
}

View File

@@ -5,104 +5,197 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Facades\Excel;
use Mpdf\Mpdf;
class AdminReportController extends Controller
{
/**
* Display the reports page with joined data
*/
// public function index(Request $request)
// {
/*********************************************************
* OLD FLOW (Order + Shipment + Invoice)
* फक्त reference साठी ठेवलेला, वापरत नाही.
*********************************************************/
/*
$reports = DB::table('orders')
->join('shipment_items', 'shipment_items.order_id', '=', 'orders.id')
->join('shipments', 'shipments.id', '=', 'shipment_items.shipment_id')
->join('invoices', 'invoices.order_id', '=', 'orders.id')
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'orders.mark_no')
->leftJoin('users', 'users.customer_id', '=', 'mark_list.customer_id')
->select(...)
->orderBy('shipments.shipment_date', 'desc')
->get();
*/
/*********************************************************
* NEW FLOW (Container + Invoice + MarkList)
*********************************************************/
// $reports = DB::table('invoices')
// ->join('containers', 'containers.id', '=', 'invoices.containerid')
// ->leftJoin('mark_list', 'mark_list.markno', '=', 'invoices.markno')
// ->select(
// 'invoices.id as invoicepk',
// 'invoices.invoicenumber',
// 'invoices.invoicedate',
// 'invoices.finalamount',
// 'invoices.finalamountwithgst',
// 'invoices.gstpercent',
// 'invoices.gstamount',
// 'invoices.status as invoicestatus',
// 'invoices.markno',
// 'containers.id as containerpk',
// 'containers.containernumber',
// 'containers.containerdate',
// 'containers.containername',
// 'mark_list.companyname',
// 'mark_list.customername'
// )
// ->orderBy('containers.containerdate', 'desc')
// ->get();
// return view('admin.reports', compact('reports'));
// }
public function index(Request $request)
// UI साठी main action
public function containerReport(Request $request)
{
$reports = DB::table('invoices')
$reports = $this->buildContainerReportQuery($request)->get();
return view('admin.reports', compact('reports'));
}
// ही common query — filters accept करते
protected function buildContainerReportQuery(Request $request = null)
{
$query = DB::table('invoices')
->join('containers', 'containers.id', '=', 'invoices.container_id')
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no')
->leftJoinSub(
DB::table('invoice_installments')
->select('invoice_id', DB::raw('COALESCE(SUM(amount), 0) as total_paid'))
->groupBy('invoice_id'),
'inst',
'inst.invoice_id',
'=',
'invoices.id'
)
->select(
// INVOICE
'invoices.id as invoicepk',
'invoices.invoice_number',
'invoices.invoice_date',
'invoices.final_amount',
'invoices.final_amount_with_gst',
'invoices.gst_percent',
'invoices.gst_amount',
'invoices.status as invoicestatus',
'invoices.mark_no',
// CONTAINER
'containers.id as containerpk',
'containers.id as container_id',
'containers.container_number',
'containers.container_date',
'containers.container_name',
// RAW FIELDS (for reference/debug if needed)
'invoices.company_name as inv_company_name',
'invoices.customer_name as inv_customer_name',
'mark_list.company_name as ml_company_name',
'mark_list.customer_name as ml_customer_name',
// FINAL FIELDS (automatically pick invoice first, else mark_list)
DB::raw('COALESCE(invoices.company_name, mark_list.company_name) as company_name'),
DB::raw('COALESCE(invoices.customer_name, mark_list.customer_name) as customer_name')
)
->orderBy('invoices.invoice_date', 'desc')
->orderBy('invoices.id', 'desc')
->get();
return view('admin.reports', compact('reports'));
DB::raw('COUNT(DISTINCT invoices.mark_no) as total_mark_nos'),
DB::raw('COUNT(DISTINCT invoices.customer_id) as total_customers'),
DB::raw('COUNT(invoices.id) as total_invoices'),
DB::raw('COALESCE(SUM(invoices.charge_groups_total), 0) as total_invoice_amount'),
DB::raw('COALESCE(SUM(invoices.gst_amount), 0) as total_gst_amount'),
DB::raw('COALESCE(SUM(invoices.grand_total_with_charges), 0) as total_payable'),
DB::raw('COALESCE(SUM(inst.total_paid), 0) as total_paid'),
DB::raw('GREATEST(0, COALESCE(SUM(invoices.grand_total_with_charges), 0) - COALESCE(SUM(inst.total_paid), 0)) as total_remaining'),
DB::raw("
CASE
WHEN COUNT(invoices.id) > 0
AND SUM(CASE WHEN invoices.status != 'paid' THEN 1 ELSE 0 END) = 0
THEN 'paid'
WHEN SUM(CASE WHEN invoices.status = 'overdue' THEN 1 ELSE 0 END) > 0
THEN 'overdue'
WHEN SUM(CASE WHEN invoices.status = 'paying' THEN 1 ELSE 0 END) > 0
THEN 'paying'
ELSE 'pending'
END as container_status
")
)
->groupBy(
'containers.id',
'containers.container_number',
'containers.container_date',
'containers.container_name'
)
->orderBy('containers.container_date', 'desc')
->orderBy('containers.id', 'desc');
// ── Filters ──────────────────────────────────────────────────────
if ($request) {
if ($request->filled('from_date')) {
$query->whereDate('containers.container_date', '>=', $request->from_date);
}
if ($request->filled('to_date')) {
$query->whereDate('containers.container_date', '<=', $request->to_date);
}
if ($request->filled('status')) {
// container_status हे aggregate expression आहे,
// त्यामुळे HAVING clause वापरतो
$query->havingRaw("
CASE
WHEN COUNT(invoices.id) > 0
AND SUM(CASE WHEN invoices.status != 'paid' THEN 1 ELSE 0 END) = 0
THEN 'paid'
WHEN SUM(CASE WHEN invoices.status = 'overdue' THEN 1 ELSE 0 END) > 0
THEN 'overdue'
WHEN SUM(CASE WHEN invoices.status = 'paying' THEN 1 ELSE 0 END) > 0
THEN 'paying'
ELSE 'pending'
END = ?
", [$request->status]);
}
}
return $query;
}
}
// ---- Excel export ----
public function containerReportExcel(Request $request)
{
$reports = $this->buildContainerReportQuery($request)->get();
$headings = [
'Container No',
'Container Date',
'Total Mark Nos',
'Total Customers',
'Total Invoices',
'Invoice Amount (Before GST)',
'GST Amount',
'Payable Amount (Incl. GST)',
'Paid Amount',
'Remaining Amount',
'Container Status',
];
$rows = $reports->map(function ($r) {
return [
$r->container_number,
$r->container_date,
$r->total_mark_nos,
$r->total_customers,
$r->total_invoices,
$r->total_invoice_amount,
$r->total_gst_amount,
$r->total_payable,
$r->total_paid,
$r->total_remaining,
$r->container_status,
];
})->toArray();
$export = new class($headings, $rows) implements
\Maatwebsite\Excel\Concerns\FromArray,
\Maatwebsite\Excel\Concerns\WithHeadings
{
private $headings;
private $rows;
public function __construct($headings, $rows)
{
$this->headings = $headings;
$this->rows = $rows;
}
public function array(): array
{
return $this->rows;
}
public function headings(): array
{
return $this->headings;
}
};
return Excel::download(
$export,
'container-report-' . now()->format('Ymd-His') . '.xlsx'
);
}
// ---- PDF export ----
public function containerReportPdf(Request $request)
{
$reports = $this->buildContainerReportQuery($request)->get();
$html = view('admin.reports', [
'reports' => $reports,
'isPdf' => true,
])->render();
$mpdf = new \Mpdf\Mpdf([
'mode' => 'utf-8',
'format' => 'A4-L',
'default_font' => 'dejavusans',
'margin_top' => 8,
'margin_right' => 8,
'margin_bottom' => 10,
'margin_left' => 8,
]);
$mpdf->SetHTMLHeader('');
$mpdf->SetHTMLFooter('');
$mpdf->WriteHTML($html);
$fileName = 'container-report-' . now()->format('Ymd-His') . '.pdf';
return response($mpdf->Output($fileName, 'S'), 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="' . $fileName . '"',
]);
}
}

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
{
@@ -193,6 +193,7 @@ class ContainerController extends Controller
'kg_col' => null,
'totalkg_col' => null,
'itemno_col' => null,
'shopno_col' => null,
];
foreach ($header as $colIndex => $headingText) {
@@ -233,6 +234,11 @@ class ContainerController extends Controller
strpos($normalized, 'ITEM') !== false
) {
$essentialColumns['itemno_col'] = $colIndex;
} elseif (
strpos($normalized, 'SHOPNO') !== false ||
strpos($normalized, 'SHOP') !== false
) {
$essentialColumns['shopno_col'] = $colIndex;
}
}
@@ -395,8 +401,8 @@ class ContainerController extends Controller
if (!empty($unmatchedMarks)) {
foreach ($cleanedRows as $item) {
$row = $item['row'];
$offset = $item['offset'];
$row = $item['row'];
$offset = $item['offset'];
$rowMark = trim((string)($row[$essentialColumns['itemno_col']] ?? ''));
if ($rowMark === '' || !in_array($rowMark, $unmatchedMarks)) {
@@ -518,17 +524,17 @@ class ContainerController extends Controller
$firstMark = $rowsForCustomer[0]['mark'];
$snap = $markToSnapshot[$firstMark] ?? null;
$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;
$invoice->mark_no = $firstMark;
$invoice->invoice_number = $this->generateInvoiceNumber();
// invoice_date = container_date
$invoice->invoice_date = $container->container_date;
// due_date = container_date + 10 days
$invoice->due_date = Carbon::parse($invoice->invoice_date)
->addDays(10)
->format('Y-m-d');
@@ -539,15 +545,17 @@ class ContainerController extends Controller
$invoice->customer_mobile = $snap['mobile_no'] ?? null;
}
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;
$invoice->customer_email = null;
$invoice->customer_address = null;
$invoice->pincode = null;
$uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark'));
$invoice->notes = 'Auto-created from Container ' . $container->container_number
. ' for Mark(s): ' . implode(', ', $uniqueMarks);
@@ -557,11 +565,10 @@ class ContainerController extends Controller
$invoice->save();
$invoiceCount++;
$totalAmount = 0;
foreach ($rowsForCustomer as $item) {
$row = $item['row'];
$offset = $item['offset'];
$mark = $item['mark']; // ✅ mark_no from Excel
$description = $essentialColumns['desc_col'] !== null ? ($row[$essentialColumns['desc_col']] ?? null) : null;
$ctn = $essentialColumns['ctn_col'] !== null ? (int) ($row[$essentialColumns['ctn_col']] ?? 0) : 0;
@@ -575,6 +582,8 @@ class ContainerController extends Controller
$kg = $essentialColumns['kg_col'] !== null ? (float) ($row[$essentialColumns['kg_col']] ?? 0) : 0;
$ttlKg = $essentialColumns['totalkg_col'] !== null ? (float) ($row[$essentialColumns['totalkg_col']] ?? $kg) : $kg;
$shopNo = $essentialColumns['shopno_col'] !== null ? ($row[$essentialColumns['shopno_col']] ?? null) : null;
$rowIndex = $headerRowIndex + 1 + $offset;
InvoiceItem::create([
@@ -592,18 +601,10 @@ class ContainerController extends Controller
'ttl_cbm' => $ttlCbm,
'kg' => $kg,
'ttl_kg' => $ttlKg,
'shop_no' => null,
'shop_no' => $shopNo,
'mark_no' => $mark, // ✅ save mark_no from Excel
]);
$totalAmount += $ttlAmount;
}
$invoice->final_amount = $totalAmount;
$invoice->gst_percent = 0;
$invoice->gst_amount = 0;
$invoice->final_amount_with_gst = $totalAmount;
$invoice->save();
}
$msg = "Container '{$container->container_number}' created with {$savedCount} rows and {$invoiceCount} customer invoice(s).";
@@ -613,7 +614,17 @@ class ContainerController extends Controller
public function show(Container $container)
{
$container->load('rows');
return view('admin.container_show', compact('container'));
$lockedRowIndexes = \App\Models\Invoice::whereIn('invoices.status', ['paid', 'paying'])
->where('invoices.container_id', $container->id)
->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)
@@ -629,14 +640,12 @@ class ContainerController extends Controller
continue;
}
// 1) update container_rows.data
$data = $row->data ?? [];
foreach ($cols as $colHeader => $value) {
$data[$colHeader] = $value;
}
$row->update(['data' => $data]);
// 2) normalize keys
$normalizedMap = [];
foreach ($data as $key => $value) {
if ($key === null || $key === '') {
@@ -648,7 +657,6 @@ class ContainerController extends Controller
$normalizedMap[$normKey] = $value;
}
// helper: get first numeric value from given keys
$getFirstNumeric = function (array $map, array $possibleKeys) {
foreach ($possibleKeys as $search) {
$normSearch = strtoupper($search);
@@ -668,23 +676,19 @@ class ContainerController extends Controller
return 0;
};
// 3) read values QTY vs TTLQTY separately
$ctnKeys = ['CTN', 'CTNS'];
$qtyKeys = ['QTY', 'PCS', 'PIECES']; // per-carton qty
$ttlQtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY']; // total qty
$qtyKeys = ['QTY', 'PCS', 'PIECES'];
$ttlQtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY'];
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
$amountKeys = ['AMOUNT', 'TTLAMOUNT', 'TOTALAMOUNT'];
$ctn = $getFirstNumeric($normalizedMap, $ctnKeys);
// per carton qty
$qty = $getFirstNumeric($normalizedMap, $qtyKeys);
// total qty direct from TOTALQTY/TTLQTY/ITLQTY
$ttlQ = $getFirstNumeric($normalizedMap, $ttlQtyKeys);
// if total column is 0 then compute ctn * qty
if ($ttlQ == 0 && $ctn && $qty) {
$ttlQ = $ctn * $qty;
}
@@ -707,7 +711,6 @@ class ContainerController extends Controller
$amount = $price * $ttlQ;
}
// 4) get description
$desc = null;
foreach (['DESCRIPTION', 'DESC'] as $dKey) {
$normD = str_replace([' ', '/', '-', '.'], '', strtoupper($dKey));
@@ -719,9 +722,31 @@ class ContainerController extends Controller
}
}
$shopNo = null;
foreach (['SHOPNO', 'SHOP'] as $sKey) {
$normS = str_replace([' ', '/', '-', '.'], '', strtoupper($sKey));
foreach ($normalizedMap as $nKey => $v) {
if (strpos($nKey, $normS) !== false) {
$shopNo = is_string($v) ? trim($v) : $v;
break 2;
}
}
}
// ✅ Get mark_no
$markNo = null;
foreach (['MARKNO', 'MARK', 'ITEMNO', 'ITEM'] as $mKey) {
$normM = str_replace([' ', '/', '-', '.'], '', strtoupper($mKey));
foreach ($normalizedMap as $nKey => $v) {
if (strpos($nKey, $normM) !== false) {
$markNo = is_string($v) ? trim($v) : $v;
break 2;
}
}
}
$rowIndex = $row->row_index;
// 5) find linked invoice_items
$items = InvoiceItem::where('container_id', $container->id)
->where('container_row_index', $rowIndex)
->get();
@@ -733,18 +758,19 @@ class ContainerController extends Controller
->get();
}
// 6) update invoice_items + recalc invoice totals
foreach ($items as $item) {
$item->description = $desc;
$item->ctn = $ctn;
$item->qty = $qty; // per carton
$item->ttl_qty = $ttlQ; // total
$item->qty = $qty;
$item->ttl_qty = $ttlQ;
$item->price = $price;
$item->ttl_amount = $amount;
$item->cbm = $cbm;
$item->ttl_cbm = $ttlC;
$item->kg = $kg;
$item->ttl_kg = $ttlK;
$item->shop_no = $shopNo;
$item->mark_no = $markNo; // ✅ update mark_no
$item->save();
$invoice = $item->invoice;
@@ -859,7 +885,6 @@ class ContainerController extends Controller
abort(404, 'Excel file not found on record.');
}
// Stored path like "containers/abc.xlsx"
$path = $container->excel_file;
if (!Storage::exists($path)) {
@@ -871,73 +896,69 @@ class ContainerController extends Controller
return Storage::download($path, $fileName);
}
public function popupPopup(Container $container)
{
// existing show सारखाच data वापरू
$container->load('rows');
{
$container->load('rows');
// summary आधीपासून index() मध्ये जसा काढतोस तसाच logic reuse
$rows = $container->rows ?? collect();
$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,
]);
}
}
}

View File

@@ -21,23 +21,33 @@ class UserOrderController extends Controller
}
// -------------------------------------
// Get all orders
// Get customer invoices with containers
// -------------------------------------
$orders = $user->orders()->with('invoice')->get();
$invoices = $user->invoices()->with('container')->get();
// Unique containers for this customer
$containers = $invoices->pluck('container')->filter()->unique('id');
// -------------------------------------
// Counts
// Counts based on container status
// -------------------------------------
$totalOrders = $orders->count();
$delivered = $orders->where('status', 'delivered')->count();
$inTransit = $orders->where('status', '!=', 'delivered')->count();
$active = $totalOrders;
$totalOrders = $containers->count();
$delivered = $containers->where('status', 'delivered')->count();
$inTransit = $containers->whereNotIn('status', [
'delivered',
'warehouse',
'domestic-distribution'
])->count();
$active = $totalOrders;
// -------------------------------------
// Total Amount = Invoice.total_with_gst
// Total Amount = sum of invoice totals
// -------------------------------------
$totalAmount = $orders->sum(function ($o) {
return $o->invoice->final_amount_with_gst ?? 0;
$totalAmount = $invoices->sum(function ($invoice) {
return $invoice->final_amount_with_gst ?? 0;
});
// Format total amount in K, L, Cr
@@ -45,13 +55,12 @@ class UserOrderController extends Controller
return response()->json([
'status' => true,
'summary' => [
'active_orders' => $active,
'in_transit_orders' => $inTransit,
'delivered_orders' => $delivered,
'total_value' => $formattedAmount, // formatted value
'total_raw' => $totalAmount // original value
'total_value' => $formattedAmount,
'total_raw' => $totalAmount
]
]);
}
@@ -90,20 +99,28 @@ class UserOrderController extends Controller
], 401);
}
// Fetch orders for this user
$orders = $user->orders()
->with(['invoice', 'shipments'])
// Get invoices with containers for this customer
$invoices = $user->invoices()
->with('container')
->orderBy('id', 'desc')
->get()
->map(function ($o) {
return [
'order_id' => $o->order_id,
'status' => $o->status,
'amount' => $o->ttl_amount,
'description'=> "Order from {$o->origin} to {$o->destination}",
'created_at' => $o->created_at,
];
});
->get();
// Extract unique containers
$containers = $invoices->pluck('container')
->filter()
->unique('id')
->values();
$orders = $containers->map(function ($container) {
return [
'order_id' => $container->id,
'container_number' => $container->container_number,
'status' => $container->status,
'container_date' => $container->container_date,
'created_at' => $container->created_at,
];
});
return response()->json([
'success' => true,
@@ -115,45 +132,73 @@ public function orderDetails($order_id)
{
$user = JWTAuth::parseToken()->authenticate();
$order = $user->orders()
if (!$user) {
return response()->json([
'success' => false,
'message' => 'Unauthorized'
], 401);
}
// Find container first
$container = \App\Models\Container::find($order_id);
if (!$container) {
return response()->json([
'success' => false,
'message' => 'Container not found'
], 404);
}
// Find invoice belonging to this user for this container
$invoice = \App\Models\Invoice::where('customer_id', $user->id)
->where('container_id', $container->id)
->with(['items'])
->where('order_id', $order_id)
->first();
if (!$order) {
return response()->json(['success' => false, 'message' => 'Order not found'], 404);
if (!$invoice) {
return response()->json([
'success' => false,
'message' => 'Order not found for this user'
], 404);
}
return response()->json([
'success' => true,
'order' => $order
'order' => [
'container_id' => $container->id,
'container_number' => $container->container_number,
'container_date' => $container->container_date,
'status' => $container->status,
'invoice_id' => $invoice->id,
'items' => $invoice->items
]
]);
}
public function orderShipment($order_id)
{
$user = JWTAuth::parseToken()->authenticate();
// public function orderShipment($order_id)
// {
// $user = JWTAuth::parseToken()->authenticate();
// Get order
$order = $user->orders()->where('order_id', $order_id)->first();
// // Get order
// $order = $user->orders()->where('order_id', $order_id)->first();
if (!$order) {
return response()->json(['success' => false, 'message' => 'Order not found'], 404);
}
// if (!$order) {
// return response()->json(['success' => false, 'message' => 'Order not found'], 404);
// }
// Find shipment only for this order
$shipment = $order->shipments()
->with(['items' => function ($q) use ($order) {
$q->where('order_id', $order->id);
}])
->first();
// // Find shipment only for this order
// $shipment = $order->shipments()
// ->with(['items' => function ($q) use ($order) {
// $q->where('order_id', $order->id);
// }])
// ->first();
return response()->json([
'success' => true,
'shipment' => $shipment
]);
}
// return response()->json([
// 'success' => true,
// 'shipment' => $shipment
// ]);
// }
public function orderInvoice($order_id)
@@ -179,23 +224,35 @@ public function trackOrder($order_id)
{
$user = JWTAuth::parseToken()->authenticate();
$order = $user->orders()
->with('shipments')
->where('order_id', $order_id)
->first();
if (!$order) {
return response()->json(['success' => false, 'message' => 'Order not found'], 404);
if (!$user) {
return response()->json([
'success' => false,
'message' => 'Unauthorized'
], 401);
}
$shipment = $order->shipments()->first();
// Ensure the container belongs to this customer via invoice
$invoice = \App\Models\Invoice::where('customer_id', $user->id)
->where('container_id', $order_id)
->with('container')
->first();
if (!$invoice || !$invoice->container) {
return response()->json([
'success' => false,
'message' => 'Order not found'
], 404);
}
$container = $invoice->container;
return response()->json([
'success' => true,
'track' => [
'order_id' => $order->order_id,
'shipment_status' => $shipment->status ?? 'pending',
'shipment_date' => $shipment->shipment_date ?? null,
'order_id' => $container->id,
'container_number' => $container->container_number,
'status' => $container->status,
'container_date' => $container->container_date,
]
]);
}
@@ -289,44 +346,44 @@ public function invoiceDetails($invoice_id)
]);
}
public function confirmOrder($order_id)
{
$user = JWTAuth::parseToken()->authenticate();
// public function confirmOrder($order_id)
// {
// $user = JWTAuth::parseToken()->authenticate();
if (! $user) {
return response()->json([
'success' => false,
'message' => 'Unauthorized'
], 401);
}
// if (! $user) {
// return response()->json([
// 'success' => false,
// 'message' => 'Unauthorized'
// ], 401);
// }
$order = $user->orders()
->where('order_id', $order_id)
->first();
// $order = $user->orders()
// ->where('order_id', $order_id)
// ->first();
if (! $order) {
return response()->json([
'success' => false,
'message' => 'Order not found'
], 404);
}
// if (! $order) {
// return response()->json([
// 'success' => false,
// 'message' => 'Order not found'
// ], 404);
// }
// 🚫 Only allow confirm from order_placed
if ($order->status !== 'order_placed') {
return response()->json([
'success' => false,
'message' => 'Order cannot be confirmed'
], 422);
}
// // 🚫 Only allow confirm from order_placed
// if ($order->status !== 'order_placed') {
// return response()->json([
// 'success' => false,
// 'message' => 'Order cannot be confirmed'
// ], 422);
// }
$order->status = 'order_confirmed';
$order->save();
// $order->status = 'order_confirmed';
// $order->save();
return response()->json([
'success' => true,
'message' => 'Order confirmed successfully'
]);
}
// return response()->json([
// 'success' => true,
// 'message' => 'Order confirmed successfully'
// ]);
// }

View File

@@ -49,10 +49,10 @@ class Invoice extends Model
return $this->hasMany(InvoiceItem::class)->orderBy('id', 'ASC');
}
public function container()
{
return $this->belongsTo(Container::class);
}
// public function container()
// {
// return $this->belongsTo(Container::class);
// }
public function customer()
{
@@ -119,4 +119,15 @@ class Invoice extends Model
return max(0, $grand - $paid);
}
public function isLockedForEdit(): bool
{
return $this->status === 'paid';
}
public function container()
{
return $this->belongsTo(\App\Models\Container::class, 'container_id');
}
}

View File

@@ -29,6 +29,7 @@ class InvoiceItem extends Model
'ttl_kg',
'shop_no',
'mark_no',
];
/****************************

View File

@@ -89,10 +89,7 @@ class User extends Authenticatable implements JWTSubject
{
return [];
}
public function invoices()
{
return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'customer_id');
}
// App\Models\User.php
@@ -108,6 +105,10 @@ public function invoiceInstallments()
);
}
public function invoices()
{
return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'id');
}
}

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'
");
}
};

View File

@@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::table('invoice_items', function (Blueprint $table) {
$table->string('mark_no')->nullable(); // after() काहीही नको
});
}
public function down()
{
Schema::table('invoice_items', function (Blueprint $table) {
$table->dropColumn('mark_no');
});
}
};

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.

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.

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

@@ -40,6 +40,72 @@
margin-top: 2px;
}
.cm-download-pdf {
background: linear-gradient(100deg, #4c6fff 0%, #8e54e9 100%) !important;
color: #fff !important;
border: none !important;
font-weight: 600 !important;
font-size: 12.5px !important;
padding: 8px 16px !important;
border-radius: 8px !important;
box-shadow: 0 4px 14px rgba(76,111,255,0.4) !important;
transition: all 0.2s ease !important;
text-decoration: none !important;
display: inline-flex !important;
align-items: center !important;
gap: 6px !important;
}
.cm-download-pdf:hover {
transform: translateY(-1px) !important;
box-shadow: 0 6px 20px rgba(76,111,255,0.5) !important;
color: #fff !important;
opacity: 0.95 !important;
}
.cm-download-excel {
background: linear-gradient(100deg, #10b981 0%, #059669 100%) !important;
color: #fff !important;
border: none !important;
font-weight: 600 !important;
font-size: 12.5px !important;
padding: 8px 16px !important;
border-radius: 8px !important;
box-shadow: 0 4px 14px rgba(16,185,129,0.4) !important;
transition: all 0.2s ease !important;
text-decoration: none !important;
display: inline-flex !important;
align-items: center !important;
gap: 6px !important;
}
.cm-download-excel:hover {
transform: translateY(-1px) !important;
box-shadow: 0 6px 20px rgba(16,185,129,0.5) !important;
color: #fff !important;
opacity: 0.95 !important;
}
.cm-back-btn {
background: linear-gradient(100deg, #6b7280 0%, #4b5563 100%) !important;
color: #fff !important;
border: none !important;
font-weight: 600 !important;
font-size: 12.5px !important;
padding: 8px 16px !important;
border-radius: 8px !important;
box-shadow: 0 4px 14px rgba(107,114,128,0.4) !important;
transition: all 0.2s ease !important;
text-decoration: none !important;
display: inline-flex !important;
align-items: center !important;
gap: 6px !important;
}
.cm-back-btn:hover {
transform: translateY(-1px) !important;
box-shadow: 0 6px 20px rgba(107,114,128,0.5) !important;
color: #fff !important;
opacity: 0.95 !important;
}
.cm-main-card {
border-radius: 14px;
border: none;
@@ -65,7 +131,7 @@
.cm-info-cards-row {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr)); /* 3 cards one row */
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14px;
padding: 14px 18px 8px 18px;
}
@@ -132,7 +198,6 @@
color: #fff7ed;
}
/* TOTAL BOXES */
.cm-total-cards-row {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
@@ -221,7 +286,7 @@
}
.cm-table-scroll-outer {
margin: 10px 14px 0 14px;
margin: 10px 14px 30px 14px;
border-radius: 14px;
border: 1.5px solid #c9a359;
box-shadow: 0 4px 14px rgba(76,111,255,0.18);
@@ -433,15 +498,18 @@
</div>
</div>
<div class="d-flex gap-2">
<a href="{{ route('containers.index') }}" class="btn btn-light btn-sm">Back to list</a>
<a href="{{ route('containers.index') }}" class="cm-back-btn">
<i class="bi bi-arrow-left"></i>
Back to list
</a>
<a href="{{ route('containers.download.pdf', $container->id) }}"
class="btn btn-sm btn-outline-primary">
<a href="{{ route('containers.download.pdf', $container->id) }}" class="cm-download-pdf">
<i class="bi bi-file-earmark-pdf"></i>
Download PDF
</a>
<a href="{{ route('containers.download.excel', $container->id) }}"
class="btn btn-sm btn-outline-success">
<a href="{{ route('containers.download.excel', $container->id) }}" class="cm-download-excel">
<i class="bi bi-file-earmark-excel"></i>
Download Excel
</a>
</div>
@@ -453,7 +521,6 @@
<h5>Container Information</h5>
</div>
{{-- 3 INFO CARDS IN SINGLE ROW --}}
<div class="cm-info-cards-row">
<div class="cm-info-card cm-card-container">
<div class="cm-info-card-icon">
@@ -608,7 +675,7 @@
<div class="cm-filter-bar">
<span class="cm-row-count">
Total rows: {{ $container->rows->count() }} &nbsp;&nbsp; Edit cells then click "Save Changes".
Total rows: {{ $container->rows->count() }} Edit cells then click "Save Changes".
</span>
<input type="text" id="cmRowSearch" class="cm-filter-input"
placeholder="Quick search..." onkeyup="cmFilterRows()">
@@ -666,17 +733,20 @@
$isPrice = (str_contains($norm, 'PRICE') || str_contains($norm, 'RATE'));
$isAmount = (
str_contains($norm, 'AMOUNT') ||
str_contains($norm, 'AMOUNT') ||
str_contains($norm, 'TTLAMOUNT') ||
str_contains($norm, 'TOTALAMOUNT')
);
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
$isLockedByInvoice = in_array($row->row_index, $lockedRowIndexes ?? []);
$isReadOnly = $isTotalColumn || $container->status !== 'pending' || $isLockedByInvoice;
@endphp
<td>
<input
type="text"
class="cm-cell-input {{ $isTotalColumn ? 'cm-cell-readonly' : '' }}"
class="cm-cell-input {{ $isReadOnly ? 'cm-cell-readonly' : '' }}"
name="rows[{{ $row->id }}][{{ $heading }}]"
value="{{ $value }}"
data-row-id="{{ $row->id }}"
@@ -690,7 +760,7 @@
data-ttlkg="{{ $isTotalKg ? '1' : '0' }}"
data-price="{{ $isPrice ? '1' : '0' }}"
data-amount="{{ $isAmount ? '1' : '0' }}"
@if($isTotalColumn) readonly @endif
{{ $isReadOnly ? 'readonly' : '' }}
>
</td>
@endforeach
@@ -706,186 +776,191 @@
</div>
@if(!$container->rows->isEmpty())
<button id="cmSaveBtnFloating" class="cm-save-btn-floating">
Save Changes
</button>
<div id="cmToast" class="cm-toast">
Changes saved successfully.
</div>
<button
id="cmSaveBtnFloating"
class="cm-save-btn-floating {{ $container->status !== 'pending' ? 'cm-disabled' : '' }}"
{{ $container->status !== 'pending' ? 'disabled' : '' }}
>
Save Changes
</button>
@endif
<div id="cmToast" class="cm-toast"></div>
<script>
function cmFilterRows() {
const input = document.getElementById('cmRowSearch');
if (!input) return;
const filter = input.value.toLowerCase();
const table = document.getElementById('cmExcelTable');
if (!table) return;
const rows = table.getElementsByTagName('tr');
for (let i = 1; i < rows.length; i++) {
const cells = rows[i].getElementsByTagName('td');
let match = false;
for (let j = 0; j < cells.length; j++) {
const txt = cells[j].textContent || cells[j].innerText;
if (txt.toLowerCase().indexOf(filter) > -1) {
match = true;
break;
}
function cmFilterRows() {
const input = document.getElementById('cmRowSearch');
if (!input) return;
const filter = input.value.toLowerCase();
const table = document.getElementById('cmExcelTable');
if (!table) return;
const rows = table.getElementsByTagName('tr');
for (let i = 1; i < rows.length; i++) {
const cells = rows[i].getElementsByTagName('td');
let match = false;
for (let j = 0; j < cells.length; j++) {
const txt = cells[j].textContent || cells[j].innerText;
if (txt.toLowerCase().indexOf(filter) > -1) {
match = true;
break;
}
rows[i].style.display = match ? '' : 'none';
}
rows[i].style.display = match ? '' : 'none';
}
}
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('cm-edit-rows-form');
const btn = document.getElementById('cmSaveBtnFloating');
const toast = document.getElementById('cmToast');
const table = document.getElementById('cmExcelTable');
function showToast(message, isError = false) {
if (!toast) return;
toast.textContent = message;
toast.style.background = isError ? '#b91c1c' : '#0f172a';
toast.classList.add('cm-show');
setTimeout(() => toast.classList.remove('cm-show'), 2500);
}
// फक्त number काढण्यासाठी helper, पण cell मध्ये format नाही करणार
function parseNumber(str) {
if (!str) return 0;
const cleaned = String(str).replace(/,/g, '').trim();
const val = parseFloat(cleaned);
return isNaN(val) ? 0 : val;
}
function recalcRow(row) {
const inputs = row.querySelectorAll('.cm-cell-input');
let ctn = 0, qty = 0, ttlQty = 0;
let cbm = 0, ttlCbm = 0;
let kg = 0, ttlKg = 0;
let price = 0, amount = 0;
let ctnInput = null,
qtyInput = null,
ttlQtyInput = null,
cbmInput = null,
ttlCbmInput = null,
kgInput = null,
ttlKgInput = null,
priceInput = null,
amountInput = null;
inputs.forEach(inp => {
const val = parseNumber(inp.value);
if (inp.dataset.ctn === '1') {
ctn = val;
ctnInput = inp;
} else if (inp.dataset.qty === '1') {
qty = val;
qtyInput = inp;
} else if (inp.dataset.ttlqty === '1') {
ttlQty = val;
ttlQtyInput = inp;
} else if (inp.dataset.cbm === '1') {
cbm = val;
cbmInput = inp;
} else if (inp.dataset.ttlcbm === '1') {
ttlCbm = val;
ttlCbmInput = inp;
} else if (inp.dataset.kg === '1') {
kg = val;
kgInput = inp;
} else if (inp.dataset.ttlkg === '1') {
ttlKg = val;
ttlKgInput = inp;
} else if (inp.dataset.price === '1') {
price = val;
priceInput = inp;
} else if (inp.dataset.amount === '1') {
amount = val;
amountInput = inp;
}
});
// इथे आपण फक्त VALUE बदलतो, कोणतंही toFixed नाही वापरत
if (ttlQtyInput && ctnInput && qtyInput) {
const newTtlQty = ctn * qty;
ttlQtyInput.value = newTtlQty === 0 ? '' : String(newTtlQty);
ttlQty = newTtlQty;
}
if (ttlCbmInput && cbmInput && ctnInput) {
const newTtlCbm = cbm * ctn;
ttlCbmInput.value = newTtlCbm === 0 ? '' : String(newTtlCbm);
ttlCbm = newTtlCbm;
}
if (ttlKgInput && kgInput && ctnInput) {
const newTtlKg = kg * ctn;
ttlKgInput.value = newTtlKg === 0 ? '' : String(newTtlKg);
ttlKg = newTtlKg;
}
if (amountInput && priceInput && ttlQtyInput) {
const newAmount = price * ttlQty;
amountInput.value = newAmount === 0 ? '' : String(newAmount);
amount = newAmount;
}
}
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('cm-edit-rows-form');
const btn = document.getElementById('cmSaveBtnFloating');
const toast = document.getElementById('cmToast');
const table = document.getElementById('cmExcelTable');
if (table) {
table.addEventListener('input', function (e) {
const target = e.target;
if (!target.classList.contains('cm-cell-input')) return;
function showToast(message, isError = false) {
if (!toast) return;
toast.textContent = message;
toast.style.background = isError ? '#b91c1c' : '#0f172a';
toast.classList.add('cm-show');
setTimeout(() => toast.classList.remove('cm-show'), 2500);
}
if (target.classList.contains('cm-cell-readonly') || target.hasAttribute('readonly')) {
target.blur();
return;
}
function parseNumber(str) {
if (!str) return 0;
const cleaned = String(str).replace(/,/g, '').trim();
const val = parseFloat(cleaned);
return isNaN(val) ? 0 : val;
}
const row = target.closest('tr');
if (row) {
recalcRow(row);
}
});
}
function formatNumber(val, decimals) {
if (isNaN(val)) val = 0;
return val.toFixed(decimals);
}
if (form && btn) {
btn.addEventListener('click', function () {
if (btn.classList.contains('cm-disabled') || btn.hasAttribute('disabled')) {
return;
}
function recalcRow(row) {
const inputs = row.querySelectorAll('.cm-cell-input');
btn.classList.add('cm-disabled');
const formData = new FormData(form);
let ctn = 0, qty = 0, ttlQty = 0;
let cbm = 0, ttlCbm = 0;
let kg = 0, ttlKg = 0;
let price = 0, amount = 0;
let ctnInput = null,
qtyInput = null,
ttlQtyInput = null,
cbmInput = null,
ttlCbmInput = null,
kgInput = null,
ttlKgInput = null,
priceInput = null,
amountInput = null;
inputs.forEach(inp => {
const val = parseNumber(inp.value);
if (inp.dataset.ctn === '1') {
ctn = val;
ctnInput = inp;
} else if (inp.dataset.qty === '1') {
qty = val;
qtyInput = inp;
} else if (inp.dataset.ttlqty === '1') {
ttlQty = val;
ttlQtyInput = inp;
} else if (inp.dataset.cbm === '1') {
cbm = val;
cbmInput = inp;
} else if (inp.dataset.ttlcbm === '1') {
ttlCbm = val;
ttlCbmInput = inp;
} else if (inp.dataset.kg === '1') {
kg = val;
kgInput = inp;
} else if (inp.dataset.ttlkg === '1') {
ttlKg = val;
ttlKgInput = inp;
} else if (inp.dataset.price === '1') {
price = val;
priceInput = inp;
} else if (inp.dataset.amount === '1') {
amount = val;
amountInput = inp;
fetch(form.action, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': document.querySelector('input[name="_token"]').value
},
body: formData
})
.then(async res => {
if (!res.ok) {
const text = await res.text();
throw new Error(text || 'Failed to save');
}
return res.json().catch(() => ({}));
})
.then(() => {
showToast('Changes saved successfully.');
})
.catch(() => {
showToast('Error while saving changes.', true);
})
.finally(() => {
btn.classList.remove('cm-disabled');
});
if (ttlQtyInput && ctnInput && qtyInput) {
const newTtlQty = ctn * qty;
ttlQtyInput.value = formatNumber(newTtlQty, 0);
ttlQty = newTtlQty;
}
if (ttlCbmInput && cbmInput && ctnInput) {
const newTtlCbm = cbm * ctn;
ttlCbmInput.value = formatNumber(newTtlCbm, 3);
ttlCbm = newTtlCbm;
}
if (ttlKgInput && kgInput && ctnInput) {
const newTtlKg = kg * ctn;
ttlKgInput.value = formatNumber(newTtlKg, 2);
ttlKg = newTtlKg;
}
if (amountInput && priceInput && ttlQtyInput) {
const newAmount = price * ttlQty;
amountInput.value = formatNumber(newAmount, 2);
amount = newAmount;
}
}
if (table) {
table.addEventListener('input', function (e) {
const target = e.target;
if (!target.classList.contains('cm-cell-input')) return;
if (target.classList.contains('cm-cell-readonly') || target.hasAttribute('readonly')) {
target.blur();
return;
}
const row = target.closest('tr');
if (row) {
recalcRow(row);
}
});
}
if (form && btn) {
btn.addEventListener('click', function () {
btn.classList.add('cm-disabled');
const formData = new FormData(form);
fetch(form.action, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': document.querySelector('input[name="_token"]').value
},
body: formData
})
.then(async res => {
if (!res.ok) {
const text = await res.text();
throw new Error(text || 'Failed to save');
}
return res.json().catch(() => ({}));
})
.then(() => {
showToast('Changes saved successfully.');
})
.catch(() => {
showToast('Error while saving changes.', true);
})
.finally(() => {
btn.classList.remove('cm-disabled');
});
});
}
});
});
}
});
</script>
@endsection

View File

@@ -661,7 +661,8 @@
<th class="table-header">Customer ID</th>
<th class="table-header">Orders</th>
<th class="table-header">Order Total</th>
<th class="table-header">Total Payable</th>
<th class="table-header">GST Amount</th>
<th class="table-header">Total Paid</th>
<th class="table-header">Remaining</th>
<th class="table-header">Create Date</th>
<th class="table-header">Status</th>
@@ -672,20 +673,27 @@
<tbody id="customersTableBody">
@forelse($customers as $c)
@php
// Orders = invoice count
// 1) Orders = total invoice count
$ordersCount = $c->invoices->count();
// Order Total = items total from all invoices (final_amount)
$orderTotal = $c->invoices->sum('final_amount');
// 2) Order Total = सर्व invoices च्या charge groups चा base total (without GST)
$orderTotal = $c->invoices->sum(function($invoice) {
return $invoice->chargeGroups->sum('total_charge');
});
// Total payable = grand total with GST + groups
$totalPayable = $c->invoices->sum('grand_total_with_charges');
// 3) GST Amount = सर्व invoices च्या gst_amount चा sum
$gstTotal = $c->invoices->sum('gst_amount');
// Total paid via installments
$totalPaid = $c->invoiceInstallments->sum('amount');
// 3) Total Payable = customer ने किती paid केले (installments sum)
$totalPaid = $c->invoices->flatMap->installments->sum('amount');
// Remaining amount
$remainingAmount = max($totalPayable - $totalPaid, 0);
// 4) Remaining = grand_total_with_charges - paid
$grandTotal = $c->invoices->sum(function($invoice) {
$base = $invoice->chargeGroups->sum('total_charge');
$gst = (float)($invoice->gst_amount ?? 0);
return $base + $gst;
});
$remainingAmount = max($grandTotal - $totalPaid, 0);
@endphp
<tr>
<td class="customer-info-column">
@@ -724,7 +732,13 @@
<td class="total-column">
<span class="total-amount">
{{ number_format($totalPayable, 2) }}
{{ number_format($gstTotal, 2) }}
</span>
</td>
<td class="total-column">
<span class="total-amount">
{{ number_format($totalPaid, 2) }}
</span>
</td>
@@ -771,7 +785,7 @@
</tr>
@empty
<tr>
<td colspan="9" class="text-center py-4">
<td colspan="10" class="text-center py-4">
<i class="bi bi-people display-4 text-muted d-block mb-2"></i>
<span class="text-muted">No customers found.</span>
</td>
@@ -836,4 +850,4 @@
});
</script>
@endsection
@endsection

View File

@@ -1,30 +1,7 @@
@extends('admin.layouts.app')
@section('page-title', 'Dashboard')
@php
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Shipment;
use App\Models\Invoice;
use App\Models\User;
use App\Models\Admin;
$totalOrders = Order::count();
$pendingOrders = Order::where('status', 'pending')->count();
$totalShipments = Shipment::count();
$totalItems = OrderItem::count();
$totalRevenue = Invoice::sum('final_amount_with_gst');
// USERS (CUSTOMERS)
$activeCustomers = User::where('status', 'active')->count();
$inactiveCustomers = User::where('status', 'inactive')->count();
// STAFF (FROM ADMINS TABLE)
$totalStaff = Admin::where('type', 'staff')->count();
$orders = Order::latest()->get();
@endphp
@section('content')
<style>
/* ===== GLOBAL STYLES (From Shipment) ===== */
@@ -1235,66 +1212,91 @@ body {
<!-- STATS CARDS -->
<div class="stats-row-wrap">
{{-- Row 1: Total Containers, Active Customers, Total Invoices, Paid Invoices --}}
<div class="stats-row">
<div class="stats-card stats-card-blue">
<span class="stats-icon">📦</span>
<span class="stats-icon">&#x1F6A2;</span>
<div>
<div class="stats-label">Total Shipments</div>
<div class="stats-value">{{ $totalShipments }}</div>
<div class="stats-label">Total Containers</div>
<div class="stats-value">{{ $totalContainers }}</div>
</div>
</div>
<div class="stats-card stats-card-blue">
<span class="stats-icon">👥</span>
<span class="stats-icon">&#x1F465;</span>
<div>
<div class="stats-label">Active Customers</div>
<div class="stats-value">{{ $activeCustomers }}</div>
</div>
</div>
<div class="stats-card stats-card-green">
<span class="stats-icon">💰</span>
<div class="stats-card stats-card-blue">
<span class="stats-icon">&#x1F9FE;</span>
<div>
<div class="stats-label">Total Revenue</div>
<div class="stats-value">{{ number_format($totalRevenue, 2) }}</div>
<div class="stats-label">Total Orders</div>
<div class="stats-value">{{ $totalInvoices }}</div>
</div>
</div>
<div class="stats-card stats-card-red">
<span class="stats-icon"></span>
<div class="stats-card stats-card-green">
<span class="stats-icon">&#x2705;</span>
<div>
<div class="stats-label">Pending Order</div>
<div class="stats-value">{{ $pendingOrders }}</div>
<div class="stats-label">Paid Invoices</div>
<div class="stats-value">{{ $paidInvoices }}</div>
</div>
</div>
</div>
{{-- Row 2: Pending Invoices, Total Staff, Inactive Customers, Total Revenue --}}
<div class="stats-row">
<div class="stats-card stats-card-blue">
<span class="stats-icon">📦</span>
<div class="stats-card stats-card-orng">
<span class="stats-icon">&#x1F550;</span>
<div>
<div class="stats-label">Total Orders</div>
<div class="stats-value">{{ $totalOrders }}</div>
<div class="stats-label">Pending Orders</div>
<div class="stats-value">{{ $pendingInvoices }}</div>
</div>
</div>
<div class="stats-card stats-card-blue">
<span class="stats-icon">🧑‍💼</span>
<span class="stats-icon">&#x1F9D1;&#x200D;&#x1F4BC;</span>
<div>
<div class="stats-label">Total Staff</div>
<div class="stats-value">{{ $totalStaff }}</div>
</div>
</div>
<div class="stats-card stats-card-blue">
<span class="stats-icon">📦</span>
<div>
<div class="stats-label">Total Items</div>
<div class="stats-value">{{ $totalItems }}</div>
</div>
</div>
<div class="stats-card stats-card-orng">
<span class="stats-icon"></span>
<span class="stats-icon">&#x26D4;</span>
<div>
<div class="stats-label">Inactive Customers</div>
<div class="stats-value">{{ $inactiveCustomers }}</div>
</div>
</div>
<div class="stats-card stats-card-green">
<span class="stats-icon">&#x1F4B0;</span>
<div>
<div class="stats-label">Total Revenue</div>
<div class="stats-value">&#8377;{{ number_format($totalRevenue, 2) }}</div>
</div>
</div>
</div>
{{-- COMMENTED OUT --}}
{{--
<div class="stats-card stats-card-red">
<div class="stats-label">Pending Orders</div>
<div class="stats-value">{{ $pendingOrders }}</div>
</div>
<div class="stats-card stats-card-red">
<div class="stats-label">Overdue Invoices</div>
<div class="stats-value">{{ $overdueInvoices }}</div>
</div>
<div class="stats-card stats-card-blue">
<div class="stats-label">Total Orders</div>
<div class="stats-value">{{ $totalOrders }}</div>
</div>
<div class="stats-card stats-card-blue">
<div class="stats-label">Delivered Orders</div>
<div class="stats-value">{{ $totalOrders - $pendingOrders }}</div>
</div>
--}}
</div>
<!-- ORDER MANAGEMENT -->

View File

@@ -169,6 +169,13 @@
color: #6b21a8;
font-weight: 500;
}
.filter-select option[value="paying"] {
background-color: #e0e7ff;
color: #4338ca;
font-weight: 500;
}
.filter-select option[value="all"] {
background-color: white;
@@ -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%);
@@ -1218,7 +1236,7 @@
<th class="column-header">Customer</th>
<th class="column-header">Container</th> {{-- NEW --}}
<th class="column-header">Final Amount</th>
<th class="column-header">GST %</th>
<th class="column-header">GST Amount</th>
<th class="column-header">Total w/GST</th>
<th class="column-header">Status</th>
<th class="column-header">Invoice Date</th>
@@ -1268,8 +1286,8 @@
{{ number_format($invoice->final_amount, 2) }}
</td>
<td class="gst-cell">
{{ $invoice->gst_percent }}%
<td class="amount-cell">
{{ number_format($invoice->gst_amount, 2) }}
</td>
<td class="amount-cell">
@@ -1277,16 +1295,19 @@
</td>
<td>
<span class="badge badge-{{ $invoice->status }}">
@if($invoice->status == 'paid')
<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 == 'overdue')
<i class="bi bi-exclamation-triangle-fill status-icon"></i>
@endif
{{ ucfirst($invoice->status) }}
</span>
<span class="badge badge-{{ $invoice->status }}">
@if($invoice->status == 'paid')
<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">
@@ -1355,8 +1379,8 @@
<span class="mobile-detail-value">{{ number_format($invoice->final_amount, 2) }}</span>
</div>
<div class="mobile-detail-item">
<span class="mobile-detail-label">GST</span>
<span class="mobile-detail-value">{{ $invoice->gst_percent }}%</span>
<span class="mobile-detail-label">GST Amount</span>
<span class="mobile-detail-value">{{ number_format($invoice->gst_amount, 2) }}</span>
</div>
<div class="mobile-detail-item">
<span class="mobile-detail-label">Total</span>
@@ -1670,13 +1694,14 @@ document.addEventListener('DOMContentLoaded', function() {
${invoice.container ? (invoice.container.container_number ?? '—') : '—'}
</td>
<td class="amount-cell">${parseFloat(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
<td class="gst-cell">${invoice.gst_percent}%</td>
<td class="amount-cell">${parseFloat(invoice.gst_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
<td class="amount-cell">${parseFloat(invoice.final_amount_with_gst).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
<td>
<span class="badge badge-${invoice.status}">
${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>
@@ -1730,8 +1756,8 @@ document.addEventListener('DOMContentLoaded', function() {
<span class="mobile-detail-value">${parseFloat(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</span>
</div>
<div class="mobile-detail-item">
<span class="mobile-detail-label">GST</span>
<span class="mobile-detail-value">${invoice.gst_percent}%</span>
<span class="mobile-detail-label">GST Amount</span>
<span class="mobile-detail-value">${parseFloat(invoice.gst_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</span>
</div>
<div class="mobile-detail-item">
<span class="mobile-detail-label">Total</span>

View File

@@ -325,93 +325,15 @@
</div>
</div>
{{-- Edit Invoice Header --}}
<div class="glass-card">
<div class="card-header-compact">
<h4>
<i class="fas fa-edit me-2"></i>
Edit Invoice Details
</h4>
</div>
<div class="card-body-compact">
<form action="{{ route('admin.invoices.update', $invoice->id) }}" method="POST">
@csrf
<div class="form-grid-compact">
{{-- Invoice Date --}}
<div class="form-group-compact">
<label class="form-label-compact">
<i class="fas fa-calendar-day"></i> Invoice Date
</label>
<input type="date"
name="invoice_date"
class="form-control-compact"
value="{{ old('invoice_date', $invoice->invoice_date) }}"
required>
</div>
{{-- Due Date --}}
<div class="form-group-compact">
<label class="form-label-compact">
<i class="fas fa-clock"></i> Due Date
</label>
<input type="date"
name="due_date"
class="form-control-compact"
value="{{ old('due_date', $invoice->due_date) }}"
required>
</div>
{{-- Final Amount (With GST) Charge Groups Total --}}
<div class="form-group-compact">
<label class="form-label-compact">
<i class="fas fa-money-bill-wave"></i> Final Amount (With GST)
</label>
<input type="number"
step="0.01"
class="form-control-compact"
value="{{ $invoice->grand_total_with_charges }}"
readonly>
</div>
{{-- Status --}}
<div class="form-group-compact">
<label class="form-label-compact">
<i class="fas fa-tasks"></i> Status
</label>
<select name="status" class="form-select-compact" required>
<option value="pending" {{ old('status', $invoice->status) === 'pending' ? 'selected' : '' }}>Pending</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 --}}
<div class="form-group-compact" style="grid-column: 1 / -1;">
<label class="form-label-compact">
<i class="fas fa-sticky-note"></i> Notes
</label>
<textarea name="notes"
rows="3"
class="form-control-compact"
placeholder="Add any additional notes...">{{ old('notes', $invoice->notes) }}</textarea>
</div>
</div>
<div class="text-end mt-3">
<button type="submit" class="btn-success-compact btn-compact">
<i class="fas fa-save me-2"></i>Update Invoice
</button>
</div>
</form>
</div>
</div>
@if(false)
{{-- Edit Invoice Details card HIDDEN --}}
{{-- तुझा full header edit form इथे hidden ठेवलेला आहे --}}
@endif
@php
$totalPaid = $invoice->totalPaid();
$remaining = $invoice->remainingAmount();
// Mixed tax type label from charge groups
$taxTypes = $invoice->chargeGroups
? $invoice->chargeGroups->pluck('tax_type')->filter()->unique()->values()
: collect([]);
@@ -438,47 +360,10 @@
}
@endphp
{{-- Amount Breakdown --}}
<div class="amount-breakdown-compact">
<h6 class="fw-bold mb-3 text-dark">
<i class="fas fa-calculator me-2"></i>Amount Breakdown
</h6>
<div class="breakdown-row">
<span class="breakdown-label">Tax Type</span>
<span class="breakdown-value text-primary">
{{ $taxTypeLabel }}
</span>
</div>
<div class="breakdown-row">
<span class="breakdown-label">GST Amount</span>
<span class="breakdown-value text-warning" id="gstAmount">
{{ number_format($invoice->gst_amount, 2) }}
</span>
</div>
<div class="breakdown-row" style="border-top: 2px solid #e2e8f0; padding-top: 0.75rem;">
<span class="breakdown-label fw-bold">Grand Total (Charges + GST)</span>
<span class="breakdown-value fw-bold text-dark" id="totalInvoiceWithGst">
{{ number_format($invoice->grand_total_with_charges, 2) }}
</span>
</div>
<div class="breakdown-row">
<span class="breakdown-label text-success">Total Paid</span>
<span class="breakdown-value fw-bold text-success" id="paidAmount">
{{ number_format($totalPaid, 2) }}
</span>
</div>
<div class="breakdown-row" style="border-bottom: none;">
<span class="breakdown-label text-danger">Remaining</span>
<span class="breakdown-value fw-bold text-danger" id="remainingAmount">
{{ number_format(max(0, $remaining), 2) }}
</span>
</div>
</div>
@if(false)
{{-- Amount Breakdown HIDDEN --}}
{{-- जुनं breakdown section इथे hidden आहे --}}
@endif
{{-- Summary cards --}}
<div class="summary-grid-compact">
@@ -489,13 +374,13 @@
<div class="summary-label-compact">Grand Total (Charges + GST)</div>
</div>
<div class="summary-card-compact paid">
<div class="summary-value-compact text-primary">
<div class="summary-value-compact text-primary" id="paidAmount">
{{ number_format($totalPaid, 2) }}
</div>
<div class="summary-label-compact">Total Paid</div>
</div>
<div class="summary-card-compact remaining">
<div class="summary-value-compact text-warning">
<div class="summary-value-compact text-warning" id="remainingAmount">
{{ number_format(max(0, $remaining), 2) }}
</div>
<div class="summary-label-compact">Remaining</div>
@@ -681,91 +566,128 @@ document.addEventListener("DOMContentLoaded", function () {
headers: {
"X-CSRF-TOKEN": submitForm.querySelector('input[name="_token"]').value,
"Accept": "application/json",
"X-Requested-With": "XMLHttpRequest",
},
body: new FormData(submitForm)
})
.then(res => res.json())
.then(data => {
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
submitBtn.disabled = false;
.then(async res => {
let data;
try {
data = await res.json();
} catch (e) {
throw new Error("Invalid server response.");
}
if (data.status === "error") {
alert(data.message);
return;
}
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
submitBtn.disabled = false;
const table = document.getElementById("installmentTable");
const index = table.rows.length + 1;
if (!res.ok) {
const msg =
(data && data.message) ||
(data && data.errors && Object.values(data.errors)[0][0]) ||
"Something went wrong.";
alert(msg);
return;
}
document.getElementById("noInstallmentsMsg")?.classList.add("d-none");
if (data.status === "error") {
alert(data.message || "Something went wrong.");
return;
}
table.insertAdjacentHTML("beforeend", `
<tr data-id="${data.installment.id}">
<td class="fw-bold text-muted">${index}</td>
<td>${data.installment.installment_date}</td>
<td>
<span class="badge-compact bg-primary bg-opacity-10 text-primary">
${data.installment.payment_method.toUpperCase()}
</span>
</td>
<td>
${data.installment.reference_no
? `<span class="text-muted">${data.installment.reference_no}</span>`
: '<span class="text-muted">-</span>'}
</td>
<td class="fw-bold text-success">
${formatINR(data.installment.amount)}
</td>
<td>
<button class="btn-danger-compact btn-compact btn-sm deleteInstallment">
<i class="fas fa-trash me-1"></i>Delete
</button>
</td>
</tr>
`);
const table = document.getElementById("installmentTable");
const index = table.querySelectorAll("tr").length + 1;
if (document.getElementById("paidAmount")) {
document.getElementById("paidAmount").textContent = "" + formatINR(data.totalPaid);
}
if (document.getElementById("remainingAmount")) {
document.getElementById("remainingAmount").textContent = "" + formatINR(data.remaining);
}
if (document.getElementById("baseAmount")) {
document.getElementById("baseAmount").textContent = "" + formatINR(data.chargeGroupsTotal);
}
if (document.getElementById("gstAmount")) {
document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount);
}
document.getElementById("noInstallmentsMsg")?.classList.add("d-none");
if (document.getElementById("totalInvoiceWithGst")) {
document.getElementById("totalInvoiceWithGst").textContent =
"" + formatINR(data.grandTotal);
}
const totalCard = document.getElementById("totalInvoiceWithGstCard");
if (totalCard) {
totalCard.textContent = "" + formatINR(data.grandTotal);
}
const pmMethod = data.installment.payment_method
? data.installment.payment_method.toUpperCase()
: "-";
const refNo = data.installment.reference_no
? `<span class="text-muted">${data.installment.reference_no}</span>`
: '<span class="text-muted">-</span>';
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
if (paidCard) paidCard.textContent = "" + formatINR(data.totalPaid);
table.insertAdjacentHTML("beforeend", `
<tr data-id="${data.installment.id}">
<td class="fw-bold text-muted">${index}</td>
<td>${data.installment.installment_date}</td>
<td>
<span class="badge-compact bg-primary bg-opacity-10 text-primary">
${pmMethod}
</span>
</td>
<td>${refNo}</td>
<td class="fw-bold text-success">
${formatINR(data.installment.amount)}
</td>
<td>
<button class="btn-danger-compact btn-compact btn-sm deleteInstallment">
<i class="fas fa-trash me-1"></i>Delete
</button>
</td>
</tr>
`);
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
if (remainingCard) remainingCard.textContent = "" + formatINR(data.remaining);
if (document.getElementById("paidAmount")) {
document.getElementById("paidAmount").textContent = "" + formatINR(data.totalPaid);
}
if (document.getElementById("remainingAmount")) {
document.getElementById("remainingAmount").textContent = "" + formatINR(data.remaining);
}
if (document.getElementById("baseAmount")) {
document.getElementById("baseAmount").textContent = "" + formatINR(data.chargeGroupsTotal);
}
if (document.getElementById("gstAmount")) {
document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount);
}
if (document.getElementById("totalInvoiceWithGst")) {
document.getElementById("totalInvoiceWithGst").textContent =
"" + formatINR(data.grandTotal);
}
const totalCard = document.getElementById("totalInvoiceWithGstCard");
if (totalCard) {
totalCard.textContent = "" + formatINR(data.grandTotal);
}
submitForm.reset();
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
if (paidCard) paidCard.textContent = "" + formatINR(data.totalPaid);
if (data.isCompleted && toggleBtn) {
toggleBtn.remove();
formBox.classList.add("d-none");
}
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
if (remainingCard) remainingCard.textContent = "" + formatINR(data.remaining);
alert(data.message);
})
.catch(() => {
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
submitBtn.disabled = false;
alert("Something went wrong. Please try again.");
});
const fsTotal = document.getElementById("finalSummaryTotalPaid");
if (fsTotal) fsTotal.textContent = "" + formatINR(data.totalPaid);
const fsRemaining = document.getElementById("finalSummaryRemaining");
if (fsRemaining) {
fsRemaining.textContent = "" + formatINR(data.remaining);
fsRemaining.style.color = data.remaining > 0 ? "#ef4444" : "#10b981";
}
const fsRemainingLabel = document.getElementById("finalSummaryRemainingLabel");
if (fsRemainingLabel) {
fsRemainingLabel.style.color = data.remaining > 0 ? "#ef4444" : "#10b981";
fsRemainingLabel.innerHTML = `<i class="fas fa-${data.remaining > 0 ? 'exclamation-circle' : 'check-circle'} me-1" style="font-size:0.8rem;"></i>Remaining`;
}
if (data.newStatus && typeof updateStatusBadge === "function") {
updateStatusBadge(data.newStatus);
}
submitForm.reset();
if (data.isCompleted && toggleBtn) {
toggleBtn.remove();
formBox.classList.add("d-none");
}
alert(data.message || "Installment added successfully.");
})
.catch((err) => {
console.error("Installment submit error:", err);
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
submitBtn.disabled = false;
alert(err.message || "Something went wrong. Please try again.");
});
});
}
@@ -789,70 +711,138 @@ document.addEventListener("DOMContentLoaded", function () {
headers: {
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').getAttribute("content"),
"Accept": "application/json",
"X-Requested-With": "XMLHttpRequest",
},
})
.then(res => res.json())
.then(data => {
if (data.status === "success") {
row.style.opacity = 0;
setTimeout(() => row.remove(), 300);
.then(async res => {
let data;
try { data = await res.json(); } catch(e) { throw new Error("Invalid server response."); }
if (document.getElementById("paidAmount")) {
document.getElementById("paidAmount").textContent = "" + formatINR(data.totalPaid);
}
if (document.getElementById("remainingAmount")) {
document.getElementById("remainingAmount").textContent = "" + formatINR(data.remaining);
}
if (document.getElementById("baseAmount")) {
document.getElementById("baseAmount").textContent = "" + formatINR(data.chargeGroupsTotal);
}
if (document.getElementById("gstAmount")) {
document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount);
}
if (document.getElementById("totalInvoiceWithGst")) {
document.getElementById("totalInvoiceWithGst").textContent =
"" + formatINR(data.grandTotal);
}
const totalCard = document.getElementById("totalInvoiceWithGstCard");
if (totalCard) {
totalCard.textContent = "" + formatINR(data.grandTotal);
}
if (!res.ok || data.status !== "success") {
const msg = data && data.message ? data.message : "Something went wrong. Please try again.";
throw new Error(msg);
}
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
if (paidCard) paidCard.textContent = "" + formatINR(data.totalPaid);
row.style.opacity = 0;
setTimeout(() => row.remove(), 300);
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
if (remainingCard) remainingCard.textContent = "" + formatINR(data.remaining);
if (document.getElementById("paidAmount")) {
document.getElementById("paidAmount").textContent = "" + formatINR(data.totalPaid);
}
if (document.getElementById("remainingAmount")) {
document.getElementById("remainingAmount").textContent = "" + formatINR(data.remaining);
}
if (document.getElementById("baseAmount")) {
document.getElementById("baseAmount").textContent = "" + formatINR(data.chargeGroupsTotal);
}
if (document.getElementById("gstAmount")) {
document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount);
}
if (document.getElementById("totalInvoiceWithGst")) {
document.getElementById("totalInvoiceWithGst").textContent =
"" + formatINR(data.grandTotal);
}
const totalCard = document.getElementById("totalInvoiceWithGstCard");
if (totalCard) {
totalCard.textContent = "" + formatINR(data.grandTotal);
}
if (data.isZero) {
document.getElementById("noInstallmentsMsg")?.classList.remove("d-none");
}
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
if (paidCard) paidCard.textContent = "" + formatINR(data.totalPaid);
alert(data.message);
} else {
alert(data.message || "Something went wrong. Please try again.");
}
})
.catch(() => {
alert("Something went wrong. Please try again.");
});
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
if (remainingCard) remainingCard.textContent = "" + formatINR(data.remaining);
const fsTotal = document.getElementById("finalSummaryTotalPaid");
if (fsTotal) fsTotal.textContent = "" + formatINR(data.totalPaid);
const fsRemaining = document.getElementById("finalSummaryRemaining");
if (fsRemaining) {
fsRemaining.textContent = "" + formatINR(data.remaining);
fsRemaining.style.color = data.remaining > 0 ? "#ef4444" : "#10b981";
}
const fsRemainingLabel = document.getElementById("finalSummaryRemainingLabel");
if (fsRemainingLabel) {
fsRemainingLabel.style.color = data.remaining > 0 ? "#ef4444" : "#10b981";
fsRemainingLabel.innerHTML = `<i class="fas fa-${data.remaining > 0 ? 'exclamation-circle' : 'check-circle'} me-1" style="font-size:0.8rem;"></i>Remaining`;
}
if (data.newStatus && typeof updateStatusBadge === "function") {
updateStatusBadge(data.newStatus);
}
if (data.isZero) {
document.getElementById("noInstallmentsMsg")?.classList.remove("d-none");
}
alert(data.message || "Installment deleted.");
})
.catch(err => {
alert(err.message || "Something went wrong. Please try again.");
});
});
// Auto due date = invoice date + 10 days
const invoiceDateInput = document.querySelector('input[name="invoice_date"]');
const dueDateInput = document.querySelector('input[name="due_date"]');
// Header AJAX (सध्या card hidden आहे, तरीही ठेवलेलं)
const headerForm = document.getElementById('invoiceHeaderForm');
const headerBtn = document.getElementById('btnHeaderSave');
const headerMsg = document.getElementById('headerUpdateMsg');
if (invoiceDateInput && dueDateInput) {
invoiceDateInput.addEventListener('change', function() {
const selectedDate = new Date(this.value);
if (headerForm && headerBtn) {
headerForm.addEventListener('submit', function(e) {
e.preventDefault();
if (!isNaN(selectedDate.getTime())) {
selectedDate.setDate(selectedDate.getDate() + 10);
const year = selectedDate.getFullYear();
const month = String(selectedDate.getMonth() + 1).padStart(2, '0');
const day = String(selectedDate.getDate()).padStart(2, '0');
dueDateInput.value = `${year}-${month}-${day}`;
}
headerMsg.textContent = '';
headerBtn.disabled = true;
headerBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Saving...';
const formData = new FormData(headerForm);
fetch(headerForm.action, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
body: formData
})
.then(async res => {
let data;
try { data = await res.json(); } catch(e) {}
if (!res.ok) {
if (data && data.errors) {
const firstError = Object.values(data.errors)[0][0] ?? 'Validation error.';
throw new Error(firstError);
}
throw new Error(data && data.message ? data.message : 'Failed to update invoice.');
}
headerMsg.textContent = 'Invoice header updated.';
headerMsg.classList.remove('text-danger');
headerMsg.classList.add('text-light');
const status = document.getElementById('statusSelect')?.value;
const badge = document.querySelector('.status-badge');
if (badge && status) {
badge.classList.remove('status-paid','status-pending','status-overdue','status-default');
if (status === 'paid') badge.classList.add('status-paid');
else if (status === 'pending') badge.classList.add('status-pending');
else if (status === 'overdue') badge.classList.add('status-overdue');
else badge.classList.add('status-default');
badge.innerHTML = status.charAt(0).toUpperCase() + status.slice(1);
}
})
.catch(err => {
headerMsg.textContent = err.message || 'Error updating invoice.';
headerMsg.classList.remove('text-light');
headerMsg.classList.add('text-warning');
})
.finally(() => {
headerBtn.disabled = false;
headerBtn.innerHTML = '<i class="fas fa-save me-2"></i>Update Invoice';
});
});
}
});

View File

@@ -546,12 +546,6 @@
</div>
<div class="stats-container">
@php
$totalInvoices = $invoices->count();
$paidInvoices = $invoices->where('invoice_status', 'paid')->count();
$pendingInvoices = $invoices->where('invoice_status', 'pending')->count();
$overdueInvoices = $invoices->where('invoice_status', 'overdue')->count();
@endphp
<div class="stat-card total">
<div class="stat-icon-wrap">
@@ -1112,4 +1106,4 @@
document.head.appendChild(style);
</script>
@endsection
@endsection

View File

@@ -1,272 +1,623 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ $invoice->invoice_number }}</title>
<title>Invoice #{{ $invoice->invoice_number }}</title>
<style>
body {
font-family: 'Segoe UI', 'Roboto', Arial, sans-serif;
background: #F7FBFC;
color: #1A222B;
font-family: 'DejaVu Sans', sans-serif;
font-size: 10px;
color: #1a202c;
line-height: 1.4;
margin: 0;
padding: 0;
font-size: 15px;
}
.container {
max-width: 850px;
margin: 24px auto 0 auto;
padding: 18px;
background: #fff;
border-radius: 13px;
box-shadow: 0 2px 14px rgba(40,105,160,0.08);
padding: 35px 32px 18px 32px;
}
.header {
display: flex;
justify-content: space-between;
align-items: flex-start;
border-bottom: 2px solid #E6EBF0;
padding-bottom: 13px;
}
.logo-company {
display: flex;
align-items: flex-start;
}
.logo {
height: 50px;
margin-right: 13px;
}
.company-details {
margin-top: 0;
font-size: 15px;
}
.company-title {
font-size: 21px;
font-weight: bold;
margin-bottom: 2px;
}
.company-sub {
font-size: 16px;
/* ── TOP HEADER ── */
.top-header {
margin-bottom: 8px;
font-weight: 500;
border-bottom: 2px solid #1a202c;
padding-bottom: 6px;
}
.invoice-details {
text-align: right;
min-width: 220px;
}
.invoice-title {
font-weight: bold;
font-size: 23px;
letter-spacing: 0.5px;
margin-bottom: 2px;
}
.paid-label {
margin-top: 8px;
margin-bottom: 2px;
}
.paid-tag {
background: #23BF47;
color: #fff;
font-weight: bold;
border-radius: 8px;
padding: 4px 16px 4px 22px;
font-size: 17px;
display: inline-block;
position: relative;
}
.paid-tag:before {
content: '';
position: absolute;
left: 7px;
top: 7px;
width: 10px;
height: 10px;
background: #fff;
border-radius: 50%;
}
.paid-date {
font-size: 14px;
color: #23BF47;
margin-top: 2px;
}
.bill-section {
background: #F3F7FB;
border-radius: 11px;
padding: 20px 18px 13px 18px;
margin: 28px 0 16px 0;
box-shadow: 0 0px 0px #0000;
}
.bill-title {
font-size: 17px;
font-weight: bold;
color: #23355D;
margin-bottom: 4px;
letter-spacing: 0.3px;
}
.bill-details {
font-size: 15px;
line-height: 1.6;
}
table {
.header-table {
width: 100%;
border-collapse: collapse;
margin-top: 9px;
margin-bottom: 13px;
background: #fff;
border-radius: 10px;
overflow: hidden;
}
th {
background: #F6F7F9;
padding: 10px 0;
font-size: 15px;
color: #6781A6;
font-weight: bold;
border: none;
.header-table td {
vertical-align: middle;
padding: 0;
}
/* Logo cell — fixed width, logo fits inside */
.logo-cell {
width: 60px;
}
.logo-cell img {
width: 55px;
height: 55px;
display: block;
}
/* Company name cell */
.title-cell {
padding-left: 10px;
text-align: left;
}
td {
padding: 7px 0;
color: #222;
font-size: 15px;
border: none;
text-align: left;
}
tbody tr:not(:last-child) td {
border-bottom: 1px solid #E6EBF0;
}
tbody tr:last-child td {
border-bottom: none;
}
.totals-row td {
.company-name {
font-size: 18px;
font-weight: bold;
color: #23355D;
color: #1a202c;
text-transform: uppercase;
letter-spacing: 2px;
}
.gst-row td {
font-weight: 500;
color: #23BF47;
}
.total-row td {
.company-subtitle {
font-size: 11px;
font-weight: bold;
font-size: 17px;
color: #222;
color: #2d3748;
letter-spacing: 1px;
}
.payment-info {
margin-top: 24px;
margin-bottom: 9px;
font-size: 15px;
.company-tagline {
font-size: 8px;
color: #718096;
margin-top: 3px;
}
.ref-number {
font-size: 14px;
color: #6781A6;
margin-bottom: 8px;
margin-top: 2px;
/* ── INFO TABLE (Customer | Invoice side by side) ── */
.info-outer {
width: 100%;
border-collapse: collapse;
border: 1px solid #cbd5e0;
margin-bottom: 10px;
}
.footer {
border-top: 1.2px solid #E6EBF0;
margin-top: 25px;
padding-top: 12px;
font-size: 16px;
color: #888;
.info-outer td {
vertical-align: top;
padding: 10px 12px;
}
.info-left { width: 50%; }
.info-right { width: 50%; border-left: 1px solid #cbd5e0; }
.section-title {
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
color: #718096;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 3px;
margin-bottom: 5px;
}
.info-line {
font-size: 9px;
color: #2d3748;
margin-bottom: 2px;
line-height: 1.5;
}
/* ── STATUS BADGE ── */
.badge {
display: inline;
padding: 2px 6px;
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
border-radius: 3px;
}
.badge-paid { background: #c6f6d5; color: #22543d; }
.badge-pending { background: #fef3c7; color: #92400e; }
.badge-paying { background: #dbeafe; color: #1e40af; }
.badge-overdue { background: #fee2e2; color: #991b1b; }
/* ── BILL TO ── */
.bill-to-box {
border: 1px solid #cbd5e0;
padding: 8px 12px;
margin-bottom: 10px;
background: #f7fafc;
}
.bill-to-label {
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
color: #718096;
margin-bottom: 4px;
}
.bill-name { font-size: 12px; font-weight: bold; color: #1a202c; margin-bottom: 2px; }
.bill-sub { font-size: 10px; font-weight: bold; color: #2d3748; margin-bottom: 2px; }
.bill-detail { font-size: 9px; color: #4a5568; line-height: 1.8; }
/* ── SUMMARY GRID — 4 boxes per row ── */
.summary-wrap { margin-bottom: 12px; }
.summary-label {
font-size: 9px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
color: #4a5568;
margin-bottom: 4px;
}
.summary-outer {
width: 100%;
border-collapse: collapse;
}
.sbox {
width: 25%;
border: 1px solid #cbd5e0;
padding: 7px 8px;
text-align: center;
vertical-align: middle;
}
.footer strong {
color: #222;
font-weight: 500;
.sbox-row1 { background: #ffffff; }
.sbox-row2 { background: #f7fafc; }
.sbox-lbl {
font-size: 7px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
color: #718096;
margin-bottom: 3px;
}
.sbox-val {
font-size: 11px;
font-weight: bold;
color: #1a202c;
}
.sbox-sub {
font-size: 7px;
color: #a0aec0;
margin-top: 1px;
}
/* ── CHARGE GROUPS ── */
.cg-header {
font-size: 10px;
font-weight: bold;
color: #ffffff;
background: #2d3748;
padding: 5px 10px;
text-transform: uppercase;
letter-spacing: 1px;
}
.cg-group-wrap {
border: 1px solid #cbd5e0;
margin-bottom: 10px;
}
.cg-sum-table {
width: 100%;
border-collapse: collapse;
font-size: 9px;
}
.cg-sum-table th {
background: #edf2f7;
padding: 5px;
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
color: #4a5568;
border: 1px solid #cbd5e0;
text-align: left;
}
.cg-sum-table td {
padding: 5px;
border: 1px solid #cbd5e0;
color: #2d3748;
font-size: 9px;
}
.cg-item-table {
width: 100%;
border-collapse: collapse;
font-size: 8px;
}
.cg-item-table th {
background: #f0fff4;
padding: 4px 5px;
font-size: 7px;
font-weight: bold;
text-transform: uppercase;
color: #276749;
border: 1px solid #c6f6d5;
text-align: left;
white-space: nowrap;
}
.cg-item-table td {
padding: 4px 5px;
border: 1px solid #e2e8f0;
color: #2d3748;
white-space: nowrap;
}
.row-even { background: #f7fafc; }
/* ── GRAND TOTAL ── */
.grand-outer {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
margin-bottom: 10px;
}
.grand-spacer { width: 60%; }
.grand-inner { width: 40%; vertical-align: top; }
.grand-table { width: 100%; border-collapse: collapse; font-size: 9px; }
.grand-table td { padding: 4px 8px; border: 1px solid #e2e8f0; }
.g-lbl { text-align: right; font-weight: bold; color: #4a5568; background: #f7fafc; }
.g-val { text-align: right; font-weight: bold; color: #1a202c; }
.g-total-lbl { text-align: right; font-size: 11px; font-weight: bold; color: #1a202c; background: #ebf8ff; border-top: 2px solid #2b6cb0; }
.g-total-val { text-align: right; font-size: 11px; font-weight: bold; color: #1a202c; background: #ebf8ff; border-top: 2px solid #2b6cb0; }
/* ── INSTALLMENTS ── */
.install-section { margin-top: 12px; }
.install-header {
font-size: 9px;
font-weight: bold;
color: #fff;
background: #2d3748;
padding: 5px 10px;
text-transform: uppercase;
letter-spacing: 1px;
}
.install-table { width: 100%; border-collapse: collapse; font-size: 9px; }
.install-table th {
background: #edf2f7;
padding: 5px 6px;
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
color: #4a5568;
border: 1px solid #cbd5e0;
}
.install-table td { padding: 4px 6px; border: 1px solid #e2e8f0; color: #2d3748; }
/* ── HELPERS ── */
.tr { text-align: right; }
.tc { text-align: center; }
.bold { font-weight: bold; }
/* ── FOOTER ── */
.footer {
margin-top: 16px;
padding-top: 8px;
border-top: 1px solid #e2e8f0;
font-size: 8px;
color: #718096;
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<!-- Header Section -->
<div class="header">
<div class="logo-company">
<img src="{{ public_path('images/kent_logo2.png') }}" class="logo">
<div class="company-details">
<div class="company-title">{{ $invoice->company_name ?? 'Kent International Pvt. Ltd.' }}</div>
<div class="company-sub"></div>
{{ $invoice->company_address ?? '123 Business Park, Sector 5' }}<br>
{{ $invoice->company_city ?? 'Gurugram, Haryana 122001' }}<br>
{{ $invoice->company_country ?? 'India' }}<br>
GST: {{ $invoice->company_gst ?? 'GST123456789' }}<br>
Email: {{ $invoice->company_email ?? 'billing@kent.com' }}<br>
Phone: {{ $invoice->company_phone ?? '+91 124 123 4567' }}
{{-- ══ VARIABLES ══ --}}
@php
$companyName = $invoice->company_name ?: ($invoice->customer->company_name ?? null);
$custName = $invoice->customer_name;
$custAddress = $invoice->customer_address ?: ($invoice->customer->address ?? null);
$custPincode = $invoice->pincode ?: ($invoice->customer->pincode ?? null);
$custEmail = $invoice->customer_email ?: ($invoice->customer->email ?? null);
$custMobile = $invoice->customer_mobile ?: ($invoice->customer->mobile_no ?? null);
@endphp
{{-- ══ 1) HEADER Logo + Company Name ══ --}}
<div class="top-header">
<table class="header-table">
<tr>
{{-- Logo: 55x55px DomPDF साठी public_path() वापरतो --}}
<td class="logo-cell">
<img src="{{ public_path('images/kent_logo2.png') }}" alt="Logo" width="55" height="55">
</td>
{{-- Company Name + Subtitle + Tagline --}}
<td class="title-cell">
<div class="company-name">KENT</div>
<div class="company-subtitle">International Pvt. Ltd.</div>
<div class="company-tagline">
Address Line 1, City, State Pincode &nbsp;|&nbsp;
Email: info@company.com &nbsp;|&nbsp;
Phone: +91 1234567890
</div>
</td>
</tr>
</table>
</div>
{{-- ══ 2) CUSTOMER | INVOICE ══ --}}
<table class="info-outer">
<tr>
<td class="info-left">
<div class="section-title">Customer Details</div>
@if($companyName)
<div class="info-line bold" style="font-size:11px;">{{ $companyName }}</div>
<div class="info-line">{{ $custName }}</div>
@else
<div class="info-line bold" style="font-size:11px;">{{ $custName }}</div>
@endif
@if($custAddress)<div class="info-line">{{ $custAddress }}</div>@endif
@if($custPincode)<div class="info-line">{{ $custPincode }}</div>@endif
@if($custEmail)<div class="info-line">Email: {{ $custEmail }}</div>@endif
@if($custMobile)<div class="info-line">Phone: {{ $custMobile }}</div>@endif
</td>
<td class="info-right">
<div class="section-title">Invoice Details</div>
<div class="info-line"><strong>Invoice #:</strong> {{ $invoice->invoice_number }}</div>
<div class="info-line"><strong>Date:</strong> {{ \Carbon\Carbon::parse($invoice->invoice_date)->format('d M, Y') }}</div>
<div class="info-line"><strong>Due Date:</strong> {{ \Carbon\Carbon::parse($invoice->due_date)->format('d M, Y') }}</div>
<div class="info-line">
<strong>Status:</strong>
<span class="badge badge-{{ strtolower($invoice->status) }}">{{ strtoupper($invoice->status) }}</span>
</div>
<div class="invoice-details">
<div class="invoice-title">INVOICE</div>
Invoice #: {{ $invoice->invoice_number }}<br>
Issue Date: {{ date('d M Y', strtotime($invoice->invoice_date)) }}<br>
Due Date: {{ date('d M Y', strtotime($invoice->due_date)) }}
@if(strtolower($invoice->status) == 'paid')
<div class="paid-label">
<span class="paid-tag">Paid</span>
</div>
<div class="paid-date">
Paid on: {{ date('d M Y', strtotime($invoice->paid_date ?? now())) }}
</div>
@else
<div class="paid-date" style="color:#d00;">Status: {{ ucfirst($invoice->status) }}</div>
@endif
</div>
</div>
<!-- Bill To Section -->
<div class="bill-section">
<div class="bill-title">Bill To</div>
<div class="bill-details">
<strong>{{ $invoice->customer_name }}</strong><br>
{{ $invoice->customer_address }}<br>
GST: {{ $invoice->customer_gst ?? '-' }}<br>
Email: {{ $invoice->customer_email }}<br>
Phone: {{ $invoice->customer_mobile }}
</div>
</div>
<!-- Items Table -->
<table>
<thead>
<tr>
<th>Description</th>
<th>Qty</th>
<th>Rate ()</th>
<th>Amount ()</th>
</tr>
</thead>
<tbody>
@foreach($invoice->items as $item)
<tr>
<td>{{ $item->description }}</td>
<td>{{ $item->qty }}</td>
<td>{{ number_format($item->price, 0) }}</td>
<td>{{ number_format($item->ttl_amount, 0) }}</td>
</tr>
@endforeach
<tr class="totals-row">
<td colspan="3" style="text-align:right;">Subtotal:</td>
<td>{{ number_format($invoice->subtotal, 0) }}</td>
</tr>
<tr class="gst-row">
<td colspan="3" style="text-align:right;">GST ({{ $invoice->gst_percent }}%):</td>
<td>{{ number_format($invoice->gst_amount, 0) }}</td>
</tr>
<tr class="total-row">
<td colspan="3" style="text-align:right;">Total:</td>
<td>{{ number_format($invoice->final_amount_with_gst, 0) }}</td>
</tr>
</tbody>
</table>
<!-- Payment Info & Reference -->
<div class="payment-info">
<strong>Payment Method:</strong> {{ $invoice->payment_method ?? 'Bank Transfer' }}
</div>
<div class="ref-number">
Reference Number: {{ $invoice->reference_no ?? "REF123456789" }}
</div>
<!-- Footer -->
<div class="footer">
Thank you for your business!<br>
For any queries, please contact us at <strong>{{ $invoice->company_email ?? 'billing@kent.com' }}</strong>
</div>
<div class="info-line"><strong>GST Type:</strong> {{ strtoupper($invoice->tax_type ?? 'GST') }}</div>
<div class="info-line"><strong>GST %:</strong> {{ number_format($invoice->gst_percent ?? 0, 2) }}%</div>
</td>
</tr>
</table>
{{-- ══ 3) BILL TO ══ --}}
<div class="bill-to-box">
<div class="bill-to-label">Bill To</div>
@if($companyName)
<div class="bill-name">{{ $companyName }}</div>
<div class="bill-sub">{{ $custName }}</div>
@else
<div class="bill-name">{{ $custName }}</div>
@endif
<div class="bill-detail">
@if($custAddress){{ $custAddress }}<br>@endif
@if($custPincode){{ $custPincode }}<br>@endif
@if($custEmail)Email: {{ $custEmail }}<br>@endif
@if($custMobile)Phone: {{ $custMobile }}@endif
</div>
</div>
{{-- ══ 4) 8 SUMMARY BOXES ══ --}}
@php
$allGroupItems = $invoice->chargeGroups->flatMap(fn($g) => $g->items);
$invoiceItemIds = $allGroupItems->pluck('invoice_item_id')->unique()->values();
$invoiceItems = $invoice->items->whereIn('id', $invoiceItemIds);
$totalMarkCount = $invoiceItems->pluck('mark_no')->filter()->unique()->count();
$totalCtn = $invoiceItems->sum('ctn');
$totalQty = $invoiceItems->sum('ttl_qty');
$totalCbm = $invoiceItems->sum('ttl_cbm');
$totalKg = $invoiceItems->sum('ttl_kg');
@endphp
<div class="summary-wrap">
<div class="summary-label">Container &amp; Shipment Summary</div>
<table class="summary-outer">
<tr>
<td class="sbox sbox-row1">
<div class="sbox-lbl">Container Name</div>
<div class="sbox-val">{{ $invoice->container->container_name ?? '—' }}</div>
</td>
<td class="sbox sbox-row1">
<div class="sbox-lbl">Container Date</div>
<div class="sbox-val">
@if($invoice->container && $invoice->container->container_date)
{{ \Carbon\Carbon::parse($invoice->container->container_date)->format('d M, Y') }}
@else@endif
</div>
</td>
<td class="sbox sbox-row1">
<div class="sbox-lbl">Container No.</div>
<div class="sbox-val">{{ $invoice->container->container_number ?? '—' }}</div>
</td>
<td class="sbox sbox-row1">
<div class="sbox-lbl">Total Mark No.</div>
<div class="sbox-val">{{ $totalMarkCount }}</div>
<div class="sbox-sub">Unique marks</div>
</td>
</tr>
<tr>
<td class="sbox sbox-row2">
<div class="sbox-lbl">Total CTN</div>
<div class="sbox-val">{{ number_format($totalCtn, 0) }}</div>
<div class="sbox-sub">Cartons</div>
</td>
<td class="sbox sbox-row2">
<div class="sbox-lbl">Total QTY</div>
<div class="sbox-val">{{ number_format($totalQty, 0) }}</div>
<div class="sbox-sub">Pieces</div>
</td>
<td class="sbox sbox-row2">
<div class="sbox-lbl">Total CBM</div>
<div class="sbox-val">{{ number_format($totalCbm, 3) }}</div>
<div class="sbox-sub">Cubic Meter</div>
</td>
<td class="sbox sbox-row2">
<div class="sbox-lbl">Total KG</div>
<div class="sbox-val">{{ number_format($totalKg, 2) }}</div>
<div class="sbox-sub">Kilograms</div>
</td>
</tr>
</table>
</div>
{{-- ══ 5) CHARGE GROUPS ══ --}}
@if($invoice->chargeGroups && $invoice->chargeGroups->count() > 0)
<div style="margin-bottom:14px;">
<div class="cg-header">Charge Groups</div>
@foreach($invoice->chargeGroups as $group)
@php
$groupItemIds = $group->items->pluck('invoice_item_id')->toArray();
$groupInvItems = $invoice->items->whereIn('id', $groupItemIds);
@endphp
<div class="cg-group-wrap">
<table class="cg-sum-table">
<thead>
<tr>
<th style="width:22%;">Group Name</th>
<th style="width:11%;">Basis</th>
<th style="width:11%;">Basis Value</th>
<th style="width:10%;">Rate</th>
<th style="width:13%;" class="tr">Total Charge</th>
<th style="width:8%;">GST %</th>
<th style="width:9%;">Tax Type</th>
<th style="width:16%;" class="tr">Total With GST</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>{{ $group->group_name ?? 'Group '.$group->id }}</strong></td>
<td>{{ strtoupper($group->basis_type) }}</td>
<td>{{ number_format($group->basis_value, 3) }}</td>
<td>{{ number_format($group->rate, 2) }}</td>
<td class="tr">{{ number_format($group->total_charge, 2) }}</td>
<td>{{ number_format($group->gst_percent ?? 0, 2) }}%</td>
<td>{{ strtoupper($group->tax_type ?? 'GST') }}</td>
<td class="tr"><strong>{{ number_format($group->total_with_gst, 2) }}</strong></td>
</tr>
</tbody>
</table>
@if($groupInvItems->count() > 0)
<table class="cg-item-table">
<thead>
<tr>
<th style="width:3%;">#</th>
<th style="width:15%;">Description</th>
<th style="width:7%;">Mark No</th>
<th style="width:5%;" class="tc">QTY</th>
<th style="width:6%;" class="tr">TTL QTY</th>
<th style="width:6%;" class="tr">CBM</th>
<th style="width:6%;" class="tr">TTL CBM</th>
<th style="width:5%;" class="tr">KG</th>
<th style="width:6%;" class="tr">TTL KG</th>
<th style="width:7%;" class="tr">TTL Amt</th>
<th style="width:5%;" class="tr">Rate</th>
<th style="width:7%;" class="tr">Total Charge</th>
<th style="width:5%;" class="tr">GST %</th>
<th style="width:6%;" class="tr">Item GST</th>
<th style="width:6%;" class="tr">Item Total</th>
</tr>
</thead>
<tbody>
@foreach($groupInvItems as $idx => $item)
@php
$groupBasisTotal = $groupInvItems->sum(function($i) use ($group) {
return match($group->basis_type) {
'ttl_qty' => $i->ttl_qty ?? 0,
'ttl_cbm' => $i->ttl_cbm ?? 0,
'ttl_kg' => $i->ttl_kg ?? 0,
'amount' => $i->ttl_amount ?? 0,
default => $i->ttl_amount ?? 0,
};
});
$itemBasisValue = match($group->basis_type) {
'ttl_qty' => $item->ttl_qty ?? 0,
'ttl_cbm' => $item->ttl_cbm ?? 0,
'ttl_kg' => $item->ttl_kg ?? 0,
'amount' => $item->ttl_amount ?? 0,
default => $item->ttl_amount ?? 0,
};
$itemCharge = $groupBasisTotal > 0
? ($itemBasisValue / $groupBasisTotal) * $group->total_charge
: 0;
$gstPct = $group->gst_percent ?? 0;
$itemGst = $itemCharge * $gstPct / 100;
$itemTotal = $itemCharge + $itemGst;
@endphp
<tr class="{{ $idx % 2 === 1 ? 'row-even' : '' }}">
<td class="tc">{{ $idx + 1 }}</td>
<td>{{ $item->description }}</td>
<td>{{ $item->mark_no ?? '—' }}</td>
<td class="tc">{{ $item->qty ?? 0 }}</td>
<td class="tr">{{ number_format($item->ttl_qty ?? 0, 0) }}</td>
<td class="tr">{{ number_format($item->cbm ?? 0, 3) }}</td>
<td class="tr">{{ number_format($item->ttl_cbm ?? 0, 3) }}</td>
<td class="tr">{{ number_format($item->kg ?? 0, 2) }}</td>
<td class="tr">{{ number_format($item->ttl_kg ?? 0, 2) }}</td>
<td class="tr">{{ number_format($item->ttl_amount ?? 0, 2) }}</td>
<td class="tr">{{ number_format($group->rate, 2) }}</td>
<td class="tr">{{ number_format($itemCharge, 2) }}</td>
<td class="tr">{{ number_format($gstPct, 2) }}%</td>
<td class="tr">{{ number_format($itemGst, 2) }}</td>
<td class="tr"><strong>{{ number_format($itemTotal, 2) }}</strong></td>
</tr>
@endforeach
</tbody>
</table>
@endif
</div>
@endforeach
</div>
@endif
{{-- ══ GRAND TOTAL ══ --}}
<table class="grand-outer">
<tr>
<td class="grand-spacer"></td>
<td class="grand-inner">
<table class="grand-table">
<tr>
<td class="g-lbl">Charge Groups Total:</td>
<td class="g-val">{{ number_format($invoice->charge_groups_total ?? 0, 2) }}</td>
</tr>
<tr>
<td class="g-lbl">GST Amount ({{ number_format($invoice->gst_percent ?? 0, 2) }}%):</td>
<td class="g-val">{{ number_format($invoice->gst_amount ?? 0, 2) }}</td>
</tr>
<tr>
<td class="g-total-lbl">Grand Total:</td>
<td class="g-total-val">{{ number_format($invoice->grand_total_with_charges ?? 0, 2) }}</td>
</tr>
</table>
</td>
</tr>
</table>
{{-- ══ INSTALLMENTS ══ --}}
@if($invoice->installments && $invoice->installments->count() > 0)
<div class="install-section">
<div class="install-header">Payment Installments</div>
<table class="install-table">
<thead>
<tr>
<th style="width:6%;" class="tc">#</th>
<th style="width:20%;">Date</th>
<th style="width:25%;">Payment Method</th>
<th style="width:25%;">Reference No</th>
<th style="width:24%;" class="tr">Amount ()</th>
</tr>
</thead>
<tbody>
@foreach($invoice->installments as $idx => $inst)
<tr>
<td class="tc">{{ $idx + 1 }}</td>
<td>{{ \Carbon\Carbon::parse($inst->installment_date)->format('d M, Y') }}</td>
<td>{{ strtoupper($inst->payment_method) }}</td>
<td>{{ $inst->reference_no ?? '—' }}</td>
<td class="tr">{{ number_format($inst->amount, 2) }}</td>
</tr>
@endforeach
</tbody>
</table>
@php
$totalPaid = $invoice->installments->sum('amount');
$grandTotal = $invoice->grand_total_with_charges ?? 0;
$remaining = max(0, $grandTotal - $totalPaid);
@endphp
<table class="grand-outer" style="margin-top:6px;">
<tr>
<td class="grand-spacer"></td>
<td class="grand-inner">
<table class="grand-table">
<tr>
<td class="g-lbl" style="color:#059669;">Total Paid:</td>
<td class="g-val" style="color:#059669;">{{ number_format($totalPaid, 2) }}</td>
</tr>
<tr>
<td class="g-lbl" style="color:#dc2626;">Remaining:</td>
<td class="g-val" style="color:#dc2626;">{{ number_format($remaining, 2) }}</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
@endif
{{-- ══ FOOTER ══ --}}
<div class="footer">
Thank you for your business! &nbsp;|&nbsp; For queries: info@company.com &nbsp;|&nbsp; +91 1234567890
</div>
</body>
</html>
</html>

View File

@@ -95,6 +95,7 @@
.status-paid { background: #dcfce7; color: #166534; }
.status-overdue { background: #fee2e2; color: #991b1b; }
.status-pending { background: #fef3c7; color: #92400e; }
.status-paying { background: #dbeafe; color: #1e40af; }
.status-default { background: #f1f5f9; color: #475569; }
/* ── ID BOXES ── */
@@ -640,6 +641,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>
@@ -663,19 +682,23 @@
</div>
<div>
@if($invoice->status == 'paid')
<span class="status-badge status-paid">
<span class="status-badge status-paid" id="invoiceStatusBadge">
<i class="fas fa-check-circle"></i> Paid
</span>
@elseif($invoice->status == 'overdue')
<span class="status-badge status-overdue">
<span class="status-badge status-overdue" id="invoiceStatusBadge">
<i class="fas fa-exclamation-circle"></i> Overdue
</span>
@elseif($invoice->status == 'paying')
<span class="status-badge status-paying" id="invoiceStatusBadge">
<i class="fas fa-spinner"></i> Paying
</span>
@elseif($invoice->status == 'pending')
<span class="status-badge status-pending">
<span class="status-badge status-pending" id="invoiceStatusBadge">
<i class="fas fa-clock"></i> Pending
</span>
@else
<span class="status-badge status-default">
<span class="status-badge status-default" id="invoiceStatusBadge">
<i class="fas fa-question-circle"></i> {{ ucfirst($invoice->status) }}
</span>
@endif
@@ -753,20 +776,25 @@
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<div class="col-md-6">
<div class="customer-name">{{ $invoice->customer_name }}</div>
@if($invoice->company_name)
<div class="customer-detail"><strong>Company:</strong> {{ $invoice->company_name }}</div>
@endif
<div class="customer-detail"><strong>Mobile:</strong> {{ $invoice->customer_mobile }}</div>
<div class="customer-detail"><strong>Email:</strong> {{ $invoice->customer_email }}</div>
<div class="customer-detail"><strong>Email:</strong>
{{ $invoice->customer_email ?: ($invoice->customer->email ?? '-') }}
</div>
</div>
<div class="col-md-6">
<div class="customer-detail"><strong>Address:</strong></div>
<div class="customer-detail">{{ $invoice->customer_address }}</div>
<div class="customer-detail"><strong>Pincode:</strong> {{ $invoice->pincode }}</div>
<div class="customer-detail">
{{ $invoice->customer_address ?: ($invoice->customer->address ?? '-') }}
</div>
<div class="customer-detail"><strong>Pincode:</strong>
{{ $invoice->pincode ?: ($invoice->customer->pincode ?? '-') }}
</div>
</div>
</div>
</div>
</div>
@@ -776,89 +804,127 @@
@endphp
<div class="panel">
<div class="panel-header">
<i class="fas fa-list"></i> Invoice Items
<div class="panel-header">
<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:120px;">Mark No</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->mark_no }}</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="16" 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 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>
@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>
{{-- 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>
@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
<nav aria-label="Items pagination">
<ul class="pagination mb-0 pagination-sm" id="itemsPages">
{{-- JS will render buttons here --}}
</ul>
</nav>
</div>
<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>
<!-- ACTION BAR -->
<div class="action-bar">
<small>Selected items for charge group: <span id="selectedItemsCount">0</span></small>
@@ -1133,6 +1199,7 @@
<tr>
<th>#</th>
<th>Description</th>
<th class="text-center">Mark No</th>
<th class="text-center">QTY</th>
<th class="text-center">TTL QTY</th>
<th class="text-center">CBM</th>
@@ -1142,7 +1209,6 @@
<th class="text-end">TTL Amount</th>
<th class="text-end">Rate</th>
<th class="text-end">Total Charge</th>
{{-- नवीन GST % कॉलम --}}
<th class="text-end">GST %</th>
<th class="text-end">Item GST</th>
<th class="text-end">Item Total (with GST)</th>
@@ -1182,33 +1248,27 @@
$itemTotalWithGst = $itemTotal + $itemGst;
}
$itemGstPercent = $groupGstPercent ?? 0; // same as group
$itemGstPercent = $groupGstPercent ?? 0;
@endphp
<tr>
<td>{{ $giIndex + 1 }}</td>
<td>{{ $it->description }}</td>
<td class="text-center">{{ $it->mark_no ?? '-' }}</td>
<td class="text-center">{{ $it->qty }}</td>
<td class="text-center">{{ $it->ttl_qty }}</td>
<td class="text-center">{{ $it->cbm }}</td>
<td class="text-center">{{ $it->ttl_cbm }}</td>
<td class="text-center">{{ $it->kg }}</td>
<td class="text-center">{{ $it->ttl_kg }}</td>
<td class="text-end">
{{ number_format($it->ttl_amount, 2) }}
</td>
<td class="text-end">
{{ number_format($rate, 2) }}
</td>
<td class="text-end">{{ number_format($it->ttl_amount, 2) }}</td>
<td class="text-end">{{ number_format($rate, 2) }}</td>
<td class="text-end" style="color:#06b6d4;font-weight:700;">
{{ number_format($itemTotal, 2) }}
</td>
{{-- GST % (per item = group GST %) --}}
<td class="text-end">
{{ number_format($itemGstPercent, 2) }}%
</td>
<td class="text-end" style="color:#ef4444;">
{{ $itemGst > 0 ? number_format($itemGst, 2) : '0.00' }}
</td>
@@ -1231,8 +1291,23 @@
</div>
</div>
@endif
{{-- ===== FINAL SUMMARY (POPUP) ===== --}}
@php
// Base amount (without GST) invoice level
$baseAmount = $invoice->final_amount;
$gstAmount = $invoice->gst_amount;
$groupsWithGst = $invoice->chargeGroups->sum('total_with_gst');
// Installments वरून live calculate (DB extra column नाही)
$summaryTotalPaid = $invoice->totalPaid();
$summaryRemaining = $invoice->remainingAmount();
// ✅ इथे isPaid define कर
$grandTotal = $invoice->grand_total_with_charges ?? 0;
$totalPaid = $invoice->installments->sum('amount') ?? 0;
$isPaid = ($invoice->status === 'paid') || ($grandTotal > 0 && round($totalPaid, 2) >= round($grandTotal, 2));
@endphp
{{-- ===== FINAL SUMMARY (POPUP) ===== --}}
<div class="summary-card-compact mt-3">
<div class="summary-header-compact">
<i class="fas fa-calculator"></i>
@@ -1240,13 +1315,6 @@
</div>
<div class="summary-body-compact">
@php
// Base amount (without GST) invoice level
$baseAmount = $invoice->final_amount; // items + groups base, जर तू तसे ठेवले असेल
$gstAmount = $invoice->gst_amount; // total GST
$groupsWithGst = $invoice->chargeGroups->sum('total_with_gst'); // सर्व charge groups with GST
@endphp
{{-- Total Charge (Before GST) --}}
<div class="summary-row-compact">
<span class="label">Total Charge (Before GST)</span>
@@ -1271,23 +1339,53 @@
</span>
</div>
{{--
<div class="summary-row-compact total">
<span class="label">Total Charge With GST</span>
<span class="value">
{{ number_format($invoice->final_amount_with_gst, 2) }}
{{-- Total Paid (installments sum) --}}
<div class="summary-row-compact">
<span class="label" style="color:#10b981;font-weight:600;">
<i class="fas fa-check-circle me-1" style="font-size:0.8rem;"></i>Total Paid
</span>
<span class="value" id="finalSummaryTotalPaid" style="color:#10b981;">
{{ number_format($summaryTotalPaid, 2) }}
</span>
</div>
{{-- Remaining Balance --}}
<div class="summary-row-compact" style="border-bottom:none;">
<span class="label" id="finalSummaryRemainingLabel"
style="color:{{ $summaryRemaining > 0 ? '#ef4444' : '#10b981' }};font-weight:600;">
<i class="fas fa-{{ $summaryRemaining > 0 ? 'exclamation-circle' : 'check-circle' }} me-1"
style="font-size:0.8rem;"></i>Remaining
</span>
<span class="value" id="finalSummaryRemaining"
style="color:{{ $summaryRemaining > 0 ? '#ef4444' : '#10b981' }};">
{{ number_format($summaryRemaining, 2) }}
</span>
</div>
--}}
</div>
</div>
@if($isPaid)
<div class="popup-download-actions" style="margin-top: 20px; padding-top: 16px; border-top: 2px solid #e5e7eb; text-align: right;">
<a href="{{ route('admin.invoice.download.pdf', $invoice->id) }}"
class="btn btn-primary"
style="display:inline-flex; align-items:center; gap:6px; padding:10px 20px; text-decoration:none; background:#4a5568; color:#fff; border-radius:6px; font-weight:600; margin-right:8px;">
<i class="fas fa-file-pdf"></i>
Download PDF
</a>
<a href="{{ route('admin.invoice.share', $invoice->id) }}"
class="btn btn-success"
style="display:inline-flex; align-items:center; gap:6px; padding:10px 20px; text-decoration:none; background:#059669; color:#fff; border-radius:6px; font-weight:600;">
<i class="fas fa-share-alt"></i>
Share
</a>
</div>
@endif
<!-- ═══════════════════════════ FOOTER ═══════════════════════════ -->
<div class="invoice-footer">
<!-- <div class="invoice-footer">
@if($invoice->pdf_path && $showActions)
<a href="{{ asset($invoice->pdf_path) }}" class="btn-download" download>
<i class="fas fa-download"></i> Download PDF
@@ -1301,7 +1399,7 @@
<p style="margin:0;">For any inquiries, contact us at support@Kent Logistic</p>
</div>
</div>
</div>
</div> -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<script>
function shareInvoice() {
@@ -1393,13 +1491,13 @@ document.addEventListener('DOMContentLoaded', function () {
row.querySelector(`td:nth-child(${n})`)?.textContent.trim() ?? '';
items.push({
description: cellText(3),
qty: cellText(5),
ttlqty: cellText(6),
cbm: cellText(10),
ttlcbm: cellText(11),
kg: cellText(12),
ttlkg: cellText(13),
amount: cellText(9),
qty: cellText(6),
ttlqty: cellText(7),
cbm: cellText(11),
ttlcbm: cellText(12),
kg: cellText(13),
ttlkg: cellText(14),
amount: cellText(10),
});
}
});
@@ -1666,8 +1764,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');
@@ -1682,6 +1779,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>

View File

@@ -5,242 +5,421 @@
@section('content')
<div class="container-fluid px-0 bg-transparent">
<style>
body {
background: linear-gradient(135deg, #f2f6ff, #fefefe);
font-family: "Poppins", sans-serif;
}
/* PROFILE HEADER */
/* ── HEADER ── */
.profile-header {
display: flex;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
gap: 1rem;
padding: 36px 32px;
background: linear-gradient(120deg, #4e73df 68%, #1cc88a 100%);
border-radius: 20px;
gap: 1.5rem;
padding: 32px 36px;
background: linear-gradient(120deg, #4e73df 60%, #1cc88a 100%);
border-radius: 22px;
color: white;
box-shadow: 0 8px 28px rgba(24,40,90,0.09);
margin-bottom: 34px;
box-shadow: 0 10px 36px rgba(78,115,223,0.22);
margin-bottom: 28px;
position: relative;
min-height: 140px;
overflow: hidden;
}
.profile-header::before {
content:"";position:absolute;top:-60px;right:-60px;
width:220px;height:220px;
background:rgba(255,255,255,0.08);border-radius:50%;
}
.profile-header::after {
content: "";
position: absolute;
top: -58px;
right: -85px;
width: 210px;
height: 210px;
background: rgba(255,255,255,0.14);
border-radius: 50%;
}
.avatar {
width: 98px;
height: 98px;
border-radius: 50%;
object-fit: cover;
border: 4px solid rgba(255,255,255,0.7);
box-shadow: 0 5px 20px rgba(0,0,0,0.15);
background: #fff;
z-index: 1;
}
.main-info {
display: flex;
flex-direction: column;
gap: 10px;
z-index: 1;
flex: 1;
}
.main-row {
display: flex;
flex-wrap: wrap;
gap: 25px;
}
.field-group {
min-width: 210px;
}
.label {
font-size: 0.9rem;
color: #dbe7f4;
font-weight: 500;
}
.value {
font-size: 1.1rem;
font-weight: 600;
color: #fff;
background: rgba(255,255,255,0.13);
border-radius: 8px;
padding: 6px 12px;
border: 1.5px solid rgba(255,255,255,0.18);
transition: background 0.13s;
}
.value:hover { background: rgba(255,255,255,0.22); }
/* STATS + CARDS COMMON */
.profile-stats-row, .profile-grid-row {
display: flex;
flex-wrap: wrap;
gap: 18px;
margin-bottom: 20px;
}
.stat-card, .profile-card {
background: #fff;
border-radius: 14px;
box-shadow: 0 3px 18px rgba(60,80,120,0.08);
padding: 20px;
transition: all 0.3s ease;
position: relative;
flex: 1 1 200px;
}
.stat-card:hover, .profile-card:hover {
transform: translateY(-4px);
box-shadow: 0 6px 25px rgba(50,70,120,0.18);
content:"";position:absolute;bottom:-80px;right:180px;
width:180px;height:180px;
background:rgba(255,255,255,0.05);border-radius:50%;
}
/* STATS */
.stat-card::before {
content: "";
position: absolute;
top: -20px;
right: -20px;
width: 90px;
height: 90px;
background: linear-gradient(135deg, #c4d7ff, #e9f0ff);
border-radius: 50%;
opacity: 0.25;
.avatar-initials {
width:100px;height:100px;border-radius:50%;
border:4px solid rgba(255,255,255,0.75);
box-shadow:0 6px 24px rgba(0,0,0,0.18);
background:#fff;z-index:1;flex-shrink:0;
display:flex;align-items:center;justify-content:center;
font-size:2.6rem;font-weight:800;color:#4e73df;
letter-spacing:-1px;
}
.main-info { display:flex;flex-direction:column;gap:14px;z-index:1;flex:1; }
.main-row { display:flex;flex-wrap:wrap;gap:18px;align-items:flex-start; }
.field-group { min-width:160px; }
.ph-label {
font-size:0.72rem;color:rgba(255,255,255,0.65);
font-weight:600;text-transform:uppercase;letter-spacing:0.07em;margin-bottom:3px;
}
.ph-value {
font-size:0.95rem;font-weight:600;color:#fff;
background:rgba(255,255,255,0.12);border-radius:9px;
padding:6px 13px;border:1.5px solid rgba(255,255,255,0.2);
backdrop-filter:blur(4px);
}
.type-badge {
display:inline-block;padding:4px 14px;border-radius:50px;
font-size:0.72rem;font-weight:700;letter-spacing:0.07em;text-transform:uppercase;
}
.type-admin { background:rgba(255,210,60,0.22);color:#ffe066;border:1px solid rgba(255,220,80,0.4); }
.type-staff { background:rgba(100,255,180,0.18);color:#a0ffda;border:1px solid rgba(100,255,180,0.35); }
.status-dot {
display:inline-block;width:9px;height:9px;
border-radius:50%;margin-right:5px;
}
.status-active { background:#22c55e;box-shadow:0 0 5px #22c55e; }
.status-inactive { background:#ef4444;box-shadow:0 0 5px #ef4444; }
/* ── STATS ── */
.profile-stats-row { display:flex;flex-wrap:wrap;gap:16px;margin-bottom:24px; }
.stat-card {
background:#fff;border-radius:16px;
box-shadow:0 3px 18px rgba(60,80,120,0.07);
padding:20px 22px;
transition:all 0.3s ease;
position:relative;
flex:1 1 160px;
overflow:hidden;
}
.stat-card::after {
content:"";position:absolute;
top:-25px;right:-25px;
width:80px;height:80px;
border-radius:50%;
opacity:0.35;
}
/* Per-card accent colors */
.stat-card.sc-blue { border-top:3px solid #4e73df; }
.stat-card.sc-blue::after { background:linear-gradient(135deg,#c4d7ff,#e9f0ff); }
.stat-card.sc-green { border-top:3px solid #1cc88a; }
.stat-card.sc-green::after { background:linear-gradient(135deg,#bbf7d0,#dcfce7); }
.stat-card.sc-yellow { border-top:3px solid #f6c23e; }
.stat-card.sc-yellow::after { background:linear-gradient(135deg,#fef9c3,#fef08a); }
.stat-card.sc-red { border-top:3px solid #e74a3b; }
.stat-card.sc-red::after { background:linear-gradient(135deg,#fee2e2,#fecaca); }
.stat-card.sc-purple { border-top:3px solid #8b5cf6; }
.stat-card.sc-purple::after { background:linear-gradient(135deg,#ede9fe,#ddd6fe); }
.stat-card.sc-teal { border-top:3px solid #0ea5e9; }
.stat-card.sc-teal::after { background:linear-gradient(135deg,#e0f2fe,#bae6fd); }
.stat-card:hover { transform:translateY(-5px);box-shadow:0 10px 30px rgba(78,115,223,0.15); }
.stat-icon-wrap {
width:42px;height:42px;border-radius:11px;
display:flex;align-items:center;justify-content:center;
font-size:1.25rem;margin-bottom:10px;
}
.sc-blue .stat-icon-wrap { background:#eff6ff; }
.sc-green .stat-icon-wrap { background:#f0fdf4; }
.sc-yellow .stat-icon-wrap { background:#fefce8; }
.sc-red .stat-icon-wrap { background:#fef2f2; }
.sc-purple .stat-icon-wrap { background:#faf5ff; }
.sc-teal .stat-icon-wrap { background:#f0f9ff; }
.stat-label {
font-size: 0.9rem;
color: #8b96ad;
font-weight: 500;
font-size:0.78rem;color:#8b96ad;font-weight:600;
text-transform:uppercase;letter-spacing:0.06em;
margin-bottom:4px;display:block;
}
.stat-value {
font-size: 1.6rem;
font-weight: 700;
display: flex;
align-items: center;
color: #1b2330;
font-size:1.7rem;font-weight:800;color:#1b2330;
line-height:1;margin-bottom:6px;
}
.stat-footer { font-size: 0.9rem; }
.positive { color: #23a25d; }
.negative { color: #e54141; }
.neutral { color: #9a9ea8; }
.stat-footer { font-size:0.78rem;color:#94a3b8; }
.positive { color:#16a34a; }
.negative { color:#dc2626; }
/* PROFILE CARDS */
/* ── PROFESSIONAL INFO ── */
.pro-section { margin-bottom:26px; }
.pro-section-title {
font-size:0.88rem;font-weight:700;color:#1b2330;
border-left:4px solid #4e73df;padding-left:9px;margin-bottom:14px;
display:flex;align-items:center;gap:7px;
}
.pro-grid { display:flex;flex-wrap:wrap;gap:14px; }
.pro-box {
background:#fff;border-radius:14px;
padding:16px 20px;
box-shadow:0 2px 14px rgba(60,80,120,0.07);
border:1px solid #e8edf5;
transition:all 0.25s cubic-bezier(.4,0,.2,1);
flex:1 1 160px;min-width:0;
position:relative;overflow:hidden;
}
.pro-box::before {
content:"";
position:absolute;top:0;left:0;right:0;height:3px;
background:linear-gradient(90deg,#4e73df,#1cc88a);
opacity:0;transition:opacity 0.25s;
}
.pro-box:hover { transform:translateY(-4px);box-shadow:0 8px 28px rgba(78,115,223,0.15);border-color:#c7d4f8; }
.pro-box:hover::before { opacity:1; }
.box-icon { font-size:1.3rem;margin-bottom:7px;display:inline-block; }
.box-label { font-size:0.68rem;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:4px; }
.box-value { font-size:0.95rem;font-weight:700;color:#1b2330;white-space:nowrap;overflow:hidden;text-overflow:ellipsis; }
/* ── BOTTOM CARDS ── */
.profile-grid-row { display:flex;flex-wrap:wrap;gap:18px;margin-bottom:10px; }
.profile-card {
background:#fff;border-radius:16px;
box-shadow:0 3px 18px rgba(60,80,120,0.07);
padding:22px;transition:all 0.3s ease;flex:1 1 220px;
}
.profile-card:hover { transform:translateY(-4px);box-shadow:0 8px 28px rgba(78,115,223,0.14); }
.profile-card h6 {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 12px;
color: #1b2330;
border-left: 4px solid #3767f4;
padding-left: 8px;
font-size:1rem;font-weight:700;margin-bottom:14px;color:#1b2330;
border-left:4px solid #4e73df;padding-left:9px;
}
.activity-list, .quick-action-list {
list-style: none;
padding: 0;
margin: 0;
.activity-list,.quick-action-list { list-style:none;padding:0;margin:0; }
.activity-list li,.quick-action-list li {
padding:9px 6px;font-size:0.92rem;border-bottom:1px solid #f2f4f8;
display:flex;align-items:center;gap:8px;transition:0.2s ease;border-radius:6px;
}
.activity-list li, .quick-action-list li {
padding: 9px 2px;
font-size: 1rem;
border-bottom: 1px solid #f2f4f8;
display: flex;
align-items: center;
gap: 8px;
transition: 0.2s ease;
}
.activity-list li:hover { background: #f7faff; border-radius: 6px; }
.activity-list li:hover { background:#f4f7ff;padding-left:10px; }
.quick-action-list li a {
text-decoration: none;
color: #2563eb;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
transition: 0.2s ease;
text-decoration:none;color:#2563eb;font-weight:600;
display:flex;align-items:center;gap:8px;transition:0.2s ease;width:100%;
}
.quick-action-list li a:hover { color: #1e40af; transform: translateX(4px); }
.quick-action-list li a:hover { color:#1e40af;transform:translateX(5px); }
@media (max-width: 1000px) {
.profile-header, .profile-stats-row, .profile-grid-row {
flex-direction: column;
text-align: center;
}
.avatar { margin: 0 auto 15px; }
.main-row { justify-content: center; }
@media (max-width:900px) {
.profile-header,.profile-stats-row,.profile-grid-row { flex-direction:column; }
.avatar-initials { margin:0 auto; }
.main-row { justify-content:center; }
.pro-grid { flex-direction:column; }
.pro-box { flex:unset;width:100%; }
}
</style>
<!-- PROFILE HEADER -->
@php
$user = $user ?? Auth::guard('admin')->user();
$isAdmin = $user->type === 'admin';
$initials = strtoupper(substr($user->name ?? 'U', 0, 1));
$joiningFormatted = $user->joining_date
? \Carbon\Carbon::parse($user->joining_date)->format('d M Y')
: '—';
$isActive = ($user->status ?? 'active') === 'active';
// stats — controller मधून येतात, fallback 0
$s = $stats ?? [];
$totalContainers = $s['total_containers'] ?? 0;
$totalInvoices = $s['total_invoices'] ?? 0;
$paidInvoices = $s['paid_invoices'] ?? 0;
$pendingInvoices = $s['pending_invoices'] ?? 0;
$totalCustomers = $s['total_customers'] ?? 0;
$totalMarklist = $s['total_marklist'] ?? 0;
$activeMarklist = $s['active_marklist'] ?? 0;
@endphp
{{-- ════════ PROFILE HEADER ════════ --}}
<div class="profile-header">
<img src="https://i.pravatar.cc/100" class="avatar" alt="Avatar" />
<div class="avatar-initials">{{ $initials }}</div>
<div class="main-info">
{{-- Row 1 --}}
<div class="main-row">
<div class="field-group">
<div class="label">Name</div>
<div class="value">John Doe</div>
<div class="ph-label">Full Name</div>
<div class="ph-value" style="font-size:1.05rem;">{{ $user->name ?? '—' }}</div>
</div>
<div class="field-group">
<div class="label">Email</div>
<div class="value">john@example.com</div>
<div class="ph-label">Account Type</div>
<div class="ph-value" style="padding:4px 10px;">
<span class="type-badge {{ $isAdmin ? 'type-admin' : 'type-staff' }}">
{{ $isAdmin ? '⭐ Admin' : '👤 Staff' }}
</span>
</div>
</div>
<div class="field-group">
<div class="label">Mobile No</div>
<div class="value">+91 12345 54321</div>
<div class="ph-label">Status</div>
<div class="ph-value">
<span class="status-dot {{ $isActive ? 'status-active' : 'status-inactive' }}"></span>
{{ ucfirst($user->status ?? 'active') }}
</div>
</div>
@if($user->employee_id)
<div class="field-group">
<div class="ph-label">Employee ID</div>
<div class="ph-value" style="font-family:monospace;letter-spacing:0.05em;">{{ $user->employee_id }}</div>
</div>
@endif
</div>
{{-- Row 2 --}}
<div class="main-row">
<div class="field-group">
<div class="label">Company Name</div>
<div class="value">XYZ Infotech</div>
<div class="ph-label">Email</div>
<div class="ph-value" style="font-size:0.88rem;">{{ $user->email ?? '—' }}</div>
</div>
<div class="field-group" style="min-width:260px;">
<div class="label">Company Address</div>
<div class="value">XYZ Infotech, East Andheri, Mumbai 400 001</div>
<div class="field-group">
<div class="ph-label">Phone</div>
<div class="ph-value">{{ $user->phone ?? '—' }}</div>
</div>
@if($user->department)
<div class="field-group">
<div class="ph-label">Department</div>
<div class="ph-value">{{ $user->department }}</div>
</div>
@endif
@if($user->designation)
<div class="field-group">
<div class="ph-label">Designation</div>
<div class="ph-value">{{ $user->designation }}</div>
</div>
@endif
@if($user->address)
<div class="field-group" style="min-width:220px;">
<div class="ph-label">Address</div>
<div class="ph-value" style="font-size:0.88rem;">{{ $user->address }}</div>
</div>
@endif
</div>
</div>
</div>
<!-- STATS ROW -->
{{-- ════════ PROFESSIONAL INFO ════════ --}}
<div class="pro-section">
<div class="pro-section-title">🏢 Professional Information</div>
<div class="pro-grid">
@if($user->employee_id)
<div class="pro-box">
<div class="box-icon">🪪</div>
<div class="box-label">Employee ID</div>
<div class="box-value" style="font-family:monospace;">{{ $user->employee_id }}</div>
</div>
@endif
@if($user->username)
<div class="pro-box">
<div class="box-icon">👤</div>
<div class="box-label">Username</div>
<div class="box-value">{{ $user->username }}</div>
</div>
@endif
@if($user->role)
<div class="pro-box">
<div class="box-icon">🎯</div>
<div class="box-label">Role</div>
<div class="box-value">{{ $user->role }}</div>
</div>
@endif
@if($user->department)
<div class="pro-box">
<div class="box-icon">🏬</div>
<div class="box-label">Department</div>
<div class="box-value">{{ $user->department }}</div>
</div>
@endif
@if($user->designation)
<div class="pro-box">
<div class="box-icon">📋</div>
<div class="box-label">Designation</div>
<div class="box-value">{{ $user->designation }}</div>
</div>
@endif
<div class="pro-box">
<div class="box-icon">📅</div>
<div class="box-label">Joining Date</div>
<div class="box-value">{{ $joiningFormatted }}</div>
</div>
@if($user->emergency_phone)
<div class="pro-box">
<div class="box-icon">🆘</div>
<div class="box-label">Emergency Phone</div>
<div class="box-value">{{ $user->emergency_phone }}</div>
</div>
@endif
@if($user->additional_info)
<div class="pro-box" style="flex:2 1 320px;">
<div class="box-icon">📝</div>
<div class="box-label">Additional Info</div>
<div class="box-value" style="font-size:0.88rem;font-weight:500;color:#475569;white-space:normal;">{{ $user->additional_info }}</div>
</div>
@endif
</div>
</div>
{{-- ════════ REAL STATS ════════ --}}
<div class="profile-stats-row">
<div class="stat-card">
<span class="stat-label">Total Shipment</span>
<span class="stat-value">📦 1,250</span>
<div class="stat-footer positive">+12% vs last month</div>
<div class="stat-card sc-blue">
<div class="stat-icon-wrap">📦</div>
<span class="stat-label">Total Containers</span>
<div class="stat-value">{{ number_format($totalContainers) }}</div>
<div class="stat-footer">All containers in system</div>
</div>
<div class="stat-card">
<span class="stat-label">In Transit</span>
<span class="stat-value" style="color:#2563eb;">🚚 320</span>
<div class="stat-footer neutral">65% of capacity</div>
<div class="stat-card sc-green">
<div class="stat-icon-wrap">🧾</div>
<span class="stat-label">Total Invoices</span>
<div class="stat-value">{{ number_format($totalInvoices) }}</div>
<div class="stat-footer positive">
{{ $paidInvoices }} paid
</div>
</div>
<div class="stat-card">
<span class="stat-label">At Customs</span>
<span class="stat-value" style="color:#f59e0b;">🛄 85</span>
<div class="stat-footer neutral">Avg 2.3 days processing</div>
<div class="stat-card sc-yellow">
<div class="stat-icon-wrap"></div>
<span class="stat-label">Pending Invoices</span>
<div class="stat-value" style="color:#d97706;">{{ number_format($pendingInvoices) }}</div>
<div class="stat-footer">Awaiting payment</div>
</div>
<div class="stat-card">
<span class="stat-label">Delayed</span>
<span class="stat-value" style="color:#e11d48;"> 55</span>
<div class="stat-footer negative">-8% vs last month</div>
<div class="stat-card sc-teal">
<div class="stat-icon-wrap"></div>
<span class="stat-label">Paid Invoices</span>
<div class="stat-value" style="color:#0ea5e9;">{{ number_format($paidInvoices) }}</div>
<div class="stat-footer positive">
@if($totalInvoices > 0)
{{ round(($paidInvoices / $totalInvoices) * 100, 1) }}% success rate
@else
No invoices yet
@endif
</div>
</div>
<div class="stat-card">
<span class="stat-label">Delivered</span>
<span class="stat-value" style="color:#22c55e;"> 780</span>
<div class="stat-footer positive">98.2% success rate</div>
<div class="stat-card sc-purple">
<div class="stat-icon-wrap">👥</div>
<span class="stat-label">Total Customers</span>
<div class="stat-value" style="color:#8b5cf6;">{{ number_format($totalCustomers) }}</div>
<div class="stat-footer">Registered customers</div>
</div>
<div class="stat-card sc-red">
<div class="stat-icon-wrap">🏷️</div>
<span class="stat-label">Mark List</span>
<div class="stat-value" style="color:#e74a3b;">{{ number_format($totalMarklist) }}</div>
<div class="stat-footer positive">
{{ $activeMarklist }} active
</div>
</div>
</div>
<!-- BOTTOM CARDS -->
{{-- ════════ BOTTOM CARDS ════════ --}}
<div class="profile-grid-row">
<div class="profile-card">
<h6>Recent Activity</h6>
<ul class="activity-list">
<li>📦 Shipment #SH-2024-001 delivered <span style="color:#a1a5ad;font-size:0.9em;margin-left:8px;">5 min ago</span></li>
<li>📝 New shipment #SH-2024-002 <span style="color:#a1a5ad;font-size:0.9em;margin-left:8px;">13 min ago</span></li>
<li> Delay for #SH-2024-003 <span style="color:#a1a5ad;font-size:0.9em;margin-left:8px;">1 hr ago</span></li>
<li>📦 Shipment #SH-2024-001 delivered <span style="color:#a1a5ad;font-size:0.85em;margin-left:auto;">5 min ago</span></li>
<li>📝 New shipment #SH-2024-002 created <span style="color:#a1a5ad;font-size:0.85em;margin-left:auto;">13 min ago</span></li>
<li> Delay reported for #SH-2024-003 <span style="color:#a1a5ad;font-size:0.85em;margin-left:auto;">1 hr ago</span></li>
</ul>
</div>
<div class="profile-card">
@@ -253,5 +432,6 @@
</ul>
</div>
</div>
</div>
@endsection
@endsection

File diff suppressed because it is too large Load Diff

View File

@@ -71,7 +71,19 @@ Route::prefix('admin')
Route::get('/dashboard', [AdminOrderController::class, 'dashboard'])
->name('admin.dashboard');
Route::get('/reports', [AdminReportController::class, 'index'])->name('admin.reports');
Route::get('/reports/containers/excel', [AdminReportController::class, 'containerReportExcel'])
->name('admin.reports.containers.excel');
Route::get('/reports/containers/pdf', [AdminReportController::class, 'containerReportPdf'])
->name('admin.reports.containers.pdf');
//reports DOWNLOAD
Route::get('/reports', [AdminReportController::class, 'containerReport'])
->name('admin.reports');
Route::get('/chat-support', fn() => view('admin.chat_support'))
->name('admin.chat_support');
@@ -82,8 +94,7 @@ Route::prefix('admin')
Route::get('/account', fn() => view('admin.account'))
->name('admin.account');
Route::get('/profile', fn() => view('admin.profile'))
->name('admin.profile');
Route::get('/profile', [AdminAuthController::class, 'profile'])->name('admin.profile');
Route::post(
'admin/orders/upload-excel-preview',
@@ -377,4 +388,17 @@ Route::get('/admin/invoices/{invoice}/download', [AdminInvoiceController::class,
Route::get('/admin/containers/{container}/popup', [\App\Http\Controllers\ContainerController::class, 'popupPopup'])
->name('containers.popup');
//Invoice pdf download route
Route::get('invoice/{id}/download-pdf', [AdminInvoiceController::class, 'downloadPdf'])
->name('admin.invoice.download.pdf');
// Route::get('invoice/{id}/download-excel', [AdminInvoiceController::class, 'downloadExcel'])
// ->name('admin.invoice.download.excel');
Route::get('invoice/{id}/share', [AdminInvoiceController::class, 'share'])
->name('admin.invoice.share');