Dashboard Changes
This commit is contained in:
@@ -10,61 +10,38 @@ use App\Models\MarkList;
|
|||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Models\InvoiceItem;
|
use App\Models\InvoiceItem;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use PDF; // barryvdh/laravel-dompdf facade
|
use PDF;
|
||||||
use Maatwebsite\Excel\Facades\Excel;
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
use App\Exports\OrdersExport;
|
use App\Exports\OrdersExport;
|
||||||
|
|
||||||
|
|
||||||
class AdminOrderController extends Controller
|
class AdminOrderController extends Controller
|
||||||
{
|
{
|
||||||
// ---------------------------
|
/* ---------------------------
|
||||||
// LIST / DASHBOARD
|
* LIST / DASHBOARD
|
||||||
// ---------------------------
|
* ---------------------------*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
// raw list for admin dashboard (simple)
|
|
||||||
$orders = Order::latest()->get();
|
$orders = Order::latest()->get();
|
||||||
$markList = MarkList::where('status', 'active')->get();
|
$markList = MarkList::where('status', 'active')->get();
|
||||||
|
|
||||||
return view('admin.dashboard', compact('orders', 'markList'));
|
return view('admin.dashboard', compact('orders', 'markList'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/* ---------------------------
|
||||||
* Orders list (detailed)
|
* CREATE NEW ORDER (simple page)
|
||||||
*/
|
* ---------------------------*/
|
||||||
// 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)
|
|
||||||
*/
|
|
||||||
public function create()
|
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();
|
$markList = MarkList::where('status', 'active')->get();
|
||||||
return view('admin.orders_create', compact('markList'));
|
return view('admin.orders_create', compact('markList'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a new order and optionally create initial invoice
|
|
||||||
*/
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
'mark_no' => 'required|string',
|
'mark_no' => 'required|string',
|
||||||
'origin' => 'nullable|string',
|
'origin' => 'nullable|string',
|
||||||
'destination' => 'nullable|string',
|
'destination' => 'nullable|string',
|
||||||
// totals optional when creating without items
|
|
||||||
'ctn' => 'nullable|numeric',
|
'ctn' => 'nullable|numeric',
|
||||||
'qty' => 'nullable|numeric',
|
'qty' => 'nullable|numeric',
|
||||||
'ttl_qty' => 'nullable|numeric',
|
'ttl_qty' => 'nullable|numeric',
|
||||||
@@ -91,16 +68,15 @@ class AdminOrderController extends Controller
|
|||||||
'status' => 'pending',
|
'status' => 'pending',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
//If you want to auto-create an invoice at order creation, uncomment:
|
|
||||||
$this->createInvoice($order);
|
$this->createInvoice($order);
|
||||||
|
|
||||||
return redirect()->route('admin.orders.show', $order->id)
|
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)
|
public function show($id)
|
||||||
{
|
{
|
||||||
$order = Order::with('items', 'markList')->findOrFail($id);
|
$order = Order::with('items', 'markList')->findOrFail($id);
|
||||||
@@ -109,20 +85,21 @@ class AdminOrderController extends Controller
|
|||||||
return view('admin.orders_show', compact('order', 'user'));
|
return view('admin.orders_show', compact('order', 'user'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// public function popup($id)
|
public function popup($id)
|
||||||
// {
|
{
|
||||||
// $order = Order::with(['items', 'markList'])->findOrFail($id);
|
$order = Order::with(['items', 'markList'])->findOrFail($id);
|
||||||
// $user = $this->getCustomerFromOrder($order);
|
|
||||||
|
|
||||||
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------
|
return view('admin.popup', compact('order', 'user'));
|
||||||
// ORDER ITEM MANAGEMENT (DB)
|
}
|
||||||
// ---------------------------
|
|
||||||
/**
|
/* ---------------------------
|
||||||
* Add an item to an existing order
|
* ORDER ITEM MANAGEMENT (existing orders)
|
||||||
*/
|
* ---------------------------*/
|
||||||
public function addItem(Request $request, $orderId)
|
public function addItem(Request $request, $orderId)
|
||||||
{
|
{
|
||||||
$order = Order::findOrFail($orderId);
|
$order = Order::findOrFail($orderId);
|
||||||
@@ -146,34 +123,25 @@ class AdminOrderController extends Controller
|
|||||||
|
|
||||||
OrderItem::create($data);
|
OrderItem::create($data);
|
||||||
|
|
||||||
// recalc totals and save to order
|
|
||||||
$this->recalcTotals($order);
|
$this->recalcTotals($order);
|
||||||
$this->updateInvoiceFromOrder($order); // <-- NEW
|
$this->updateInvoiceFromOrder($order);
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Item added and totals updated.');
|
return redirect()->back()->with('success', 'Item added and totals updated.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Soft-delete an order item and recalc totals
|
|
||||||
*/
|
|
||||||
public function deleteItem($id)
|
public function deleteItem($id)
|
||||||
{
|
{
|
||||||
$item = OrderItem::findOrFail($id);
|
$item = OrderItem::findOrFail($id);
|
||||||
$order = $item->order;
|
$order = $item->order;
|
||||||
|
|
||||||
$item->delete(); // soft delete
|
$item->delete();
|
||||||
|
|
||||||
// recalc totals
|
|
||||||
$this->recalcTotals($order);
|
$this->recalcTotals($order);
|
||||||
$this->updateInvoiceFromOrder($order);
|
$this->updateInvoiceFromOrder($order);
|
||||||
|
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Item deleted and totals updated.');
|
return redirect()->back()->with('success', 'Item deleted and totals updated.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Restore soft-deleted item and recalc totals
|
|
||||||
*/
|
|
||||||
public function restoreItem($id)
|
public function restoreItem($id)
|
||||||
{
|
{
|
||||||
$item = OrderItem::withTrashed()->findOrFail($id);
|
$item = OrderItem::withTrashed()->findOrFail($id);
|
||||||
@@ -181,17 +149,15 @@ class AdminOrderController extends Controller
|
|||||||
|
|
||||||
$item->restore();
|
$item->restore();
|
||||||
|
|
||||||
// recalc totals
|
|
||||||
$this->recalcTotals($order);
|
$this->recalcTotals($order);
|
||||||
$this->updateInvoiceFromOrder($order);
|
$this->updateInvoiceFromOrder($order);
|
||||||
|
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Item restored and totals updated.');
|
return redirect()->back()->with('success', 'Item restored and totals updated.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------
|
/* ---------------------------
|
||||||
// ORDER CRUD: update / destroy
|
* ORDER CRUD: update / destroy
|
||||||
// ---------------------------
|
* ---------------------------*/
|
||||||
public function update(Request $request, $id)
|
public function update(Request $request, $id)
|
||||||
{
|
{
|
||||||
$order = Order::findOrFail($id);
|
$order = Order::findOrFail($id);
|
||||||
@@ -208,39 +174,28 @@ class AdminOrderController extends Controller
|
|||||||
'destination' => $data['destination'] ?? null,
|
'destination' => $data['destination'] ?? null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// optionally recalc totals (not necessary unless you change item-level fields here)
|
|
||||||
$this->recalcTotals($order);
|
$this->recalcTotals($order);
|
||||||
|
|
||||||
return redirect()->route('admin.orders.show', $order->id)
|
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)
|
public function destroy($id)
|
||||||
{
|
{
|
||||||
$order = Order::findOrFail($id);
|
$order = Order::findOrFail($id);
|
||||||
|
|
||||||
// soft-delete items first (so they show up in onlyTrashed for restore)
|
|
||||||
OrderItem::where('order_id', $order->id)->delete();
|
OrderItem::where('order_id', $order->id)->delete();
|
||||||
|
|
||||||
// then soft-delete order
|
|
||||||
$order->delete();
|
$order->delete();
|
||||||
|
|
||||||
return redirect()->route('admin.orders.index')
|
return redirect()->route('admin.orders.index')
|
||||||
->with('success', 'Order deleted successfully.');
|
->with('success', 'Order deleted successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------
|
/* ---------------------------
|
||||||
// HELPERS
|
* HELPERS
|
||||||
// ---------------------------
|
* ---------------------------*/
|
||||||
/**
|
|
||||||
* Recalculate totals for the order from current (non-deleted) items
|
|
||||||
*/
|
|
||||||
private function recalcTotals(Order $order)
|
private function recalcTotals(Order $order)
|
||||||
{
|
{
|
||||||
// make sure we re-query live items (non-deleted)
|
|
||||||
$items = $order->items()->get();
|
$items = $order->items()->get();
|
||||||
|
|
||||||
$order->update([
|
$order->update([
|
||||||
@@ -255,9 +210,6 @@ class AdminOrderController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate order id (keeps old format)
|
|
||||||
*/
|
|
||||||
private function generateOrderId()
|
private function generateOrderId()
|
||||||
{
|
{
|
||||||
$year = date('y');
|
$year = date('y');
|
||||||
@@ -269,9 +221,9 @@ class AdminOrderController extends Controller
|
|||||||
return $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT);
|
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)
|
private function createInvoice(Order $order)
|
||||||
{
|
{
|
||||||
$invoiceNumber = $this->generateInvoiceNumber();
|
$invoiceNumber = $this->generateInvoiceNumber();
|
||||||
@@ -302,7 +254,6 @@ class AdminOrderController extends Controller
|
|||||||
'pdf_path' => null,
|
'pdf_path' => null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// clone order items into invoice items
|
|
||||||
foreach ($order->items as $item) {
|
foreach ($order->items as $item) {
|
||||||
InvoiceItem::create([
|
InvoiceItem::create([
|
||||||
'invoice_id' => $invoice->id,
|
'invoice_id' => $invoice->id,
|
||||||
@@ -350,21 +301,9 @@ class AdminOrderController extends Controller
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function popup($id)
|
/* ---------------------------
|
||||||
{
|
* SEE (detailed)
|
||||||
// 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'));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function see($id)
|
public function see($id)
|
||||||
{
|
{
|
||||||
$order = Order::with([
|
$order = Order::with([
|
||||||
@@ -385,7 +324,6 @@ class AdminOrderController extends Controller
|
|||||||
}
|
}
|
||||||
])->findOrFail($id);
|
])->findOrFail($id);
|
||||||
|
|
||||||
/* ---------------- ORDER DATA ---------------- */
|
|
||||||
$orderData = [
|
$orderData = [
|
||||||
'order_id' => $order->order_id,
|
'order_id' => $order->order_id,
|
||||||
'status' => $order->status,
|
'status' => $order->status,
|
||||||
@@ -402,11 +340,8 @@ class AdminOrderController extends Controller
|
|||||||
'items' => $order->items,
|
'items' => $order->items,
|
||||||
];
|
];
|
||||||
|
|
||||||
/* ---------------- SHIPMENTS DATA ---------------- */
|
|
||||||
$shipmentsData = [];
|
$shipmentsData = [];
|
||||||
|
|
||||||
foreach ($order->shipments as $shipment) {
|
foreach ($order->shipments as $shipment) {
|
||||||
|
|
||||||
$shipmentOrders = [];
|
$shipmentOrders = [];
|
||||||
$totals = [
|
$totals = [
|
||||||
'ctn' => 0, 'qty' => 0, 'ttl_qty' => 0,
|
'ctn' => 0, 'qty' => 0, 'ttl_qty' => 0,
|
||||||
@@ -417,7 +352,6 @@ class AdminOrderController extends Controller
|
|||||||
|
|
||||||
foreach ($shipment->orders as $shipOrder) {
|
foreach ($shipment->orders as $shipOrder) {
|
||||||
foreach ($shipOrder->items as $item) {
|
foreach ($shipOrder->items as $item) {
|
||||||
|
|
||||||
$shipmentOrders[] = [
|
$shipmentOrders[] = [
|
||||||
'order_id' => $shipOrder->order_id,
|
'order_id' => $shipOrder->order_id,
|
||||||
'origin' => $shipOrder->origin,
|
'origin' => $shipOrder->origin,
|
||||||
@@ -454,7 +388,6 @@ class AdminOrderController extends Controller
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- INVOICE DATA ---------------- */
|
|
||||||
$invoiceData = null;
|
$invoiceData = null;
|
||||||
if ($order->invoice) {
|
if ($order->invoice) {
|
||||||
$invoice = $order->invoice;
|
$invoice = $order->invoice;
|
||||||
@@ -488,89 +421,61 @@ class AdminOrderController extends Controller
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------------------------
|
||||||
public function resetTemp()
|
* FILTERED LIST + EXPORTS
|
||||||
{
|
* ---------------------------*/
|
||||||
session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']);
|
|
||||||
|
|
||||||
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
|
|
||||||
->with('success', 'Order reset successfully.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function orderShow()
|
public function orderShow()
|
||||||
{
|
{
|
||||||
$orders = Order::with([
|
$orders = Order::with([
|
||||||
'markList', // company, customer, origin, destination, date
|
'markList',
|
||||||
'shipments', // shipment_id, shipment_date, status
|
'shipments',
|
||||||
'invoice' // invoice number, dates, amounts, status
|
'invoice'
|
||||||
])
|
])->latest('id')->get();
|
||||||
->latest('id') // show latest orders first
|
|
||||||
->get();
|
|
||||||
|
|
||||||
return view('admin.orders', compact('orders'));
|
return view('admin.orders', compact('orders'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// inside AdminOrderController
|
|
||||||
|
|
||||||
private function buildOrdersQueryFromRequest(Request $request)
|
private function buildOrdersQueryFromRequest(Request $request)
|
||||||
{
|
{
|
||||||
$query = Order::query()
|
$query = Order::query()
|
||||||
->with(['markList', 'invoice', 'shipments']);
|
->with(['markList', 'invoice', 'shipments']);
|
||||||
|
|
||||||
/* ----------------------------------
|
|
||||||
| SEARCH FILTER
|
|
||||||
|----------------------------------*/
|
|
||||||
if ($request->filled('search')) {
|
if ($request->filled('search')) {
|
||||||
$search = trim($request->search);
|
$search = trim($request->search);
|
||||||
|
|
||||||
$query->where(function ($q) use ($search) {
|
$query->where(function ($q) use ($search) {
|
||||||
$q->where('orders.order_id', 'like', "%{$search}%")
|
$q->where('orders.order_id', 'like', "%{$search}%")
|
||||||
|
|
||||||
->orWhereHas('markList', function ($q2) use ($search) {
|
->orWhereHas('markList', function ($q2) use ($search) {
|
||||||
$q2->where('company_name', 'like', "%{$search}%")
|
$q2->where('company_name', 'like', "%{$search}%")
|
||||||
->orWhere('customer_id', 'like', "%{$search}%")
|
->orWhere('customer_id', 'like', "%{$search}%")
|
||||||
->orWhere('origin', 'like', "%{$search}%")
|
->orWhere('origin', 'like', "%{$search}%")
|
||||||
->orWhere('destination', 'like', "%{$search}%");
|
->orWhere('destination', 'like', "%{$search}%");
|
||||||
})
|
})
|
||||||
|
|
||||||
->orWhereHas('invoice', function ($q3) use ($search) {
|
->orWhereHas('invoice', function ($q3) use ($search) {
|
||||||
$q3->where('invoice_number', 'like', "%{$search}%");
|
$q3->where('invoice_number', 'like', "%{$search}%");
|
||||||
})
|
})
|
||||||
|
|
||||||
->orWhereHas('shipments', function ($q4) use ($search) {
|
->orWhereHas('shipments', function ($q4) use ($search) {
|
||||||
// ✅ VERY IMPORTANT: table name added
|
|
||||||
$q4->where('shipments.shipment_id', 'like', "%{$search}%");
|
$q4->where('shipments.shipment_id', 'like', "%{$search}%");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------
|
|
||||||
| INVOICE STATUS FILTER
|
|
||||||
|----------------------------------*/
|
|
||||||
if ($request->filled('status')) {
|
if ($request->filled('status')) {
|
||||||
$query->where(function ($q) use ($request) {
|
$query->where(function ($q) use ($request) {
|
||||||
$q->whereHas('invoice', function ($q2) use ($request) {
|
$q->whereHas('invoice', function ($q2) use ($request) {
|
||||||
$q2->where('status', $request->status);
|
$q2->where('status', $request->status);
|
||||||
})
|
})->orWhereDoesntHave('invoice');
|
||||||
->orWhereDoesntHave('invoice');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------
|
|
||||||
| SHIPMENT STATUS FILTER
|
|
||||||
|----------------------------------*/
|
|
||||||
if ($request->filled('shipment')) {
|
if ($request->filled('shipment')) {
|
||||||
$query->where(function ($q) use ($request) {
|
$query->where(function ($q) use ($request) {
|
||||||
$q->whereHas('shipments', function ($q2) use ($request) {
|
$q->whereHas('shipments', function ($q2) use ($request) {
|
||||||
$q2->where('status', $request->shipment);
|
$q2->where('status', $request->shipment);
|
||||||
})
|
})->orWhereDoesntHave('shipments');
|
||||||
->orWhereDoesntHave('shipments');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------
|
|
||||||
| DATE RANGE FILTER (🔥 FIXED)
|
|
||||||
|----------------------------------*/
|
|
||||||
if ($request->filled('from_date')) {
|
if ($request->filled('from_date')) {
|
||||||
$query->whereDate('orders.created_at', '>=', $request->from_date);
|
$query->whereDate('orders.created_at', '>=', $request->from_date);
|
||||||
}
|
}
|
||||||
@@ -582,33 +487,9 @@ class AdminOrderController extends Controller
|
|||||||
return $query->latest('orders.id');
|
return $query->latest('orders.id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function downloadPdf(Request $request)
|
public function downloadPdf(Request $request)
|
||||||
{
|
{
|
||||||
// Build same filtered query used for table
|
|
||||||
// $query = $this->buildOrdersQueryFromRequest($request);
|
|
||||||
|
|
||||||
// $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();
|
$orders = $this->buildOrdersQueryFromRequest($request)->get();
|
||||||
|
|
||||||
$filters = [
|
$filters = [
|
||||||
'search' => $request->search,
|
'search' => $request->search,
|
||||||
'status' => $request->status,
|
'status' => $request->status,
|
||||||
@@ -627,97 +508,52 @@ class AdminOrderController extends Controller
|
|||||||
|
|
||||||
public function downloadExcel(Request $request)
|
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(
|
return Excel::download(
|
||||||
new OrdersExport($request),
|
new OrdersExport($request),
|
||||||
'orders-report-' . now()->format('Y-m-d') . '.xlsx'
|
'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)
|
public function addTempItem(Request $request)
|
||||||
{
|
{
|
||||||
// Validate item fields
|
// 1) order-level 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)
|
|
||||||
{
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'mark_no' => 'required',
|
'mark_no' => 'required',
|
||||||
'origin' => 'required',
|
'origin' => 'required',
|
||||||
'destination' => '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)) {
|
if (empty($items)) {
|
||||||
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
|
return back()->with('error', 'Add at least one item.');
|
||||||
->with('error', 'Add at least one item before finishing.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =======================
|
// 3) totals
|
||||||
// GENERATE ORDER ID
|
|
||||||
// =======================
|
|
||||||
$year = date('y');
|
|
||||||
$prefix = "KNT-$year-";
|
|
||||||
|
|
||||||
$lastOrder = Order::latest('id')->first();
|
|
||||||
$nextNumber = $lastOrder ? intval(substr($lastOrder->order_id, -8)) + 1 : 1;
|
|
||||||
|
|
||||||
$orderId = $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT);
|
|
||||||
|
|
||||||
// =======================
|
|
||||||
// TOTAL SUMS
|
|
||||||
// =======================
|
|
||||||
$total_ctn = array_sum(array_column($items, 'ctn'));
|
$total_ctn = array_sum(array_column($items, 'ctn'));
|
||||||
$total_qty = array_sum(array_column($items, 'qty'));
|
$total_qty = array_sum(array_column($items, 'qty'));
|
||||||
$total_ttl_qty = array_sum(array_column($items, 'ttl_qty'));
|
$total_ttl_qty = array_sum(array_column($items, 'ttl_qty'));
|
||||||
@@ -727,9 +563,10 @@ class AdminOrderController extends Controller
|
|||||||
$total_kg = array_sum(array_column($items, 'kg'));
|
$total_kg = array_sum(array_column($items, 'kg'));
|
||||||
$total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
|
$total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
|
||||||
|
|
||||||
// =======================
|
// 4) order id generate
|
||||||
// CREATE ORDER
|
$orderId = $this->generateOrderId();
|
||||||
// =======================
|
|
||||||
|
// 5) order create
|
||||||
$order = Order::create([
|
$order = Order::create([
|
||||||
'order_id' => $orderId,
|
'order_id' => $orderId,
|
||||||
'mark_no' => $request->mark_no,
|
'mark_no' => $request->mark_no,
|
||||||
@@ -746,7 +583,7 @@ class AdminOrderController extends Controller
|
|||||||
'status' => 'pending',
|
'status' => 'pending',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// SAVE ORDER ITEMS
|
// 6) order items
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
OrderItem::create([
|
OrderItem::create([
|
||||||
'order_id' => $order->id,
|
'order_id' => $order->id,
|
||||||
@@ -765,56 +602,44 @@ class AdminOrderController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =======================
|
// 7) invoice number
|
||||||
// INVOICE CREATION START
|
$invoiceNumber = $this->generateInvoiceNumber();
|
||||||
// =======================
|
|
||||||
|
|
||||||
// 1. Auto-generate invoice number
|
// 8) customer fetch
|
||||||
$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)
|
|
||||||
$markList = MarkList::where('mark_no', $order->mark_no)->first();
|
$markList = MarkList::where('mark_no', $order->mark_no)->first();
|
||||||
$customer = null;
|
$customer = null;
|
||||||
|
|
||||||
if ($markList && $markList->customer_id) {
|
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
|
// 9) invoice create
|
||||||
$invoice = \App\Models\Invoice::create([
|
$invoice = Invoice::create([
|
||||||
'order_id' => $order->id,
|
'order_id' => $order->id,
|
||||||
'customer_id' => $customer->id ?? null,
|
'customer_id' => $customer->id ?? null,
|
||||||
'mark_no' => $order->mark_no,
|
'mark_no' => $order->mark_no,
|
||||||
|
|
||||||
'invoice_number' => $invoiceNumber,
|
'invoice_number' => $invoiceNumber,
|
||||||
'invoice_date' => now(),
|
'invoice_date' => now(),
|
||||||
'due_date' => now()->addDays(10),
|
'due_date' => now()->addDays(10),
|
||||||
|
|
||||||
'payment_method' => null,
|
'payment_method' => null,
|
||||||
'reference_no' => null,
|
'reference_no' => null,
|
||||||
'status' => 'pending',
|
'status' => 'pending',
|
||||||
|
|
||||||
'final_amount' => $total_amount,
|
'final_amount' => $total_amount,
|
||||||
'gst_percent' => 0,
|
'gst_percent' => 0,
|
||||||
'gst_amount' => 0,
|
'gst_amount' => 0,
|
||||||
'final_amount_with_gst' => $total_amount,
|
'final_amount_with_gst' => $total_amount,
|
||||||
|
|
||||||
// snapshot customer fields
|
|
||||||
'customer_name' => $customer->customer_name ?? null,
|
'customer_name' => $customer->customer_name ?? null,
|
||||||
'company_name' => $customer->company_name ?? null,
|
'company_name' => $customer->company_name ?? null,
|
||||||
'customer_email' => $customer->email ?? null,
|
'customer_email' => $customer->email ?? null,
|
||||||
'customer_mobile' => $customer->mobile_no ?? null,
|
'customer_mobile' => $customer->mobile_no ?? null,
|
||||||
'customer_address' => $customer->address ?? null,
|
'customer_address' => $customer->address ?? null,
|
||||||
'pincode' => $customer->pincode ?? null,
|
'pincode' => $customer->pincode ?? null,
|
||||||
|
|
||||||
'notes' => null,
|
'notes' => null,
|
||||||
|
'pdf_path' => null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 4. Clone order items into invoice_items
|
// 10) invoice items
|
||||||
foreach ($order->items as $item) {
|
foreach ($order->items as $item) {
|
||||||
\App\Models\InvoiceItem::create([
|
InvoiceItem::create([
|
||||||
'invoice_id' => $invoice->id,
|
'invoice_id' => $invoice->id,
|
||||||
'description' => $item->description,
|
'description' => $item->description,
|
||||||
'ctn' => $item->ctn,
|
'ctn' => $item->ctn,
|
||||||
@@ -831,24 +656,13 @@ class AdminOrderController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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')
|
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)
|
public function updateItem(Request $request, $id)
|
||||||
{
|
{
|
||||||
$item = OrderItem::findOrFail($id);
|
$item = OrderItem::findOrFail($id);
|
||||||
@@ -870,31 +684,27 @@ class AdminOrderController extends Controller
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$this->recalcTotals($order);
|
$this->recalcTotals($order);
|
||||||
$this->updateInvoiceFromOrder($order); // <-- NEW
|
$this->updateInvoiceFromOrder($order);
|
||||||
|
|
||||||
return back()->with('success', 'Item updated successfully');
|
return back()->with('success', 'Item updated successfully');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private function updateInvoiceFromOrder(Order $order)
|
private function updateInvoiceFromOrder(Order $order)
|
||||||
{
|
{
|
||||||
$invoice = Invoice::where('order_id', $order->id)->first();
|
$invoice = Invoice::where('order_id', $order->id)->first();
|
||||||
|
|
||||||
if (!$invoice) {
|
if (!$invoice) {
|
||||||
return; // No invoice exists (should not happen normally)
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update invoice totals
|
|
||||||
$invoice->final_amount = $order->ttl_amount;
|
$invoice->final_amount = $order->ttl_amount;
|
||||||
$invoice->gst_percent = 0;
|
$invoice->gst_percent = 0;
|
||||||
$invoice->gst_amount = 0;
|
$invoice->gst_amount = 0;
|
||||||
$invoice->final_amount_with_gst = $order->ttl_amount;
|
$invoice->final_amount_with_gst = $order->ttl_amount;
|
||||||
$invoice->save();
|
$invoice->save();
|
||||||
|
|
||||||
// Delete old invoice items
|
|
||||||
InvoiceItem::where('invoice_id', $invoice->id)->delete();
|
InvoiceItem::where('invoice_id', $invoice->id)->delete();
|
||||||
|
|
||||||
// Re-create invoice items from updated order items
|
|
||||||
foreach ($order->items as $item) {
|
foreach ($order->items as $item) {
|
||||||
InvoiceItem::create([
|
InvoiceItem::create([
|
||||||
'invoice_id' => $invoice->id,
|
'invoice_id' => $invoice->id,
|
||||||
@@ -913,5 +723,4 @@ class AdminOrderController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -261,7 +261,6 @@ body, .container-fluid {
|
|||||||
font-family: 'Inter', sans-serif;
|
font-family: 'Inter', sans-serif;
|
||||||
border-bottom: 2px solid #e9ecef;
|
border-bottom: 2px solid #e9ecef;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table thead th:first-child { border-radius: 9px 0 0 0;}
|
.table thead th:first-child { border-radius: 9px 0 0 0;}
|
||||||
.table thead th:last-child { border-radius: 0 9px 0 0;}
|
.table thead th:last-child { border-radius: 0 9px 0 0;}
|
||||||
|
|
||||||
@@ -466,45 +465,47 @@ body, .container-fluid {
|
|||||||
min-width: 2000px;
|
min-width: 2000px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== CREATE ORDER MODAL STYLES ===== */
|
/* ===== CREATE ORDER MODAL STYLES ===== */
|
||||||
.create-order-modal {
|
.create-order-modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100vw; /* full screen */
|
||||||
height: 100%;
|
height: 100vh; /* full screen */
|
||||||
background: rgba(0, 0, 0, 0.6);
|
background: #f8fafc; /* backdrop काढून direct page सारखा bg */
|
||||||
display: none;
|
display: none;
|
||||||
justify-content: center;
|
justify-content: flex-start; /* top पासून सुरू कर */
|
||||||
align-items: center;
|
align-items: stretch; /* full height */
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
|
overflow-y: auto; /* content scroll */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* JS madhun modal.classList.add('show') already aahe */
|
||||||
.create-order-modal.show {
|
.create-order-modal.show {
|
||||||
display: flex;
|
display: block; /* flex ऐवजी block => full page section सारखा */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Card ला full-width/height सारखं कर */
|
||||||
.create-order-modal .modal-card {
|
.create-order-modal .modal-card {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 16px;
|
border-radius: 0; /* corner radius काढला => normal page feel */
|
||||||
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
|
box-shadow: none; /* popup सारखा shadow काढला */
|
||||||
width: 95%;
|
width: 100%;
|
||||||
max-width: 900px;
|
max-width: 100%;
|
||||||
max-height: 90vh;
|
min-height: 100vh;
|
||||||
overflow-y: auto;
|
max-height: none;
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-order-modal .modal-header {
|
.create-order-modal .modal-header {
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 20px 25px;
|
padding: 20px 25px;
|
||||||
border-radius: 16px 16px 0 0;
|
border-radius: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-order-modal .modal-title {
|
.create-order-modal .modal-title {
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -630,6 +631,13 @@ body, .container-fluid {
|
|||||||
font-family: 'Inter', sans-serif;
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* items table inputs */
|
||||||
|
.items-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 4px 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== ORDER DETAILS MODAL STYLES ===== */
|
/* ===== ORDER DETAILS MODAL STYLES ===== */
|
||||||
.modal.fade .modal-dialog {
|
.modal.fade .modal-dialog {
|
||||||
transition: transform 0.3s ease-out;
|
transition: transform 0.3s ease-out;
|
||||||
@@ -869,6 +877,27 @@ body, .container-fluid {
|
|||||||
.pagination-controls {
|
.pagination-controls {
|
||||||
justify-content: center;
|
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) */
|
/* Mobile Landscape (768px and below) */
|
||||||
@@ -1131,7 +1160,6 @@ body, .container-fluid {
|
|||||||
break-inside: avoid;
|
break-inside: avoid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="container-fluid py-3">
|
<div class="container-fluid py-3">
|
||||||
@@ -1169,7 +1197,6 @@ body, .container-fluid {
|
|||||||
@endcan
|
@endcan
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="order-mgmt-main">
|
<div class="order-mgmt-main">
|
||||||
<!-- RECENT ORDERS TABLE -->
|
<!-- RECENT ORDERS TABLE -->
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
@@ -1348,22 +1375,35 @@ body, .container-fluid {
|
|||||||
|
|
||||||
{{-- ITEM INPUTS --}}
|
{{-- ITEM INPUTS --}}
|
||||||
<h6 class="text-primary">Add Item</h6>
|
<h6 class="text-primary">Add Item</h6>
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-md-4">
|
{{-- NEW ITEMS TABLE (INSTEAD OF SINGLE-ROW INPUTS) --}}
|
||||||
<label class="form-label">Description</label>
|
<div class="table-wrapper mb-3">
|
||||||
<input type="text" class="form-control" name="description" id="itemDescription" required placeholder="Enter item description">
|
<table class="table table-bordered table-sm align-middle text-center" id="itemsTable">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>CTN</th>
|
||||||
|
<th>QTY</th>
|
||||||
|
<th>TTL/QTY</th>
|
||||||
|
<th>Unit</th>
|
||||||
|
<th>Price</th>
|
||||||
|
<th>TTL Amount</th>
|
||||||
|
<th>CBM</th>
|
||||||
|
<th>TTL CBM</th>
|
||||||
|
<th>KG</th>
|
||||||
|
<th>TTL KG</th>
|
||||||
|
<th>Shop No</th>
|
||||||
|
<th>Remove</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="itemsTableBody">
|
||||||
|
{{-- JS will create default 2 blank rows --}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2"><label class="form-label">CTN</label><input type="number" name="ctn" id="itemCtn" class="form-control" placeholder="CTN"></div>
|
|
||||||
<div class="col-md-2"><label class="form-label">QTY</label><input type="number" name="qty" id="itemQty" class="form-control" placeholder="QTY"></div>
|
<div class="row g-3">
|
||||||
<div class="col-md-2"><label class="form-label">TTL/QTY</label><input type="number" name="ttl_qty" id="itemTtlQty" class="form-control" placeholder="TTL/QTY"></div>
|
|
||||||
<div class="col-md-2"><label class="form-label">Unit</label><input type="text" name="unit" id="itemUnit" class="form-control" placeholder="Unit"></div>
|
|
||||||
<div class="col-md-2"><label class="form-label">Price</label><input type="number" step="0.01" name="price" id="itemPrice" class="form-control" placeholder="Price"></div>
|
|
||||||
<div class="col-md-2"><label class="form-label">TTL Amount</label><input type="number" step="0.01" name="ttl_amount" id="itemTtlAmount" class="form-control" placeholder="TTL Amount"></div>
|
|
||||||
<div class="col-md-2"><label class="form-label">CBM</label><input type="number" step="0.001" name="cbm" id="itemCbm" class="form-control" placeholder="CBM"></div>
|
|
||||||
<div class="col-md-2"><label class="form-label">TTL CBM</label><input type="number" step="0.001" name="ttl_cbm" id="itemTtlCbm" class="form-control" placeholder="TTL CBM"></div>
|
|
||||||
<div class="col-md-2"><label class="form-label">KG</label><input type="number" step="0.001" name="kg" id="itemKg" class="form-control" placeholder="KG"></div>
|
|
||||||
<div class="col-md-2"><label class="form-label">TTL KG</label><input type="number" step="0.001" name="ttl_kg" id="itemTtlKg" class="form-control" placeholder="TTL KG"></div>
|
|
||||||
<div class="col-md-3"><label class="form-label">Shop No</label><input type="text" name="shop_no" id="itemShopNo" class="form-control" placeholder="Shop No"></div>
|
|
||||||
<div class="col-md-12 text-end mt-3">
|
<div class="col-md-12 text-end mt-3">
|
||||||
<button type="button" class="btn btn-secondary clear-form-btn" id="clearForm">
|
<button type="button" class="btn btn-secondary clear-form-btn" id="clearForm">
|
||||||
<i class="bi bi-arrow-clockwise"></i> Clear Form
|
<i class="bi bi-arrow-clockwise"></i> Clear Form
|
||||||
@@ -1485,7 +1525,60 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const ordersPerPage = 10;
|
const ordersPerPage = 10;
|
||||||
let allOrders = @json($orders->values());
|
let allOrders = @json($orders->values());
|
||||||
|
|
||||||
// Initialize pagination
|
// ------- ITEMS TABLE LOGIC (NEW) -------
|
||||||
|
|
||||||
|
const itemsTableBody = document.getElementById('itemsTableBody');
|
||||||
|
|
||||||
|
function addRow(index) {
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td class="align-middle fw-bold">${index + 1}</td>
|
||||||
|
<td><input type="text" class="form-control form-control-sm items-input" name="items[${index}][description]" data-field="description"></td>
|
||||||
|
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][ctn]" data-field="ctn"></td>
|
||||||
|
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][qty]" data-field="qty"></td>
|
||||||
|
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][ttl_qty]" data-field="ttl_qty"></td>
|
||||||
|
<td><input type="text" class="form-control form-control-sm items-input" name="items[${index}][unit]" data-field="unit"></td>
|
||||||
|
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][price]" data-field="price" step="0.01"></td>
|
||||||
|
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][ttl_amount]" data-field="ttl_amount" step="0.01"></td>
|
||||||
|
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][cbm]" data-field="cbm" step="0.001"></td>
|
||||||
|
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][ttl_cbm]" data-field="ttl_cbm" step="0.001"></td>
|
||||||
|
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][kg]" data-field="kg" step="0.001"></td>
|
||||||
|
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][ttl_kg]" data-field="ttl_kg" step="0.001"></td>
|
||||||
|
<td><input type="text" class="form-control form-control-sm items-input" name="items[${index}][shop_no]" data-field="shop_no"></td>
|
||||||
|
<td><button type="button" class="btn btn-sm btn-danger remove-row-btn">×</button></td>
|
||||||
|
`;
|
||||||
|
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();
|
initializePagination();
|
||||||
|
|
||||||
// Reset temp data function
|
// Reset temp data function
|
||||||
@@ -1504,7 +1597,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
modal.classList.add('show');
|
modal.classList.add('show');
|
||||||
document.body.style.overflow = 'hidden';
|
document.body.style.overflow = 'hidden';
|
||||||
document.querySelector('.alert-success')?.remove();
|
document.querySelector('.alert-success')?.remove();
|
||||||
clearForm();
|
generateDefaultRows();
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
@@ -1518,11 +1611,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
@endif
|
@endif
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clear form function
|
// Clear form -> clear items table
|
||||||
const clearForm = () => {
|
const clearForm = () => {
|
||||||
['itemDescription','itemCtn','itemQty','itemTtlQty','itemUnit','itemPrice','itemTtlAmount','itemCbm','itemTtlCbm','itemKg','itemTtlKg','itemShopNo']
|
generateDefaultRows();
|
||||||
.forEach(id => document.getElementById(id).value = '');
|
|
||||||
document.getElementById('itemDescription').focus();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Event listeners
|
// Event listeners
|
||||||
@@ -1533,7 +1624,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
modal.addEventListener('click', (e) => e.target === modal && closeModal());
|
modal.addEventListener('click', (e) => e.target === modal && closeModal());
|
||||||
document.addEventListener('keydown', (e) => e.key === 'Escape' && modal.classList.contains('show') && 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');
|
const markNoSelect = document.getElementById('markNoSelect');
|
||||||
if (markNoSelect) {
|
if (markNoSelect) {
|
||||||
markNoSelect.addEventListener('change', function() {
|
markNoSelect.addEventListener('change', function() {
|
||||||
@@ -1553,7 +1644,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
@if(session('temp_order_items') && count(session('temp_order_items')) > 0)
|
@if(session('temp_order_items') && count(session('temp_order_items')) > 0)
|
||||||
modal.classList.add('show');
|
modal.classList.add('show');
|
||||||
document.body.style.overflow = 'hidden';
|
document.body.style.overflow = 'hidden';
|
||||||
clearForm();
|
generateDefaultRows();
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
// Reset confirmation
|
// 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 => {
|
document.querySelectorAll('.open-order-modal').forEach(button => {
|
||||||
button.addEventListener('click', function() {
|
button.addEventListener('click', function() {
|
||||||
let id = this.dataset.id;
|
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 =
|
document.getElementById('orderDetailsBody').innerHTML =
|
||||||
"<p class='text-center text-muted'>Loading...</p>";
|
"<p class='text-center text-muted'>Loading...</p>";
|
||||||
|
|
||||||
modal.show();
|
modalInstance.show();
|
||||||
|
|
||||||
fetch(`/admin/orders/view/${id}`)
|
fetch(`/admin/orders/view/${id}`)
|
||||||
.then(response => response.text())
|
.then(response => response.text())
|
||||||
@@ -1587,12 +1678,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/* ---------- Pagination Functions ---------- */
|
/* ---------- Pagination Functions (unchanged) ---------- */
|
||||||
function initializePagination() {
|
function initializePagination() {
|
||||||
renderOrdersTable(allOrders);
|
renderOrdersTable(allOrders);
|
||||||
updatePaginationControls();
|
updatePaginationControls();
|
||||||
|
|
||||||
// Bind pagination buttons
|
|
||||||
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
|
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
|
||||||
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
|
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
|
||||||
}
|
}
|
||||||
@@ -1624,21 +1714,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
prevBtn.disabled = currentPage === 1;
|
prevBtn.disabled = currentPage === 1;
|
||||||
nextBtn.disabled = currentPage === totalPages || totalPages === 0;
|
nextBtn.disabled = currentPage === totalPages || totalPages === 0;
|
||||||
|
|
||||||
// Update page info text
|
|
||||||
const startIndex = (currentPage - 1) * ordersPerPage + 1;
|
const startIndex = (currentPage - 1) * ordersPerPage + 1;
|
||||||
const endIndex = Math.min(currentPage * ordersPerPage, allOrders.length);
|
const endIndex = Math.min(currentPage * ordersPerPage, allOrders.length);
|
||||||
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${allOrders.length} entries`;
|
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${allOrders.length} entries`;
|
||||||
|
|
||||||
// Generate page numbers
|
|
||||||
paginationPages.innerHTML = '';
|
paginationPages.innerHTML = '';
|
||||||
|
|
||||||
if (totalPages <= 7) {
|
if (totalPages <= 7) {
|
||||||
// Show all pages
|
|
||||||
for (let i = 1; i <= totalPages; i++) {
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
addPageButton(i, paginationPages);
|
addPageButton(i, paginationPages);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Show first page, current page range, and last page
|
|
||||||
addPageButton(1, paginationPages);
|
addPageButton(1, paginationPages);
|
||||||
|
|
||||||
if (currentPage > 3) {
|
if (currentPage > 3) {
|
||||||
@@ -1684,7 +1770,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate pagination
|
|
||||||
const startIndex = (currentPage - 1) * ordersPerPage;
|
const startIndex = (currentPage - 1) * ordersPerPage;
|
||||||
const endIndex = startIndex + ordersPerPage;
|
const endIndex = startIndex + ordersPerPage;
|
||||||
const paginatedOrders = orders.slice(startIndex, endIndex);
|
const paginatedOrders = orders.slice(startIndex, endIndex);
|
||||||
@@ -1729,17 +1814,16 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
`;
|
`;
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
|
|
||||||
// Re-bind order details modal for newly rendered rows
|
|
||||||
const orderLink = tr.querySelector('.open-order-modal');
|
const orderLink = tr.querySelector('.open-order-modal');
|
||||||
if (orderLink) {
|
if (orderLink) {
|
||||||
orderLink.addEventListener('click', function() {
|
orderLink.addEventListener('click', function() {
|
||||||
let id = this.dataset.id;
|
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 =
|
document.getElementById('orderDetailsBody').innerHTML =
|
||||||
"<p class='text-center text-muted'>Loading...</p>";
|
"<p class='text-center text-muted'>Loading...</p>";
|
||||||
|
|
||||||
modal.show();
|
modalInstance.show();
|
||||||
|
|
||||||
fetch(`/admin/orders/view/${id}`)
|
fetch(`/admin/orders/view/${id}`)
|
||||||
.then(response => response.text())
|
.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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("hidden.bs.modal", function () {
|
document.addEventListener("hidden.bs.modal", function () {
|
||||||
// Remove Bootstrap backdrops
|
|
||||||
document.querySelectorAll(".modal-backdrop").forEach(el => el.remove());
|
document.querySelectorAll(".modal-backdrop").forEach(el => el.remove());
|
||||||
|
|
||||||
// Fix page scroll locking
|
|
||||||
document.body.classList.remove("modal-open");
|
document.body.classList.remove("modal-open");
|
||||||
document.body.style.overflow = "";
|
document.body.style.overflow = "";
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
@@ -133,6 +133,10 @@ Route::prefix('admin')
|
|||||||
Route::get('/orders/view/{id}', [AdminOrderController::class, 'popup'])
|
Route::get('/orders/view/{id}', [AdminOrderController::class, 'popup'])
|
||||||
->name('admin.orders.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'])
|
// Route::get('/orders/{id}', [AdminOrderController::class, 'view'])
|
||||||
// ->name('admin.orders.view');
|
// ->name('admin.orders.view');
|
||||||
Route::get('/orders/{order:order_id}/see', [AdminOrderController::class, 'see'])
|
Route::get('/orders/{order:order_id}/see', [AdminOrderController::class, 'see'])
|
||||||
|
|||||||
Reference in New Issue
Block a user