diff --git a/.env.example b/.env.example
index c187aef..16a85a2 100644
--- a/.env.example
+++ b/.env.example
@@ -20,12 +20,12 @@ LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
-DB_CONNECTION=sqlite
-# DB_HOST=127.0.0.1
-# DB_PORT=3306
-# DB_DATABASE=laravel
-# DB_USERNAME=root
-# DB_PASSWORD=
+DB_CONNECTION=mysql
+DB_HOST=127.0.0.1
+DB_PORT=3306
+DB_DATABASE=kent_logistics6
+DB_USERNAME=root
+DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
diff --git a/app/Exports/InvoicesExport.php b/app/Exports/InvoicesExport.php
new file mode 100644
index 0000000..d43606f
--- /dev/null
+++ b/app/Exports/InvoicesExport.php
@@ -0,0 +1,63 @@
+request = $request;
+ }
+
+ public function view(): View
+ {
+ $request = $this->request;
+
+ $invoices = DB::table('invoices')
+ ->leftJoin('containers', 'containers.id', '=', 'invoices.container_id')
+ ->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no')
+ ->select(
+ 'invoices.invoice_number',
+ 'invoices.invoice_date',
+ 'invoices.mark_no',
+ 'containers.container_number',
+ 'containers.container_date',
+ 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'),
+ 'invoices.final_amount',
+ 'invoices.final_amount_with_gst',
+ 'invoices.status as invoice_status'
+ )
+ ->when($request->filled('search'), function ($q) use ($request) {
+ $search = trim($request->search);
+ $q->where(function ($qq) use ($search) {
+ $qq->where('invoices.invoice_number', 'like', "%{$search}%")
+ ->orWhere('invoices.mark_no', 'like', "%{$search}%")
+ ->orWhere('containers.container_number', 'like', "%{$search}%")
+ ->orWhere('mark_list.company_name', 'like', "%{$search}%")
+ ->orWhere('mark_list.customer_name', 'like', "%{$search}%");
+ });
+ })
+ ->when($request->filled('status'), function ($q) use ($request) {
+ $q->where('invoices.status', $request->status);
+ })
+ ->when($request->filled('from_date'), function ($q) use ($request) {
+ $q->whereDate('invoices.invoice_date', '>=', $request->from_date);
+ })
+ ->when($request->filled('to_date'), function ($q) use ($request) {
+ $q->whereDate('invoices.invoice_date', '<=', $request->to_date);
+ })
+ ->orderByDesc('containers.container_date')
+ ->orderByDesc('invoices.id')
+ ->get();
+
+ return view('admin.pdf.invoices_excel', compact('invoices'));
+ }
+}
diff --git a/app/Http/Controllers/Admin/AdminInvoiceController.php b/app/Http/Controllers/Admin/AdminInvoiceController.php
index 19d313e..00cbe82 100644
--- a/app/Http/Controllers/Admin/AdminInvoiceController.php
+++ b/app/Http/Controllers/Admin/AdminInvoiceController.php
@@ -3,21 +3,51 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
-use Illuminate\Http\Request;
use App\Models\Invoice;
use App\Models\InvoiceItem;
-use Mpdf\Mpdf;
use App\Models\InvoiceInstallment;
+use App\Models\InvoiceChargeGroup;
+use App\Models\InvoiceChargeGroupItem;
+use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Storage;
+use Mpdf\Mpdf;
class AdminInvoiceController extends Controller
{
// -------------------------------------------------------------
// INVOICE LIST PAGE
// -------------------------------------------------------------
- public function index()
+ public function index(Request $request)
{
- $invoices = Invoice::with(['order.shipments'])->latest()->get();
+ $query = Invoice::with(['items', 'customer', 'container']);
+
+ // Search (जर पुढे form मधून पाठवलंस तर)
+ if ($request->filled('search')) {
+ $search = $request->search;
+ $query->where(function ($q) use ($search) {
+ $q->where('invoice_number', 'like', "%{$search}%")
+ ->orWhere('customer_name', 'like', "%{$search}%");
+ });
+ }
+
+ // Status filter
+ if ($request->filled('status') && $request->status !== 'all') {
+ $query->where('status', $request->status);
+ }
+
+ // Date range filter (invoice_date वर)
+ if ($request->filled('start_date')) {
+ $query->whereDate('invoice_date', '>=', $request->start_date);
+ }
+
+ if ($request->filled('end_date')) {
+ $query->whereDate('invoice_date', '<=', $request->end_date);
+ }
+
+ // Latest first
+ $invoices = $query->latest()->get();
+
return view('admin.invoice', compact('invoices'));
}
@@ -26,13 +56,16 @@ class AdminInvoiceController extends Controller
// -------------------------------------------------------------
public function popup($id)
{
- $invoice = Invoice::with(['items', 'order', 'installments'])->findOrFail($id);
+ $invoice = Invoice::with([
+ 'items',
+ 'customer',
+ 'container',
+ 'chargeGroups.items.item',
+ ])->findOrFail($id);
- $shipment = \App\Models\Shipment::whereHas('items', function ($q) use ($invoice) {
- $q->where('order_id', $invoice->order_id);
- })->first();
+ $shipment = null;
- return view('admin.popup_invoice', compact('invoice', 'shipment'));
+ return view('admin.popup_invoice', compact('invoice', 'shipment'));
}
// -------------------------------------------------------------
@@ -40,76 +73,102 @@ class AdminInvoiceController extends Controller
// -------------------------------------------------------------
public function edit($id)
{
- $invoice = Invoice::with(['order.shipments'])->findOrFail($id);
- $shipment = $invoice->order?->shipments?->first();
+ $invoice = Invoice::with([
+ 'items',
+ 'customer',
+ 'container',
+ 'chargeGroups.items',
+ ])->findOrFail($id);
- return view('admin.invoice_edit', compact('invoice', 'shipment'));
+ $shipment = null;
+
+ $groupedItemIds = $invoice->chargeGroups
+ ->flatMap(function ($group) {
+ return $group->items->pluck('invoice_item_id');
+ })
+ ->unique()
+ ->values()
+ ->toArray();
+
+ return view('admin.invoice_edit', compact('invoice', 'shipment', 'groupedItemIds'));
}
// -------------------------------------------------------------
- // UPDATE INVOICE
+ // UPDATE INVOICE (HEADER LEVEL)
// -------------------------------------------------------------
public function update(Request $request, $id)
{
- Log::info("🟡 Invoice Update Request Received", [
+ 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',
- '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',
- 'notes' => 'nullable|string',
+ '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',
+ 'notes' => 'nullable|string',
]);
- Log::info("✅ Validated Invoice Update Data", $data);
+ Log::info('✅ Validated Invoice Update Data', $data);
- $finalAmount = floatval($data['final_amount']);
- $taxPercent = floatval($data['tax_percent']);
- $taxAmount = 0;
+ $finalAmount = (float) $data['final_amount'];
+ $taxPercent = (float) $data['tax_percent'];
if ($data['tax_type'] === 'gst') {
- Log::info("🟢 GST Selected", compact('taxPercent'));
+ 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'));
+ Log::info('🔵 IGST Selected', compact('taxPercent'));
+
$data['cgst_percent'] = 0;
$data['sgst_percent'] = 0;
$data['igst_percent'] = $taxPercent;
}
- $taxAmount = ($finalAmount * $taxPercent) / 100;
+ $gstAmount = ($finalAmount * $taxPercent) / 100;
+ $data['gst_amount'] = $gstAmount;
+ $data['final_amount_with_gst'] = $finalAmount + $gstAmount;
+ $data['gst_percent'] = $taxPercent;
- $data['gst_amount'] = $taxAmount;
- $data['final_amount_with_gst'] = $finalAmount + $taxAmount;
- $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('📌 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'],
]);
$invoice->update($data);
- Log::info("✅ Invoice Updated Successfully", [
- 'invoice_id' => $invoice->id
+ Log::info('✅ Invoice Updated Successfully', [
+ 'invoice_id' => $invoice->id,
+ ]);
+
+ $invoice->refresh();
+ Log::info('🔍 Invoice AFTER UPDATE (DB values)', [
+ 'invoice_id' => $invoice->id,
+ 'final_amount' => $invoice->final_amount,
+ 'gst_percent' => $invoice->gst_percent,
+ '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,
]);
- // regenerate PDF
$this->generateInvoicePDF($invoice);
return redirect()
@@ -117,50 +176,120 @@ class AdminInvoiceController extends Controller
->with('success', 'Invoice updated & PDF generated successfully.');
}
+ // -------------------------------------------------------------
+ // 🔹 UPDATE INVOICE ITEMS (price + ttl_amount)
+ // -------------------------------------------------------------
+ public function updateItems(Request $request, Invoice $invoice)
+ {
+ Log::info('🟡 Invoice Items Update Request', [
+ 'invoice_id' => $invoice->id,
+ 'payload' => $request->all(),
+ ]);
+
+ $data = $request->validate([
+ 'items' => ['required', 'array'],
+ 'items.*.price' => ['required', 'numeric', 'min:0'],
+ 'items.*.ttl_amount' => ['required', 'numeric', 'min:0'],
+ ]);
+
+ $itemsInput = $data['items'];
+
+ foreach ($itemsInput as $itemId => $itemData) {
+ $item = InvoiceItem::where('id', $itemId)
+ ->where('invoice_id', $invoice->id)
+ ->first();
+
+ if (!$item) {
+ Log::warning('Invoice item not found or mismatched invoice', [
+ 'invoice_id' => $invoice->id,
+ 'item_id' => $itemId,
+ ]);
+ continue;
+ }
+
+ $item->price = $itemData['price'];
+ $item->ttl_amount = $itemData['ttl_amount'];
+ $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();
+
+ Log::info('✅ Invoice items updated & totals recalculated', [
+ '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,
+ ]);
+
+ return back()->with('success', 'Invoice items updated successfully.');
+ }
+
// -------------------------------------------------------------
// PDF GENERATION USING mPDF
// -------------------------------------------------------------
public function generateInvoicePDF($invoice)
{
- $invoice->load(['items', 'order.shipments']);
- $shipment = $invoice->order?->shipments?->first();
+ $invoice->load(['items', 'customer', 'container']);
+ $shipment = null;
+
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
- $folder = public_path('invoices/');
+ $folder = public_path('invoices/');
if (!file_exists($folder)) {
mkdir($folder, 0777, true);
}
$filePath = $folder . $fileName;
+
if (file_exists($filePath)) {
unlink($filePath);
}
- $mpdf = new Mpdf(['mode' => 'utf-8', 'format' => 'A4', 'default_font' => 'sans-serif']);
- $html = view('admin.pdf.invoice', ['invoice' => $invoice, 'shipment' => $shipment])->render();
+ $mpdf = new Mpdf([
+ 'mode' => 'utf-8',
+ 'format' => 'A4',
+ 'default_font' => 'sans-serif',
+ ]);
+
+ $html = view('admin.pdf.invoice', [
+ 'invoice' => $invoice,
+ 'shipment' => $shipment,
+ ])->render();
+
$mpdf->WriteHTML($html);
$mpdf->Output($filePath, 'F');
+
$invoice->update(['pdf_path' => 'invoices/' . $fileName]);
}
- public function downloadInvoice($id)
-{
- $invoice = Invoice::findOrFail($id);
-
- // Generate PDF if missing
- if (
- !$invoice->pdf_path ||
- !file_exists(public_path($invoice->pdf_path))
- ) {
- $this->generateInvoicePDF($invoice);
- $invoice->refresh();
- }
-
- return response()->download(public_path($invoice->pdf_path));
-}
-
// -------------------------------------------------------------
- // INSTALLMENTS (ADD/DELETE)
+ // INSTALLMENTS (ADD)
// -------------------------------------------------------------
public function storeInstallment(Request $request, $invoice_id)
{
@@ -171,16 +300,14 @@ class AdminInvoiceController extends Controller
'amount' => 'required|numeric|min:1',
]);
- $invoice = Invoice::findOrFail($invoice_id);
-
+ $invoice = Invoice::findOrFail($invoice_id);
$paidTotal = $invoice->installments()->sum('amount');
- // Use GST-inclusive total for all calculations/checks
$remaining = $invoice->final_amount_with_gst - $paidTotal;
if ($request->amount > $remaining) {
return response()->json([
- 'status' => 'error',
- 'message' => 'Installment amount exceeds remaining balance.'
+ 'status' => 'error',
+ 'message' => 'Installment amount exceeds remaining balance.',
], 422);
}
@@ -194,52 +321,96 @@ class AdminInvoiceController extends Controller
$newPaid = $paidTotal + $request->amount;
- // Mark as 'paid' if GST-inclusive total is cleared
if ($newPaid >= $invoice->final_amount_with_gst) {
$invoice->update(['status' => 'paid']);
-
- $this->generateInvoicePDF($invoice);
}
return response()->json([
- 'status' => 'success',
- 'message' => 'Installment added successfully.',
- 'installment' => $installment,
- 'totalPaid' => $newPaid,
- 'gstAmount' => $invoice->gst_amount,
- 'finalAmountWithGst' => $invoice->final_amount_with_gst,
- 'baseAmount' => $invoice->final_amount,
- 'remaining' => max(0, $invoice->final_amount_with_gst - $newPaid),
- 'isCompleted' => $newPaid >= $invoice->final_amount_with_gst
+ 'status' => 'success',
+ 'message' => 'Installment added successfully.',
+ 'installment' => $installment,
+ 'totalPaid' => $newPaid,
+ 'gstAmount' => $invoice->gst_amount,
+ 'finalAmountWithGst' => $invoice->final_amount_with_gst,
+ 'baseAmount' => $invoice->final_amount,
+ 'remaining' => max(0, $invoice->final_amount_with_gst - $newPaid),
+ 'isCompleted' => $newPaid >= $invoice->final_amount_with_gst,
]);
}
+
+ // -------------------------------------------------------------
+ // INSTALLMENTS (DELETE)
+ // -------------------------------------------------------------
public function deleteInstallment($id)
{
$installment = InvoiceInstallment::findOrFail($id);
- $invoice = $installment->invoice;
-
+ $invoice = $installment->invoice;
+
$installment->delete();
-
+
$paidTotal = $invoice->installments()->sum('amount');
$remaining = $invoice->final_amount_with_gst - $paidTotal;
-
- // Update status if not fully paid anymore
- if ($remaining > 0 && $invoice->status === "paid") {
- $invoice->update(['status' => 'pending']);
- $this->generateInvoicePDF($invoice);
+ if ($remaining > 0 && $invoice->status === 'paid') {
+ $invoice->update(['status' => 'pending']);
}
-
+
return response()->json([
- 'status' => 'success',
- 'message' => 'Installment deleted.',
- 'totalPaid' => $paidTotal,
- 'gstAmount' => $invoice->gst_amount,
- 'finalAmountWithGst' => $invoice->final_amount_with_gst,
- 'baseAmount' => $invoice->final_amount,
- 'remaining' => $remaining,
- 'isZero' => $paidTotal == 0
+ 'status' => 'success',
+ 'message' => 'Installment deleted.',
+ 'totalPaid' => $paidTotal,
+ 'gstAmount' => $invoice->gst_amount,
+ 'finalAmountWithGst' => $invoice->final_amount_with_gst,
+ 'baseAmount' => $invoice->final_amount,
+ 'remaining' => $remaining,
+ 'isZero' => $paidTotal == 0,
]);
}
-
+
+ // -------------------------------------------------------------
+ // CHARGE GROUP SAVE (NEW)
+ // -------------------------------------------------------------
+ public function storeChargeGroup(Request $request, $invoiceId)
+ {
+ $invoice = Invoice::with('items')->findOrFail($invoiceId);
+
+ $data = $request->validate([
+ 'group_name' => 'nullable|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',
+ ]);
+
+ $group = InvoiceChargeGroup::create([
+ 'invoice_id' => $invoice->id,
+ 'group_name' => $data['group_name'] ?? null,
+ 'basis_type' => $data['basis_type'],
+ 'basis_value' => $data['basis_value'],
+ 'rate' => $data['rate'],
+ 'total_charge' => $data['auto_total'],
+ ]);
+
+ foreach ($data['item_ids'] as $itemId) {
+ InvoiceChargeGroupItem::create([
+ 'group_id' => $group->id,
+ 'invoice_item_id' => $itemId,
+ ]);
+ }
+
+ return redirect()
+ ->back()
+ ->with('success', 'Charge group saved successfully.');
+ }
+
+ 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');
+ }
}
diff --git a/app/Http/Controllers/Admin/AdminOrderController.php b/app/Http/Controllers/Admin/AdminOrderController.php
index 8d887ff..9bfaa3d 100644
--- a/app/Http/Controllers/Admin/AdminOrderController.php
+++ b/app/Http/Controllers/Admin/AdminOrderController.php
@@ -10,26 +10,89 @@ use App\Models\MarkList;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\User;
+use App\Models\Container;
+use App\Models\Admin;
+use App\Models\Shipment;
use PDF;
use Maatwebsite\Excel\Facades\Excel;
use App\Exports\OrdersExport;
use App\Imports\OrderItemsPreviewImport;
-
-
use Illuminate\Validation\ValidationException;
-
+use Illuminate\Support\Facades\DB;
+use App\Exports\InvoicesExport;
class AdminOrderController extends Controller
{
/* ---------------------------
- * LIST / DASHBOARD
+ * DASHBOARD (old UI: stats + recent orders)
* ---------------------------*/
- public function index()
+ public function dashboard()
{
- $orders = Order::latest()->get();
- $markList = MarkList::where('status', 'active')->get();
+ $totalOrders = Order::count();
+ $pendingOrders = Order::where('status', 'pending')->count();
+ $totalShipments = Shipment::count();
+ $totalItems = OrderItem::count();
+ $totalRevenue = Invoice::sum('final_amount_with_gst');
+ $activeCustomers = User::where('status', 'active')->count();
+ $inactiveCustomers = User::where('status', 'inactive')->count();
+ $totalStaff = Admin::where('type', 'staff')->count();
- return view('admin.dashboard', compact('orders', 'markList'));
+ $markList = MarkList::where('status', 'active')->get();
+ $orders = Order::latest()->get();
+
+ return view('admin.dashboard', compact(
+ 'totalOrders',
+ 'pendingOrders',
+ 'totalShipments',
+ 'totalItems',
+ 'totalRevenue',
+ 'activeCustomers',
+ 'inactiveCustomers',
+ 'totalStaff',
+ 'orders',
+ 'markList'
+ ));
+ }
+
+ /* ---------------------------
+ * LIST (new: Invoices Management for Orders page)
+ * ---------------------------*/
+ public function index(Request $request)
+ {
+ $invoices = DB::table('invoices')
+ ->leftJoin('containers', 'containers.id', '=', 'invoices.container_id')
+ ->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no')
+ ->select(
+ 'invoices.id',
+ 'invoices.invoice_number',
+ 'invoices.invoice_date',
+ 'invoices.final_amount',
+ 'invoices.final_amount_with_gst',
+ 'invoices.status as invoice_status',
+ 'invoices.mark_no',
+ 'containers.container_number',
+ 'containers.container_date',
+ 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')
+ )
+ ->when($request->filled('search'), function ($q) use ($request) {
+ $search = trim($request->search);
+ $q->where(function ($qq) use ($search) {
+ $qq->where('invoices.invoice_number', 'like', "%{$search}%")
+ ->orWhere('containers.container_number', 'like', "%{$search}%")
+ ->orWhere('invoices.mark_no', 'like', "%{$search}%")
+ ->orWhere('mark_list.company_name', 'like', "%{$search}%")
+ ->orWhere('mark_list.customer_name', 'like', "%{$search}%");
+ });
+ })
+ ->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
+ ->get();
+
+ return view('admin.orders', compact('invoices'));
}
/* ---------------------------
@@ -68,42 +131,39 @@ class AdminOrderController extends Controller
* ORDER ITEM MANAGEMENT (existing orders)
* ---------------------------*/
public function addItem(Request $request, $orderId)
-{
- $order = Order::findOrFail($orderId);
+ {
+ $order = Order::findOrFail($orderId);
- $data = $request->validate([
- 'description' => 'required|string',
- 'ctn' => 'nullable|numeric',
- 'qty' => 'nullable|numeric',
- 'unit' => 'nullable|string',
- 'price' => 'nullable|numeric',
- 'cbm' => 'nullable|numeric',
- 'kg' => 'nullable|numeric',
- 'shop_no' => 'nullable|string',
- ]);
+ $data = $request->validate([
+ 'description' => 'required|string',
+ 'ctn' => 'nullable|numeric',
+ 'qty' => 'nullable|numeric',
+ 'unit' => 'nullable|string',
+ 'price' => 'nullable|numeric',
+ 'cbm' => 'nullable|numeric',
+ 'kg' => 'nullable|numeric',
+ 'shop_no' => 'nullable|string',
+ ]);
- // ✅ BACKEND CALCULATION
- $ctn = (float) ($data['ctn'] ?? 0);
- $qty = (float) ($data['qty'] ?? 0);
- $price = (float) ($data['price'] ?? 0);
- $cbm = (float) ($data['cbm'] ?? 0);
- $kg = (float) ($data['kg'] ?? 0);
+ $ctn = (float) ($data['ctn'] ?? 0);
+ $qty = (float) ($data['qty'] ?? 0);
+ $price = (float) ($data['price'] ?? 0);
+ $cbm = (float) ($data['cbm'] ?? 0);
+ $kg = (float) ($data['kg'] ?? 0);
- $data['ttl_qty'] = $ctn * $qty;
- $data['ttl_amount'] = $data['ttl_qty'] * $price;
- $data['ttl_cbm'] = $cbm * $ctn;
- $data['ttl_kg'] = $ctn * $kg;
+ $data['ttl_qty'] = $ctn * $qty;
+ $data['ttl_amount'] = $data['ttl_qty'] * $price;
+ $data['ttl_cbm'] = $cbm * $ctn;
+ $data['ttl_kg'] = $ctn * $kg;
- $data['order_id'] = $order->id;
+ $data['order_id'] = $order->id;
- OrderItem::create($data);
+ OrderItem::create($data);
- $this->recalcTotals($order);
- $this->updateInvoiceFromOrder($order);
-
- return redirect()->back()->with('success', 'Item added and totals updated.');
-}
+ $this->recalcTotals($order);
+ return redirect()->back()->with('success', 'Item added and totals updated.');
+ }
public function deleteItem($id)
{
@@ -113,7 +173,6 @@ class AdminOrderController extends Controller
$item->delete();
$this->recalcTotals($order);
- $this->updateInvoiceFromOrder($order);
return redirect()->back()->with('success', 'Item deleted and totals updated.');
}
@@ -126,7 +185,6 @@ class AdminOrderController extends Controller
$item->restore();
$this->recalcTotals($order);
- $this->updateInvoiceFromOrder($order);
return redirect()->back()->with('success', 'Item restored and totals updated.');
}
@@ -175,14 +233,14 @@ class AdminOrderController extends Controller
$items = $order->items()->get();
$order->update([
- 'ctn' => (int) $items->sum(fn($i) => (int) ($i->ctn ?? 0)),
- 'qty' => (int) $items->sum(fn($i) => (int) ($i->qty ?? 0)),
- 'ttl_qty' => (int) $items->sum(fn($i) => (int) ($i->ttl_qty ?? 0)),
- 'ttl_amount' => (float) $items->sum(fn($i) => (float) ($i->ttl_amount ?? 0)),
- 'cbm' => (float) $items->sum(fn($i) => (float) ($i->cbm ?? 0)),
- 'ttl_cbm' => (float) $items->sum(fn($i) => (float) ($i->ttl_cbm ?? 0)),
- 'kg' => (float) $items->sum(fn($i) => (float) ($i->kg ?? 0)),
- 'ttl_kg' => (float) $items->sum(fn($i) => (float) ($i->ttl_kg ?? 0)),
+ 'ctn' => (int) $items->sum(fn($i) => (int) ($i->ctn ?? 0)),
+ 'qty' => (int) $items->sum(fn($i) => (int) ($i->qty ?? 0)),
+ 'ttl_qty' => (int) $items->sum(fn($i) => (int) ($i->ttl_qty ?? 0)),
+ 'ttl_amount'=> (float) $items->sum(fn($i) => (float) ($i->ttl_amount ?? 0)),
+ 'cbm' => (float) $items->sum(fn($i) => (float) ($i->cbm ?? 0)),
+ 'ttl_cbm' => (float) $items->sum(fn($i) => (float) ($i->ttl_cbm ?? 0)),
+ 'kg' => (float) $items->sum(fn($i) => (float) ($i->kg ?? 0)),
+ 'ttl_kg' => (float) $items->sum(fn($i) => (float) ($i->ttl_kg ?? 0)),
]);
}
@@ -285,7 +343,6 @@ class AdminOrderController extends Controller
$order = Order::with([
'markList',
'items',
- 'invoice.items',
'shipments' => function ($q) use ($id) {
$q->whereHas('orders', function ($oq) use ($id) {
$oq->where('orders.id', $id)
@@ -304,14 +361,14 @@ class AdminOrderController extends Controller
'order_id' => $order->order_id,
'status' => $order->status,
'totals' => [
- 'ctn' => $order->ctn,
- 'qty' => $order->qty,
- 'ttl_qty' => $order->ttl_qty,
- 'cbm' => $order->cbm,
- 'ttl_cbm' => $order->ttl_cbm,
- 'kg' => $order->kg,
- 'ttl_kg' => $order->ttl_kg,
- 'amount' => $order->ttl_amount,
+ 'ctn' => $order->ctn,
+ 'qty' => $order->qty,
+ 'ttl_qty' => $order->ttl_qty,
+ 'cbm' => $order->cbm,
+ 'ttl_cbm' => $order->ttl_cbm,
+ 'kg' => $order->kg,
+ 'ttl_kg' => $order->ttl_kg,
+ 'amount' => $order->ttl_amount,
],
'items' => $order->items,
];
@@ -365,29 +422,6 @@ class AdminOrderController extends Controller
}
$invoiceData = null;
- if ($order->invoice) {
- $invoice = $order->invoice;
- $invoiceData = [
- 'invoice_no' => $invoice->invoice_number,
- 'status' => $invoice->status,
- 'invoice_date' => $invoice->invoice_date,
- 'due_date' => $invoice->due_date,
- 'customer' => [
- 'name' => $invoice->customer_name,
- 'mobile' => $invoice->customer_mobile,
- 'email' => $invoice->customer_email,
- 'address' => $invoice->customer_address,
- 'pincode' => $invoice->pincode,
- ],
- 'items' => $invoice->items,
- 'summary' => [
- 'amount' => $invoice->final_amount,
- 'cgst' => 0,
- 'sgst' => 0,
- 'total' => $invoice->final_amount_with_gst,
- ],
- ];
- }
return view('admin.see_order', compact(
'order',
@@ -398,14 +432,13 @@ class AdminOrderController extends Controller
}
/* ---------------------------
- * FILTERED LIST + EXPORTS
+ * FILTERED LIST + EXPORTS (old orders listing)
* ---------------------------*/
public function orderShow()
{
$orders = Order::with([
'markList',
'shipments',
- 'invoice'
])->latest('id')->get();
return view('admin.orders', compact('orders'));
@@ -414,7 +447,7 @@ class AdminOrderController extends Controller
private function buildOrdersQueryFromRequest(Request $request)
{
$query = Order::query()
- ->with(['markList', 'invoice', 'shipments']);
+ ->with(['markList', 'shipments']);
if ($request->filled('search')) {
$search = trim($request->search);
@@ -427,23 +460,12 @@ class AdminOrderController extends Controller
->orWhere('origin', 'like', "%{$search}%")
->orWhere('destination', 'like', "%{$search}%");
})
- ->orWhereHas('invoice', function ($q3) use ($search) {
- $q3->where('invoice_number', 'like', "%{$search}%");
- })
->orWhereHas('shipments', function ($q4) use ($search) {
$q4->where('shipments.shipment_id', 'like', "%{$search}%");
});
});
}
- if ($request->filled('status')) {
- $query->where(function ($q) use ($request) {
- $q->whereHas('invoice', function ($q2) use ($request) {
- $q2->where('status', $request->status);
- })->orWhereDoesntHave('invoice');
- });
- }
-
if ($request->filled('shipment')) {
$query->where(function ($q) use ($request) {
$q->whereHas('shipments', function ($q2) use ($request) {
@@ -465,62 +487,83 @@ class AdminOrderController extends Controller
public function downloadPdf(Request $request)
{
- $orders = $this->buildOrdersQueryFromRequest($request)->get();
- $filters = [
- 'search' => $request->search,
- 'status' => $request->status,
- 'shipment' => $request->shipment,
- 'from' => $request->from_date,
- 'to' => $request->to_date,
- ];
-
- $pdf = PDF::loadView('admin.orders.pdf', compact('orders', 'filters'))
+ $invoices = DB::table('invoices')
+ ->leftJoin('containers', 'containers.id', '=', 'invoices.container_id')
+ ->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no')
+ ->select(
+ 'invoices.invoice_number',
+ 'invoices.invoice_date',
+ 'invoices.mark_no',
+ 'containers.container_number',
+ 'containers.container_date',
+ 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'),
+ 'invoices.final_amount',
+ 'invoices.final_amount_with_gst',
+ 'invoices.status as invoice_status'
+ )
+ ->when($request->filled('search'), function ($q) use ($request) {
+ $search = trim($request->search);
+ $q->where(function ($qq) use ($search) {
+ $qq->where('invoices.invoice_number', 'like', "%{$search}%")
+ ->orWhere('invoices.mark_no', 'like', "%{$search}%")
+ ->orWhere('containers.container_number', 'like', "%{$search}%")
+ ->orWhere('mark_list.company_name', 'like', "%{$search}%")
+ ->orWhere('mark_list.customer_name', 'like', "%{$search}%");
+ });
+ })
+ ->when($request->filled('status'), function ($q) use ($request) {
+ $q->where('invoices.status', $request->status);
+ })
+ ->when($request->filled('from_date'), function ($q) use ($request) {
+ $q->whereDate('invoices.invoice_date', '>=', $request->from_date);
+ })
+ ->when($request->filled('to_date'), function ($q) use ($request) {
+ $q->whereDate('invoices.invoice_date', '<=', $request->to_date);
+ })
+ ->orderByDesc('containers.container_date')
+ ->orderByDesc('invoices.id')
+ ->get();
+
+ $pdf = PDF::loadView('admin.pdf.invoices_report', compact('invoices'))
->setPaper('a4', 'landscape');
-
+
return $pdf->download(
- 'orders-report-' . now()->format('Y-m-d') . '.pdf'
+ 'invoices-report-' . now()->format('Y-m-d') . '.pdf'
);
}
-
+
public function downloadExcel(Request $request)
{
return Excel::download(
- new OrdersExport($request),
- 'orders-report-' . now()->format('Y-m-d') . '.xlsx'
+ new InvoicesExport($request),
+ 'invoices-report-' . now()->format('Y-m-d') . '.xlsx'
);
}
/* --------------------------------------------------
* NEW: Create Order + Invoice directly from popup
- * route: admin.orders.temp.add (Create New Order form)
* --------------------------------------------------*/
public function addTempItem(Request $request)
{
- // 1) order-level fields
$request->validate([
'mark_no' => 'required',
- 'origin' => 'required',
- 'destination' => 'required',
+ 'origin' => 'nullable',
+ 'destination' => 'nullable',
]);
- // 2) multi-row items
$items = $request->validate([
'items' => 'required|array',
'items.*.description' => 'required|string',
'items.*.ctn' => 'nullable|numeric',
'items.*.qty' => 'nullable|numeric',
-
'items.*.unit' => 'nullable|string',
'items.*.price' => 'nullable|numeric',
-
'items.*.cbm' => 'nullable|numeric',
-
'items.*.kg' => 'nullable|numeric',
-
'items.*.shop_no' => 'nullable|string',
])['items'];
- // रिकामे rows काढा
$items = array_filter($items, function ($row) {
return trim($row['description'] ?? '') !== '';
});
@@ -529,38 +572,31 @@ class AdminOrderController extends Controller
return back()->with('error', 'Add at least one item.');
}
- // ✅ BACKEND CALCULATION (DO NOT TRUST FRONTEND)
foreach ($items as &$item) {
-
$ctn = (float) ($item['ctn'] ?? 0);
$qty = (float) ($item['qty'] ?? 0);
$price = (float) ($item['price'] ?? 0);
$cbm = (float) ($item['cbm'] ?? 0);
$kg = (float) ($item['kg'] ?? 0);
- // Calculated fields
$item['ttl_qty'] = $ctn * $qty;
$item['ttl_amount'] = $item['ttl_qty'] * $price;
$item['ttl_cbm'] = $cbm * $ctn;
$item['ttl_kg'] = $ctn * $kg;
}
- unset($item); // VERY IMPORTANT
+ unset($item);
+ $total_ctn = array_sum(array_column($items, 'ctn'));
+ $total_qty = array_sum(array_column($items, 'qty'));
+ $total_ttl_qty = array_sum(array_column($items, 'ttl_qty'));
+ $total_amount = array_sum(array_column($items, 'ttl_amount'));
+ $total_cbm = array_sum(array_column($items, 'cbm'));
+ $total_ttl_cbm = array_sum(array_column($items, 'ttl_cbm'));
+ $total_kg = array_sum(array_column($items, 'kg'));
+ $total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
- // 3) totals
- $total_ctn = array_sum(array_column($items, 'ctn'));
- $total_qty = array_sum(array_column($items, 'qty'));
- $total_ttl_qty = array_sum(array_column($items, 'ttl_qty'));
- $total_amount = array_sum(array_column($items, 'ttl_amount'));
- $total_cbm = array_sum(array_column($items, 'cbm'));
- $total_ttl_cbm = array_sum(array_column($items, 'ttl_cbm'));
- $total_kg = array_sum(array_column($items, 'kg'));
- $total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
-
- // 4) order id generate
$orderId = $this->generateOrderId();
- // 5) order create
$order = Order::create([
'order_id' => $orderId,
'mark_no' => $request->mark_no,
@@ -577,7 +613,6 @@ class AdminOrderController extends Controller
'status' => 'order_placed',
]);
- // 6) order items
foreach ($items as $item) {
OrderItem::create([
'order_id' => $order->id,
@@ -596,17 +631,14 @@ class AdminOrderController extends Controller
]);
}
- // 7) invoice number
$invoiceNumber = $this->generateInvoiceNumber();
- // 8) customer fetch
$markList = MarkList::where('mark_no', $order->mark_no)->first();
$customer = null;
if ($markList && $markList->customer_id) {
$customer = User::where('customer_id', $markList->customer_id)->first();
}
- // 9) invoice create
$invoice = Invoice::create([
'order_id' => $order->id,
'customer_id' => $customer->id ?? null,
@@ -631,7 +663,6 @@ class AdminOrderController extends Controller
'pdf_path' => null,
]);
- // 10) invoice items
foreach ($order->items as $item) {
InvoiceItem::create([
'invoice_id' => $invoice->id,
@@ -658,114 +689,72 @@ class AdminOrderController extends Controller
* UPDATE ORDER ITEM (existing orders)
* ---------------------------*/
public function updateItem(Request $request, $id)
-{
- $item = OrderItem::findOrFail($id);
- $order = $item->order;
-
- $request->validate([
- 'description' => 'required|string',
- 'ctn' => 'nullable|numeric',
- 'qty' => 'nullable|numeric',
- 'unit' => 'nullable|string',
- 'price' => 'nullable|numeric',
- 'cbm' => 'nullable|numeric',
- 'kg' => 'nullable|numeric',
- 'shop_no' => 'nullable|string',
- ]);
-
- // ✅ BACKEND CALCULATION
- $ctn = (float) ($request->ctn ?? 0);
- $qty = (float) ($request->qty ?? 0);
- $price = (float) ($request->price ?? 0);
- $cbm = (float) ($request->cbm ?? 0);
- $kg = (float) ($request->kg ?? 0);
-
- $item->update([
- 'description' => $request->description,
- 'ctn' => $ctn,
- 'qty' => $qty,
- 'ttl_qty' => $ctn * $qty,
- 'unit' => $request->unit,
- 'price' => $price,
- 'ttl_amount' => ($ctn * $qty) * $price,
- 'cbm' => $cbm,
- 'ttl_cbm' => $cbm * $ctn,
- 'kg' => $kg,
- 'ttl_kg' => $ctn * $kg,
- 'shop_no' => $request->shop_no,
- ]);
-
- $this->recalcTotals($order);
- $this->updateInvoiceFromOrder($order);
-
- return back()->with('success', 'Item updated successfully');
-}
-
-
- private function updateInvoiceFromOrder(Order $order)
{
- $invoice = Invoice::where('order_id', $order->id)->first();
+ $item = OrderItem::findOrFail($id);
+ $order = $item->order;
- if (!$invoice) {
- return;
- }
-
- $invoice->final_amount = $order->ttl_amount;
- $invoice->gst_percent = 0;
- $invoice->gst_amount = 0;
- $invoice->final_amount_with_gst = $order->ttl_amount;
- $invoice->save();
-
- InvoiceItem::where('invoice_id', $invoice->id)->delete();
-
- foreach ($order->items as $item) {
- InvoiceItem::create([
- 'invoice_id' => $invoice->id,
- 'description' => $item->description,
- 'ctn' => $item->ctn,
- 'qty' => $item->qty,
- 'ttl_qty' => $item->ttl_qty,
- 'unit' => $item->unit,
- 'price' => $item->price,
- 'ttl_amount' => $item->ttl_amount,
- 'cbm' => $item->cbm,
- 'ttl_cbm' => $item->ttl_cbm,
- 'kg' => $item->kg,
- 'ttl_kg' => $item->ttl_kg,
- 'shop_no' => $item->shop_no,
- ]);
- }
- }
-
-
-
-public function uploadExcelPreview(Request $request)
-{
- try {
$request->validate([
- 'excel' => 'required|file|mimes:xlsx,xls'
+ 'description' => 'required|string',
+ 'ctn' => 'nullable|numeric',
+ 'qty' => 'nullable|numeric',
+ 'unit' => 'nullable|string',
+ 'price' => 'nullable|numeric',
+ 'cbm' => 'nullable|numeric',
+ 'kg' => 'nullable|numeric',
+ 'shop_no' => 'nullable|string',
]);
- $import = new OrderItemsPreviewImport();
- Excel::import($import, $request->file('excel'));
+ $ctn = (float) ($request->ctn ?? 0);
+ $qty = (float) ($request->qty ?? 0);
+ $price = (float) ($request->price ?? 0);
+ $cbm = (float) ($request->cbm ?? 0);
+ $kg = (float) ($request->kg ?? 0);
- return response()->json([
- 'success' => true,
- 'items' => $import->rows
+ $item->update([
+ 'description' => $request->description,
+ 'ctn' => $ctn,
+ 'qty' => $qty,
+ 'ttl_qty' => $ctn * $qty,
+ 'unit' => $request->unit,
+ 'price' => $price,
+ 'ttl_amount' => ($ctn * $qty) * $price,
+ 'cbm' => $cbm,
+ 'ttl_cbm' => $cbm * $ctn,
+ 'kg' => $kg,
+ 'ttl_kg' => $ctn * $kg,
+ 'shop_no' => $request->shop_no,
]);
- } catch (ValidationException $e) {
- return response()->json([
- 'success' => false,
- 'message' => 'Invalid Excel file format'
- ], 422);
- } catch (\Throwable $e) {
- \Log::error($e);
- return response()->json([
- 'success' => false,
- 'message' => 'Server error'
- ], 500);
+
+ $this->recalcTotals($order);
+
+ return back()->with('success', 'Item updated successfully');
+ }
+
+ public function uploadExcelPreview(Request $request)
+ {
+ try {
+ $request->validate([
+ 'excel' => 'required|file|mimes:xlsx,xls'
+ ]);
+
+ $import = new OrderItemsPreviewImport();
+ Excel::import($import, $request->file('excel'));
+
+ return response()->json([
+ 'success' => true,
+ 'items' => $import->rows
+ ]);
+ } catch (ValidationException $e) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Invalid Excel file format'
+ ], 422);
+ } catch (\Throwable $e) {
+ \Log::error($e);
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Server error'
+ ], 500);
+ }
}
}
-
-
-}
diff --git a/app/Http/Controllers/Admin/AdminReportController.php b/app/Http/Controllers/Admin/AdminReportController.php
index c47cb62..60f0dab 100644
--- a/app/Http/Controllers/Admin/AdminReportController.php
+++ b/app/Http/Controllers/Admin/AdminReportController.php
@@ -3,7 +3,6 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
-use App\Models\Order;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
@@ -12,45 +11,98 @@ class AdminReportController extends Controller
/**
* Display the reports page with joined data
*/
- public function index(Request $request)
- {
- // -------------------------------
- // FETCH REPORT DATA
- // ONLY orders that have BOTH:
- // 1. Invoice
- // 2. Shipment
- // -------------------------------
+ // 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)
+ {
+ $reports = DB::table('invoices')
+ ->join('containers', 'containers.id', '=', 'invoices.container_id')
+ ->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no')
->select(
- 'orders.id as order_pk',
- 'orders.order_id',
- 'orders.mark_no',
- 'orders.origin',
- 'orders.destination',
-
- 'shipments.id as shipment_pk',
- 'shipments.shipment_id',
- 'shipments.status as shipment_status',
- 'shipments.shipment_date',
-
+ // INVOICE
+ 'invoices.id as invoicepk',
'invoices.invoice_number',
'invoices.invoice_date',
'invoices.final_amount',
- 'invoices.status as invoice_status',
-
- 'mark_list.company_name',
- 'mark_list.customer_name'
+ 'invoices.final_amount_with_gst',
+ 'invoices.gst_percent',
+ 'invoices.gst_amount',
+ 'invoices.status as invoicestatus',
+ 'invoices.mark_no',
+
+ // CONTAINER
+ 'containers.id as containerpk',
+ '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')
- ->orderBy('shipments.shipment_date', 'desc')
->get();
-
+
return view('admin.reports', compact('reports'));
}
+
}
diff --git a/app/Http/Controllers/ContainerController.php b/app/Http/Controllers/ContainerController.php
new file mode 100644
index 0000000..af3ecd8
--- /dev/null
+++ b/app/Http/Controllers/ContainerController.php
@@ -0,0 +1,755 @@
+latest()->get();
+
+ $containers->each(function ($container) {
+ $rows = $container->rows;
+
+ $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'];
+
+ $getFirstNumeric = function (array $data, array $possibleKeys) {
+ $normalizedMap = [];
+
+ foreach ($data as $key => $value) {
+ if ($key === null || $key === '') continue;
+
+ $normKey = strtoupper((string)$key);
+ $normKey = str_replace([' ', '/', '-', '.'], '', $normKey);
+ $normalizedMap[$normKey] = $value;
+ }
+
+ foreach ($possibleKeys as $search) {
+ $normSearch = strtoupper($search);
+ $normSearch = str_replace([' ', '/', '-', '.'], '', $normSearch);
+
+ foreach ($normalizedMap as $nKey => $value) {
+ if (
+ strpos($nKey, $normSearch) !== false &&
+ (is_numeric($value) || (is_string($value) && is_numeric(trim($value))))
+ ) {
+ return (float) trim($value);
+ }
+ }
+ }
+
+ return 0;
+ };
+
+ foreach ($rows as $row) {
+ $data = $row->data ?? [];
+
+ $totalCtn += $getFirstNumeric($data, $ctnKeys);
+ $totalQty += $getFirstNumeric($data, $qtyKeys);
+ $totalCbm += $getFirstNumeric($data, $cbmKeys);
+ $totalKg += $getFirstNumeric($data, $kgKeys);
+ }
+
+ $container->summary = [
+ 'total_ctn' => round($totalCtn, 2),
+ 'total_qty' => round($totalQty, 2),
+ 'total_cbm' => round($totalCbm, 3),
+ 'total_kg' => round($totalKg, 2),
+ ];
+ });
+
+ return view('admin.container', compact('containers'));
+ }
+
+ public function create()
+ {
+ return view('admin.container_create');
+ }
+
+ private function isValidExcelFormat($rows, $header)
+ {
+ if (empty($header) || count($rows) < 2) return false;
+
+ $validKeywords = [
+ 'MARK', 'DESCRIPTION', 'DESC', 'CTN', 'CTNS', 'QTY', 'TOTALQTY', 'ITLQTY', 'ITL QTY',
+ 'UNIT', 'CBM', 'TOTAL CBM', 'KG', 'TOTAL KG',
+ 'METAL BUCKLE', 'WATCH MOVEMENT', 'STEEL BOTTLE',
+ 'MEHULPAID', 'ITEM NO', 'ITEM NO.', 'SAHILPAID', 'PINAKIN', 'GST',
+ 'MOON LAMP', 'TRANSPARENT BOTTLE', 'PLASTIC FONDANT',
+ ];
+
+ $headerText = implode(' ', array_map('strtoupper', $header));
+ $requiredHeaders = ['CTN', 'QTY', 'DESCRIPTION', 'DESC'];
+
+ $hasValidHeaders = false;
+ foreach ($requiredHeaders as $key) {
+ if (stripos($headerText, $key) !== false) {
+ $hasValidHeaders = true;
+ break;
+ }
+ }
+
+ if (!$hasValidHeaders) return false;
+
+ $dataPreview = '';
+ for ($i = 0; $i < min(5, count($rows)); $i++) {
+ $rowText = implode(' ', array_slice($rows[$i], 0, 10));
+ $dataPreview .= ' ' . strtoupper((string)$rowText);
+ }
+
+ $validMatches = 0;
+ foreach ($validKeywords as $keyword) {
+ if (stripos($headerText . $dataPreview, strtoupper($keyword)) !== false) {
+ $validMatches++;
+ }
+ }
+
+ return $validMatches >= 3;
+ }
+
+ private function normalizeKey($value): string
+ {
+ $norm = strtoupper((string)$value);
+ return str_replace([' ', '/', '-', '.'], '', $norm);
+ }
+
+ public function store(Request $request)
+ {
+ $request->validate([
+ 'container_name' => 'required|string',
+ 'container_number' => 'required|string|unique:containers,container_number',
+ 'container_date' => 'required|date',
+ 'excel_file' => 'required|file|mimes:xls,xlsx',
+ ]);
+
+ $file = $request->file('excel_file');
+ $sheets = Excel::toArray([], $file);
+ $rows = $sheets[0] ?? [];
+
+ if (count($rows) < 2) {
+ return back()
+ ->withErrors(['excel_file' => 'Excel file is empty.'])
+ ->withInput();
+ }
+
+ // HEADER DETECTION
+ $headerRowIndex = null;
+ $header = [];
+
+ foreach ($rows as $i => $row) {
+ $trimmed = array_map(fn($v) => trim((string)$v), $row);
+ $nonEmpty = array_filter($trimmed, fn($v) => $v !== '');
+ if (empty($nonEmpty)) continue;
+
+ if (count($nonEmpty) >= 4) {
+ $headerRowIndex = $i;
+ $header = $trimmed;
+ break;
+ }
+ }
+
+ if ($headerRowIndex === null) {
+ return back()
+ ->withErrors(['excel_file' => 'Header row not found in Excel.'])
+ ->withInput();
+ }
+
+ if (!$this->isValidExcelFormat($rows, $header)) {
+ return back()
+ ->withErrors(['excel_file' => 'Only MEHUL / SAHIL / PINAKIN / GST loading list formats allowed.'])
+ ->withInput();
+ }
+
+ // COLUMN INDEXES
+ $essentialColumns = [
+ 'desc_col' => null,
+ 'ctn_col' => null,
+ 'qty_col' => null,
+ 'totalqty_col' => null,
+ 'unit_col' => null,
+ 'price_col' => null,
+ 'amount_col' => null,
+ 'cbm_col' => null,
+ 'totalcbm_col' => null,
+ 'kg_col' => null,
+ 'totalkg_col' => null,
+ 'itemno_col' => null,
+ ];
+
+ foreach ($header as $colIndex => $headingText) {
+ if (empty($headingText)) continue;
+
+ $normalized = $this->normalizeKey($headingText);
+
+ if (strpos($normalized, 'DESCRIPTION') !== false || strpos($normalized, 'DESC') !== false) {
+ $essentialColumns['desc_col'] = $colIndex;
+ } elseif (strpos($normalized, 'CTN') !== false || strpos($normalized, 'CTNS') !== false) {
+ $essentialColumns['ctn_col'] = $colIndex;
+ } elseif (
+ strpos($normalized, 'ITLQTY') !== false ||
+ strpos($normalized, 'TOTALQTY') !== false ||
+ strpos($normalized, 'TTLQTY') !== false
+ ) {
+ $essentialColumns['totalqty_col'] = $colIndex;
+ } elseif (strpos($normalized, 'QTY') !== false) {
+ $essentialColumns['qty_col'] = $colIndex;
+ } elseif (strpos($normalized, 'UNIT') !== false) {
+ $essentialColumns['unit_col'] = $colIndex;
+ } elseif (strpos($normalized, 'PRICE') !== false) {
+ $essentialColumns['price_col'] = $colIndex;
+ } elseif (strpos($normalized, 'AMOUNT') !== false) {
+ $essentialColumns['amount_col'] = $colIndex;
+ } elseif (strpos($normalized, 'TOTALCBM') !== false || strpos($normalized, 'ITLCBM') !== false) {
+ $essentialColumns['totalcbm_col'] = $colIndex;
+ } elseif (strpos($normalized, 'CBM') !== false) {
+ $essentialColumns['cbm_col'] = $colIndex;
+ } elseif (strpos($normalized, 'TOTALKG') !== false || strpos($normalized, 'TTKG') !== false) {
+ $essentialColumns['totalkg_col'] = $colIndex;
+ } elseif (strpos($normalized, 'KG') !== false) {
+ $essentialColumns['kg_col'] = $colIndex;
+ } elseif (
+ strpos($normalized, 'MARKNO') !== false ||
+ strpos($normalized, 'MARK') !== false ||
+ strpos($normalized, 'ITEMNO') !== false ||
+ strpos($normalized, 'ITEM') !== false
+ ) {
+ $essentialColumns['itemno_col'] = $colIndex;
+ }
+ }
+
+ if (is_null($essentialColumns['itemno_col'])) {
+ return back()
+ ->withErrors(['excel_file' => 'Mark / Item column not found in Excel (expected headers like MARK NO / Mark_No / Item_No).'])
+ ->withInput();
+ }
+
+ // ROWS CLEANING
+ $dataRows = array_slice($rows, $headerRowIndex + 1);
+ $cleanedRows = [];
+ $unmatchedRowsData = [];
+
+ foreach ($dataRows as $offset => $row) {
+ $trimmedRow = array_map(fn($v) => trim((string)$v), $row);
+ $nonEmptyCells = array_filter($trimmedRow, fn($v) => $v !== '');
+ if (count($nonEmptyCells) < 2) continue;
+
+ $rowText = strtoupper(implode(' ', $trimmedRow));
+ if (
+ stripos($rowText, 'TOTAL') !== false ||
+ stripos($rowText, 'TTL') !== false ||
+ stripos($rowText, 'GRAND') !== false
+ ) {
+ continue;
+ }
+
+ $descValue = '';
+ if ($essentialColumns['desc_col'] !== null) {
+ $descValue = trim($row[$essentialColumns['desc_col']] ?? '');
+ }
+
+ if ($essentialColumns['desc_col'] !== null && $descValue === '' && count($nonEmptyCells) >= 1) {
+ continue;
+ }
+
+ $cleanedRows[] = [
+ 'row' => $row,
+ 'offset' => $offset,
+ ];
+ }
+
+ if (empty($cleanedRows)) {
+ return back()
+ ->withErrors(['excel_file' => 'No valid item rows found in Excel.'])
+ ->withInput();
+ }
+
+ /*
+ * FORMULA CHECK – UPDATED WITH AMOUNT FIX + NUMBER SANITIZER
+ */
+
+ $cleanNumber = function ($value) {
+ if (is_string($value)) {
+ $value = str_replace(',', '', trim($value));
+ }
+ return is_numeric($value) ? (float)$value : 0;
+ };
+
+ $formulaErrors = [];
+
+ foreach ($cleanedRows as $item) {
+ $row = $item['row'];
+ $offset = $item['offset'];
+
+ $ctn = $essentialColumns['ctn_col'] !== null ? $cleanNumber($row[$essentialColumns['ctn_col']] ?? 0) : 0;
+ $qty = $essentialColumns['qty_col'] !== null ? $cleanNumber($row[$essentialColumns['qty_col']] ?? 0) : 0;
+ $ttlQ = $essentialColumns['totalqty_col'] !== null ? $cleanNumber($row[$essentialColumns['totalqty_col']] ?? 0) : 0;
+ $cbm = $essentialColumns['cbm_col'] !== null ? $cleanNumber($row[$essentialColumns['cbm_col']] ?? 0) : 0;
+ $ttlC = $essentialColumns['totalcbm_col'] !== null ? $cleanNumber($row[$essentialColumns['totalcbm_col']] ?? 0) : 0;
+ $kg = $essentialColumns['kg_col'] !== null ? $cleanNumber($row[$essentialColumns['kg_col']] ?? 0) : 0;
+ $ttlK = $essentialColumns['totalkg_col'] !== null ? $cleanNumber($row[$essentialColumns['totalkg_col']] ?? 0) : 0;
+
+ $price = $essentialColumns['price_col'] !== null ? $cleanNumber($row[$essentialColumns['price_col']] ?? 0) : 0;
+ $ttlAmount = $essentialColumns['amount_col'] !== null ? $cleanNumber($row[$essentialColumns['amount_col']] ?? 0) : 0;
+
+ $desc = $essentialColumns['desc_col'] !== null ? (string)($row[$essentialColumns['desc_col']] ?? '') : '';
+ $mark = $essentialColumns['itemno_col'] !== null ? (string)($row[$essentialColumns['itemno_col']] ?? '') : '';
+
+ // expected
+ $expTtlQty = $qty * $ctn;
+ $expTtlCbm = $cbm * $ctn;
+ $expTtlKg = $kg * $ctn;
+ $expTtlAmount = ($qty * $ctn) * $price;
+
+ $rowErrors = [];
+
+ if (abs($ttlQ - $expTtlQty) > 0.01) {
+ $rowErrors['TOTAL QTY'] = [
+ 'actual' => $ttlQ,
+ 'expected' => $expTtlQty,
+ ];
+ }
+
+ if (abs($ttlC - $expTtlCbm) > 0.0005) {
+ $rowErrors['TOTAL CBM'] = [
+ 'actual' => $ttlC,
+ 'expected' => $expTtlCbm,
+ ];
+ }
+
+ if (abs($ttlK - $expTtlKg) > 0.01) {
+ $rowErrors['TOTAL KG'] = [
+ 'actual' => $ttlK,
+ 'expected' => $expTtlKg,
+ ];
+ }
+
+ if ($essentialColumns['amount_col'] !== null && $essentialColumns['price_col'] !== null) {
+ if (abs($ttlAmount - $expTtlAmount) > 0.01) {
+ $rowErrors['TOTAL AMOUNT'] = [
+ 'actual' => $ttlAmount,
+ 'expected' => $expTtlAmount,
+ ];
+ }
+ }
+
+ if (!empty($rowErrors)) {
+ // full row data map for excel table
+ $rowData = [];
+ foreach ($header as $colIndex => $headingText) {
+ $value = $row[$colIndex] ?? null;
+ if (is_string($value)) $value = trim($value);
+ $rowData[$headingText] = $value;
+ }
+
+ $formulaErrors[] = [
+ 'excel_row' => $headerRowIndex + 1 + $offset,
+ 'mark_no' => $mark,
+ 'description' => $desc,
+ 'errors' => $rowErrors,
+ 'data' => $rowData,
+ ];
+ }
+ }
+
+ // MARK CHECK: strict - collect ALL marks + unmatched rows
+ $marksFromExcel = [];
+ foreach ($cleanedRows as $item) {
+ $row = $item['row'];
+ $rawMark = $row[$essentialColumns['itemno_col']] ?? null;
+ $mark = trim((string)($rawMark ?? ''));
+ if ($mark !== '') {
+ $marksFromExcel[] = $mark;
+ }
+ }
+
+ $marksFromExcel = array_values(array_unique($marksFromExcel));
+
+ if (empty($marksFromExcel)) {
+ return back()
+ ->withErrors(['excel_file' => 'No mark numbers found in Excel file.'])
+ ->withInput();
+ }
+
+ $validMarks = MarkList::whereIn('mark_no', $marksFromExcel)
+ ->where('status', 'active')
+ ->pluck('mark_no')
+ ->toArray();
+
+ $unmatchedMarks = array_values(array_diff($marksFromExcel, $validMarks));
+
+ $markErrors = [];
+
+ if (!empty($unmatchedMarks)) {
+
+ foreach ($cleanedRows as $item) {
+ $row = $item['row'];
+ $offset = $item['offset'];
+ $rowMark = trim((string)($row[$essentialColumns['itemno_col']] ?? ''));
+
+ if ($rowMark === '' || !in_array($rowMark, $unmatchedMarks)) {
+ continue;
+ }
+
+ $rowData = [];
+ foreach ($header as $colIndex => $headingText) {
+ $value = $row[$colIndex] ?? null;
+ if (is_string($value)) $value = trim($value);
+ $rowData[$headingText] = $value;
+ }
+
+ $markErrors[] = [
+ 'excel_row' => $headerRowIndex + 1 + $offset,
+ 'mark_no' => $rowMark,
+ 'data' => $rowData,
+ ];
+ }
+ }
+
+ if (!empty($formulaErrors) || !empty($markErrors)) {
+ return back()
+ ->withInput()
+ ->with([
+ 'formula_errors' => $formulaErrors,
+ 'mark_errors' => $markErrors,
+ ]);
+ }
+
+ // STEP 1: Marks → customers mapping + grouping
+ $markRecords = MarkList::whereIn('mark_no', $marksFromExcel)
+ ->where('status', 'active')
+ ->get();
+
+ $markToCustomerId = [];
+ $markToSnapshot = [];
+
+ foreach ($markRecords as $mr) {
+ $markToCustomerId[$mr->mark_no] = $mr->customer_id;
+
+ $markToSnapshot[$mr->mark_no] = [
+ 'customer_name' => $mr->customer_name,
+ 'company_name' => $mr->company_name,
+ 'mobile_no' => $mr->mobile_no,
+ ];
+ }
+
+ $groupedByCustomer = [];
+
+ foreach ($cleanedRows as $item) {
+ $row = $item['row'];
+ $offset = $item['offset'];
+
+ $rawMark = $row[$essentialColumns['itemno_col']] ?? null;
+ $mark = trim((string)($rawMark ?? ''));
+
+ if ($mark === '') {
+ continue;
+ }
+
+ $customerId = $markToCustomerId[$mark] ?? null;
+ if (!$customerId) {
+ continue;
+ }
+
+ if (!isset($groupedByCustomer[$customerId])) {
+ $groupedByCustomer[$customerId] = [];
+ }
+
+ $groupedByCustomer[$customerId][] = [
+ 'row' => $row,
+ 'offset' => $offset,
+ 'mark' => $mark,
+ ];
+ }
+
+ // STEP 2: Container + ContainerRows save
+ $container = Container::create([
+ 'container_name' => $request->container_name,
+ 'container_number' => $request->container_number,
+ 'container_date' => $request->container_date,
+ 'status' => 'pending',
+ ]);
+
+ $path = $file->store('containers');
+ $container->update(['excel_file' => $path]);
+
+ $savedCount = 0;
+
+ foreach ($cleanedRows as $item) {
+ $row = $item['row'];
+ $offset = $item['offset'];
+
+ $data = [];
+ foreach ($header as $colIndex => $headingText) {
+ $value = $row[$colIndex] ?? null;
+ if (is_string($value)) $value = trim($value);
+ $data[$headingText] = $value;
+ }
+
+ ContainerRow::create([
+ 'container_id' => $container->id,
+ 'row_index' => $headerRowIndex + 1 + $offset,
+ 'data' => $data,
+ ]);
+
+ $savedCount++;
+ }
+
+ // STEP 3: per-customer invoices + invoice items
+ $invoiceCount = 0;
+
+ foreach ($groupedByCustomer as $customerId => $rowsForCustomer) {
+ if (empty($rowsForCustomer)) {
+ continue;
+ }
+
+ $firstMark = $rowsForCustomer[0]['mark'];
+ $snap = $markToSnapshot[$firstMark] ?? null;
+
+ $invoice = new Invoice();
+ $invoice->container_id = $container->id;
+ // $invoice->customer_id = $customerId;
+
+ // इथे Mark No सेट करतो
+ $invoice->mark_no = $firstMark;
+
+ $invoice->invoice_number = $this->generateInvoiceNumber();
+ $invoice->invoice_date = now()->toDateString();
+ $invoice->due_date = null;
+
+ if ($snap) {
+ $invoice->customer_name = $snap['customer_name'] ?? null;
+ $invoice->company_name = $snap['company_name'] ?? null;
+ $invoice->customer_mobile = $snap['mobile_no'] ?? null;
+ }
+
+ $invoice->final_amount = 0;
+ $invoice->gst_percent = 0;
+ $invoice->gst_amount = 0;
+ $invoice->final_amount_with_gst = 0;
+
+ $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);
+ $invoice->pdf_path = null;
+ $invoice->status = 'pending';
+
+ $invoice->save();
+ $invoiceCount++;
+
+ $totalAmount = 0;
+
+ foreach ($rowsForCustomer as $item) {
+ $row = $item['row'];
+ $offset = $item['offset'];
+
+ $description = $essentialColumns['desc_col'] !== null ? ($row[$essentialColumns['desc_col']] ?? null) : null;
+ $ctn = $essentialColumns['ctn_col'] !== null ? (int) ($row[$essentialColumns['ctn_col']] ?? 0) : 0;
+ $qty = $essentialColumns['qty_col'] !== null ? (int) ($row[$essentialColumns['qty_col']] ?? 0) : 0;
+ $ttlQty = $essentialColumns['totalqty_col'] !== null ? (int) ($row[$essentialColumns['totalqty_col']] ?? 0) : $qty;
+ $unit = $essentialColumns['unit_col'] !== null ? ($row[$essentialColumns['unit_col']] ?? null) : null;
+ $price = $essentialColumns['price_col'] !== null ? (float) ($row[$essentialColumns['price_col']] ?? 0) : 0;
+ $ttlAmount = $essentialColumns['amount_col'] !== null ? (float) ($row[$essentialColumns['amount_col']] ?? 0) : 0;
+ $cbm = $essentialColumns['cbm_col'] !== null ? (float) ($row[$essentialColumns['cbm_col']] ?? 0) : 0;
+ $ttlCbm = $essentialColumns['totalcbm_col'] !== null ? (float) ($row[$essentialColumns['totalcbm_col']] ?? $cbm) : $cbm;
+ $kg = $essentialColumns['kg_col'] !== null ? (float) ($row[$essentialColumns['kg_col']] ?? 0) : 0;
+ $ttlKg = $essentialColumns['totalkg_col'] !== null ? (float) ($row[$essentialColumns['totalkg_col']] ?? $kg) : $kg;
+
+ $rowIndex = $headerRowIndex + 1 + $offset;
+
+ InvoiceItem::create([
+ 'invoice_id' => $invoice->id,
+ 'container_id' => $container->id,
+ 'container_row_index' => $rowIndex,
+ 'description' => $description,
+ 'ctn' => $ctn,
+ 'qty' => $qty,
+ 'ttl_qty' => $ttlQty,
+ 'unit' => $unit,
+ 'price' => $price,
+ 'ttl_amount' => $ttlAmount,
+ 'cbm' => $cbm,
+ 'ttl_cbm' => $ttlCbm,
+ 'kg' => $kg,
+ 'ttl_kg' => $ttlKg,
+ 'shop_no' => null,
+ ]);
+
+ $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).";
+ return redirect()->route('containers.index')->with('success', $msg);
+ }
+
+ public function show(Container $container)
+ {
+ $container->load('rows');
+ return view('admin.container_show', compact('container'));
+ }
+
+ public function updateRows(Request $request, Container $container)
+ {
+ $rowsInput = $request->input('rows', []);
+
+ foreach ($rowsInput as $rowId => $cols) {
+ $row = ContainerRow::where('container_id', $container->id)
+ ->where('id', $rowId)
+ ->first();
+
+ if (!$row) continue;
+
+ // original update
+ $data = $row->data ?? [];
+ foreach ($cols as $colHeader => $value) {
+ $data[$colHeader] = $value;
+ }
+ $row->update([
+ 'data' => $data,
+ ]);
+
+ // extra: update linked invoice items & invoice totals
+ $rowIndex = $row->row_index;
+
+ $ctn = (float) ($data['CTN'] ?? $data['CTNS'] ?? 0);
+ $qty = (float) ($data['QTY'] ?? 0);
+ $ttlQ = (float) ($data['TTLQTY'] ?? $data['TOTALQTY'] ?? $data['TTL/QTY'] ?? ($ctn * $qty));
+ $price = (float) ($data['PRICE'] ?? 0);
+ $cbm = (float) ($data['CBM'] ?? 0);
+ $ttlC = (float) ($data['TOTALCBM'] ?? $data['TTL CBM'] ?? ($cbm * $ctn));
+ $kg = (float) ($data['KG'] ?? 0);
+ $ttlK = (float) ($data['TOTALKG'] ?? $data['TTL KG'] ?? ($kg * $ctn));
+ $amount = (float) ($data['AMOUNT'] ?? ($price * $ttlQ));
+ $desc = $data['DESCRIPTION'] ?? $data['DESC'] ?? null;
+
+ $items = InvoiceItem::where('container_id', $container->id)
+ ->where('container_row_index', $rowIndex)
+ ->get();
+
+ foreach ($items as $item) {
+ $item->description = $desc;
+ $item->ctn = $ctn;
+ $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->save();
+
+ $invoice = $item->invoice;
+ if ($invoice) {
+ $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();
+ }
+ }
+ }
+
+ return redirect()
+ ->route('containers.show', $container->id)
+ ->with('success', 'Excel rows updated successfully.');
+ }
+
+ // app/Http/Controllers/ContainerController.php
+public function updateStatus(Request $request, Container $container)
+{
+ $request->validate([
+ 'status' => 'required|in:pending,in-progress,completed,cancelled',
+ ]);
+
+ $container->status = $request->status;
+ $container->save();
+
+ // जर AJAX असेल तर JSON दे
+ if ($request->wantsJson() || $request->ajax()) {
+ return response()->json([
+ 'success' => true,
+ 'status' => $container->status,
+ ]);
+ }
+
+ // normal form submit असेल तर redirect
+ return back()->with('success', 'Container status updated.');
+}
+
+
+ public function destroy(Container $container)
+ {
+ $container->delete();
+ return redirect()->route('containers.index')->with('success', 'Container deleted.');
+ }
+
+ private function generateInvoiceNumber(): string
+ {
+ $year = now()->format('Y');
+
+ $last = Invoice::whereYear('created_at', $year)
+ ->orderBy('id', 'desc')
+ ->first();
+
+ if ($last) {
+ $parts = explode('-', $last->invoice_number);
+ $seq = 0;
+
+ if (count($parts) === 3) {
+ $seq = (int) $parts[2];
+ }
+
+ $nextSeq = $seq + 1;
+ } else {
+ $nextSeq = 1;
+ }
+
+ return 'INV-' . $year . '-' . str_pad($nextSeq, 6, '0', STR_PAD_LEFT);
+ }
+}
diff --git a/app/Models/Container.php b/app/Models/Container.php
new file mode 100644
index 0000000..eedeeb0
--- /dev/null
+++ b/app/Models/Container.php
@@ -0,0 +1,30 @@
+ 'date',
+ ];
+
+ public function rows()
+ {
+ return $this->hasMany(ContainerRow::class);
+ }
+
+ public function invoices()
+ {
+ return $this->hasMany(Invoice::class);
+ }
+}
diff --git a/app/Models/ContainerRow.php b/app/Models/ContainerRow.php
new file mode 100644
index 0000000..9d6059b
--- /dev/null
+++ b/app/Models/ContainerRow.php
@@ -0,0 +1,23 @@
+ 'array',
+ ];
+
+ public function container()
+ {
+ return $this->belongsTo(Container::class);
+ }
+}
diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php
index d9dc394..367766f 100644
--- a/app/Models/Invoice.php
+++ b/app/Models/Invoice.php
@@ -9,41 +9,29 @@ class Invoice extends Model
{
use HasFactory;
- protected $fillable = [
- 'order_id',
- 'customer_id',
- 'mark_no',
-
- 'invoice_number',
- 'invoice_date',
- 'due_date',
-
- 'payment_method',
- 'reference_no',
- 'status',
-
- 'final_amount', // without tax
-
- 'tax_type', // gst / igst
- 'gst_percent', // only used for gst UI input
- 'cgst_percent',
- 'sgst_percent',
- 'igst_percent',
-
- 'gst_amount', // total tax amount
- 'final_amount_with_gst',
-
- 'customer_name',
- 'company_name',
- 'customer_email',
- 'customer_mobile',
- 'customer_address',
- 'pincode',
-
- 'pdf_path',
- 'notes',
-];
-
+ protected $fillable = [
+ 'container_id',
+ 'customer_id',
+ 'mark_no',
+ 'invoice_number',
+ 'invoice_date',
+ 'due_date',
+ 'payment_method',
+ 'reference_no',
+ 'status',
+ 'final_amount',
+ 'gst_percent',
+ 'gst_amount',
+ 'final_amount_with_gst',
+ 'customer_name',
+ 'company_name',
+ 'customer_email',
+ 'customer_mobile',
+ 'customer_address',
+ 'pincode',
+ 'pdf_path',
+ 'notes',
+ ];
/****************************
* Relationships
@@ -54,9 +42,9 @@ class Invoice extends Model
return $this->hasMany(InvoiceItem::class)->orderBy('id', 'ASC');
}
- public function order()
+ public function container()
{
- return $this->belongsTo(Order::class);
+ return $this->belongsTo(Container::class);
}
public function customer()
@@ -64,19 +52,28 @@ class Invoice extends Model
return $this->belongsTo(User::class, 'customer_id');
}
+ public function installments()
+ {
+ return $this->hasMany(InvoiceInstallment::class);
+ }
+
+ // ✅ SINGLE, correct relation
+ public function chargeGroups()
+ {
+ return $this->hasMany(\App\Models\InvoiceChargeGroup::class, 'invoice_id');
+ }
+
/****************************
* Helper Functions
****************************/
- // Auto calculate GST fields (you can call this in controller before saving)
public function calculateTotals()
{
$gst = ($this->final_amount * $this->gst_percent) / 100;
- $this->gst_amount = $gst;
+ $this->gst_amount = $gst;
$this->final_amount_with_gst = $this->final_amount + $gst;
}
- // Check overdue status condition
public function isOverdue()
{
return $this->status === 'pending' && now()->gt($this->due_date);
@@ -84,27 +81,31 @@ class Invoice extends Model
public function getShipment()
{
- return $this->order?->shipments?->first();
+ return null;
}
- public function installments()
+ // ✅ Charge groups total accessor
+ public function getChargeGroupsTotalAttribute()
+ {
+ // relation already loaded असेल तर collection वरून sum होईल
+ return (float) $this->chargeGroups->sum('total_charge');
+ }
+
+ // ✅ Grand total accessor (items + GST + charge groups)
+ public function getGrandTotalWithChargesAttribute()
+ {
+ return (float) ($this->final_amount_with_gst ?? 0) + $this->charge_groups_total;
+ }
+
+ public function totalPaid()
{
- return $this->hasMany(InvoiceInstallment::class);
+ return $this->installments->sum('amount');
}
-// App\Models\Invoice.php
-
-public function totalPaid()
-{
- return $this->installments()->sum('amount');
-}
public function remainingAmount()
{
- return max(
- ($this->final_amount_with_gst ?? 0) - $this->totalPaid(),
- 0
- );
+ return $this->grand_total_with_charges - $this->totalPaid();
}
diff --git a/app/Models/InvoiceChargeGroup.php b/app/Models/InvoiceChargeGroup.php
new file mode 100644
index 0000000..ee7175c
--- /dev/null
+++ b/app/Models/InvoiceChargeGroup.php
@@ -0,0 +1,27 @@
+belongsTo(Invoice::class);
+ }
+
+ public function items()
+ {
+ return $this->hasMany(InvoiceChargeGroupItem::class, 'group_id');
+ }
+}
diff --git a/app/Models/InvoiceChargeGroupItem.php b/app/Models/InvoiceChargeGroupItem.php
new file mode 100644
index 0000000..ab3f18e
--- /dev/null
+++ b/app/Models/InvoiceChargeGroupItem.php
@@ -0,0 +1,23 @@
+belongsTo(InvoiceChargeGroup::class, 'group_id');
+ }
+
+ public function item()
+ {
+ return $this->belongsTo(InvoiceItem::class, 'invoice_item_id');
+ }
+}
diff --git a/app/Models/InvoiceItem.php b/app/Models/InvoiceItem.php
index 9e3a6ff..d56ee0a 100644
--- a/app/Models/InvoiceItem.php
+++ b/app/Models/InvoiceItem.php
@@ -37,4 +37,81 @@ class InvoiceItem extends Model
{
return $this->belongsTo(Invoice::class);
}
+
+
+ public function chargeGroupItems()
+ {
+ return $this->hasMany(InvoiceChargeGroupItem::class, 'invoice_item_id');
+ }
+
+ // हे helper: पहिला group fetch करून त्यावरून rate/total काढणे
+ public function getChargeRateAttribute()
+ {
+ $pivot = $this->chargeGroupItems->first();
+ if (!$pivot || !$pivot->group) {
+ return 0;
+ }
+
+ $group = $pivot->group;
+
+ // basis नुसार या item चा basis value
+ $basis = 0;
+ switch ($group->basis_type) {
+ case 'ttl_qty':
+ $basis = $this->ttl_qty;
+ break;
+ case 'amount':
+ $basis = $this->ttl_amount;
+ break;
+ case 'ttl_cbm':
+ $basis = $this->ttl_cbm;
+ break;
+ case 'ttl_kg':
+ $basis = $this->ttl_kg;
+ break;
+ }
+
+ if ($basis <= 0 || ($group->basis_value ?? 0) <= 0) {
+ return 0;
+ }
+
+ // group चा rate field आधीच आहे, ते direct वापरू
+ return (float) $group->rate;
+ }
+
+ public function getChargeTotalAttribute()
+ {
+ $pivot = $this->chargeGroupItems->first();
+ if (!$pivot || !$pivot->group) {
+ return 0;
+ }
+
+ $group = $pivot->group;
+
+ $basis = 0;
+ switch ($group->basis_type) {
+ case 'ttl_qty':
+ $basis = $this->ttl_qty;
+ break;
+ case 'amount':
+ $basis = $this->ttl_amount;
+ break;
+ case 'ttl_cbm':
+ $basis = $this->ttl_cbm;
+ break;
+ case 'ttl_kg':
+ $basis = $this->ttl_kg;
+ break;
+ }
+
+ if ($basis <= 0 || ($group->basis_value ?? 0) <= 0) {
+ return 0;
+ }
+
+ // per unit rate
+ $rate = (float) $group->rate;
+ // item total = basis * rate
+ return $basis * $rate;
+ }
}
+
diff --git a/app/Models/Order.php b/app/Models/Order.php
index 9362328..3eca49d 100644
--- a/app/Models/Order.php
+++ b/app/Models/Order.php
@@ -58,10 +58,10 @@ class Order extends Model
return $this->belongsToMany(\App\Models\Shipment::class, 'shipment_items', 'order_id', 'shipment_id');
}
- public function invoice()
- {
- return $this->hasOne(\App\Models\Invoice::class, 'order_id', 'id');
- }
+ // public function invoice()
+ // {
+ // return $this->hasOne(\App\Models\Invoice::class, 'order_id', 'id');
+ // }
const STATUS_LABELS = [
diff --git a/database/migrations/2025_11_15_120425_create_invoice_items_table.php b/database/migrations/2025_11_15_120425_create_invoice_items_table.php
index cc4da76..c0ca2e3 100644
--- a/database/migrations/2025_11_15_120425_create_invoice_items_table.php
+++ b/database/migrations/2025_11_15_120425_create_invoice_items_table.php
@@ -33,6 +33,9 @@ class CreateInvoiceItemsTable extends Migration
$table->string('shop_no')->nullable();
$table->timestamps();
+
+ $table->unsignedBigInteger('container_id')->nullable()->after('invoice_id');
+ $table->integer('container_row_index')->nullable()->after('container_id');
// FK
$table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
@@ -49,4 +52,6 @@ class CreateInvoiceItemsTable extends Migration
});
Schema::dropIfExists('invoice_items');
}
+
+
}
diff --git a/database/migrations/2026_02_07_061825_create_containers_table.php b/database/migrations/2026_02_07_061825_create_containers_table.php
new file mode 100644
index 0000000..4e4cbc6
--- /dev/null
+++ b/database/migrations/2026_02_07_061825_create_containers_table.php
@@ -0,0 +1,25 @@
+id();
+ $table->string('container_name');
+ $table->string('container_number')->unique();
+ $table->date('container_date');
+ $table->string('excel_file')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('containers');
+ }
+};
diff --git a/database/migrations/2026_02_07_071829_create_loading_list_items_table.php b/database/migrations/2026_02_07_071829_create_loading_list_items_table.php
new file mode 100644
index 0000000..7f144d8
--- /dev/null
+++ b/database/migrations/2026_02_07_071829_create_loading_list_items_table.php
@@ -0,0 +1,37 @@
+id();
+ $table->foreignId('container_id')
+ ->constrained('containers')
+ ->onDelete('cascade');
+
+ $table->string('mark')->nullable(); // MARK / ITEM NO
+ $table->string('description')->nullable();
+ $table->integer('ctn')->nullable();
+ $table->integer('qty')->nullable();
+ $table->integer('total_qty')->nullable();
+ $table->string('unit')->nullable();
+ $table->decimal('price', 15, 3)->nullable(); // SAHIL format साठी
+ $table->decimal('cbm', 15, 5)->nullable();
+ $table->decimal('total_cbm', 15, 5)->nullable();
+ $table->decimal('kg', 15, 3)->nullable();
+ $table->decimal('total_kg', 15, 3)->nullable();
+
+ $table->timestamps();
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('loading_list_items');
+ }
+};
diff --git a/database/migrations/2026_02_07_113151_create_container_rows_table.php b/database/migrations/2026_02_07_113151_create_container_rows_table.php
new file mode 100644
index 0000000..3df78ac
--- /dev/null
+++ b/database/migrations/2026_02_07_113151_create_container_rows_table.php
@@ -0,0 +1,31 @@
+id();
+ $table->foreignId('container_id')
+ ->constrained('containers')
+ ->onDelete('cascade');
+
+ // Excel मधल्या row क्रमांकासाठी (optional)
+ $table->unsignedInteger('row_index')->nullable();
+
+ // या row चा full data: "heading text" => "cell value"
+ $table->json('data');
+
+ $table->timestamps();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('container_rows');
+ }
+};
diff --git a/database/migrations/2026_02_07_132057_add_status_to_containers_table.php b/database/migrations/2026_02_07_132057_add_status_to_containers_table.php
new file mode 100644
index 0000000..314efcf
--- /dev/null
+++ b/database/migrations/2026_02_07_132057_add_status_to_containers_table.php
@@ -0,0 +1,24 @@
+string('status', 20)
+ ->default('pending')
+ ->after('container_date');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('containers', function (Blueprint $table) {
+ $table->dropColumn('status');
+ });
+ }
+};
diff --git a/database/migrations/2026_02_16_060157_update_invoices_for_container_relation.php b/database/migrations/2026_02_16_060157_update_invoices_for_container_relation.php
new file mode 100644
index 0000000..17ebecb
--- /dev/null
+++ b/database/migrations/2026_02_16_060157_update_invoices_for_container_relation.php
@@ -0,0 +1,43 @@
+dropForeign(['order_id']);
+
+ // 2) order_id column काढा
+ $table->dropColumn('order_id');
+
+ // 3) container_id add करा
+ $table->unsignedBigInteger('container_id')->nullable()->after('id');
+
+ // 4) container_id FK
+ $table->foreign('container_id')
+ ->references('id')
+ ->on('containers')
+ ->onDelete('cascade');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('invoices', function (Blueprint $table) {
+ // rollback: container_id काढून order_id परत add
+ $table->dropForeign(['container_id']);
+ $table->dropColumn('container_id');
+
+ $table->unsignedBigInteger('order_id')->index();
+ $table->foreign('order_id')
+ ->references('id')
+ ->on('orders')
+ ->onDelete('cascade');
+ });
+ }
+};
diff --git a/database/migrations/2026_02_17_163431_create_invoice_charge_groups_table.php b/database/migrations/2026_02_17_163431_create_invoice_charge_groups_table.php
new file mode 100644
index 0000000..f23ef2e
--- /dev/null
+++ b/database/migrations/2026_02_17_163431_create_invoice_charge_groups_table.php
@@ -0,0 +1,37 @@
+id();
+ $table->unsignedBigInteger('invoice_id');
+ $table->string('group_name')->nullable(); // उदा. "FREIGHT", "HANDLING"
+ $table->enum('basis_type', ['ttl_qty', 'amount', 'ttl_cbm', 'ttl_kg']);
+ $table->decimal('basis_value', 15, 3)->default(0); // auto calculate केलेला total basis
+ $table->decimal('rate', 15, 3)->default(0); // per basis rate (helper)
+ $table->decimal('total_charge', 15, 2); // admin नी manually टाकलेला total
+ $table->timestamps();
+
+ $table->foreign('invoice_id')
+ ->references('id')->on('invoices')
+ ->onDelete('cascade');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('invoice_charge_groups');
+ }
+
+
+
+};
diff --git a/database/migrations/2026_02_17_163544_create_invoice_charge_group_items_table.php b/database/migrations/2026_02_17_163544_create_invoice_charge_group_items_table.php
new file mode 100644
index 0000000..d86234f
--- /dev/null
+++ b/database/migrations/2026_02_17_163544_create_invoice_charge_group_items_table.php
@@ -0,0 +1,35 @@
+id();
+ $table->unsignedBigInteger('group_id');
+ $table->unsignedBigInteger('invoice_item_id');
+ $table->timestamps();
+
+ $table->foreign('group_id')
+ ->references('id')->on('invoice_charge_groups')
+ ->onDelete('cascade');
+
+ $table->foreign('invoice_item_id')
+ ->references('id')->on('invoice_items')
+ ->onDelete('cascade');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('invoice_charge_group_items');
+ }
+
+};
diff --git a/database/migrations/2026_02_21_101207_add_container_link_to_invoice_items.php b/database/migrations/2026_02_21_101207_add_container_link_to_invoice_items.php
new file mode 100644
index 0000000..2cfb73d
--- /dev/null
+++ b/database/migrations/2026_02_21_101207_add_container_link_to_invoice_items.php
@@ -0,0 +1,27 @@
+unsignedBigInteger('container_id')->nullable()->after('invoice_id');
+ $table->integer('container_row_index')->nullable()->after('container_id');
+ });
+ }
+
+ public function down()
+ {
+ Schema::table('invoice_items', function (Blueprint $table) {
+ $table->dropColumn(['container_id', 'container_row_index']);
+ });
+ }
+
+};
diff --git a/resources/views/admin/account.blade.php b/resources/views/admin/account.blade.php
index 14d0b08..db330cf 100644
--- a/resources/views/admin/account.blade.php
+++ b/resources/views/admin/account.blade.php
@@ -6,6 +6,7 @@
@@ -1323,7 +1547,7 @@ html, body {
-
+
@@ -1415,111 +1639,159 @@ html, body {
-
-
-
-
-
-
Entry Details — -
-
Complete view of all installments for this entry.
+
+
+
+
+
+
+
+
+
+
+ Original Amount
+
+
-
+
+
+
+
+ Total Processed
+
+
-
+
+
+
+
+ Pending Balance
+
+
-
+
+
+
+
+ Total Installments
+
+
-
+
+
-
-
-
-
-
-
Total Installments
-
-
+
+
+ Installment History
+
+
+
+
+
+
+ | Installment |
+ Date |
+ Description |
+ Region |
+ Amount |
+ Status |
+
+
+
+ | No installments yet |
+
+
+
-
-
-
- | Installment |
- Date |
- Description |
- Region |
- Amount |
- Status |
-
-
-
- | No installments yet |
-
-
-
-
-
+
+
@can('account.add_installment')
-
+
@endcan
-
-
-
-
-
-
- Entry Orders
-
-
-
- All orders associated with this entry.
+
+
+
+
+
+
+
+
+
+
+ | Order ID |
+ Mark No |
+ Origin |
+ Destination |
+ CTN |
+ QTY |
+ Amount |
+
+
+
+
+ | Loading orders... |
+
+
+
+
+
+
-
-
-
-
-
-
-
- | Order ID |
- Mark No |
- Origin |
- Destination |
- CTN |
- QTY |
- Amount |
-
-
-
-
- | No orders associated with this entry |
-
-
-
+
+
+
-
-
-
-
-
-
-
+
@@ -2784,76 +3056,98 @@ async function submitEditEntry(e) {
}
- function openEntryOrdersModal(entryNo) {
- document.getElementById('entryOrdersEntryNo-span').textContent = `(${entryNo})`;
+function openEntryOrdersModal(entryNo) {
+ // Set entry number in header
+ document.getElementById('entryOrdersEntryNo-span').textContent = `(${entryNo})`;
- const tbody = document.getElementById('entryOrdersTableBody');
+ // Reset summary values
+ document.getElementById('ordersTotalCount').textContent = '0';
+ document.getElementById('ordersTotalQuantity').textContent = '0';
+ document.getElementById('ordersTotalAmount').textContent = '₹0';
+
+ // table loading state
+ const tbody = document.getElementById('entryOrdersTableBody');
+ tbody.innerHTML = `
+
+ | Loading orders... |
+
+ `;
+
+ // API call: /admin/account/entry-orders/{entryno}
+ jsonFetch(`/admin/account/entry-orders/${encodeURIComponent(entryNo)}`, {
+ method: 'GET'
+ })
+ .then(res => {
+ if (!res.success) {
tbody.innerHTML = `
-
- | Loading orders... |
-
+
+ | Failed to load orders |
+
`;
+ return;
+ }
- jsonFetch(`/admin/account/entry-orders/${encodeURIComponent(entryNo)}`, {
- method: 'GET'
- })
- .then(res => {
- if (!res.success) {
- tbody.innerHTML = `
-
- | Failed to load orders |
-
- `;
- return;
- }
+ const orders = res.orders || [];
+ if (!orders.length) {
+ tbody.innerHTML = `
+
+ | No orders associated with this entry |
+
+ `;
+ return;
+ }
- const orders = res.orders || [];
- if (!orders.length) {
- tbody.innerHTML = `
-
- | No orders associated with this entry |
-
- `;
- return;
- }
+ tbody.innerHTML = '';
+
+ let totalQuantity = 0;
+ let totalAmount = 0;
- tbody.innerHTML = '';
+ orders.forEach(order => {
+ const tr = document.createElement('tr');
- orders.forEach(order => {
- const tr = document.createElement('tr');
+ const idString = (order.orderid ?? order.id ?? '').toString().trim();
+ const numericId = parseInt(idString, 10);
+ const formattedId = isNaN(numericId)
+ ? escapeHtml(idString)
+ : 'KNT-25-' + String(numericId).padStart(8, '0');
- const amountValue =
- order.ttl_amount ??
- order.ttlamount ??
- order.total_amount ??
- order.order_amount ??
- order.amount ??
- 0;
+ // Calculate values for summary
+ const quantity = Number(order.qty || order.order_qty || 0);
+ const amountValue = Number(order.ttl_amount ?? order.ttlamount ?? order.total_amount ?? order.order_amount ?? order.amount ?? 0);
+
+ totalQuantity += quantity;
+ totalAmount += amountValue;
- tr.innerHTML = `
-
${escapeHtml(order.order_id)} |
-
${escapeHtml(order.mark_no ?? '')} |
-
${escapeHtml(order.origin ?? '')} |
-
${escapeHtml(order.destination ?? '')} |
-
${escapeHtml(order.ctn ?? '')} |
-
${escapeHtml(order.qty ?? '')} |
-
${formatCurrency(amountValue)} |
- `;
+ tr.innerHTML = `
+
+ ${formattedId}
+ |
+
${escapeHtml(order.markno ?? order.mark_no ?? '')} |
+
${escapeHtml(order.origin ?? '')} |
+
${escapeHtml(order.destination ?? '')} |
+
${escapeHtml(order.ctn ?? '')} |
+
${quantity} |
+
${formatCurrency(amountValue)} |
+ `;
+ tbody.appendChild(tr);
+ });
- tbody.appendChild(tr);
- });
- })
- .catch(() => {
- tbody.innerHTML = `
-
- | Error loading orders |
-
- `;
- });
+ // Update summary
+ document.getElementById('ordersTotalCount').textContent = orders.length;
+ document.getElementById('ordersTotalQuantity').textContent = totalQuantity.toLocaleString();
+ document.getElementById('ordersTotalAmount').textContent = formatCurrency(totalAmount);
- document.getElementById('entryOrdersModal').classList.add('modal-open');
- }
+ })
+ .catch(() => {
+ tbody.innerHTML = `
+
+ | Error loading orders |
+
+ `;
+ });
+ document.getElementById('entryOrdersModal').classList.add('modal-open');
+}
function closeEntryOrdersModal() {
document.getElementById('entryOrdersModal').classList.remove('modal-open');
@@ -2989,7 +3283,7 @@ function handleSearch(){
updatePaginationControls();
}
-/* ---------- Entry details & installments ---------- */
+/* ---------- Entry details & installments (Enhanced) ---------- */
async function openEntryDetailsModal(entryNo) {
try {
const res = await jsonFetch('/admin/account/entry/' + encodeURIComponent(entryNo));
@@ -2999,58 +3293,109 @@ async function openEntryDetailsModal(entryNo) {
const entry = res.entry;
currentEntry = entry;
+ // Set header info
document.getElementById('entryDetailsId').textContent = entry.entry_no;
- document.getElementById('originalAmount').textContent = formatCurrency(entry.amount);
-
- const totalProcessed = Number(entry.amount) - Number(entry.pending_amount);
+
+ // Calculate values
+ const originalAmount = Number(entry.amount || 0);
+ const pendingAmount = Number(entry.pending_amount || 0);
+ const totalProcessed = originalAmount - pendingAmount;
+
+ // Update summary cards
+ document.getElementById('originalAmount').textContent = formatCurrency(originalAmount);
document.getElementById('totalProcessed').textContent = formatCurrency(totalProcessed);
+ document.getElementById('pendingBalance').textContent = formatCurrency(pendingAmount);
+ document.getElementById('totalInstallments').textContent = entry.installments?.length || 0;
- document.getElementById('pendingBalance').textContent = formatCurrency(entry.pending_amount);
- document.getElementById('totalInstallments').textContent = entry.installments.length;
-
+ // Render installments table
const tbody = document.getElementById('installmentsTableBody');
tbody.innerHTML = '';
- entry.installments.forEach((ins, idx) => {
- const tr = document.createElement('tr');
- tr.innerHTML = `
-
${idx === 0 ? 'Original Entry' : 'Installment ' + idx} |
-
${escapeHtml(ins.proc_date)} |
-
${escapeHtml(ins.description)} |
-
${escapeHtml(ins.region)} |
-
${formatCurrency(ins.amount)} |
-
-
-
-
- |
+ if (!entry.installments || entry.installments.length === 0) {
+ tbody.innerHTML = `
+
+
+
+ 📋
+
+ No installments found for this entry
+
+
+ Add your first installment to get started
+
+
+ |
+
`;
- tbody.appendChild(tr);
- });
+ } else {
+ entry.installments.forEach((ins, idx) => {
+ const tr = document.createElement('tr');
+
+ // Determine installment label
+ let installmentLabel = 'Original Entry';
+ if (idx > 0) {
+ installmentLabel = `Installment ${idx}`;
+ }
+
+ // Status dropdown options with colors
+ const statusOptions = [
+ { value: 'Pending', label: '⏳ Pending', color: '#f59e0b' },
+ { value: 'Loading', label: '📦 Loading', color: '#3b82f6' },
+ { value: 'Packed', label: '📦 Packed', color: '#8b5cf6' },
+ { value: 'Dispatched', label: '🚚 Dispatched', color: '#10b981' },
+ { value: 'Delivered', label: '✅ Delivered', color: '#0c6b2e' }
+ ];
+
+ let statusOptionsHtml = '';
+ statusOptions.forEach(opt => {
+ const selected = ins.status === opt.value ? 'selected' : '';
+ statusOptionsHtml += `
`;
+ });
+ tr.innerHTML = `
+
+
+
+ ${idx + 1}
+
+ ${installmentLabel}
+
+ |
+
+
+
+ ${escapeHtml(ins.proc_date || ins.date || '')}
+
+ |
+
${escapeHtml(ins.description || '')} |
+
+
+ ${escapeHtml(ins.region || '')}
+
+ |
+
+
+ ${formatCurrency(ins.amount || 0)}
+
+ |
+
+
+ |
+ `;
+ tbody.appendChild(tr);
+ });
+ }
+
+ // Show modal
document.getElementById('entryDetailsModal').classList.add('modal-open');
} catch (err) {
diff --git a/resources/views/admin/container.blade.php b/resources/views/admin/container.blade.php
new file mode 100644
index 0000000..53f3cc2
--- /dev/null
+++ b/resources/views/admin/container.blade.php
@@ -0,0 +1,802 @@
+@extends('admin.layouts.app')
+
+@section('page-title', 'Containers')
+
+@section('content')
+
+
+
+
+
+ @if(session('success'))
+
+
+ {{ session('success') }}
+
+ @endif
+
+
+
+
+ Filter Containers
+
+
+
+
+
+
+
+ @if($containers->isEmpty())
+
+
+
+
+
No containers found
+
Get started by creating your first container
+
+ @else
+ @foreach($containers as $container)
+ @php
+ $status = $container->status;
+ $statusClass = match ($status) {
+ 'completed' => 'status-completed',
+ 'in-progress' => 'status-in-progress',
+ 'cancelled' => 'status-cancelled',
+ default => 'status-pending',
+ };
+ @endphp
+
+
+
+
+
+
+
+
{{ number_format($container->summary['total_ctn'], 1) }}
+
Total CTN
+
+
+
{{ number_format($container->summary['total_qty'], 0) }}
+
Total QTY
+
+
+
{{ number_format($container->summary['total_cbm'], 3) }}
+
Total CBM
+
+
+
{{ number_format($container->summary['total_kg'], 1) }}
+
Total KG
+
+
+
+ @endforeach
+ @endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+@endsection
diff --git a/resources/views/admin/container_create.blade.php b/resources/views/admin/container_create.blade.php
new file mode 100644
index 0000000..f947a4d
--- /dev/null
+++ b/resources/views/admin/container_create.blade.php
@@ -0,0 +1,325 @@
+@extends('admin.layouts.app')
+
+@section('page-title', 'Add Container')
+
+@section('content')
+
+
+
+
+
+
+
+
+
+
+ {{-- SUCCESS --}}
+ @if (session('success'))
+
+ {{ session('success') }}
+
+ @endif
+
+ {{-- VALIDATION --}}
+ @if ($errors->any())
+
+
+ @foreach ($errors->all() as $error)
+ - {{ $error }}
+ @endforeach
+
+
+ @endif
+
+ {{-- COMBINED ERROR PANEL (summary text) --}}
+ @if(session('formula_errors') || session('mark_errors'))
+
+
+
+
+
+ Some rows in your Excel file have formula or mark issues.
+ See the table below, and detailed messages after the table.
+
+
+
+
+ {{-- 1) Excel-style table at top --}}
+ @php
+ $formulaErrors = session('formula_errors') ?? [];
+ $markErrors = session('mark_errors') ?? [];
+
+ $allRowsData = [];
+
+ foreach ($formulaErrors as $fe) {
+ if (!empty($fe['data'] ?? null)) {
+ $allRowsData[] = $fe['data'];
+ }
+ }
+
+ foreach ($markErrors as $me) {
+ if (!empty($me['data'] ?? null)) {
+ $allRowsData[] = $me['data'];
+ }
+ }
+
+ $headings = [];
+ if (!empty($allRowsData)) {
+ $headings = array_keys($allRowsData[0]);
+ }
+ @endphp
+
+ @if(!empty($headings))
+
+
+
+
+
+
+
+ | Excel Row |
+ Mark No |
+ @foreach($headings as $head)
+ {{ $head }} |
+ @endforeach
+
+
+
+ {{-- Formula error rows --}}
+ @foreach($formulaErrors as $fe)
+ @php
+ $rowData = $fe['data'] ?? [];
+ @endphp
+ @if(!empty($rowData))
+
+ | {{ $fe['excel_row'] }} |
+ {{ $fe['mark_no'] }} |
+ @foreach($headings as $head)
+ {{ $rowData[$head] ?? '' }} |
+ @endforeach
+
+ @endif
+ @endforeach
+
+ {{-- Mark error rows --}}
+ @foreach($markErrors as $me)
+ @php
+ $rowData = $me['data'] ?? [];
+ @endphp
+ @if(!empty($rowData))
+
+ | {{ $me['excel_row'] }} |
+ {{ $me['mark_no'] }} |
+ @foreach($headings as $head)
+ {{ $rowData[$head] ?? '' }} |
+ @endforeach
+
+ @endif
+ @endforeach
+
+
+
+
+
+ @endif
+
+ {{-- 2) Detailed per-row error boxes BELOW the table --}}
+
+
+
+
+ {{-- Formula Errors (detailed) --}}
+ @if(session('formula_errors'))
+ @foreach(session('formula_errors') as $fe)
+
+
+ Row {{ $fe['excel_row'] }}
+ @if($fe['mark_no']) | Mark: {{ $fe['mark_no'] }} @endif
+ @if($fe['description']) | {{ $fe['description'] }} @endif
+
+
+ @foreach($fe['errors'] as $field => $detail)
+
+ ❌ {{ $field }} →
+ Expected: {{ number_format($detail['expected'],4) }}
+ | Got: {{ number_format($detail['actual'],4) }}
+
+ @endforeach
+
+ @endforeach
+ @endif
+
+ {{-- Mark Errors (detailed) --}}
+ @if(session('mark_errors'))
+ @foreach(session('mark_errors') as $me)
+
+
+ Row {{ $me['excel_row'] }}
+
+
+ ❌ Mark {{ $me['mark_no'] }} not found in database
+
+
+ @endforeach
+ @endif
+
+
+
+
+ @endif
+
+ {{-- FORM --}}
+ @if (!session('formula_errors') && !session('mark_errors'))
+
+ @endif
+
+
+
+
+@endsection
diff --git a/resources/views/admin/container_show.blade.php b/resources/views/admin/container_show.blade.php
new file mode 100644
index 0000000..2010f18
--- /dev/null
+++ b/resources/views/admin/container_show.blade.php
@@ -0,0 +1,413 @@
+@extends('admin.layouts.app')
+
+@section('page-title', 'Container Details')
+
+@section('content')
+
+
+
+
+ {{-- TOP GRADIENT HEADER --}}
+
+
+ {{-- MAIN CARD --}}
+
+
+
+
+ {{-- INFO STRIP --}}
+
+
+
Container
+
{{ $container->container_name }}
+
+
+
Date
+
{{ $container->container_date?->format('d-m-Y') }}
+
+
+
Excel File
+ @if($container->excel_file)
+
+ @else
+
Not uploaded
+ @endif
+
+
+
+ @if($container->rows->isEmpty())
+
No entries found for this container.
+ @else
+ @php
+ $allHeadings = [];
+ foreach ($container->rows as $row) {
+ if (is_array($row->data)) {
+ $allHeadings = array_unique(array_merge($allHeadings, array_keys($row->data)));
+ }
+ }
+ @endphp
+
+ {{-- FILTER BAR --}}
+
+
+ Total rows: {{ $container->rows->count() }} • Edit cells then click "Save Changes"
+
+
+
+
+ {{-- EDITABLE TABLE FORM --}}
+
+ @endif
+
+
+
+
+
+@endsection
diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php
index b3a7eed..41ae9c7 100644
--- a/resources/views/admin/dashboard.blade.php
+++ b/resources/views/admin/dashboard.blade.php
@@ -27,1527 +27,1635 @@
@endphp
@section('content')
-
-
-
-
Admin Dashboard
-
Monitor operations and manage system
-
-
-
-
-
-
📦Total Shipments
{{ $totalShipments }}
-
👥Active Customers
{{ $activeCustomers }}
-
💰Total Revenue
₹{{ number_format($totalRevenue, 2) }}
-
⏳Pending Order
{{ $pendingOrders }}
+
+
+
Admin Dashboard
+
Monitor operations and manage system
-
-
📦Total Orders
{{ $totalOrders }}
-
🧑💼Total Staff
{{ $totalStaff }}
-
📦Total Items
{{ $totalItems }}
-
⛔Inactive Customers
{{ $inactiveCustomers }}
-
-
-
-
-
- Order Management
- @can('order.create')
-
- @endcan
-
-
-
-
-
-
-
-
-
-
-
- | # |
- Order ID |
- Mark No |
- Date |
- Origin |
- Destination |
- Total CTN |
- Total QTY |
- Total TTL/QTY |
- Total Amount (₹) |
- Total CBM |
- Total TTL CBM |
- Total KG |
- Total TTL KG |
- Status |
- Actions |
-
-
-
- @forelse($orders as $order)
-
- | {{ $order->id }} |
-
-
- {{ $order->order_id }}
-
- |
-
- {{ $order->mark_no }} |
- {{ $order->origin }} |
- {{ $order->destination }} |
- {{ $order->ctn }} |
- {{ $order->qty }} |
- {{ $order->ttl_qty }} |
- ₹{{ number_format($order->ttl_amount, 2) }} |
- {{ $order->cbm }} |
- {{ $order->ttl_cbm }} |
- {{ $order->kg }} |
- {{ $order->ttl_kg }} |
-
- @php
- // Badge color mapping
- $badgeMap = [
- 'order_placed' => 'secondary',
- 'order_confirmed' => 'info',
- 'supplier_warehouse' => 'warning',
- 'consolidate_warehouse' => 'warning',
- 'export_custom' => 'primary',
- 'international_transit' => 'primary',
- 'arrived_india' => 'info',
- 'import_custom' => 'info',
- 'warehouse' => 'dark',
- 'domestic_distribution' => 'primary',
- 'out_for_delivery' => 'success',
- 'delivered' => 'success',
- ];
-
- // Icon mapping
- $iconMap = [
- 'order_placed' => 'bi-clock-fill',
- 'order_confirmed' => 'bi-check-circle',
- 'supplier_warehouse' => 'bi-box-seam',
- 'consolidate_warehouse' => 'bi-boxes',
- 'export_custom' => 'bi-upload',
- 'international_transit' => 'bi-truck',
- 'arrived_india' => 'bi-geo-alt',
- 'import_custom' => 'bi-download',
- 'warehouse' => 'bi-building',
- 'domestic_distribution' => 'bi-diagram-3',
- 'out_for_delivery' => 'bi-truck-flatbed',
- 'delivered' => 'bi-check-circle-fill',
- ];
-
- $badgeClass = $badgeMap[$order->status] ?? 'secondary';
- $iconClass = $iconMap[$order->status] ?? 'bi-info-circle';
- @endphp
-
-
-
- {{ $order->status_label }}
-
- |
-
- {{ $order->created_at->format('d-m-Y') }} |
-
-
- View
-
- |
-
- @empty
-
- | No orders found |
-
- @endforelse
-
-
-
-
-
-
-
-
-
-
- @if(session('success'))
-
- {{ session('success') }}
-
+
+
- @endif
+
+ @if(session('success'))
+
+ {{ session('success') }}
+
+
+ @endif
-
-
+ {{-- RESET ORDER BUTTON --}}
+ @if(session('temp_order_items'))
+
+
+
+ @endif
- {{-- RESET ORDER BUTTON --}}
- @if(session('temp_order_items'))
-
-
-
- @endif
-
- {{-- TEMPORARY ITEMS TABLE --}}
- @if(session('temp_order_items') && count(session('temp_order_items')) > 0)
-
-
Temporary Items ({{ count(session('temp_order_items')) }} items)
-
-
-
-
- | # |
- Description |
- CTN |
- QTY |
- TTL/QTY |
- Unit |
- Price |
- TTL Amount |
- CBM |
- TTL CBM |
- KG |
- TTL KG |
- Shop No |
- Remove |
-
-
-
- @foreach(session('temp_order_items') as $index => $item)
-
- | {{ $index + 1 }} |
- {{ $item['description'] }} |
- {{ $item['ctn'] }} |
- {{ $item['qty'] }} |
- {{ $item['ttl_qty'] }} |
- {{ $item['unit'] }} |
- {{ $item['price'] }} |
- {{ $item['ttl_amount'] }} |
- {{ $item['cbm'] }} |
- {{ $item['ttl_cbm'] }} |
- {{ $item['kg'] }} |
- {{ $item['ttl_kg'] }} |
- {{ $item['shop_no'] }} |
-
-
+ @csrf
+
+
+
+
- |
-
- @endforeach
-
-
+
+ @endif
-
-
- @csrf
-
-
-
-
-
-
- @endif
-
-
-
-@endsection
+@endsection
\ No newline at end of file
diff --git a/resources/views/admin/invoice.blade.php b/resources/views/admin/invoice.blade.php
index 84aa37a..31683a7 100644
--- a/resources/views/admin/invoice.blade.php
+++ b/resources/views/admin/invoice.blade.php
@@ -27,7 +27,7 @@
justify-content: space-between;
align-items: center;
border-radius: 17px 17px 0 0;
- background: #fceeb8ff;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 54px;
padding: 15px 26px 10px 22px;
border-bottom: 1.4px solid #e8e2cf;
@@ -37,7 +37,7 @@
.invoice-management-title {
font-size: 1.32rem;
font-weight: 800;
- color: #2451af;
+ color: #ffffffff;
letter-spacing: .08em;
display: flex;
align-items: center;
@@ -223,7 +223,7 @@
/* Center all table content */
.table thead tr {
- background: #feebbe !important;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.table thead th:first-child {
@@ -237,7 +237,7 @@
background: transparent !important;
border: none;
font-weight: 700;
- color: #343535;
+ color: #ffffffff;
letter-spacing: 0.02em;
font-size: 14px;
padding: 20px 15px;
@@ -258,25 +258,33 @@
/* Soft blue background for ALL table rows */
.table-striped tbody tr {
- background: #f0f8ff !important;
- transition: all 0.2s ease;
- border-radius: 12px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
- }
+ background: #f0f8ff !important;
+ transition: all 0.15s ease;
+
+
+ border-radius: 6px;
+ box-shadow: 0 1px 4px rgba(0,0,0,0.05);
+}
+
+
+.table-striped tbody tr td {
+ padding: 6px 10px;
+ font-size: 14px;
+}
+
+.table-striped tbody tr:hover {
+ background: #e6f3ff !important;
+ box-shadow: 0 2px 6px rgba(0,0,0,0.08);
+ transform: translateY(-0.5px);
+}
- .table-striped tbody tr:hover {
- background: #e6f3ff !important;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
- transform: translateY(-1px);
- }
- /* Remove striped pattern - all rows same soft blue */
.table-striped tbody tr:nth-of-type(odd),
.table-striped tbody tr:nth-of-type(even) {
background: #f0f8ff !important;
}
- /* Center all table cells with proper spacing */
+
.table td {
padding: 18px 15px;
border: none;
@@ -291,7 +299,7 @@
font-weight: 400;
}
- /* First and last cell rounded corners */
+
.table td:first-child {
padding-left: 30px;
font-weight: 600;
@@ -587,7 +595,7 @@
}
.date-separator {
- color: #64748b;
+ color: #000000ff;
font-weight: 500;
font-family: 'Inter', sans-serif;
}
@@ -1091,31 +1099,48 @@