diff --git a/app/Http/Controllers/Admin/AdminOrderController.php b/app/Http/Controllers/Admin/AdminOrderController.php index 84fed2c..486040f 100644 --- a/app/Http/Controllers/Admin/AdminOrderController.php +++ b/app/Http/Controllers/Admin/AdminOrderController.php @@ -10,314 +10,265 @@ use App\Models\MarkList; use App\Models\Invoice; use App\Models\InvoiceItem; use App\Models\User; -use PDF; // barryvdh/laravel-dompdf facade +use PDF; use Maatwebsite\Excel\Facades\Excel; use App\Exports\OrdersExport; - class AdminOrderController extends Controller { - // --------------------------- - // LIST / DASHBOARD - // --------------------------- + /* --------------------------- + * LIST / DASHBOARD + * ---------------------------*/ public function index() { - // raw list for admin dashboard (simple) - $orders = Order::latest()->get(); + $orders = Order::latest()->get(); $markList = MarkList::where('status', 'active')->get(); return view('admin.dashboard', compact('orders', 'markList')); } - /** - * Orders list (detailed) - */ - // public function orderShow() - // { - // $orders = Order::with(['markList', 'shipments', 'invoice']) - // ->latest('id') - // ->get(); - - // return view('admin.orders', compact('orders')); - // } - - // --------------------------- - // CREATE NEW ORDER (simple DB flow) - // --------------------------- - /** - * Show create form (you can place create UI on separate view or dashboard) - */ + /* --------------------------- + * CREATE NEW ORDER (simple page) + * ---------------------------*/ public function create() { - // return a dedicated create view - create it at resources/views/admin/orders_create.blade.php - // If you prefer create UI on dashboard, change this to redirect route('admin.orders.index') etc. $markList = MarkList::where('status', 'active')->get(); return view('admin.orders_create', compact('markList')); } - /** - * Store a new order and optionally create initial invoice - */ public function store(Request $request) { $data = $request->validate([ - 'mark_no' => 'required|string', - 'origin' => 'nullable|string', + 'mark_no' => 'required|string', + 'origin' => 'nullable|string', 'destination' => 'nullable|string', - // totals optional when creating without items - 'ctn' => 'nullable|numeric', - 'qty' => 'nullable|numeric', - 'ttl_qty' => 'nullable|numeric', - 'ttl_amount' => 'nullable|numeric', - 'cbm' => 'nullable|numeric', - 'ttl_cbm' => 'nullable|numeric', - 'kg' => 'nullable|numeric', - 'ttl_kg' => 'nullable|numeric', + 'ctn' => 'nullable|numeric', + 'qty' => 'nullable|numeric', + 'ttl_qty' => 'nullable|numeric', + 'ttl_amount' => 'nullable|numeric', + 'cbm' => 'nullable|numeric', + 'ttl_cbm' => 'nullable|numeric', + 'kg' => 'nullable|numeric', + 'ttl_kg' => 'nullable|numeric', ]); $order = Order::create([ - 'order_id' => $this->generateOrderId(), - 'mark_no' => $data['mark_no'], - 'origin' => $data['origin'] ?? null, - 'destination' => $data['destination'] ?? null, - 'ctn' => $data['ctn'] ?? 0, - 'qty' => $data['qty'] ?? 0, - 'ttl_qty' => $data['ttl_qty'] ?? 0, + 'order_id' => $this->generateOrderId(), + 'mark_no' => $data['mark_no'], + 'origin' => $data['origin'] ?? null, + 'destination'=> $data['destination'] ?? null, + 'ctn' => $data['ctn'] ?? 0, + 'qty' => $data['qty'] ?? 0, + 'ttl_qty' => $data['ttl_qty'] ?? 0, 'ttl_amount' => $data['ttl_amount'] ?? 0, - 'cbm' => $data['cbm'] ?? 0, - 'ttl_cbm' => $data['ttl_cbm'] ?? 0, - 'kg' => $data['kg'] ?? 0, - 'ttl_kg' => $data['ttl_kg'] ?? 0, - 'status' => 'pending', + 'cbm' => $data['cbm'] ?? 0, + 'ttl_cbm' => $data['ttl_cbm'] ?? 0, + 'kg' => $data['kg'] ?? 0, + 'ttl_kg' => $data['ttl_kg'] ?? 0, + 'status' => 'pending', ]); - //If you want to auto-create an invoice at order creation, uncomment: $this->createInvoice($order); return redirect()->route('admin.orders.show', $order->id) - ->with('success', 'Order created successfully.'); + ->with('success', 'Order created successfully.'); } - // --------------------------- - // SHOW / POPUP - // --------------------------- + /* --------------------------- + * SHOW / POPUP + * ---------------------------*/ public function show($id) { $order = Order::with('items', 'markList')->findOrFail($id); - $user = $this->getCustomerFromOrder($order); + $user = $this->getCustomerFromOrder($order); return view('admin.orders_show', compact('order', 'user')); } - // public function popup($id) - // { - // $order = Order::with(['items', 'markList'])->findOrFail($id); - // $user = $this->getCustomerFromOrder($order); + public function popup($id) + { + $order = Order::with(['items', 'markList'])->findOrFail($id); - // return view('admin.popup', compact('order', 'user')); - // } + $user = null; + if ($order->markList && $order->markList->customer_id) { + $user = User::where('customer_id', $order->markList->customer_id)->first(); + } - // --------------------------- - // ORDER ITEM MANAGEMENT (DB) - // --------------------------- - /** - * Add an item to an existing order - */ + return view('admin.popup', compact('order', 'user')); + } + + /* --------------------------- + * ORDER ITEM MANAGEMENT (existing orders) + * ---------------------------*/ public function addItem(Request $request, $orderId) { $order = Order::findOrFail($orderId); $data = $request->validate([ 'description' => 'required|string', - 'ctn' => 'nullable|numeric', - 'qty' => 'nullable|numeric', - 'ttl_qty' => 'nullable|numeric', - 'unit' => 'nullable|string', - 'price' => 'nullable|numeric', - 'ttl_amount' => 'nullable|numeric', - 'cbm' => 'nullable|numeric', - 'ttl_cbm' => 'nullable|numeric', - 'kg' => 'nullable|numeric', - 'ttl_kg' => 'nullable|numeric', - 'shop_no' => 'nullable|string', + 'ctn' => 'nullable|numeric', + 'qty' => 'nullable|numeric', + 'ttl_qty' => 'nullable|numeric', + 'unit' => 'nullable|string', + 'price' => 'nullable|numeric', + 'ttl_amount' => 'nullable|numeric', + 'cbm' => 'nullable|numeric', + 'ttl_cbm' => 'nullable|numeric', + 'kg' => 'nullable|numeric', + 'ttl_kg' => 'nullable|numeric', + 'shop_no' => 'nullable|string', ]); $data['order_id'] = $order->id; OrderItem::create($data); - // recalc totals and save to order $this->recalcTotals($order); - $this->updateInvoiceFromOrder($order); // <-- NEW + $this->updateInvoiceFromOrder($order); return redirect()->back()->with('success', 'Item added and totals updated.'); } - /** - * Soft-delete an order item and recalc totals - */ public function deleteItem($id) { - $item = OrderItem::findOrFail($id); + $item = OrderItem::findOrFail($id); $order = $item->order; - $item->delete(); // soft delete + $item->delete(); - // recalc totals $this->recalcTotals($order); $this->updateInvoiceFromOrder($order); - return redirect()->back()->with('success', 'Item deleted and totals updated.'); } - /** - * Restore soft-deleted item and recalc totals - */ public function restoreItem($id) { - $item = OrderItem::withTrashed()->findOrFail($id); + $item = OrderItem::withTrashed()->findOrFail($id); $order = Order::findOrFail($item->order_id); $item->restore(); - // recalc totals $this->recalcTotals($order); $this->updateInvoiceFromOrder($order); - return redirect()->back()->with('success', 'Item restored and totals updated.'); } - // --------------------------- - // ORDER CRUD: update / destroy - // --------------------------- + /* --------------------------- + * ORDER CRUD: update / destroy + * ---------------------------*/ public function update(Request $request, $id) { $order = Order::findOrFail($id); $data = $request->validate([ - 'mark_no' => 'required|string', - 'origin' => 'nullable|string', + 'mark_no' => 'required|string', + 'origin' => 'nullable|string', 'destination' => 'nullable|string', ]); $order->update([ - 'mark_no' => $data['mark_no'], - 'origin' => $data['origin'] ?? null, + 'mark_no' => $data['mark_no'], + 'origin' => $data['origin'] ?? null, 'destination' => $data['destination'] ?? null, ]); - // optionally recalc totals (not necessary unless you change item-level fields here) $this->recalcTotals($order); return redirect()->route('admin.orders.show', $order->id) - ->with('success', 'Order updated successfully.'); + ->with('success', 'Order updated successfully.'); } - /** - * Soft-delete whole order and its items (soft-delete items first then order) - */ public function destroy($id) { $order = Order::findOrFail($id); - // soft-delete items first (so they show up in onlyTrashed for restore) OrderItem::where('order_id', $order->id)->delete(); - - // then soft-delete order $order->delete(); return redirect()->route('admin.orders.index') - ->with('success', 'Order deleted successfully.'); + ->with('success', 'Order deleted successfully.'); } - // --------------------------- - // HELPERS - // --------------------------- - /** - * Recalculate totals for the order from current (non-deleted) items - */ + /* --------------------------- + * HELPERS + * ---------------------------*/ private function recalcTotals(Order $order) { - // make sure we re-query live items (non-deleted) $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)), ]); } - /** - * Generate order id (keeps old format) - */ private function generateOrderId() { - $year = date('y'); + $year = date('y'); $prefix = "KNT-$year-"; - $lastOrder = Order::latest('id')->first(); + $lastOrder = Order::latest('id')->first(); $nextNumber = $lastOrder ? intval(substr($lastOrder->order_id, -8)) + 1 : 1; return $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT); } - // --------------------------- - // INVOICE CREATION (optional helper used by store/finish) - // --------------------------- + /* --------------------------- + * INVOICE CREATION HELPERS + * ---------------------------*/ private function createInvoice(Order $order) { $invoiceNumber = $this->generateInvoiceNumber(); - $customer = $this->getCustomerFromMarkList($order->mark_no); - $totalAmount = $order->ttl_amount; + $customer = $this->getCustomerFromMarkList($order->mark_no); + $totalAmount = $order->ttl_amount; $invoice = Invoice::create([ - 'order_id' => $order->id, - 'customer_id' => $customer->id ?? null, - 'mark_no' => $order->mark_no, - 'invoice_number' => $invoiceNumber, - 'invoice_date' => now(), - 'due_date' => now()->addDays(10), - 'payment_method' => null, - 'reference_no' => null, - 'status' => 'pending', - 'final_amount' => $totalAmount, - 'gst_percent' => 0, - 'gst_amount' => 0, + 'order_id' => $order->id, + 'customer_id' => $customer->id ?? null, + 'mark_no' => $order->mark_no, + 'invoice_number' => $invoiceNumber, + 'invoice_date' => now(), + 'due_date' => now()->addDays(10), + 'payment_method' => null, + 'reference_no' => null, + 'status' => 'pending', + 'final_amount' => $totalAmount, + 'gst_percent' => 0, + 'gst_amount' => 0, 'final_amount_with_gst' => $totalAmount, - 'customer_name' => $customer->customer_name ?? null, - 'company_name' => $customer->company_name ?? null, - 'customer_email' => $customer->email ?? null, - 'customer_mobile' => $customer->mobile_no ?? null, - 'customer_address' => $customer->address ?? null, - 'pincode' => $customer->pincode ?? null, - 'notes' => null, - 'pdf_path' => null, + 'customer_name' => $customer->customer_name ?? null, + 'company_name' => $customer->company_name ?? null, + 'customer_email' => $customer->email ?? null, + 'customer_mobile' => $customer->mobile_no ?? null, + 'customer_address' => $customer->address ?? null, + 'pincode' => $customer->pincode ?? null, + 'notes' => null, + 'pdf_path' => null, ]); - // clone order items into invoice items foreach ($order->items as $item) { InvoiceItem::create([ - 'invoice_id' => $invoice->id, + '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, + '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, ]); } } @@ -350,21 +301,9 @@ class AdminOrderController extends Controller return null; } - public function popup($id) - { - // Load order with items + markList - $order = Order::with(['items', 'markList'])->findOrFail($id); - - // Fetch user based on markList customer_id (same as show method) - $user = null; - if ($order->markList && $order->markList->customer_id) { - $user = \App\Models\User::where('customer_id', $order->markList->customer_id)->first(); - } - - return view('admin.popup', compact('order', 'user')); - } - - + /* --------------------------- + * SEE (detailed) + * ---------------------------*/ public function see($id) { $order = Order::with([ @@ -374,50 +313,45 @@ class AdminOrderController extends Controller 'shipments' => function ($q) use ($id) { $q->whereHas('orders', function ($oq) use ($id) { $oq->where('orders.id', $id) - ->whereNull('orders.deleted_at'); + ->whereNull('orders.deleted_at'); })->with([ 'orders' => function ($oq) use ($id) { $oq->where('orders.id', $id) - ->whereNull('orders.deleted_at') - ->with('items'); + ->whereNull('orders.deleted_at') + ->with('items'); } ]); } ])->findOrFail($id); - /* ---------------- ORDER DATA ---------------- */ $orderData = [ '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, ]; - /* ---------------- SHIPMENTS DATA ---------------- */ $shipmentsData = []; - foreach ($order->shipments as $shipment) { - $shipmentOrders = []; $totals = [ 'ctn' => 0, 'qty' => 0, 'ttl_qty' => 0, 'cbm' => 0, 'ttl_cbm' => 0, - 'kg' => 0, 'ttl_kg' => 0, + 'kg' => 0, 'ttl_kg' => 0, 'amount' => 0, ]; foreach ($shipment->orders as $shipOrder) { foreach ($shipOrder->items as $item) { - $shipmentOrders[] = [ 'order_id' => $shipOrder->order_id, 'origin' => $shipOrder->origin, @@ -429,14 +363,14 @@ class AdminOrderController extends Controller 'amount' => $item->ttl_amount, ]; - $totals['ctn'] += $item->ctn; - $totals['qty'] += $item->qty; - $totals['ttl_qty'] += $item->ttl_qty; - $totals['cbm'] += $item->cbm; - $totals['ttl_cbm'] += $item->ttl_cbm; - $totals['kg'] += $item->kg; - $totals['ttl_kg'] += $item->ttl_kg; - $totals['amount'] += $item->ttl_amount; + $totals['ctn'] += $item->ctn; + $totals['qty'] += $item->qty; + $totals['ttl_qty'] += $item->ttl_qty; + $totals['cbm'] += $item->cbm; + $totals['ttl_cbm'] += $item->ttl_cbm; + $totals['kg'] += $item->kg; + $totals['ttl_kg'] += $item->ttl_kg; + $totals['amount'] += $item->ttl_amount; } } @@ -454,23 +388,22 @@ class AdminOrderController extends Controller ]; } - /* ---------------- INVOICE DATA ---------------- */ $invoiceData = null; if ($order->invoice) { $invoice = $order->invoice; $invoiceData = [ - 'invoice_no' => $invoice->invoice_number, - 'status' => $invoice->status, + 'invoice_no' => $invoice->invoice_number, + 'status' => $invoice->status, 'invoice_date' => $invoice->invoice_date, 'due_date' => $invoice->due_date, - 'customer' => [ + 'customer' => [ 'name' => $invoice->customer_name, 'mobile' => $invoice->customer_mobile, 'email' => $invoice->customer_email, 'address' => $invoice->customer_address, 'pincode' => $invoice->pincode, ], - 'items' => $invoice->items, + 'items' => $invoice->items, 'summary' => [ 'amount' => $invoice->final_amount, 'cgst' => 0, @@ -488,127 +421,75 @@ class AdminOrderController extends Controller )); } - - public function resetTemp() - { - session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']); - - return redirect()->to(route('admin.orders.index') . '#createOrderForm') - ->with('success', 'Order reset successfully.'); - } - + /* --------------------------- + * FILTERED LIST + EXPORTS + * ---------------------------*/ public function orderShow() { $orders = Order::with([ - 'markList', // company, customer, origin, destination, date - 'shipments', // shipment_id, shipment_date, status - 'invoice' // invoice number, dates, amounts, status - ]) - ->latest('id') // show latest orders first - ->get(); + 'markList', + 'shipments', + 'invoice' + ])->latest('id')->get(); return view('admin.orders', compact('orders')); } - - // inside AdminOrderController private function buildOrdersQueryFromRequest(Request $request) - { - $query = Order::query() - ->with(['markList', 'invoice', 'shipments']); + { + $query = Order::query() + ->with(['markList', 'invoice', 'shipments']); - /* ---------------------------------- - | SEARCH FILTER - |----------------------------------*/ - if ($request->filled('search')) { - $search = trim($request->search); - - $query->where(function ($q) use ($search) { - $q->where('orders.order_id', 'like', "%{$search}%") + if ($request->filled('search')) { + $search = trim($request->search); + $query->where(function ($q) use ($search) { + $q->where('orders.order_id', 'like', "%{$search}%") ->orWhereHas('markList', function ($q2) use ($search) { $q2->where('company_name', 'like', "%{$search}%") ->orWhere('customer_id', 'like', "%{$search}%") ->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) { - // ✅ VERY IMPORTANT: table name added $q4->where('shipments.shipment_id', 'like', "%{$search}%"); }); - }); - } - - /* ---------------------------------- - | INVOICE STATUS FILTER - |----------------------------------*/ - if ($request->filled('status')) { - $query->where(function ($q) use ($request) { - $q->whereHas('invoice', function ($q2) use ($request) { - $q2->where('status', $request->status); - }) - ->orWhereDoesntHave('invoice'); - }); - } - - /* ---------------------------------- - | SHIPMENT STATUS FILTER - |----------------------------------*/ - if ($request->filled('shipment')) { - $query->where(function ($q) use ($request) { - $q->whereHas('shipments', function ($q2) use ($request) { - $q2->where('status', $request->shipment); - }) - ->orWhereDoesntHave('shipments'); - }); - } - - /* ---------------------------------- - | DATE RANGE FILTER (🔥 FIXED) - |----------------------------------*/ - if ($request->filled('from_date')) { - $query->whereDate('orders.created_at', '>=', $request->from_date); - } - - if ($request->filled('to_date')) { - $query->whereDate('orders.created_at', '<=', $request->to_date); - } - - return $query->latest('orders.id'); + }); } + 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) { + $q2->where('status', $request->shipment); + })->orWhereDoesntHave('shipments'); + }); + } + if ($request->filled('from_date')) { + $query->whereDate('orders.created_at', '>=', $request->from_date); + } - public function downloadPdf(Request $request) - { - // Build same filtered query used for table - // $query = $this->buildOrdersQueryFromRequest($request); + if ($request->filled('to_date')) { + $query->whereDate('orders.created_at', '<=', $request->to_date); + } - // $orders = $query->get(); - - // // optional: pass filters to view for header - // $filters = [ - // 'search' => $request->search ?? null, - // 'status' => $request->status ?? null, - // 'shipment' => $request->shipment ?? null, - // ]; - - // $pdf = PDF::loadView('admin.orders.pdf', compact('orders', 'filters')) - // ->setPaper('a4', 'landscape'); // adjust if needed - - // $fileName = 'orders-report' - // . ($filters['status'] ? "-{$filters['status']}" : '') - // . '-' . date('Y-m-d') . '.pdf'; - - // return $pdf->download($fileName); - $orders = $this->buildOrdersQueryFromRequest($request)->get(); + return $query->latest('orders.id'); + } + public function downloadPdf(Request $request) + { + $orders = $this->buildOrdersQueryFromRequest($request)->get(); $filters = [ 'search' => $request->search, 'status' => $request->status, @@ -618,240 +499,173 @@ class AdminOrderController extends Controller ]; $pdf = PDF::loadView('admin.orders.pdf', compact('orders', 'filters')) - ->setPaper('a4', 'landscape'); + ->setPaper('a4', 'landscape'); return $pdf->download( 'orders-report-' . now()->format('Y-m-d') . '.pdf' ); - } + } - public function downloadExcel(Request $request) - { - // pass request to OrdersExport which will build Filtered query internally - // return Excel::download(new OrdersExport($request), 'orders-report-' . date('Y-m-d') . '.xlsx'); - return Excel::download( + public function downloadExcel(Request $request) + { + return Excel::download( new OrdersExport($request), 'orders-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) { - // Validate item fields - $item = $request->validate([ - 'mark_no' => 'required', - 'origin' => 'required', - 'destination' => 'required', - 'description' => 'required|string', - 'ctn' => 'nullable|numeric', - 'qty' => 'nullable|numeric', - 'ttl_qty' => 'nullable|numeric', - 'unit' => 'nullable|string', - 'price' => 'nullable|numeric', - 'ttl_amount' => 'nullable|numeric', - 'cbm' => 'nullable|numeric', - 'ttl_cbm' => 'nullable|numeric', - 'kg' => 'nullable|numeric', - 'ttl_kg' => 'nullable|numeric', - 'shop_no' => 'nullable|string', - ]); - - // ❌ Prevent changing mark_no once first item added - if (session()->has('mark_no') && session('mark_no') != $request->mark_no) { - return redirect()->to(route('admin.orders.index') . '#createOrderForm') - ->with('error', 'You must finish or clear the current order before changing Mark No.'); - } - - // Save mark, origin, destination ONLY ONCE - if (!session()->has('mark_no')) { - session([ - 'mark_no' => $request->mark_no, - 'origin' => $request->origin, - 'destination' => $request->destination - ]); - } - - // ❌ DO NOT overwrite these values again - // session(['mark_no' => $request->mark_no]); - // session(['origin' => $request->origin]); - // session(['destination' => $request->destination]); - - // Add new sub-item into session - session()->push('temp_order_items', $item); - - return redirect()->to(route('admin.orders.index') . '#createOrderForm') - - ->with('success', 'Item added.'); - } - - // ------------------------------------------------------------------------- - // STEP 3 : FINISH ORDER - // ------------------------------------------------------------------------- - - public function finishOrder(Request $request) - { + // 1) order-level fields $request->validate([ - 'mark_no' => 'required', - 'origin' => 'required', + 'mark_no' => 'required', + 'origin' => 'required', 'destination' => 'required', ]); - $items = session('temp_order_items', []); + // 2) multi-row items + $items = $request->validate([ + 'items' => 'required|array', + 'items.*.description' => 'required|string', + 'items.*.ctn' => 'nullable|numeric', + 'items.*.qty' => 'nullable|numeric', + 'items.*.ttl_qty' => 'nullable|numeric', + 'items.*.unit' => 'nullable|string', + 'items.*.price' => 'nullable|numeric', + 'items.*.ttl_amount' => 'nullable|numeric', + 'items.*.cbm' => 'nullable|numeric', + 'items.*.ttl_cbm' => 'nullable|numeric', + 'items.*.kg' => 'nullable|numeric', + 'items.*.ttl_kg' => 'nullable|numeric', + 'items.*.shop_no' => 'nullable|string', + ])['items']; + + // रिकामे rows काढा + $items = array_filter($items, function ($row) { + return trim($row['description'] ?? '') !== ''; + }); if (empty($items)) { - return redirect()->to(route('admin.orders.index') . '#createOrderForm') - ->with('error', 'Add at least one item before finishing.'); + return back()->with('error', 'Add at least one item.'); } - // ======================= - // GENERATE ORDER ID - // ======================= - $year = date('y'); - $prefix = "KNT-$year-"; + // 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')); - $lastOrder = Order::latest('id')->first(); - $nextNumber = $lastOrder ? intval(substr($lastOrder->order_id, -8)) + 1 : 1; + // 4) order id generate + $orderId = $this->generateOrderId(); - $orderId = $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT); - - // ======================= - // TOTAL SUMS - // ======================= - $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')); - - // ======================= - // CREATE ORDER - // ======================= + // 5) order create $order = Order::create([ - 'order_id' => $orderId, - 'mark_no' => $request->mark_no, - 'origin' => $request->origin, - 'destination' => $request->destination, - 'ctn' => $total_ctn, - 'qty' => $total_qty, - 'ttl_qty' => $total_ttl_qty, + 'order_id' => $orderId, + 'mark_no' => $request->mark_no, + 'origin' => $request->origin, + 'destination'=> $request->destination, + 'ctn' => $total_ctn, + 'qty' => $total_qty, + 'ttl_qty' => $total_ttl_qty, 'ttl_amount' => $total_amount, - 'cbm' => $total_cbm, - 'ttl_cbm' => $total_ttl_cbm, - 'kg' => $total_kg, - 'ttl_kg' => $total_ttl_kg, - 'status' => 'pending', + 'cbm' => $total_cbm, + 'ttl_cbm' => $total_ttl_cbm, + 'kg' => $total_kg, + 'ttl_kg' => $total_ttl_kg, + 'status' => 'pending', ]); - // SAVE ORDER ITEMS + // 6) order items foreach ($items as $item) { OrderItem::create([ - 'order_id' => $order->id, - 'description' => $item['description'], - 'ctn' => $item['ctn'], - 'qty' => $item['qty'], - 'ttl_qty' => $item['ttl_qty'], - 'unit' => $item['unit'], - 'price' => $item['price'], + 'order_id' => $order->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'], + 'cbm' => $item['cbm'], + 'ttl_cbm' => $item['ttl_cbm'], + 'kg' => $item['kg'], + 'ttl_kg' => $item['ttl_kg'], + 'shop_no' => $item['shop_no'], ]); } - // ======================= - // INVOICE CREATION START - // ======================= + // 7) invoice number + $invoiceNumber = $this->generateInvoiceNumber(); - // 1. Auto-generate invoice number - $lastInvoice = \App\Models\Invoice::latest()->first(); - $nextInvoice = $lastInvoice ? $lastInvoice->id + 1 : 1; - $invoiceNumber = 'INV-' . date('Y') . '-' . str_pad($nextInvoice, 6, '0', STR_PAD_LEFT); - - // 2. Fetch customer (using mark list → customer_id) + // 8) customer fetch $markList = MarkList::where('mark_no', $order->mark_no)->first(); $customer = null; - if ($markList && $markList->customer_id) { - $customer = \App\Models\User::where('customer_id', $markList->customer_id)->first(); + $customer = User::where('customer_id', $markList->customer_id)->first(); } - // 3. Create Invoice Record - $invoice = \App\Models\Invoice::create([ - 'order_id' => $order->id, - 'customer_id' => $customer->id ?? null, - 'mark_no' => $order->mark_no, - - 'invoice_number' => $invoiceNumber, - 'invoice_date' => now(), - 'due_date' => now()->addDays(10), - - 'payment_method' => null, - 'reference_no' => null, - 'status' => 'pending', - - 'final_amount' => $total_amount, - 'gst_percent' => 0, - 'gst_amount' => 0, + // 9) invoice create + $invoice = Invoice::create([ + 'order_id' => $order->id, + 'customer_id' => $customer->id ?? null, + 'mark_no' => $order->mark_no, + 'invoice_number' => $invoiceNumber, + 'invoice_date' => now(), + 'due_date' => now()->addDays(10), + 'payment_method' => null, + 'reference_no' => null, + 'status' => 'pending', + 'final_amount' => $total_amount, + 'gst_percent' => 0, + 'gst_amount' => 0, 'final_amount_with_gst' => $total_amount, - - // snapshot customer fields - 'customer_name' => $customer->customer_name ?? null, - 'company_name' => $customer->company_name ?? null, - 'customer_email' => $customer->email ?? null, - 'customer_mobile' => $customer->mobile_no ?? null, - 'customer_address' => $customer->address ?? null, - 'pincode' => $customer->pincode ?? null, - - 'notes' => null, + 'customer_name' => $customer->customer_name ?? null, + 'company_name' => $customer->company_name ?? null, + 'customer_email' => $customer->email ?? null, + 'customer_mobile' => $customer->mobile_no ?? null, + 'customer_address' => $customer->address ?? null, + 'pincode' => $customer->pincode ?? null, + 'notes' => null, + 'pdf_path' => null, ]); - // 4. Clone order items into invoice_items + // 10) invoice items foreach ($order->items as $item) { - \App\Models\InvoiceItem::create([ - 'invoice_id' => $invoice->id, + 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, + '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, ]); } - // 5. TODO: PDF generation (I will add this later) - $invoice->pdf_path = null; // placeholder for now - $invoice->save(); - - // ======================= - // END INVOICE CREATION - // ======================= - - // CLEAR TEMP DATA - session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']); - return redirect()->route('admin.orders.index') - ->with('success', 'Order + Invoice created successfully.'); + ->with('success', 'Order + Invoice created successfully.'); } - // --------------------------- - // ORDER CRUD: update / destroy - // --------------------------- + /* --------------------------- + * UPDATE ORDER ITEM (existing orders) + * ---------------------------*/ public function updateItem(Request $request, $id) { - $item = OrderItem::findOrFail($id); + $item = OrderItem::findOrFail($id); $order = $item->order; $item->update([ @@ -870,31 +684,27 @@ class AdminOrderController extends Controller ]); $this->recalcTotals($order); - $this->updateInvoiceFromOrder($order); // <-- NEW + $this->updateInvoiceFromOrder($order); return back()->with('success', 'Item updated successfully'); } - private function updateInvoiceFromOrder(Order $order) { $invoice = Invoice::where('order_id', $order->id)->first(); if (!$invoice) { - return; // No invoice exists (should not happen normally) + return; } - // Update invoice totals - $invoice->final_amount = $order->ttl_amount; - $invoice->gst_percent = 0; - $invoice->gst_amount = 0; + $invoice->final_amount = $order->ttl_amount; + $invoice->gst_percent = 0; + $invoice->gst_amount = 0; $invoice->final_amount_with_gst = $order->ttl_amount; $invoice->save(); - // Delete old invoice items InvoiceItem::where('invoice_id', $invoice->id)->delete(); - // Re-create invoice items from updated order items foreach ($order->items as $item) { InvoiceItem::create([ 'invoice_id' => $invoice->id, @@ -913,5 +723,4 @@ class AdminOrderController extends Controller ]); } } - -} \ No newline at end of file +} diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index 1586d1c..260c7f0 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -261,7 +261,6 @@ body, .container-fluid { font-family: 'Inter', sans-serif; border-bottom: 2px solid #e9ecef; } - .table thead th:first-child { border-radius: 9px 0 0 0;} .table thead th:last-child { border-radius: 0 9px 0 0;} @@ -466,45 +465,47 @@ body, .container-fluid { min-width: 2000px; border-radius: 10px; } - /* ===== CREATE ORDER MODAL STYLES ===== */ .create-order-modal { position: fixed; top: 0; left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.6); + width: 100vw; /* full screen */ + height: 100vh; /* full screen */ + background: #f8fafc; /* backdrop काढून direct page सारखा bg */ display: none; - justify-content: center; - align-items: center; + justify-content: flex-start; /* top पासून सुरू कर */ + align-items: stretch; /* full height */ z-index: 9999; + overflow-y: auto; /* content scroll */ } +/* JS madhun modal.classList.add('show') already aahe */ .create-order-modal.show { - display: flex; + display: block; /* flex ऐवजी block => full page section सारखा */ } +/* Card ला full-width/height सारखं कर */ .create-order-modal .modal-card { background: #fff; - border-radius: 16px; - box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3); - width: 95%; - max-width: 900px; - max-height: 90vh; - overflow-y: auto; + border-radius: 0; /* corner radius काढला => normal page feel */ + box-shadow: none; /* popup सारखा shadow काढला */ + width: 100%; + max-width: 100%; + min-height: 100vh; + max-height: none; + overflow: visible; } .create-order-modal .modal-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px 25px; - border-radius: 16px 16px 0 0; + border-radius: 0; display: flex; justify-content: space-between; align-items: center; } - .create-order-modal .modal-title { font-size: 1.4rem; font-weight: 700; @@ -630,6 +631,13 @@ body, .container-fluid { font-family: 'Inter', sans-serif; } +/* items table inputs */ +.items-input { + width: 100%; + padding: 4px 6px; + font-size: 13px; +} + /* ===== ORDER DETAILS MODAL STYLES ===== */ .modal.fade .modal-dialog { transition: transform 0.3s ease-out; @@ -869,6 +877,27 @@ body, .container-fluid { .pagination-controls { justify-content: center; } + + .create-order-modal .modal-card { + width: 98%; + margin: 10px; + } + + .create-order-modal .modal-body { + padding: 15px; + } + + .create-order-modal .modal-header { + padding: 15px 20px; + } + + .modal-body { + padding: 20px 15px; + } + + .modal-header { + padding: 10px 15px; + } } /* Mobile Landscape (768px and below) */ @@ -1131,7 +1160,6 @@ body, .container-fluid { break-inside: avoid; } } -
@@ -1169,7 +1197,6 @@ body, .container-fluid { @endcan
-
@@ -1348,22 +1375,35 @@ body, .container-fluid { {{-- ITEM INPUTS --}}
Add Item
+ + {{-- NEW ITEMS TABLE (INSTEAD OF SINGLE-ROW INPUTS) --}} +
+ + + + + + + + + + + + + + + + + + + + + {{-- JS will create default 2 blank rows --}} + +
#DescriptionCTNQTYTTL/QTYUnitPriceTTL AmountCBMTTL CBMKGTTL KGShop NoRemove
+
+
-
- - -
-
-
-
-
-
-
-
-
-
-
-
+ `; + itemsTableBody.appendChild(tr); + } + + function generateDefaultRows() { + itemsTableBody.innerHTML = ''; + addRow(0); + addRow(1); + focusFirstInput(); + } + + function reindexRows() { + const rows = itemsTableBody.querySelectorAll('tr'); + rows.forEach((tr, idx) => { + tr.querySelector('td:first-child').textContent = idx + 1; + tr.querySelectorAll('input').forEach(input => { + const field = input.getAttribute('data-field'); + input.name = `items[${idx}][${field}]`; + }); + }); + } + + function rowHasData(row) { + const inputs = row.querySelectorAll('input'); + return Array.from(inputs).some(inp => inp.value.trim() !== ''); + } + + function focusFirstInput() { + const first = itemsTableBody.querySelector('tr:first-child input'); + if (first) first.focus(); + } + + // ------- EXISTING PAGINATION INITIALIZE ------- initializePagination(); // Reset temp data function @@ -1504,7 +1597,7 @@ document.addEventListener('DOMContentLoaded', function() { modal.classList.add('show'); document.body.style.overflow = 'hidden'; document.querySelector('.alert-success')?.remove(); - clearForm(); + generateDefaultRows(); }; const closeModal = () => { @@ -1518,11 +1611,9 @@ document.addEventListener('DOMContentLoaded', function() { @endif }; - // Clear form function + // Clear form -> clear items table const clearForm = () => { - ['itemDescription','itemCtn','itemQty','itemTtlQty','itemUnit','itemPrice','itemTtlAmount','itemCbm','itemTtlCbm','itemKg','itemTtlKg','itemShopNo'] - .forEach(id => document.getElementById(id).value = ''); - document.getElementById('itemDescription').focus(); + generateDefaultRows(); }; // Event listeners @@ -1533,7 +1624,7 @@ document.addEventListener('DOMContentLoaded', function() { modal.addEventListener('click', (e) => e.target === modal && closeModal()); document.addEventListener('keydown', (e) => e.key === 'Escape' && modal.classList.contains('show') && closeModal()); - // Mark No functionality + // Mark No functionality (unchanged) const markNoSelect = document.getElementById('markNoSelect'); if (markNoSelect) { markNoSelect.addEventListener('change', function() { @@ -1553,7 +1644,7 @@ document.addEventListener('DOMContentLoaded', function() { @if(session('temp_order_items') && count(session('temp_order_items')) > 0) modal.classList.add('show'); document.body.style.overflow = 'hidden'; - clearForm(); + generateDefaultRows(); @endif // Reset confirmation @@ -1564,16 +1655,16 @@ document.addEventListener('DOMContentLoaded', function() { } })); - // Order details modal functionality + // Order details modal functionality (unchanged) document.querySelectorAll('.open-order-modal').forEach(button => { button.addEventListener('click', function() { let id = this.dataset.id; - let modal = new bootstrap.Modal(document.getElementById('orderDetailsModal')); + let modalInstance = new bootstrap.Modal(document.getElementById('orderDetailsModal')); document.getElementById('orderDetailsBody').innerHTML = "

Loading...

"; - modal.show(); + modalInstance.show(); fetch(`/admin/orders/view/${id}`) .then(response => response.text()) @@ -1587,12 +1678,11 @@ document.addEventListener('DOMContentLoaded', function() { }); }); - /* ---------- Pagination Functions ---------- */ + /* ---------- Pagination Functions (unchanged) ---------- */ function initializePagination() { renderOrdersTable(allOrders); updatePaginationControls(); - // Bind pagination buttons document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage); document.getElementById('nextPageBtn').addEventListener('click', goToNextPage); } @@ -1624,21 +1714,17 @@ document.addEventListener('DOMContentLoaded', function() { prevBtn.disabled = currentPage === 1; nextBtn.disabled = currentPage === totalPages || totalPages === 0; - // Update page info text const startIndex = (currentPage - 1) * ordersPerPage + 1; const endIndex = Math.min(currentPage * ordersPerPage, allOrders.length); pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${allOrders.length} entries`; - // Generate page numbers paginationPages.innerHTML = ''; if (totalPages <= 7) { - // Show all pages for (let i = 1; i <= totalPages; i++) { addPageButton(i, paginationPages); } } else { - // Show first page, current page range, and last page addPageButton(1, paginationPages); if (currentPage > 3) { @@ -1684,7 +1770,6 @@ document.addEventListener('DOMContentLoaded', function() { return; } - // Calculate pagination const startIndex = (currentPage - 1) * ordersPerPage; const endIndex = startIndex + ordersPerPage; const paginatedOrders = orders.slice(startIndex, endIndex); @@ -1729,17 +1814,16 @@ document.addEventListener('DOMContentLoaded', function() { `; tbody.appendChild(tr); - // Re-bind order details modal for newly rendered rows const orderLink = tr.querySelector('.open-order-modal'); if (orderLink) { orderLink.addEventListener('click', function() { let id = this.dataset.id; - let modal = new bootstrap.Modal(document.getElementById('orderDetailsModal')); + let modalInstance = new bootstrap.Modal(document.getElementById('orderDetailsModal')); document.getElementById('orderDetailsBody').innerHTML = "

Loading...

"; - modal.show(); + modalInstance.show(); fetch(`/admin/orders/view/${id}`) .then(response => response.text()) @@ -1754,19 +1838,65 @@ document.addEventListener('DOMContentLoaded', function() { } }); } + + // Enter key behavior for items/table + itemsTableBody.addEventListener('keydown', function(e) { + if (e.key !== 'Enter' || e.target.tagName !== 'INPUT') return; + e.preventDefault(); + + const currentInput = e.target; + const currentRow = currentInput.closest('tr'); + const rows = Array.from(itemsTableBody.querySelectorAll('tr')); + const currentRowIndex = rows.indexOf(currentRow); + + const inputs = Array.from(currentRow.querySelectorAll('input')); + const currentInputIndex = inputs.indexOf(currentInput); + + const isLastRow = currentRowIndex === rows.length - 1; + const hasData = rowHasData(currentRow); + + if (currentInputIndex < inputs.length - 1) { + inputs[currentInputIndex + 1].focus(); + return; + } + + if (!isLastRow) { + const nextRow = rows[currentRowIndex + 1]; + const firstInputNextRow = nextRow.querySelector('input'); + if (firstInputNextRow) firstInputNextRow.focus(); + return; + } + + if (isLastRow && hasData) { + const newIndex = rows.length; + addRow(newIndex); + reindexRows(); + const newRow = itemsTableBody.querySelector('tr:last-child'); + const firstInput = newRow.querySelector('input'); + if (firstInput) firstInput.focus(); + } + }); + + // Remove row + itemsTableBody.addEventListener('click', function(e) { + if (!e.target.classList.contains('remove-row-btn')) return; + const rows = itemsTableBody.querySelectorAll('tr'); + if (rows.length <= 1) { + alert('At least one row must exist.'); + return; + } + e.target.closest('tr').remove(); + reindexRows(); + }); }); - -@endsection \ No newline at end of file +@endsection diff --git a/routes/web.php b/routes/web.php index f79b3ce..8930fcb 100644 --- a/routes/web.php +++ b/routes/web.php @@ -133,6 +133,10 @@ Route::prefix('admin') Route::get('/orders/view/{id}', [AdminOrderController::class, 'popup']) ->name('admin.orders.popup'); + Route::post('/admin/orders/temp/add', [AdminOrderController::class, 'addTempItem']) + ->name('admin.orders.temp.add'); + + // Route::get('/orders/{id}', [AdminOrderController::class, 'view']) // ->name('admin.orders.view'); Route::get('/orders/{order:order_id}/see', [AdminOrderController::class, 'see'])