Compare commits

..

10 Commits

Author SHA256 Message Date
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
Abhishek Mali
43b1a64911 ajax update 2026-03-12 11:48:42 +05:30
Utkarsh Khedkar
ff4c006ca4 Gst Updates 2026-03-11 20:02:43 +05:30
42 changed files with 2264 additions and 1471 deletions

View File

@@ -1,6 +1,6 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_KEY=base64:/ulhRgiCOFjZV6xUDkXLfiR9X8iFRZ4QIiX3UJbdwY4=
APP_DEBUG=true
APP_URL=http://localhost

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
{
@@ -35,12 +39,11 @@ class AdminAuthController extends Controller
'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

@@ -8,10 +8,8 @@ use App\Models\InvoiceItem;
use App\Models\InvoiceInstallment;
use App\Models\InvoiceChargeGroup;
use App\Models\InvoiceChargeGroupItem;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Mpdf\Mpdf;
class AdminInvoiceController extends Controller
@@ -21,7 +19,6 @@ class AdminInvoiceController extends Controller
// -------------------------------------------------------------
public function index(Request $request)
{
// Container relation सह invoices load करतो
$query = Invoice::with(['items', 'customer', 'container']);
if ($request->filled('search')) {
@@ -50,7 +47,7 @@ class AdminInvoiceController extends Controller
}
// -------------------------------------------------------------
// POPUP VIEW + CUSTOMER DATA SYNC
// POPUP VIEW
// -------------------------------------------------------------
public function popup($id)
{
@@ -59,16 +56,8 @@ 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;
// आधीच group मध्ये असलेले item ids
$groupedItemIds = $invoice->chargeGroups
->flatMap(fn($group) => $group->items->pluck('invoice_item_id'))
->unique()
@@ -88,8 +77,31 @@ class AdminInvoiceController extends Controller
'customer',
'container',
'chargeGroups.items',
'installments',
])->findOrFail($id);
// ✅ Customer details sync — जर test data आला असेल तर fix होईल
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
@@ -104,7 +116,7 @@ class AdminInvoiceController extends Controller
}
// -------------------------------------------------------------
// UPDATE INVOICE (HEADER LEVEL)
// UPDATE INVOICE (HEADER ONLY)
// -------------------------------------------------------------
public function update(Request $request, $id)
{
@@ -118,76 +130,31 @@ class AdminInvoiceController extends Controller
$data = $request->validate([
'invoice_date' => 'required|date',
'due_date' => 'required|date|after_or_equal:invoice_date',
'final_amount' => 'required|numeric|min:0',
'tax_type' => 'required|in:gst,igst',
'tax_percent' => 'required|numeric|min:0|max:28',
'status' => 'required|in:pending,paid,overdue',
'status' => 'required|in:pending,paying,paid,overdue',
'notes' => 'nullable|string',
]);
Log::info('✅ Validated Invoice Update Data', $data);
$finalAmount = (float) $data['final_amount'];
$taxPercent = (float) $data['tax_percent'];
if ($data['tax_type'] === 'gst') {
Log::info('🟢 GST Selected', compact('taxPercent'));
$data['cgst_percent'] = $taxPercent / 2;
$data['sgst_percent'] = $taxPercent / 2;
$data['igst_percent'] = 0;
} else {
Log::info('🔵 IGST Selected', compact('taxPercent'));
$data['cgst_percent'] = 0;
$data['sgst_percent'] = 0;
$data['igst_percent'] = $taxPercent;
}
$gstAmount = ($finalAmount * $taxPercent) / 100;
$data['gst_amount'] = $gstAmount;
$data['final_amount_with_gst'] = $finalAmount + $gstAmount;
$data['gst_percent'] = $taxPercent;
Log::info('📌 Final Calculated Invoice Values', [
'invoice_id' => $invoice->id,
'final_amount' => $finalAmount,
'gst_amount' => $data['gst_amount'],
'final_amount_with_gst' => $data['final_amount_with_gst'],
'tax_type' => $data['tax_type'],
'cgst_percent' => $data['cgst_percent'],
'sgst_percent' => $data['sgst_percent'],
'igst_percent' => $data['igst_percent'],
]);
Log::info('✅ Validated Invoice Header Update Data', $data);
$invoice->update($data);
Log::info('✅ Invoice Updated Successfully', [
'invoice_id' => $invoice->id,
]);
$invoice->refresh();
Log::info('🔍 Invoice AFTER UPDATE (DB values)', [
Log::info('🔍 Invoice AFTER HEADER UPDATE', [
'invoice_id' => $invoice->id,
'final_amount' => $invoice->final_amount,
'gst_percent' => $invoice->gst_percent,
'charge_groups_total' => $invoice->charge_groups_total,
'gst_amount' => $invoice->gst_amount,
'final_amount_with_gst' => $invoice->final_amount_with_gst,
'tax_type' => $invoice->tax_type,
'cgst_percent' => $invoice->cgst_percent,
'sgst_percent' => $invoice->sgst_percent,
'igst_percent' => $invoice->igst_percent,
'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.');
}
// -------------------------------------------------------------
// UPDATE INVOICE ITEMS
// UPDATE INVOICE ITEMS (फक्त items save)
// -------------------------------------------------------------
public function updateItems(Request $request, Invoice $invoice)
{
@@ -202,9 +169,7 @@ class AdminInvoiceController extends Controller
'items.*.ttl_amount' => ['required', 'numeric', 'min:0'],
]);
$itemsInput = $data['items'];
foreach ($itemsInput as $itemId => $itemData) {
foreach ($data['items'] as $itemId => $itemData) {
$item = InvoiceItem::where('id', $itemId)
->where('invoice_id', $invoice->id)
->first();
@@ -222,47 +187,8 @@ class AdminInvoiceController extends Controller
$item->save();
}
$newBaseAmount = InvoiceItem::where('invoice_id', $invoice->id)
->sum('ttl_amount');
$taxType = $invoice->tax_type;
$cgstPercent = (float) ($invoice->cgst_percent ?? 0);
$sgstPercent = (float) ($invoice->sgst_percent ?? 0);
$igstPercent = (float) ($invoice->igst_percent ?? 0);
$gstPercent = 0;
if ($taxType === 'gst') {
$gstPercent = $cgstPercent + $sgstPercent;
} elseif ($taxType === 'igst') {
$gstPercent = $igstPercent;
}
$gstAmount = $newBaseAmount * $gstPercent / 100;
$finalWithGst = $newBaseAmount + $gstAmount;
$invoice->final_amount = $newBaseAmount;
$invoice->gst_amount = $gstAmount;
$invoice->final_amount_with_gst = $finalWithGst;
$invoice->gst_percent = $gstPercent;
$invoice->save();
// ⭐ Total Charges (groups समावेत) पुन्हा कॅलक्युलेट
$chargeGroupsTotal = $invoice->chargeGroups()->sum('total_charge');
$invoice->charge_groups_total = $chargeGroupsTotal;
$invoice->grand_total_with_charges = $invoice->final_amount_with_gst + $chargeGroupsTotal;
$invoice->save();
Log::info('✅ Invoice items updated & totals recalculated', [
Log::info('✅ Invoice items updated (no totals recalculation)', [
'invoice_id' => $invoice->id,
'final_amount' => $invoice->final_amount,
'gst_amount' => $invoice->gst_amount,
'final_amount_with_gst' => $invoice->final_amount_with_gst,
'tax_type' => $invoice->tax_type,
'cgst_percent' => $invoice->cgst_percent,
'sgst_percent' => $invoice->sgst_percent,
'igst_percent' => $invoice->igst_percent,
'charge_groups_total' => $invoice->charge_groups_total,
'grand_total_with_charges' => $invoice->grand_total_with_charges,
]);
return back()->with('success', 'Invoice items updated successfully.');
@@ -319,10 +245,10 @@ class AdminInvoiceController extends Controller
]);
$invoice = Invoice::findOrFail($invoice_id);
$paidTotal = $invoice->installments()->sum('amount');
// 👇 Total Charges (grand_total_with_charges) वरून remaining
$grandTotal = $invoice->grand_total_with_charges;
$grandTotal = $invoice->grand_total_with_charges ?? 0;
$paidTotal = $invoice->installments()->sum('amount');
$remaining = $grandTotal - $paidTotal;
if ($request->amount > $remaining) {
@@ -341,23 +267,38 @@ class AdminInvoiceController extends Controller
]);
$newPaid = $paidTotal + $request->amount;
$remaining = max(0, $grandTotal - $newPaid);
if ($newPaid >= $invoice->final_amount_with_gst) {
$invoice->update(['status' => 'paid']);
}
// Full payment logic (जर पूर्ण भरले तर status paid करणे, नाहीतर pending राहील)
// if ($newPaid >= $grandTotal && $grandTotal > 0) {
// $invoice->update([
// 'payment_method' => $request->payment_method,
// 'reference_no' => $request->reference_no,
// 'status' => ($newPaid >= $grandTotal && $grandTotal > 0) ? 'paid' : $invoice->status,
// ]);
// }
// Partial payment status logic:
$invoice->update([
'payment_method' => $request->payment_method,
'reference_no' => $request->reference_no,
'status' => ($newPaid >= $grandTotal && $grandTotal > 0) ? 'paid' : $invoice->status,
]);
return response()->json([
'status' => 'success',
'message' => 'Installment added successfully.',
'installment' => $installment,
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
'gstAmount' => $invoice->gst_amount ?? 0,
'grandTotal' => $grandTotal,
'totalPaid' => $newPaid,
'gstAmount' => $invoice->gst_amount,
'finalAmountWithGst' => $grandTotal, // इथे grand total पाठव
'baseAmount' => $invoice->final_amount,
'remaining' => max(0, $grandTotal - $newPaid),
'isCompleted' => $newPaid >= $grandTotal,
'remaining' => $remaining,
'isCompleted' => $remaining <= 0,
'isZero' => $newPaid == 0,
]);
}
// -------------------------------------------------------------
@@ -369,87 +310,158 @@ class AdminInvoiceController extends Controller
$invoice = $installment->invoice;
$installment->delete();
$invoice->refresh();
$grandTotal = $invoice->grand_total_with_charges ?? 0;
$paidTotal = $invoice->installments()->sum('amount');
$grandTotal = $invoice->grand_total_with_charges;
$remaining = $grandTotal - $paidTotal;
$remaining = max(0, $grandTotal - $paidTotal);
if ($paidTotal <= 0 && $grandTotal > 0) {
$invoice->update(['status' => 'pending']);
} elseif ($paidTotal > 0 && $paidTotal < $grandTotal) {
$invoice->update(['status' => 'pending']);
}
return response()->json([
'status' => 'success',
'message' => 'Installment deleted.',
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
'gstAmount' => $invoice->gst_amount ?? 0,
'grandTotal' => $grandTotal,
'totalPaid' => $paidTotal,
'gstAmount' => $invoice->gst_amount,
'finalAmountWithGst' => $grandTotal, // इथेही
'baseAmount' => $invoice->final_amount,
'remaining' => $remaining,
'isZero' => $paidTotal == 0,
]);
}
// -------------------------------------------------------------
// CHARGE GROUP SAVE (no AJAX branch)
// CHARGE GROUP SAVE
// -------------------------------------------------------------
public function storeChargeGroup(Request $request, $invoiceId)
{
$invoice = Invoice::with('items')->findOrFail($invoiceId);
$data = $request->validate([
'group_name' => 'required|string|max:255',
'basis_type' => 'required|in:ttl_qty,amount,ttl_cbm,ttl_kg',
'basis_value' => 'required|numeric',
'rate' => 'required|numeric|min:0.0001',
'auto_total' => 'required|numeric|min:0.01',
'item_ids' => 'required|array',
'item_ids.*' => 'integer|exists:invoice_items,id',
Log::info('🟡 storeChargeGroup HIT', [
'invoice_id' => $invoiceId,
'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',
'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['group_name'])
->where('group_name', $data['groupname'])
->exists();
if ($exists) {
return back()
->withErrors(['group_name' => 'This group name is already used for this invoice.'])
->withErrors(['groupname' => 'This group name is already used for this invoice.'])
->withInput();
}
$taxType = $data['tax_type'] ?? 'gst';
$gstPercent = $data['gst_percent'] ?? 0;
$baseTotal = $data['autototal'];
$totalWithGst = $data['total_with_gst'] ?? $baseTotal;
if ($totalWithGst == 0 && $gstPercent > 0) {
$gstAmount = ($baseTotal * $gstPercent) / 100;
$totalWithGst = $baseTotal + $gstAmount;
}
// 1) Group create
$group = InvoiceChargeGroup::create([
'invoice_id' => $invoice->id,
'group_name' => $data['group_name'],
'basis_type' => $data['basis_type'],
'basis_value' => $data['basis_value'],
'group_name' => $data['groupname'],
'basis_type' => $data['basistype'],
'basis_value' => $data['basisvalue'],
'rate' => $data['rate'],
'total_charge' => $data['auto_total'],
'total_charge' => $baseTotal,
'tax_type' => $taxType,
'gst_percent' => $gstPercent,
'total_with_gst' => $totalWithGst,
]);
foreach ($data['item_ids'] as $itemId) {
// 2) Items link
foreach ($data['itemids'] as $itemId) {
InvoiceChargeGroupItem::create([
'group_id' => $group->id,
'invoice_item_id' => $itemId,
]);
}
// ⭐ Charge groups नुसार Total Charges सेट करा
$chargeGroupsTotal = $invoice->chargeGroups()->sum('total_charge');
$grandTotal = $invoice->final_amount_with_gst + $chargeGroupsTotal;
// 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
$invoiceGstPercent = $group->gst_percent ?? 0;
$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' => $chargeGroupsTotal,
'grand_total_with_charges' => $grandTotal,
'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,
]);
return redirect()
->back()
->with('success', 'Charge group saved successfully.');
}
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,
'grand_total_with_charges'=> $invoice->grand_total_with_charges,
]);
public function download(Invoice $invoice)
{
if (!$invoice->pdf_path || !Storage::exists($invoice->pdf_path)) {
return back()->with('error', 'PDF not found.');
}
return Storage::download($invoice->pdf_path, $invoice->invoice_number . '.pdf');
return response()->json([
'success' => true,
'message' => 'Charge group saved successfully.',
'group_id' => $group->id,
]);
}
}

View File

@@ -28,11 +28,23 @@ class AdminOrderController extends Controller
* ---------------------------*/
public function dashboard()
{
// ── Order counts (from Invoice Management / Orders table) ──
$totalOrders = Order::count();
$pendingOrders = Order::where('status', 'pending')->count();
$totalShipments = Shipment::count();
$totalItems = OrderItem::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'
));
}
/* ---------------------------

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;
}
}
@@ -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);
@@ -562,6 +570,7 @@ class ContainerController extends Controller
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 +584,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,7 +603,8 @@ 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;
@@ -613,7 +625,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 +651,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 +668,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 +687,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 +722,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 +733,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 +769,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 +896,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,13 +907,10 @@ class ContainerController extends Controller
return Storage::download($path, $fileName);
}
public function popupPopup(Container $container)
{
// existing show सारखाच data वापरू
$container->load('rows');
// summary आधीपासून index() मध्ये जसा काढतोस तसाच logic reuse
$rows = $container->rows ?? collect();
$totalCtn = 0;
@@ -939,5 +972,4 @@ class ContainerController extends Controller
'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();
$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,18 +99,26 @@ 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) {
->get();
// Extract unique containers
$containers = $invoices->pluck('container')
->filter()
->unique('id')
->values();
$orders = $containers->map(function ($container) {
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,
'order_id' => $container->id,
'container_number' => $container->container_number,
'status' => $container->status,
'container_date' => $container->container_date,
'created_at' => $container->created_at,
];
});
@@ -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

@@ -31,6 +31,13 @@ class Invoice extends Model
'pincode',
'pdf_path',
'notes',
// totals from charge groups
'charge_groups_total',
'grand_total_with_charges',
'tax_type',
'cgst_percent',
'sgst_percent',
'igst_percent',
];
/****************************
@@ -42,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()
{
@@ -57,16 +64,16 @@ class Invoice extends Model
return $this->hasMany(InvoiceInstallment::class);
}
// ✅ SINGLE, correct relation
public function chargeGroups()
{
return $this->hasMany(\App\Models\InvoiceChargeGroup::class, 'invoice_id');
return $this->hasMany(InvoiceChargeGroup::class, 'invoice_id');
}
/****************************
* Helper Functions
****************************/
// (Items based calculateTotals वापरणार नाहीस तरी ठेवू शकतोस)
public function calculateTotals()
{
$gst = ($this->final_amount * $this->gst_percent) / 100;
@@ -84,29 +91,43 @@ class Invoice extends Model
return null;
}
// ✅ Charge groups total accessor
// ✅ Charge groups base total (WITHOUT GST)
public function getChargeGroupsTotalAttribute()
{
// relation already loaded असेल तर collection वरून sum होईल
// base = total_charge sum
return (float) $this->chargeGroups->sum('total_charge');
}
// ✅ Grand total accessor (items + GST + charge groups)
// ✅ Grand total: Charge groups base + GST (items ignore)
public function getGrandTotalWithChargesAttribute()
{
return (float) ($this->final_amount_with_gst ?? 0) + $this->charge_groups_total;
$base = (float) ($this->charge_groups_total ?? 0);
$gst = (float) ($this->gst_amount ?? 0);
return $base + $gst;
}
public function totalPaid()
public function totalPaid(): float
{
return $this->installments->sum('amount');
return (float) $this->installments()->sum('amount');
}
public function remainingAmount()
public function remainingAmount(): float
{
return $this->grand_total_with_charges - $this->totalPaid();
$grand = (float) $this->grand_total_with_charges;
$paid = (float) $this->totalPaid();
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

@@ -13,6 +13,10 @@ class InvoiceChargeGroup extends Model
'basis_value',
'rate',
'total_charge',
'tax_type',
'gst_percent',
'total_with_gst',
];
public function invoice()

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

@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('invoice_charge_groups', function (Blueprint $table) {
$table->string('tax_type')->nullable()->after('total_charge');
$table->decimal('gst_percent', 5, 2)->default(0)->after('tax_type');
$table->decimal('total_with_gst', 15, 2)->default(0)->after('gst_percent');
});
}
public function down(): void
{
Schema::table('invoice_charge_groups', function (Blueprint $table) {
$table->dropColumn(['tax_type', 'gst_percent', 'total_with_gst']);
});
}
};

View File

@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddChargeColumnsToInvoicesTable extends Migration
{
public function up()
{
Schema::table('invoices', function (Blueprint $table) {
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) {
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.

View File

@@ -40,6 +40,73 @@
margin-top: 2px;
}
/* DOWNLOAD BUTTONS - NEW STYLES */
.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 +132,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 +199,6 @@
color: #fff7ed;
}
/* TOTAL BOXES */
.cm-total-cards-row {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
@@ -421,6 +487,11 @@
grid-template-columns: 1fr;
}
}
.cm-table-scroll-outer {
margin: 10px 14px 30px 14px; /* फक्त हे बदल करा */
}
</style>
<div class="container-fluid cm-wrapper">
@@ -433,27 +504,30 @@
</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>
</div>
</div>
<!-- बाकीचा सगळा code same आहे - काही बदल नाही -->
<div class="card cm-main-card">
<div class="card-header">
<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 +682,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()">
@@ -672,11 +746,14 @@
);
$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 +767,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,14 +783,18 @@
</div>
@if(!$container->rows->isEmpty())
<button id="cmSaveBtnFloating" class="cm-save-btn-floating">
<button
id="cmSaveBtnFloating"
class="cm-save-btn-floating {{ $container->status !== 'pending' ? 'cm-disabled' : '' }}"
{{ $container->status !== 'pending' ? 'disabled' : '' }}
>
Save Changes
</button>
<div id="cmToast" class="cm-toast">
Changes saved successfully.
</div>
@endif
<!-- Toast notification missing होती, add केली -->
<div id="cmToast" class="cm-toast"></div>
<script>
function cmFilterRows() {
const input = document.getElementById('cmRowSearch');
@@ -857,6 +938,10 @@
if (form && btn) {
btn.addEventListener('click', function () {
if (btn.classList.contains('cm-disabled') || btn.hasAttribute('disabled')) {
return;
}
btn.classList.add('cm-disabled');
const formData = new FormData(form);
@@ -888,4 +973,5 @@
}
});
</script>
@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,67 +1212,92 @@ 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>
<div class="stats-label">Total Revenue</div>
<div class="stats-value">{{ number_format($totalRevenue, 2) }}</div>
</div>
</div>
<div class="stats-card stats-card-red">
<span class="stats-icon"></span>
<div>
<div class="stats-label">Pending Order</div>
<div class="stats-value">{{ $pendingOrders }}</div>
</div>
</div>
</div>
<div class="stats-row">
<div class="stats-card stats-card-blue">
<span class="stats-icon">📦</span>
<span class="stats-icon">&#x1F9FE;</span>
<div>
<div class="stats-label">Total Orders</div>
<div class="stats-value">{{ $totalOrders }}</div>
<div class="stats-value">{{ $totalInvoices }}</div>
</div>
</div>
<div class="stats-card stats-card-green">
<span class="stats-icon">&#x2705;</span>
<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-orng">
<span class="stats-icon">&#x1F550;</span>
<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 -->
<!-- <div class="order-mgmt-box">

View File

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

View File

@@ -1,9 +1,7 @@
@extends('admin.layouts.app')
@section('page-title', 'Edit Invoice')
@section('content')
<style>
:root {
@@ -314,7 +312,7 @@
</style>
<div class="container-fluid py-3">
{{-- Invoice Preview / Overview --}}
{{-- Invoice Overview --}}
<div class="glass-card">
<div class="card-header-compact">
<h4>
@@ -323,21 +321,21 @@
</h4>
</div>
<div class="card-body-compact">
{{-- Read-only popup: items price/total cannot be edited here --}}
@include('admin.popup_invoice', ['invoice' => $invoice, 'shipment' => $shipment])
</div>
</div>
{{-- Edit Invoice Header Details (POST) --}}
{{-- Edit Invoice Header --}}
<div class="glass-card">
<div class="card-header-compact">
<div class="card-header-compact d-flex justify-content-between align-items-center">
<h4>
<i class="fas fa-edit me-2"></i>
Edit Invoice Details
</h4>
<small id="headerUpdateMsg" class="text-light"></small>
</div>
<div class="card-body-compact">
<form action="{{ route('admin.invoices.update', $invoice->id) }}" method="POST">
<form id="invoiceHeaderForm" action="{{ route('admin.invoices.update', $invoice->id) }}" method="POST">
@csrf
<div class="form-grid-compact">
@@ -365,61 +363,16 @@
required>
</div>
{{-- Final Amount (Base) --}}
{{-- 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 (Before GST)
<i class="fas fa-money-bill-wave"></i> Final Amount (With GST)
</label>
<input type="number"
step="0.01"
name="final_amount"
class="form-control-compact"
value="{{ old('final_amount', $invoice->final_amount) }}"
required>
</div>
{{-- Tax Type --}}
<div class="form-group-compact">
<label class="form-label-compact">
<i class="fas fa-receipt"></i> Tax Type
</label>
<div class="d-flex gap-3 mt-1">
<div class="form-check">
<input class="form-check-input"
type="radio"
name="tax_type"
value="gst"
{{ old('tax_type', $invoice->tax_type) === 'gst' ? 'checked' : '' }}>
<label class="form-check-label fw-semibold">GST (CGST + SGST)</label>
</div>
<div class="form-check">
<input class="form-check-input"
type="radio"
name="tax_type"
value="igst"
{{ old('tax_type', $invoice->tax_type) === 'igst' ? 'checked' : '' }}>
<label class="form-check-label fw-semibold">IGST</label>
</div>
</div>
</div>
{{-- Tax Percentage --}}
<div class="form-group-compact">
<label class="form-label-compact">
<i class="fas fa-percentage"></i> Tax Percentage
</label>
<input type="number"
step="0.01"
min="0"
max="28"
name="tax_percent"
class="form-control-compact"
value="{{ old('tax_percent',
$invoice->tax_type === 'gst'
? ($invoice->cgst_percent + $invoice->sgst_percent)
: $invoice->igst_percent
) }}"
required>
value="{{ $invoice->grand_total_with_charges }}"
readonly>
</div>
{{-- Status --}}
@@ -427,16 +380,11 @@
<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 name="status" id="statusSelect" class="form-select-compact" required>
<option value="pending" {{ old('status', $invoice->status) === 'pending' ? 'selected' : '' }}>Pending</option>
<option value="paying" {{ old('status', $invoice->status) === 'paying' ? 'selected' : '' }}>Paying</option>
<option value="paid" {{ old('status', $invoice->status) === 'paid' ? 'selected' : '' }}>Paid</option>
<option value="overdue" {{ old('status', $invoice->status) === 'overdue' ? 'selected' : '' }}>Overdue</option>
</select>
</div>
@@ -453,7 +401,7 @@
</div>
<div class="text-end mt-3">
<button type="submit" class="btn-success-compact btn-compact">
<button type="submit" id="btnHeaderSave" class="btn-success-compact btn-compact">
<i class="fas fa-save me-2"></i>Update Invoice
</button>
</div>
@@ -462,43 +410,46 @@
</div>
@php
// आता helpers वापरू: totalPaid() आणि remainingAmount()
$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([]);
$taxTypeLabel = 'None';
if ($taxTypes->count() === 1) {
if ($taxTypes[0] === 'gst') {
$taxTypeLabel = 'GST (CGST + SGST)';
} elseif ($taxTypes[0] === 'igst') {
$taxTypeLabel = 'IGST';
} else {
$taxTypeLabel = strtoupper($taxTypes[0]);
}
} elseif ($taxTypes->count() > 1) {
$parts = [];
if ($taxTypes->contains('gst')) {
$parts[] = 'GST (CGST + SGST)';
}
if ($taxTypes->contains('igst')) {
$parts[] = 'IGST';
}
$taxTypeLabel = implode(' + ', $parts);
}
@endphp
{{-- Amount Breakdown (items + GST + groups) --}}
{{-- 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">Total Amount Before Tax</span>
<span class="breakdown-value" id="baseAmount">
{{ number_format($invoice->final_amount, 2) }}
</span>
</div>
<div class="breakdown-row">
<span class="breakdown-label">Tax Type</span>
<span class="breakdown-value text-primary">
@if($invoice->tax_type === 'gst')
GST (CGST + SGST)
@else
IGST
@endif
</span>
</div>
<div class="breakdown-row">
<span class="breakdown-label">Tax Percentage</span>
<span class="breakdown-value text-primary">
@if($invoice->tax_type === 'gst')
{{ $invoice->cgst_percent + $invoice->sgst_percent }}%
@else
{{ $invoice->igst_percent }}%
@endif
{{ $taxTypeLabel }}
</span>
</div>
@@ -509,15 +460,8 @@
</span>
</div>
<div class="breakdown-row">
<span class="breakdown-label">Charge Groups Total</span>
<span class="breakdown-value text-info" id="chargeGroupsTotal">
{{ number_format($invoice->charge_groups_total, 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 (Items + GST + Groups)</span>
<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>
@@ -538,13 +482,13 @@
</div>
</div>
{{-- Installment Summary (top cards) --}}
{{-- Summary cards --}}
<div class="summary-grid-compact">
<div class="summary-card-compact total">
<div class="summary-value-compact text-success" id="totalInvoiceWithGstCard">
{{ number_format($invoice->grand_total_with_charges, 2) }}
</div>
<div class="summary-label-compact">Grand Total (Items + GST + Groups)</div>
<div class="summary-label-compact">Grand Total (Charges + GST)</div>
</div>
<div class="summary-card-compact paid">
<div class="summary-value-compact text-primary">
@@ -789,15 +733,19 @@ document.addEventListener("DOMContentLoaded", function () {
document.getElementById("remainingAmount").textContent = "" + formatINR(data.remaining);
}
if (document.getElementById("baseAmount")) {
document.getElementById("baseAmount").textContent = "" + formatINR(data.baseAmount);
document.getElementById("baseAmount").textContent = "" + formatINR(data.chargeGroupsTotal);
}
if (document.getElementById("gstAmount")) {
document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount);
}
// grand total आता finalAmountWithGst नाही, पण API अजून तेच key देत आहे,
// त्यामुळे इथे फक्त card आणि breakdown value update करतो:
if (document.getElementById("totalInvoiceWithGst")) {
document.getElementById("totalInvoiceWithGst").textContent = "" + formatINR(data.finalAmountWithGst);
document.getElementById("totalInvoiceWithGst").textContent =
"" + formatINR(data.grandTotal);
}
const totalCard = document.getElementById("totalInvoiceWithGstCard");
if (totalCard) {
totalCard.textContent = "" + formatINR(data.grandTotal);
}
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
@@ -858,13 +806,18 @@ document.addEventListener("DOMContentLoaded", function () {
document.getElementById("remainingAmount").textContent = "" + formatINR(data.remaining);
}
if (document.getElementById("baseAmount")) {
document.getElementById("baseAmount").textContent = "" + formatINR(data.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.finalAmountWithGst);
document.getElementById("totalInvoiceWithGst").textContent =
"" + formatINR(data.grandTotal);
}
const totalCard = document.getElementById("totalInvoiceWithGstCard");
if (totalCard) {
totalCard.textContent = "" + formatINR(data.grandTotal);
}
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
@@ -886,9 +839,8 @@ document.addEventListener("DOMContentLoaded", function () {
alert("Something went wrong. Please try again.");
});
});
});
document.addEventListener('DOMContentLoaded', function() {
// Auto due date = invoice date + 10 days
const invoiceDateInput = document.querySelector('input[name="invoice_date"]');
const dueDateInput = document.querySelector('input[name="due_date"]');
@@ -905,6 +857,72 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
}
// ✅ Invoice header AJAX save (no page refresh)
const headerForm = document.getElementById('invoiceHeaderForm');
const headerBtn = document.getElementById('btnHeaderSave');
const headerMsg = document.getElementById('headerUpdateMsg');
if (headerForm && headerBtn) {
headerForm.addEventListener('submit', function(e) {
e.preventDefault();
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 = null;
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');
// popup_invoice वरचा status badge update करायचा असल्यास:
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';
});
});
}
});
</script>
@endsection

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">

File diff suppressed because it is too large Load Diff

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%;
content:"";position:absolute;bottom:-80px;right:180px;
width:180px;height:180px;
background:rgba(255,255,255,0.05);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;
.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;
}
.stat-card, .profile-card {
background: #fff;
border-radius: 14px;
box-shadow: 0 3px 18px rgba(60,80,120,0.08);
padding: 20px;
.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 200px;
flex:1 1 160px;
overflow:hidden;
}
.stat-card:hover, .profile-card:hover {
transform: translateY(-4px);
box-shadow: 0 6px 25px rgba(50,70,120,0.18);
.stat-card::after {
content:"";position:absolute;
top:-25px;right:-25px;
width:80px;height:80px;
border-radius:50%;
opacity:0.35;
}
/* 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;
/* 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;
}
.activity-list, .quick-action-list {
list-style: none;
padding: 0;
margin: 0;
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 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;
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: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; }
@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>
<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>
<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>
<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-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>
<!-- BOTTOM CARDS -->
<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 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 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 ════════ --}}
<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

View File

@@ -82,8 +82,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',