Compare commits
12 Commits
44b8299b0e
...
main
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
0a65d5f596 | ||
|
|
0a1d0a9c55 | ||
|
|
409a854d7b | ||
|
|
4dab96b8d1 | ||
|
|
e7fef314fc | ||
|
|
5114357ff2 | ||
|
|
0afcb23511 | ||
|
|
340c2b2132 | ||
|
|
9b8c50fcec | ||
|
|
7a814dff1d | ||
|
|
3b24ee860a | ||
|
|
2dcd9fe332 |
@@ -11,13 +11,16 @@ use Illuminate\Support\Facades\DB;
|
||||
|
||||
class AdminAccountController extends Controller
|
||||
{
|
||||
public function updateEntry(Request $request)
|
||||
public function updateEntry(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $request->validate([
|
||||
'entry_no' => 'required|exists:entries,entry_no',
|
||||
'description' => 'required|string|max:255',
|
||||
'order_quantity' => 'required|numeric|min:0',
|
||||
'entry_no' => 'required|exists:entries,entry_no',
|
||||
'description' => 'required|string|max:255',
|
||||
'order_quantity' => 'required|numeric|min:0',
|
||||
'region' => 'required|string|max:50',
|
||||
'amount' => 'required|numeric|min:0',
|
||||
//'payment_status' => 'required|string|max:50',
|
||||
]);
|
||||
|
||||
$entry = Entry::where('entry_no', $data['entry_no'])->first();
|
||||
@@ -31,6 +34,10 @@ class AdminAccountController extends Controller
|
||||
|
||||
$entry->description = $data['description'];
|
||||
$entry->order_quantity = $data['order_quantity'];
|
||||
$entry->region = $data['region'];
|
||||
$entry->amount = $data['amount'];
|
||||
//$entry->payment_status = $data['payment_status'];
|
||||
|
||||
$entry->save();
|
||||
|
||||
return response()->json([
|
||||
@@ -46,36 +53,6 @@ class AdminAccountController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteEntry(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $request->validate([
|
||||
'entry_no' => 'required|exists:entries,entry_no',
|
||||
]);
|
||||
|
||||
$entry = Entry::where('entry_no', $data['entry_no'])->first();
|
||||
|
||||
if (!$entry) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Entry not found.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$entry->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Entry deleted successfully.',
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Server error: '.$e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚀 1. Get dashboard entries
|
||||
*/
|
||||
@@ -96,15 +73,17 @@ public function deleteEntry(Request $request)
|
||||
*/
|
||||
public function getAvailableOrders()
|
||||
{
|
||||
$orders = Order::whereDoesntHave('entries')
|
||||
->orderBy('id', 'desc')
|
||||
->get();
|
||||
|
||||
$orders = Order::whereDoesntHave('entries')
|
||||
->orderBy('id', 'desc')
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'orders' => $orders
|
||||
'orders' => $orders,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 🚀 3. Create new entry
|
||||
@@ -325,10 +304,9 @@ public function deleteEntry(Request $request)
|
||||
return DB::transaction(function () use ($data) {
|
||||
$entry = Entry::where('entry_no', $data['entry_no'])->firstOrFail();
|
||||
|
||||
// आधीचे orders काढू नका, फक्त नवीन add करा
|
||||
|
||||
$entry->orders()->syncWithoutDetaching($data['order_ids']);
|
||||
|
||||
// इथे quantity auto update
|
||||
$entry->order_quantity = $entry->orders()->count();
|
||||
$entry->save();
|
||||
|
||||
@@ -387,5 +365,35 @@ public function removeOrderFromEntry(Request $request)
|
||||
]);
|
||||
});
|
||||
}
|
||||
public function deleteEntry(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $request->validate([
|
||||
'entry_no' => 'required|exists:entries,entry_no',
|
||||
]);
|
||||
|
||||
$entry = Entry::where('entry_no', $data['entry_no'])->first();
|
||||
|
||||
if (!$entry) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Entry not found.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$entry->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Entry deleted successfully.',
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Server error: '.$e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -5,50 +5,52 @@ namespace App\Http\Controllers\Admin;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use App\Models\Admin;
|
||||
|
||||
|
||||
|
||||
class AdminAuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the admin login page
|
||||
*/
|
||||
public function showLoginForm()
|
||||
{
|
||||
return view('admin.login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle admin login
|
||||
*/
|
||||
public function login(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
'login' => 'required',
|
||||
'password' => 'required|string|min:6',
|
||||
]);
|
||||
|
||||
// Try to log in using the 'admin' guard
|
||||
if (Auth::guard('admin')->attempt($request->only('email', 'password'))) {
|
||||
return redirect()->route('admin.dashboard')->with('success', 'Welcome back, Admin!');
|
||||
$loginInput = $request->input('login');
|
||||
|
||||
if (filter_var($loginInput, FILTER_VALIDATE_EMAIL)) {
|
||||
$field = 'email';
|
||||
} elseif (preg_match('/^EMP\d+$/i', $loginInput)) {
|
||||
$field = 'employee_id';
|
||||
} else {
|
||||
$field = 'username';
|
||||
}
|
||||
|
||||
return back()->withErrors(['email' => 'Invalid email or password.']);
|
||||
$credentials = [
|
||||
$field => $loginInput,
|
||||
'password' => $request->password,
|
||||
];
|
||||
|
||||
// attempt login
|
||||
if (Auth::guard('admin')->attempt($credentials)) {
|
||||
$request->session()->regenerate();
|
||||
$user = Auth::guard('admin')->user();
|
||||
|
||||
return redirect()->route('admin.dashboard')->with('success', 'Welcome back, ' . $user->name . '!');
|
||||
}
|
||||
|
||||
return back()->withErrors(['login' => 'Invalid login credentials.']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout admin
|
||||
*/
|
||||
public function logout(Request $request)
|
||||
{
|
||||
Auth::guard('admin')->logout();
|
||||
|
||||
// Destroy the session completely
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect()->route('admin.login')->with('success', 'Logged out successfully.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +148,7 @@ class AdminOrderController extends Controller
|
||||
|
||||
// recalc totals and save to order
|
||||
$this->recalcTotals($order);
|
||||
$this->updateInvoiceFromOrder($order); // <-- NEW
|
||||
|
||||
return redirect()->back()->with('success', 'Item added and totals updated.');
|
||||
}
|
||||
@@ -164,6 +165,8 @@ class AdminOrderController extends Controller
|
||||
|
||||
// recalc totals
|
||||
$this->recalcTotals($order);
|
||||
$this->updateInvoiceFromOrder($order);
|
||||
|
||||
|
||||
return redirect()->back()->with('success', 'Item deleted and totals updated.');
|
||||
}
|
||||
@@ -180,6 +183,8 @@ class AdminOrderController extends Controller
|
||||
|
||||
// recalc totals
|
||||
$this->recalcTotals($order);
|
||||
$this->updateInvoiceFromOrder($order);
|
||||
|
||||
|
||||
return redirect()->back()->with('success', 'Item restored and totals updated.');
|
||||
}
|
||||
@@ -383,79 +388,79 @@ class AdminOrderController extends Controller
|
||||
return view('admin.orders', compact('orders'));
|
||||
}
|
||||
|
||||
// inside AdminOrderController
|
||||
// inside AdminOrderController
|
||||
|
||||
private function buildOrdersQueryFromRequest(Request $request)
|
||||
{
|
||||
$query = Order::with(['markList', 'invoice', 'shipments']);
|
||||
private function buildOrdersQueryFromRequest(Request $request)
|
||||
{
|
||||
$query = Order::with(['markList', 'invoice', 'shipments']);
|
||||
|
||||
// Search across order_id, markList.company_name, markList.customer_id, invoice.invoice_number
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->search;
|
||||
$query->where(function($q) use ($search) {
|
||||
$q->where('order_id', 'like', "%{$search}%")
|
||||
->orWhereHas('markList', function($q2) use ($search) {
|
||||
$q2->where('company_name', 'like', "%{$search}%")
|
||||
->orWhere('customer_id', 'like', "%{$search}%");
|
||||
})
|
||||
->orWhereHas('invoice', function($q3) use ($search) {
|
||||
$q3->where('invoice_number', 'like', "%{$search}%");
|
||||
});
|
||||
});
|
||||
// Search across order_id, markList.company_name, markList.customer_id, invoice.invoice_number
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->search;
|
||||
$query->where(function($q) use ($search) {
|
||||
$q->where('order_id', 'like', "%{$search}%")
|
||||
->orWhereHas('markList', function($q2) use ($search) {
|
||||
$q2->where('company_name', 'like', "%{$search}%")
|
||||
->orWhere('customer_id', 'like', "%{$search}%");
|
||||
})
|
||||
->orWhereHas('invoice', function($q3) use ($search) {
|
||||
$q3->where('invoice_number', 'like', "%{$search}%");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Invoice status filter
|
||||
if ($request->filled('status')) {
|
||||
$query->whereHas('invoice', function($q) use ($request) {
|
||||
$q->where('status', $request->status);
|
||||
});
|
||||
}
|
||||
|
||||
// Shipment status filter
|
||||
if ($request->filled('shipment')) {
|
||||
$query->whereHas('shipments', function($q) use ($request) {
|
||||
$q->where('status', $request->shipment);
|
||||
});
|
||||
}
|
||||
|
||||
// optional ordering
|
||||
$query->latest('id');
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
// Invoice status filter
|
||||
if ($request->filled('status')) {
|
||||
$query->whereHas('invoice', function($q) use ($request) {
|
||||
$q->where('status', $request->status);
|
||||
});
|
||||
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);
|
||||
}
|
||||
|
||||
// Shipment status filter
|
||||
if ($request->filled('shipment')) {
|
||||
$query->whereHas('shipments', function($q) use ($request) {
|
||||
$q->where('status', $request->shipment);
|
||||
});
|
||||
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');
|
||||
}
|
||||
|
||||
// optional ordering
|
||||
$query->latest('id');
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
|
||||
public function addTempItem(Request $request)
|
||||
public function addTempItem(Request $request)
|
||||
{
|
||||
// Validate item fields
|
||||
$item = $request->validate([
|
||||
@@ -509,161 +514,228 @@ public function addTempItem(Request $request)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function finishOrder(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'mark_no' => 'required',
|
||||
'origin' => 'required',
|
||||
'destination' => 'required',
|
||||
]);
|
||||
{
|
||||
$request->validate([
|
||||
'mark_no' => 'required',
|
||||
'origin' => 'required',
|
||||
'destination' => 'required',
|
||||
]);
|
||||
|
||||
$items = session('temp_order_items', []);
|
||||
$items = session('temp_order_items', []);
|
||||
|
||||
if (empty($items)) {
|
||||
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
|
||||
->with('error', 'Add at least one item before finishing.');
|
||||
}
|
||||
if (empty($items)) {
|
||||
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
|
||||
->with('error', 'Add at least one item before finishing.');
|
||||
}
|
||||
|
||||
// =======================
|
||||
// GENERATE ORDER ID
|
||||
// =======================
|
||||
$year = date('y');
|
||||
$prefix = "KNT-$year-";
|
||||
// =======================
|
||||
// GENERATE ORDER ID
|
||||
// =======================
|
||||
$year = date('y');
|
||||
$prefix = "KNT-$year-";
|
||||
|
||||
$lastOrder = Order::latest('id')->first();
|
||||
$nextNumber = $lastOrder ? intval(substr($lastOrder->order_id, -8)) + 1 : 1;
|
||||
$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);
|
||||
$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'));
|
||||
// =======================
|
||||
// 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
|
||||
// =======================
|
||||
$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,
|
||||
'ttl_amount' => $total_amount,
|
||||
'cbm' => $total_cbm,
|
||||
'ttl_cbm' => $total_ttl_cbm,
|
||||
'kg' => $total_kg,
|
||||
'ttl_kg' => $total_ttl_kg,
|
||||
'status' => 'pending',
|
||||
]);
|
||||
// =======================
|
||||
// CREATE ORDER
|
||||
// =======================
|
||||
$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,
|
||||
'ttl_amount' => $total_amount,
|
||||
'cbm' => $total_cbm,
|
||||
'ttl_cbm' => $total_ttl_cbm,
|
||||
'kg' => $total_kg,
|
||||
'ttl_kg' => $total_ttl_kg,
|
||||
'status' => 'pending',
|
||||
]);
|
||||
|
||||
// SAVE ORDER ITEMS
|
||||
foreach ($items as $item) {
|
||||
OrderItem::create([
|
||||
// SAVE 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'],
|
||||
'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'],
|
||||
]);
|
||||
}
|
||||
|
||||
// =======================
|
||||
// INVOICE CREATION START
|
||||
// =======================
|
||||
|
||||
// 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)
|
||||
$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();
|
||||
}
|
||||
|
||||
// 3. Create Invoice Record
|
||||
$invoice = \App\Models\Invoice::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'],
|
||||
'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'],
|
||||
'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,
|
||||
]);
|
||||
|
||||
// 4. Clone order items into invoice_items
|
||||
foreach ($order->items as $item) {
|
||||
\App\Models\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,
|
||||
]);
|
||||
}
|
||||
|
||||
// 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.');
|
||||
}
|
||||
|
||||
// =======================
|
||||
// INVOICE CREATION START
|
||||
// =======================
|
||||
// ---------------------------
|
||||
// ORDER CRUD: update / destroy
|
||||
// ---------------------------
|
||||
public function updateItem(Request $request, $id)
|
||||
{
|
||||
$item = OrderItem::findOrFail($id);
|
||||
$order = $item->order;
|
||||
|
||||
// 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)
|
||||
$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();
|
||||
}
|
||||
|
||||
// 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,
|
||||
'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,
|
||||
]);
|
||||
|
||||
// 4. Clone order items into invoice_items
|
||||
foreach ($order->items as $item) {
|
||||
\App\Models\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,
|
||||
$item->update([
|
||||
'description' => $request->description,
|
||||
'ctn' => $request->ctn,
|
||||
'qty' => $request->qty,
|
||||
'ttl_qty' => $request->ttl_qty,
|
||||
'unit' => $request->unit,
|
||||
'price' => $request->price,
|
||||
'ttl_amount' => $request->ttl_amount,
|
||||
'cbm' => $request->cbm,
|
||||
'ttl_cbm' => $request->ttl_cbm,
|
||||
'kg' => $request->kg,
|
||||
'ttl_kg' => $request->ttl_kg,
|
||||
'shop_no' => $request->shop_no,
|
||||
]);
|
||||
|
||||
$this->recalcTotals($order);
|
||||
$this->updateInvoiceFromOrder($order); // <-- NEW
|
||||
|
||||
return back()->with('success', 'Item updated successfully');
|
||||
}
|
||||
|
||||
// 5. TODO: PDF generation (I will add this later)
|
||||
$invoice->pdf_path = null; // placeholder for now
|
||||
$invoice->save();
|
||||
|
||||
// =======================
|
||||
// END INVOICE CREATION
|
||||
// =======================
|
||||
private function updateInvoiceFromOrder(Order $order)
|
||||
{
|
||||
$invoice = Invoice::where('order_id', $order->id)->first();
|
||||
|
||||
// CLEAR TEMP DATA
|
||||
session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']);
|
||||
if (!$invoice) {
|
||||
return; // No invoice exists (should not happen normally)
|
||||
}
|
||||
|
||||
return redirect()->route('admin.orders.index')
|
||||
->with('success', 'Order + Invoice created successfully.');
|
||||
}
|
||||
// Update invoice totals
|
||||
$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,
|
||||
'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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
179
app/Http/Controllers/Admin/AdminStaffController.php
Normal file
179
app/Http/Controllers/Admin/AdminStaffController.php
Normal file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use App\Models\Admin;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class AdminStaffController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$staff = Admin::where('type', 'staff')->orderBy('id', 'DESC')->get();
|
||||
return view('admin.staff.index', compact('staff'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$permissions = Permission::where('guard_name', 'admin')->get()->groupBy(function ($p) {
|
||||
return explode('.', $p->name)[0];
|
||||
});
|
||||
|
||||
return view('admin.staff.create', compact('permissions'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
// Personal Info
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:admins,email',
|
||||
'phone' => 'required|string|max:20',
|
||||
'emergency_phone' => 'nullable|string|max:20',
|
||||
'address' => 'nullable|string|max:255',
|
||||
|
||||
// Professional info
|
||||
'role' => 'nullable|string|max:100',
|
||||
'department' => 'nullable|string|max:100',
|
||||
'designation' => 'nullable|string|max:100',
|
||||
'joining_date' => 'nullable|date',
|
||||
'status' => 'required|string|in:active,inactive',
|
||||
'additional_info' => 'nullable|string',
|
||||
|
||||
// System access
|
||||
'username' => 'nullable|string|unique:admins,username',
|
||||
'password' => 'required|string|min:6',
|
||||
|
||||
// Permissions
|
||||
'permissions' => 'nullable|array',
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
$admin = Admin::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'phone' => $request->phone,
|
||||
'emergency_phone' => $request->emergency_phone,
|
||||
'address' => $request->address,
|
||||
|
||||
'role' => $request->role,
|
||||
'department' => $request->department,
|
||||
'designation' => $request->designation,
|
||||
'joining_date' => $request->joining_date,
|
||||
'status' => $request->status,
|
||||
'additional_info' => $request->additional_info,
|
||||
|
||||
'username' => $request->username,
|
||||
'password' => Hash::make($request->password),
|
||||
'type' => 'staff',
|
||||
]);
|
||||
|
||||
// Generate EMPLOYEE ID using admin ID (safe)
|
||||
$employeeId = 'EMP' . str_pad($admin->id, 4, '0', STR_PAD_LEFT);
|
||||
$admin->update(['employee_id' => $employeeId]);
|
||||
|
||||
// Assign permissions (if any)
|
||||
if ($request->permissions) {
|
||||
$admin->givePermissionTo($request->permissions);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
return redirect()->route('admin.staff.index')
|
||||
->with('success', 'Staff created successfully.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return back()->withErrors(['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$staff = Admin::where('type', 'staff')->findOrFail($id);
|
||||
|
||||
$permissions = Permission::where('guard_name', 'admin')->get()->groupBy(function ($p) {
|
||||
return explode('.', $p->name)[0];
|
||||
});
|
||||
|
||||
$staffPermissions = $staff->permissions->pluck('name')->toArray();
|
||||
|
||||
return view('admin.staff.edit', compact('staff', 'permissions', 'staffPermissions'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$staff = Admin::where('type', 'staff')->findOrFail($id);
|
||||
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:admins,email,' . $staff->id,
|
||||
'phone' => 'required|string|max:20',
|
||||
'emergency_phone' => 'nullable|string|max:20',
|
||||
'address' => 'nullable|string|max:255',
|
||||
|
||||
'role' => 'nullable|string|max:100',
|
||||
'department' => 'nullable|string|max:100',
|
||||
'designation' => 'nullable|string|max:100',
|
||||
'joining_date' => 'nullable|date',
|
||||
'status' => 'required|string|in:active,inactive',
|
||||
'additional_info' => 'nullable|string',
|
||||
|
||||
'username' => 'nullable|string|unique:admins,username,' . $staff->id,
|
||||
'password' => 'nullable|string|min:6',
|
||||
|
||||
'permissions' => 'nullable|array',
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
$staff->update([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'phone' => $request->phone,
|
||||
'emergency_phone' => $request->emergency_phone,
|
||||
'address' => $request->address,
|
||||
|
||||
'role' => $request->role,
|
||||
'department' => $request->department,
|
||||
'designation' => $request->designation,
|
||||
'joining_date' => $request->joining_date,
|
||||
'status' => $request->status,
|
||||
'additional_info' => $request->additional_info,
|
||||
|
||||
'username' => $request->username,
|
||||
]);
|
||||
|
||||
if ($request->password) {
|
||||
$staff->update(['password' => Hash::make($request->password)]);
|
||||
}
|
||||
|
||||
$staff->syncPermissions($request->permissions ?? []);
|
||||
|
||||
DB::commit();
|
||||
|
||||
return redirect()->route('admin.staff.index')
|
||||
->with('success', 'Staff updated successfully.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return back()->withErrors(['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$staff = Admin::where('type', 'staff')->findOrFail($id);
|
||||
$staff->delete();
|
||||
|
||||
return redirect()->route('admin.staff.index')
|
||||
->with('success', 'Staff removed successfully.');
|
||||
}
|
||||
}
|
||||
@@ -209,4 +209,20 @@ class ShipmentController extends Controller
|
||||
return redirect()->route('admin.shipments')
|
||||
->with('success', 'Shipment deleted successfully.');
|
||||
}
|
||||
|
||||
public function dummy($id)
|
||||
{
|
||||
// Load shipment
|
||||
$shipment = Shipment::with('orders')->findOrFail($id);
|
||||
|
||||
// Dummy data (you can modify anytime)
|
||||
$dummyData = [
|
||||
'title' => 'Dummy Shipment Preview',
|
||||
'generated_on' => now()->format('d M Y h:i A'),
|
||||
'note' => 'This is dummy shipment information for testing page layout.'
|
||||
];
|
||||
|
||||
return view('admin.view_shipment', compact('shipment', 'dummyData'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +1,43 @@
|
||||
<?php
|
||||
|
||||
// app/Models/Admin.php
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class Admin extends Authenticatable
|
||||
{
|
||||
use Notifiable;
|
||||
use HasFactory, Notifiable, HasRoles;
|
||||
|
||||
protected $guard = 'admin';
|
||||
protected $guard_name = 'admin';
|
||||
|
||||
protected $fillable = [
|
||||
'name', 'email', 'password', 'role',
|
||||
'name', 'email', 'password', 'username',
|
||||
'phone', 'emergency_phone', 'address',
|
||||
'role', 'department', 'designation', 'joining_date',
|
||||
'status', 'additional_info', 'type', // admin/staff indicator
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'password', 'remember_token',
|
||||
];
|
||||
|
||||
public function setPasswordAttribute($value)
|
||||
{
|
||||
if (!$value) return;
|
||||
|
||||
if (Hash::needsRehash($value)) {
|
||||
$this->attributes['password'] = Hash::make($value);
|
||||
} else {
|
||||
$this->attributes['password'] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function getDisplayNameAttribute()
|
||||
{
|
||||
return "{$this->name}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,27 @@ class ShipmentItem extends Model
|
||||
|
||||
protected $fillable = [
|
||||
'shipment_id',
|
||||
'order_id',
|
||||
'order_ctn',
|
||||
'order_qty',
|
||||
'order_ttl_qty',
|
||||
'order_ttl_amount',
|
||||
'order_ttl_kg',
|
||||
'order_id',
|
||||
|
||||
// OLD fields (keep them if old data exists)
|
||||
'order_ctn',
|
||||
'order_qty',
|
||||
'order_ttl_qty',
|
||||
'order_ttl_amount',
|
||||
'order_ttl_kg',
|
||||
|
||||
// NEW fields (added for correct shipments)
|
||||
'ctn',
|
||||
'qty',
|
||||
'ttl_qty',
|
||||
|
||||
'cbm',
|
||||
'ttl_cbm',
|
||||
|
||||
'kg',
|
||||
'ttl_kg',
|
||||
|
||||
'ttl_amount',
|
||||
];
|
||||
|
||||
// ---------------------------
|
||||
@@ -36,11 +51,11 @@ class ShipmentItem extends Model
|
||||
// Helper: return order data with fallback to snapshot
|
||||
public function getDisplayQty()
|
||||
{
|
||||
return $this->order->qty ?? $this->order_qty;
|
||||
return $this->qty;
|
||||
}
|
||||
|
||||
public function getDisplayAmount()
|
||||
{
|
||||
return $this->order->ttl_amount ?? $this->order_ttl_amount;
|
||||
return $this->ttl_amount;
|
||||
}
|
||||
}
|
||||
|
||||
81
app/Models/Staff.php
Normal file
81
app/Models/Staff.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class Staff extends Authenticatable
|
||||
{
|
||||
use Notifiable, HasRoles, SoftDeletes;
|
||||
|
||||
/**
|
||||
* The guard name used by Spatie.
|
||||
* Make sure this matches the guard you'll use for admin/staff auth (usually 'web' or 'admin').
|
||||
*/
|
||||
protected $guard_name = 'admin';
|
||||
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected $fillable = [
|
||||
'employee_id',
|
||||
'name',
|
||||
'email',
|
||||
'phone',
|
||||
'emergency_phone',
|
||||
'address',
|
||||
'role', // business role/title (not Spatie role)
|
||||
'department',
|
||||
'designation',
|
||||
'joining_date',
|
||||
'status',
|
||||
'additional_info',
|
||||
'username',
|
||||
'password',
|
||||
];
|
||||
|
||||
/**
|
||||
* Hidden attributes (not returned in arrays / JSON).
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
];
|
||||
|
||||
/**
|
||||
* Casts
|
||||
*/
|
||||
protected $casts = [
|
||||
'joining_date' => 'date',
|
||||
];
|
||||
|
||||
/**
|
||||
* Mutator: automatically hash password when set.
|
||||
* Accepts plain text and hashes it with bcrypt.
|
||||
*/
|
||||
public function setPasswordAttribute($value)
|
||||
{
|
||||
if (empty($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If already hashed (starts with $2y$), don't double-hash
|
||||
if (Hash::needsRehash($value)) {
|
||||
$this->attributes['password'] = Hash::make($value);
|
||||
} else {
|
||||
$this->attributes['password'] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional helper to get display name (useful in views/logs).
|
||||
*/
|
||||
public function getDisplayNameAttribute()
|
||||
{
|
||||
return $this->name . ' (' . $this->employee_id . ')';
|
||||
}
|
||||
}
|
||||
40
app/Providers/AuthServiceProvider.php
Normal file
40
app/Providers/AuthServiceProvider.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The policy mappings for the application.
|
||||
*
|
||||
* @var array<class-string, class-string>
|
||||
*/
|
||||
protected $policies = [
|
||||
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
|
||||
];
|
||||
|
||||
/**
|
||||
* Register any authentication / authorization services.
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
// SUPER ADMIN bypass
|
||||
Gate::before(function ($user, $ability) {
|
||||
if ($user->hasRole('super-admin')) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// ADMIN bypass
|
||||
Gate::before(function ($user, $ability) {
|
||||
if ($user->hasRole('admin')) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,5 @@
|
||||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\AuthServiceProvider::class,
|
||||
];
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
"barryvdh/laravel-dompdf": "^3.1",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"maatwebsite/excel": "^3.1",
|
||||
"maatwebsite/excel": "^1.1",
|
||||
"mpdf/mpdf": "^8.2",
|
||||
"php-open-source-saver/jwt-auth": "2.8"
|
||||
"php-open-source-saver/jwt-auth": "2.8",
|
||||
"spatie/laravel-permission": "^6.23"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
|
||||
1332
composer.lock
generated
1332
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -85,6 +85,11 @@ return [
|
||||
// 'driver' => 'database',
|
||||
// 'table' => 'users',
|
||||
// ],
|
||||
'staff' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => App\Models\Staff::class,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|
||||
202
config/permission.php
Normal file
202
config/permission.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'models' => [
|
||||
|
||||
/*
|
||||
* When using the "HasPermissions" trait from this package, we need to know which
|
||||
* Eloquent model should be used to retrieve your permissions. Of course, it
|
||||
* is often just the "Permission" model but you may use whatever you like.
|
||||
*
|
||||
* The model you want to use as a Permission model needs to implement the
|
||||
* `Spatie\Permission\Contracts\Permission` contract.
|
||||
*/
|
||||
|
||||
'permission' => Spatie\Permission\Models\Permission::class,
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* Eloquent model should be used to retrieve your roles. Of course, it
|
||||
* is often just the "Role" model but you may use whatever you like.
|
||||
*
|
||||
* The model you want to use as a Role model needs to implement the
|
||||
* `Spatie\Permission\Contracts\Role` contract.
|
||||
*/
|
||||
|
||||
'role' => Spatie\Permission\Models\Role::class,
|
||||
|
||||
],
|
||||
|
||||
'table_names' => [
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* table should be used to retrieve your roles. We have chosen a basic
|
||||
* default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'roles' => 'roles',
|
||||
|
||||
/*
|
||||
* When using the "HasPermissions" trait from this package, we need to know which
|
||||
* table should be used to retrieve your permissions. We have chosen a basic
|
||||
* default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'permissions' => 'permissions',
|
||||
|
||||
/*
|
||||
* When using the "HasPermissions" trait from this package, we need to know which
|
||||
* table should be used to retrieve your models permissions. We have chosen a
|
||||
* basic default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'model_has_permissions' => 'model_has_permissions',
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* table should be used to retrieve your models roles. We have chosen a
|
||||
* basic default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'model_has_roles' => 'model_has_roles',
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* table should be used to retrieve your roles permissions. We have chosen a
|
||||
* basic default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'role_has_permissions' => 'role_has_permissions',
|
||||
],
|
||||
|
||||
'column_names' => [
|
||||
/*
|
||||
* Change this if you want to name the related pivots other than defaults
|
||||
*/
|
||||
'role_pivot_key' => null, // default 'role_id',
|
||||
'permission_pivot_key' => null, // default 'permission_id',
|
||||
|
||||
/*
|
||||
* Change this if you want to name the related model primary key other than
|
||||
* `model_id`.
|
||||
*
|
||||
* For example, this would be nice if your primary keys are all UUIDs. In
|
||||
* that case, name this `model_uuid`.
|
||||
*/
|
||||
|
||||
'model_morph_key' => 'model_id',
|
||||
|
||||
/*
|
||||
* Change this if you want to use the teams feature and your related model's
|
||||
* foreign key is other than `team_id`.
|
||||
*/
|
||||
|
||||
'team_foreign_key' => 'team_id',
|
||||
],
|
||||
|
||||
/*
|
||||
* When set to true, the method for checking permissions will be registered on the gate.
|
||||
* Set this to false if you want to implement custom logic for checking permissions.
|
||||
*/
|
||||
|
||||
'register_permission_check_method' => true,
|
||||
|
||||
/*
|
||||
* When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered
|
||||
* this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated
|
||||
* NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it.
|
||||
*/
|
||||
'register_octane_reset_listener' => false,
|
||||
|
||||
/*
|
||||
* Events will fire when a role or permission is assigned/unassigned:
|
||||
* \Spatie\Permission\Events\RoleAttached
|
||||
* \Spatie\Permission\Events\RoleDetached
|
||||
* \Spatie\Permission\Events\PermissionAttached
|
||||
* \Spatie\Permission\Events\PermissionDetached
|
||||
*
|
||||
* To enable, set to true, and then create listeners to watch these events.
|
||||
*/
|
||||
'events_enabled' => false,
|
||||
|
||||
/*
|
||||
* Teams Feature.
|
||||
* When set to true the package implements teams using the 'team_foreign_key'.
|
||||
* If you want the migrations to register the 'team_foreign_key', you must
|
||||
* set this to true before doing the migration.
|
||||
* If you already did the migration then you must make a new migration to also
|
||||
* add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions'
|
||||
* (view the latest version of this package's migration file)
|
||||
*/
|
||||
|
||||
'teams' => false,
|
||||
|
||||
/*
|
||||
* The class to use to resolve the permissions team id
|
||||
*/
|
||||
'team_resolver' => \Spatie\Permission\DefaultTeamResolver::class,
|
||||
|
||||
/*
|
||||
* Passport Client Credentials Grant
|
||||
* When set to true the package will use Passports Client to check permissions
|
||||
*/
|
||||
|
||||
'use_passport_client_credentials' => false,
|
||||
|
||||
/*
|
||||
* When set to true, the required permission names are added to exception messages.
|
||||
* This could be considered an information leak in some contexts, so the default
|
||||
* setting is false here for optimum safety.
|
||||
*/
|
||||
|
||||
'display_permission_in_exception' => false,
|
||||
|
||||
/*
|
||||
* When set to true, the required role names are added to exception messages.
|
||||
* This could be considered an information leak in some contexts, so the default
|
||||
* setting is false here for optimum safety.
|
||||
*/
|
||||
|
||||
'display_role_in_exception' => false,
|
||||
|
||||
/*
|
||||
* By default wildcard permission lookups are disabled.
|
||||
* See documentation to understand supported syntax.
|
||||
*/
|
||||
|
||||
'enable_wildcard_permission' => false,
|
||||
|
||||
/*
|
||||
* The class to use for interpreting wildcard permissions.
|
||||
* If you need to modify delimiters, override the class and specify its name here.
|
||||
*/
|
||||
// 'wildcard_permission' => Spatie\Permission\WildcardPermission::class,
|
||||
|
||||
/* Cache-specific settings */
|
||||
|
||||
'cache' => [
|
||||
|
||||
/*
|
||||
* By default all permissions are cached for 24 hours to speed up performance.
|
||||
* When permissions or roles are updated the cache is flushed automatically.
|
||||
*/
|
||||
|
||||
'expiration_time' => \DateInterval::createFromDateString('24 hours'),
|
||||
|
||||
/*
|
||||
* The cache key used to store all permissions.
|
||||
*/
|
||||
|
||||
'key' => 'spatie.permission.cache',
|
||||
|
||||
/*
|
||||
* You may optionally indicate a specific cache driver to use for permission and
|
||||
* role caching using any of the `store` drivers listed in the cache.php config
|
||||
* file. Using 'default' here means to use the `default` set in cache.php.
|
||||
*/
|
||||
|
||||
'store' => 'default',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('shipment_items', function (Blueprint $table) {
|
||||
|
||||
if (!Schema::hasColumn('shipment_items', 'ctn')) {
|
||||
$table->integer('ctn')->default(0);
|
||||
}
|
||||
|
||||
if (!Schema::hasColumn('shipment_items', 'qty')) {
|
||||
$table->integer('qty')->default(0);
|
||||
}
|
||||
|
||||
if (!Schema::hasColumn('shipment_items', 'ttl_qty')) {
|
||||
$table->integer('ttl_qty')->default(0);
|
||||
}
|
||||
|
||||
if (!Schema::hasColumn('shipment_items', 'cbm')) {
|
||||
$table->double('cbm')->default(0);
|
||||
}
|
||||
|
||||
if (!Schema::hasColumn('shipment_items', 'ttl_cbm')) {
|
||||
$table->double('ttl_cbm')->default(0);
|
||||
}
|
||||
|
||||
if (!Schema::hasColumn('shipment_items', 'kg')) {
|
||||
$table->double('kg')->default(0);
|
||||
}
|
||||
|
||||
if (!Schema::hasColumn('shipment_items', 'ttl_kg')) {
|
||||
$table->double('ttl_kg')->default(0);
|
||||
}
|
||||
|
||||
if (!Schema::hasColumn('shipment_items', 'ttl_amount')) {
|
||||
$table->double('ttl_amount')->default(0);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('shipment_items', function (Blueprint $table) {
|
||||
// safely remove columns (optional)
|
||||
$columns = [
|
||||
'ctn', 'qty', 'ttl_qty', 'cbm',
|
||||
'ttl_cbm', 'kg', 'ttl_kg', 'ttl_amount'
|
||||
];
|
||||
|
||||
foreach ($columns as $col) {
|
||||
if (Schema::hasColumn('shipment_items', $col)) {
|
||||
$table->dropColumn($col);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$teams = config('permission.teams');
|
||||
$tableNames = config('permission.table_names');
|
||||
$columnNames = config('permission.column_names');
|
||||
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
|
||||
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
|
||||
|
||||
throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), Exception::class, 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
|
||||
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
|
||||
// $table->engine('InnoDB');
|
||||
$table->bigIncrements('id'); // permission id
|
||||
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
|
||||
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['name', 'guard_name']);
|
||||
});
|
||||
|
||||
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
|
||||
// $table->engine('InnoDB');
|
||||
$table->bigIncrements('id'); // role id
|
||||
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
|
||||
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
|
||||
}
|
||||
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
|
||||
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
|
||||
$table->timestamps();
|
||||
if ($teams || config('permission.testing')) {
|
||||
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
|
||||
} else {
|
||||
$table->unique(['name', 'guard_name']);
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->on($tableNames['permissions'])
|
||||
->onDelete('cascade');
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->on($tableNames['roles'])
|
||||
->onDelete('cascade');
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->on($tableNames['permissions'])
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->on($tableNames['roles'])
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
|
||||
});
|
||||
|
||||
app('cache')
|
||||
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
||||
->forget(config('permission.cache.key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$tableNames = config('permission.table_names');
|
||||
|
||||
throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
|
||||
|
||||
Schema::drop($tableNames['role_has_permissions']);
|
||||
Schema::drop($tableNames['model_has_roles']);
|
||||
Schema::drop($tableNames['model_has_permissions']);
|
||||
Schema::drop($tableNames['roles']);
|
||||
Schema::drop($tableNames['permissions']);
|
||||
}
|
||||
};
|
||||
50
database/migrations/2025_12_04_071300_create_staff_table.php
Normal file
50
database/migrations/2025_12_04_071300_create_staff_table.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('staff', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
// Personal Information
|
||||
$table->string('employee_id')->unique();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->string('phone');
|
||||
$table->string('emergency_phone')->nullable();
|
||||
$table->text('address')->nullable();
|
||||
|
||||
// Professional Information
|
||||
$table->string('role')->nullable(); // Job title
|
||||
$table->string('department')->nullable();
|
||||
$table->string('designation')->nullable();
|
||||
$table->date('joining_date')->nullable();
|
||||
$table->string('status')->default('active'); // active/inactive
|
||||
$table->text('additional_info')->nullable();
|
||||
|
||||
// System Access
|
||||
$table->string('username')->unique();
|
||||
$table->string('password');
|
||||
|
||||
$table->softDeletes();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('staff');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('admins', function (Blueprint $table) {
|
||||
$table->string('employee_id')->unique()->nullable();
|
||||
$table->string('phone')->nullable();
|
||||
$table->string('emergency_phone')->nullable();
|
||||
$table->text('address')->nullable();
|
||||
|
||||
$table->string('department')->nullable();
|
||||
$table->string('designation')->nullable();
|
||||
$table->date('joining_date')->nullable();
|
||||
$table->enum('status', ['active','inactive'])->default('active');
|
||||
$table->text('additional_info')->nullable();
|
||||
|
||||
$table->string('username')->unique()->nullable();
|
||||
$table->enum('type', ['admin','staff'])->default('staff');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('admins', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up()
|
||||
{
|
||||
Schema::table('admins', function (Blueprint $table) {
|
||||
$table->string('role')->nullable()->change(); // <-- Fix problem
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::table('admins', function (Blueprint $table) {
|
||||
$table->enum('role', ['admin', 'super-admin'])->nullable()->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('support_tickets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('user_id'); // user who owns the chat
|
||||
$table->string('status')->default('open'); // open / closed
|
||||
$table->timestamps();
|
||||
|
||||
// foreign key constraint (optional but recommended)
|
||||
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('support_tickets');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('chat_messages', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('ticket_id'); // support ticket ID
|
||||
$table->unsignedBigInteger('sender_id'); // user or admin/staff
|
||||
$table->text('message')->nullable(); // message content
|
||||
$table->string('file_path')->nullable(); // image/pdf/video
|
||||
$table->string('file_type')->default('text'); // text/image/pdf/video
|
||||
$table->timestamps();
|
||||
|
||||
// foreign keys
|
||||
$table->foreign('ticket_id')->references('id')->on('support_tickets')->onDelete('cascade');
|
||||
$table->foreign('sender_id')->references('id')->on('users')->onDelete('cascade'); // admin also stored in users table? If admin separate, change later.
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('chat_messages');
|
||||
}
|
||||
};
|
||||
103
database/seeders/PermissionSeeder.php
Normal file
103
database/seeders/PermissionSeeder.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class PermissionSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
// ------------------------------------------------------
|
||||
// FINAL PERMISSION LIST (YOUR DATA)
|
||||
// ------------------------------------------------------
|
||||
|
||||
$permissions = [
|
||||
|
||||
// ORDER
|
||||
'order.view',
|
||||
'order.create',
|
||||
'order.edit',
|
||||
'order.delete',
|
||||
|
||||
// EXTRA (ORDERS)
|
||||
'orders.view', // you added this separately
|
||||
|
||||
// SHIPMENT
|
||||
'shipment.view',
|
||||
'shipment.create',
|
||||
'shipment.delete',
|
||||
|
||||
// INVOICE
|
||||
'invoice.view',
|
||||
'invoice.edit',
|
||||
'invoice.add_installment',
|
||||
|
||||
// CUSTOMER
|
||||
'customer.view',
|
||||
'customer.create',
|
||||
|
||||
// REQUEST
|
||||
'request.view',
|
||||
'request.update_profile',
|
||||
|
||||
|
||||
|
||||
// @can('')
|
||||
// @endcan
|
||||
|
||||
|
||||
|
||||
|
||||
// ACCOUNT
|
||||
'account.view',
|
||||
'account.create_order',
|
||||
'account.edit_order',
|
||||
'account.delete_order',
|
||||
'account.toggle_payment_status',
|
||||
'account.add_installment',
|
||||
'account.view_installments',
|
||||
|
||||
// REPORT
|
||||
'report.view',
|
||||
|
||||
// MARK LIST
|
||||
'mark_list.view',
|
||||
];
|
||||
|
||||
// ------------------------------------------------------
|
||||
// CREATE PERMISSIONS
|
||||
// ------------------------------------------------------
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
Permission::firstOrCreate(
|
||||
['name' => $permission, 'guard_name' => 'admin']
|
||||
);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
// ROLES
|
||||
// ------------------------------------------------------
|
||||
|
||||
// Create super-admin role
|
||||
$superAdminRole = Role::firstOrCreate(
|
||||
['name' => 'super-admin', 'guard_name' => 'admin']
|
||||
);
|
||||
|
||||
// Create admin role
|
||||
$adminRole = Role::firstOrCreate(
|
||||
['name' => 'admin', 'guard_name' => 'admin']
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// ASSIGN ALL PERMISSIONS TO BOTH ROLES
|
||||
// ------------------------------------------------------
|
||||
|
||||
$allPermissions = Permission::where('guard_name', 'admin')->get();
|
||||
|
||||
$superAdminRole->syncPermissions($allPermissions);
|
||||
$adminRole->syncPermissions($allPermissions);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000028.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000028.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000029.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000029.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000030.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000030.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000032.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000032.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000033.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000033.pdf
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -809,9 +809,11 @@
|
||||
All
|
||||
</a>
|
||||
|
||||
@can('customer.create')
|
||||
<a href="{{ route('admin.customers.add') }}" class="add-customer-btn">
|
||||
<i class="bi bi-plus-circle me-1"></i>Add Customer
|
||||
</a>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1131,9 +1131,12 @@ body, .container-fluid {
|
||||
<div class="order-mgmt-box">
|
||||
<div class="order-mgmt-bar">
|
||||
<span class="order-mgmt-title"><i class="bi bi-table"></i> Order Management</span>
|
||||
<button class="create-order-btn" id="openCreateOrderModal">
|
||||
<i class="bi bi-plus-circle"></i> Create Order
|
||||
</button>
|
||||
@can('order.create')
|
||||
<button class="create-order-btn" id="openCreateOrderModal">
|
||||
<i class="bi bi-plus-circle"></i> Create Order
|
||||
</button>
|
||||
@endcan
|
||||
|
||||
</div>
|
||||
|
||||
<div class="order-mgmt-main">
|
||||
|
||||
@@ -452,9 +452,11 @@ body {
|
||||
</div>
|
||||
|
||||
<div class="text-end mt-3">
|
||||
@can('invoice.edit')
|
||||
<button type="submit" class="btn-success-compact btn-compact">
|
||||
<i class="fas fa-save me-2"></i>Update Invoice
|
||||
</button>
|
||||
@endcan
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -541,11 +543,13 @@ body {
|
||||
<h4>
|
||||
<i class="fas fa-credit-card me-2"></i>Installment Payments
|
||||
</h4>
|
||||
@if($remaining > 0)
|
||||
<button id="toggleInstallmentForm" class="btn-primary-compact btn-compact">
|
||||
<i class="fas fa-plus-circle me-2"></i>Add Installment
|
||||
</button>
|
||||
@endif
|
||||
@can('invoice.add_installment')
|
||||
@if($remaining > 0)
|
||||
<button id="toggleInstallmentForm" class="btn-primary-compact btn-compact">
|
||||
<i class="fas fa-plus-circle me-2"></i>Add Installment
|
||||
</button>
|
||||
@endif
|
||||
@endcan
|
||||
</div>
|
||||
|
||||
<div class="card-body-compact">
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
font-family: 'Inter', Arial, sans-serif;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
/* ✨ Sidebar Glass + Animated Highlight Effect */
|
||||
@@ -36,7 +37,13 @@
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
}
|
||||
|
||||
/* Sidebar collapsed state */
|
||||
.sidebar.collapsed {
|
||||
transform: translateX(-100%);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.sidebar .logo {
|
||||
@@ -73,7 +80,6 @@
|
||||
overflow: hidden;
|
||||
transition: all 0.25s ease;
|
||||
z-index: 0;
|
||||
|
||||
}
|
||||
|
||||
/* Background Animation */
|
||||
@@ -151,6 +157,39 @@
|
||||
flex-direction: column;
|
||||
width: calc(100vw - 190px);
|
||||
margin-left: 190px;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
/* Main content when sidebar is collapsed */
|
||||
.main-content.expanded {
|
||||
margin-left: 0;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
/* Header hamburger button */
|
||||
.header-toggle {
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
margin-right: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.3s ease;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.header-toggle:hover {
|
||||
transform: scale(1.1);
|
||||
background: rgba(43, 92, 182, 0.1);
|
||||
}
|
||||
|
||||
.header-toggle i {
|
||||
font-size: 1.6rem;
|
||||
color: #2b5cb6;
|
||||
}
|
||||
|
||||
header {
|
||||
@@ -165,6 +204,11 @@
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 18px 16px 16px 16px;
|
||||
flex-grow: 1;
|
||||
@@ -184,54 +228,96 @@
|
||||
<div class="word"><strong>KENT</strong><br /><small>International Pvt. Ltd.</small></div>
|
||||
</div>
|
||||
|
||||
<a href="{{ route('admin.dashboard') }}" class="{{ request()->routeIs('admin.dashboard') ? 'active' : '' }}"><i class="bi bi-house"></i> Dashboard</a>
|
||||
<a href="{{ route('admin.shipments') }}" class="{{ request()->routeIs('admin.shipments') ? 'active' : '' }}"><i class="bi bi-truck"></i> Shipments</a>
|
||||
<a href="{{ route('admin.invoices.index') }}"
|
||||
class="{{ request()->routeIs('admin.invoices.index') ? 'active' : '' }}">
|
||||
{{-- Dashboard (requires order.view) --}}
|
||||
@can('order.view')
|
||||
<a href="{{ route('admin.dashboard') }}" class="{{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
|
||||
<i class="bi bi-house"></i> Dashboard
|
||||
</a>
|
||||
@endcan
|
||||
|
||||
{{-- Shipments --}}
|
||||
@can('shipment.view')
|
||||
<a href="{{ route('admin.shipments') }}" class="{{ request()->routeIs('admin.shipments') ? 'active' : '' }}">
|
||||
<i class="bi bi-truck"></i> Shipments
|
||||
</a>
|
||||
@endcan
|
||||
|
||||
{{-- Invoice --}}
|
||||
@can('invoice.view')
|
||||
<a href="{{ route('admin.invoices.index') }}" class="{{ request()->routeIs('admin.invoices.index') ? 'active' : '' }}">
|
||||
<i class="bi bi-receipt"></i> Invoice
|
||||
</a>
|
||||
@endcan
|
||||
|
||||
<a href="{{ route('admin.customers.index') }}"
|
||||
class="{{ request()->routeIs('admin.customers.index') ? 'active' : '' }}">
|
||||
{{-- Customers --}}
|
||||
@can('customer.view')
|
||||
<a href="{{ route('admin.customers.index') }}" class="{{ request()->routeIs('admin.customers.index') ? 'active' : '' }}">
|
||||
<i class="bi bi-people"></i> Customers
|
||||
</a>
|
||||
@endcan
|
||||
|
||||
<a href="{{ route('admin.reports') }}"
|
||||
class="{{ request()->routeIs('admin.reports') ? 'active' : '' }}">
|
||||
{{-- Reports --}}
|
||||
@can('report.view')
|
||||
<a href="{{ route('admin.reports') }}" class="{{ request()->routeIs('admin.reports') ? 'active' : '' }}">
|
||||
<i class="bi bi-graph-up"></i> Reports
|
||||
</a>
|
||||
@endcan
|
||||
|
||||
|
||||
<a href="{{ route('admin.chat_support') }}" class="{{ request()->routeIs('admin.chat_support') ? 'active' : '' }}"><i class="bi bi-chat-dots"></i> Chat Support</a>
|
||||
<!-- <a href="{{ route('admin.orders.index') }}"
|
||||
class="{{ request()->routeIs('admin.orders.*') ? 'active' : '' }}">
|
||||
<i class="bi bi-bag"></i> Orders
|
||||
</a> -->
|
||||
<a href="{{ route('admin.orders') }}"
|
||||
class="{{ request()->routeIs('admin.orders') ? 'active' : '' }}">
|
||||
<i class="bi bi-bag"></i> Orders
|
||||
</a>
|
||||
<a href="{{ route('admin.requests') }}" class="{{ request()->routeIs('admin.requests') ? 'active' : '' }}"><i class="bi bi-envelope"></i> Requests</a>
|
||||
<li>
|
||||
<a href="{{ route('admin.profile.requests') }}">
|
||||
<i class="bi bi-person-lines-fill"></i>
|
||||
Profile Update Requests
|
||||
{{-- Chat Support (NO PERMISSION REQUIRED) --}}
|
||||
<a href="{{ route('admin.chat_support') }}" class="{{ request()->routeIs('admin.chat_support') ? 'active' : '' }}">
|
||||
<i class="bi bi-chat-dots"></i> Chat Support
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<a href="{{ route('admin.staff') }}" class="{{ request()->routeIs('admin.staff') ? 'active' : '' }}"><i class="bi bi-person-badge"></i> Staff</a>
|
||||
<a href="{{ route('admin.account') }}" class="{{ request()->routeIs('admin.account') ? 'active' : '' }}"><i class="bi bi-gear"></i> Account</a>
|
||||
<a href="{{ route('admin.marklist.index') }}" class="{{ request()->routeIs('admin.marklist.index') ? 'active' : '' }}"><i class="bi bi-list-check"></i> Mark List</a>
|
||||
{{-- Orders --}}
|
||||
@can('orders.view')
|
||||
<a href="{{ route('admin.orders') }}" class="{{ request()->routeIs('admin.orders') ? 'active' : '' }}">
|
||||
<i class="bi bi-bag"></i> Orders
|
||||
</a>
|
||||
@endcan
|
||||
|
||||
<!-- <form method="POST" action="{{ route('admin.logout') }}" class="mt-4 px-3">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-danger w-100"><i class="bi bi-box-arrow-right"></i> Logout</button>
|
||||
</form> -->
|
||||
{{-- Requests --}}
|
||||
@can('request.view')
|
||||
<a href="{{ route('admin.requests') }}" class="{{ request()->routeIs('admin.requests') ? 'active' : '' }}">
|
||||
<i class="bi bi-envelope"></i> Requests
|
||||
</a>
|
||||
@endcan
|
||||
|
||||
{{-- Profile Update Requests --}}
|
||||
@can('request.update_profile')
|
||||
<a href="{{ route('admin.profile.requests') }}">
|
||||
<i class="bi bi-person-lines-fill"></i> Profile Update Requests
|
||||
</a>
|
||||
@endcan
|
||||
|
||||
{{-- Staff (NO PERMISSION REQUIRED) --}}
|
||||
<a href="{{ route('admin.staff.index') }}" class="{{ request()->routeIs('admin.staff.*') ? 'active' : '' }}">
|
||||
<i class="bi bi-person-badge"></i> Staff
|
||||
</a>
|
||||
|
||||
{{-- Account Section --}}
|
||||
@can('account.view')
|
||||
<a href="{{ route('admin.account') }}" class="{{ request()->routeIs('admin.account') ? 'active' : '' }}">
|
||||
<i class="bi bi-gear"></i> Account
|
||||
</a>
|
||||
@endcan
|
||||
|
||||
{{-- Mark List --}}
|
||||
@can('mark_list.view')
|
||||
<a href="{{ route('admin.marklist.index') }}" class="{{ request()->routeIs('admin.marklist.index') ? 'active' : '' }}">
|
||||
<i class="bi bi-list-check"></i> Mark List
|
||||
</a>
|
||||
@endcan
|
||||
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<header>
|
||||
<h5>@yield('page-title')</h5>
|
||||
<div class="header-left">
|
||||
<button class="header-toggle" id="headerToggle">
|
||||
<i class="bi bi-list"></i>
|
||||
</button>
|
||||
<h5 class="mb-0">@yield('page-title')</h5>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<i class="bi bi-bell position-relative fs-4">
|
||||
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">2</span>
|
||||
@@ -260,5 +346,23 @@
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const headerToggle = document.getElementById('headerToggle');
|
||||
const sidebar = document.querySelector('.sidebar');
|
||||
const mainContent = document.querySelector('.main-content');
|
||||
|
||||
// Function to toggle sidebar
|
||||
function toggleSidebar() {
|
||||
sidebar.classList.toggle('collapsed');
|
||||
mainContent.classList.toggle('expanded');
|
||||
}
|
||||
|
||||
// Header toggle button click event
|
||||
if (headerToggle) {
|
||||
headerToggle.addEventListener('click', toggleSidebar);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -120,19 +120,33 @@
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('admin.login.submit') }}">
|
||||
@csrf
|
||||
<div class="mb-3">
|
||||
<label>Email</label>
|
||||
<input type="email" name="email" class="form-control" value="{{ old('email') }}" required>
|
||||
</div>
|
||||
@csrf
|
||||
|
||||
<div class="mb-3">
|
||||
<label>Password</label>
|
||||
<input type="password" name="password" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Username / Email / Employee ID</label>
|
||||
<input
|
||||
type="text"
|
||||
name="login"
|
||||
class="form-control"
|
||||
value="{{ old('login') }}"
|
||||
placeholder="Enter Email or Username or EMP ID"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label>Password</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
class="form-control"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100">Login</button>
|
||||
</form>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100">Login</button>
|
||||
</form>
|
||||
|
||||
<div class="secure-encrypted">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="green" class="bi bi-lock-fill" viewBox="0 0 16 16">
|
||||
|
||||
@@ -748,10 +748,13 @@
|
||||
|
||||
<script>
|
||||
// Pagination state
|
||||
let currentPage = 1;
|
||||
const itemsPerPage = 10;
|
||||
let allOrders = @json($orders);
|
||||
let filteredOrders = [...allOrders];
|
||||
let currentPage = 1;
|
||||
const itemsPerPage = 10;
|
||||
let allOrders = @json($orders);
|
||||
let filteredOrders = [...allOrders];
|
||||
console.log('ORDERS SAMPLE:', allOrders[0]);
|
||||
|
||||
|
||||
|
||||
// Status icon helper functions
|
||||
function getInvoiceStatusIcon(status) {
|
||||
@@ -931,10 +934,11 @@
|
||||
const shipmentFilter = document.getElementById('shipmentFilter').value;
|
||||
|
||||
filteredOrders = allOrders.filter(order => {
|
||||
const matchesSearch = !searchTerm ||
|
||||
(order.order_id && order.order_id.toLowerCase().includes(searchTerm)) ||
|
||||
(order.markList?.company_name && order.markList.company_name.toLowerCase().includes(searchTerm)) ||
|
||||
(order.invoice?.invoice_number && order.invoice.invoice_number.toLowerCase().includes(searchTerm));
|
||||
const matchesSearch = !searchTerm ||
|
||||
order.order_id?.toLowerCase().includes(searchTerm) ||
|
||||
order.mark_list?.company_name?.toLowerCase().includes(searchTerm) ||
|
||||
order.invoice?.invoice_number?.toLowerCase().includes(searchTerm);
|
||||
|
||||
|
||||
const matchesStatus = !statusFilter ||
|
||||
(order.invoice?.status && order.invoice.status.toLowerCase() === statusFilter);
|
||||
@@ -1049,7 +1053,7 @@
|
||||
const paginatedItems = filteredOrders.slice(startIndex, endIndex);
|
||||
|
||||
paginatedItems.forEach(order => {
|
||||
const mark = order.markList || null;
|
||||
const mark = order.mark_list || null;
|
||||
const invoice = order.invoice || null;
|
||||
const shipment = order.shipments?.[0] || null;
|
||||
const invoiceStatus = (invoice?.status || '').toLowerCase();
|
||||
|
||||
@@ -15,27 +15,32 @@
|
||||
<h4 class="fw-bold mb-0">Order Details</h4>
|
||||
<small class="text-muted">Detailed view of this shipment order</small>
|
||||
</div>
|
||||
<a href="{{ route('admin.dashboard') }}" class="btn-close"></a>
|
||||
</div>
|
||||
|
||||
{{-- ACTION BUTTONS --}}
|
||||
<div class="mt-3 d-flex gap-2">
|
||||
|
||||
{{-- ADD ITEM --}}
|
||||
@can('order.create')
|
||||
<button class="btn btn-add-item" data-bs-toggle="modal" data-bs-target="#addItemModal">
|
||||
<i class="fas fa-plus-circle me-2"></i>Add New Item
|
||||
</button>
|
||||
@endcan
|
||||
|
||||
{{-- EDIT ORDER --}}
|
||||
@if($order->status === 'pending')
|
||||
<a href="{{ route('admin.dashboard') }}" class="btn-close"></a>
|
||||
</div>
|
||||
|
||||
<!-- {{-- ACTION BUTTONS --}}
|
||||
<div class="mt-3 d-flex gap-2">
|
||||
|
||||
|
||||
|
||||
{{-- EDIT ORDER --}} -->
|
||||
<!-- @if($order->status === 'pending')
|
||||
<button class="btn btn-edit-order" onclick="document.getElementById('editOrderForm').style.display='block'">
|
||||
<i class="fas fa-edit me-2"></i>Edit Order
|
||||
</button>
|
||||
@else
|
||||
<button class="btn btn-edit-order" disabled><i class="fas fa-edit me-2"></i>Edit Order</button>
|
||||
@endif
|
||||
@endif -->
|
||||
|
||||
{{-- DELETE ORDER --}}
|
||||
<!-- {{-- DELETE ORDER --}}
|
||||
@if($order->status === 'pending')
|
||||
<form action="{{ route('admin.orders.destroy', $order->id) }}"
|
||||
method="POST"
|
||||
@@ -46,9 +51,9 @@
|
||||
<i class="fas fa-trash-alt me-2"></i>Delete Order
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
@endif -->
|
||||
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
|
||||
<hr>
|
||||
|
||||
@@ -56,7 +61,7 @@
|
||||
<div id="editOrderForm" class="p-3 bg-light rounded mb-4 shadow-sm" style="display:none;">
|
||||
<h5 class="fw-bold mb-3">Edit Order</h5>
|
||||
|
||||
<form action="{{ route('admin.orders.update', $order->id) }}" method="POST">
|
||||
<form action="{{ route('admin.orders.updateItem', $order->id) }}" method="POST">
|
||||
@csrf
|
||||
|
||||
<div class="row">
|
||||
@@ -185,23 +190,161 @@
|
||||
<td>{{ $item->ttl_kg }}</td>
|
||||
<td>{{ $item->shop_no }}</td>
|
||||
|
||||
<td>
|
||||
<form action="{{ route('admin.orders.deleteItem', $item->id) }}"
|
||||
method="POST"
|
||||
onsubmit="return confirm('Delete this item?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button class="btn btn-sm btn-delete-item">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td class="d-flex justify-content-center gap-2">
|
||||
|
||||
{{-- EDIT BUTTON --}}
|
||||
@can('order.edit')
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-edit-item"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#editItemModal{{ $item->id }}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
@endcan
|
||||
|
||||
@can('order.delete')
|
||||
{{-- DELETE BUTTON --}}
|
||||
<form action="{{ route('admin.orders.deleteItem', $item->id) }}"
|
||||
method="POST"
|
||||
onsubmit="return confirm('Delete this item?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button class="btn btn-sm btn-delete-item">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
@endcan
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
@foreach($order->items as $item)
|
||||
<div class="modal fade" id="editItemModal{{ $item->id }}" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content custom-modal">
|
||||
|
||||
<div class="modal-header modal-gradient-header">
|
||||
<h5 class="modal-title text-white">
|
||||
<i class="fas fa-edit me-2"></i>Edit Item
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
|
||||
<form action="{{ route('admin.orders.updateItem', $item->id) }}" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Description</label>
|
||||
<input type="text" name="description"
|
||||
value="{{ $item->description }}"
|
||||
class="form-control custom-input" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">CTN</label>
|
||||
<input type="number" name="ctn"
|
||||
value="{{ $item->ctn }}"
|
||||
class="form-control custom-input">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">QTY</label>
|
||||
<input type="number" name="qty"
|
||||
value="{{ $item->qty }}"
|
||||
class="form-control custom-input">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">TTL QTY</label>
|
||||
<input type="number" name="ttl_qty"
|
||||
value="{{ $item->ttl_qty }}"
|
||||
class="form-control custom-input">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Unit</label>
|
||||
<input type="text" name="unit"
|
||||
value="{{ $item->unit }}"
|
||||
class="form-control custom-input">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Price</label>
|
||||
<input type="number" name="price"
|
||||
value="{{ $item->price }}"
|
||||
class="form-control custom-input">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Total Amount</label>
|
||||
<input type="number" name="ttl_amount"
|
||||
value="{{ $item->ttl_amount }}"
|
||||
class="form-control custom-input">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">CBM</label>
|
||||
<input type="number" name="cbm"
|
||||
value="{{ $item->cbm }}"
|
||||
class="form-control custom-input">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">TTL CBM</label>
|
||||
<input type="number" name="ttl_cbm"
|
||||
value="{{ $item->ttl_cbm }}"
|
||||
class="form-control custom-input">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">KG</label>
|
||||
<input type="number" name="kg"
|
||||
value="{{ $item->kg }}"
|
||||
class="form-control custom-input">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">TTL KG</label>
|
||||
<input type="number" name="ttl_kg"
|
||||
value="{{ $item->ttl_kg }}"
|
||||
class="form-control custom-input">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Shop No</label>
|
||||
<input type="text" name="shop_no"
|
||||
value="{{ $item->shop_no }}"
|
||||
class="form-control custom-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-modal-close" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-modal-add">Edit Item</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
|
||||
{{-- TOTALS --}}
|
||||
<div class="row text-center mt-4">
|
||||
<div class="col-md-3">
|
||||
@@ -239,7 +382,7 @@
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
|
||||
<form action="{{ route('admin.orders.addItem', $order->id) }}" method="POST">
|
||||
<form id="addItemForm" action="{{ route('admin.orders.addItem', $order->id) }}" method="POST">
|
||||
@csrf
|
||||
|
||||
<div class="modal-body">
|
||||
@@ -253,54 +396,75 @@
|
||||
|
||||
<h5 class="section-title">Deleted Items (Use / Restore)</h5>
|
||||
|
||||
<ul class="list-group mb-3">
|
||||
<div class="table-responsive mb-4">
|
||||
<table class="table table-bordered align-middle text-center">
|
||||
<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>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@forelse($deletedItems as $deleted)
|
||||
<li class="list-group-item">
|
||||
<tbody>
|
||||
@forelse($deletedItems as $key => $deleted)
|
||||
<tr>
|
||||
<td>{{ $key + 1 }}</td>
|
||||
<td>{{ $deleted->description }}</td>
|
||||
<td>{{ $deleted->ctn }}</td>
|
||||
<td>{{ $deleted->qty }}</td>
|
||||
<td>{{ $deleted->ttl_qty }}</td>
|
||||
<td>{{ $deleted->unit }}</td>
|
||||
<td>{{ number_format($deleted->price, 2) }}</td>
|
||||
<td>{{ number_format($deleted->ttl_amount, 2) }}</td>
|
||||
<td>{{ $deleted->cbm }}</td>
|
||||
<td>{{ $deleted->ttl_cbm }}</td>
|
||||
<td>{{ $deleted->kg }}</td>
|
||||
<td>{{ $deleted->ttl_kg }}</td>
|
||||
<td>{{ $deleted->shop_no }}</td>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<td class="d-flex justify-content-center gap-2">
|
||||
|
||||
<div>
|
||||
<strong>{{ $deleted->description }}</strong><br>
|
||||
<small>
|
||||
CTN: {{ $deleted->ctn }},
|
||||
QTY: {{ $deleted->qty }},
|
||||
TTL/QTY: {{ $deleted->ttl_qty }},
|
||||
Unit: {{ $deleted->unit }},
|
||||
Price: ₹{{ $deleted->price }},
|
||||
TTL Amt: ₹{{ $deleted->ttl_amount }},
|
||||
CBM: {{ $deleted->cbm }},
|
||||
TTL CBM: {{ $deleted->ttl_cbm }},
|
||||
KG: {{ $deleted->kg }},
|
||||
TTL KG: {{ $deleted->ttl_kg }},
|
||||
Shop: {{ $deleted->shop_no }}
|
||||
</small>
|
||||
</div>
|
||||
{{-- USE THIS ITEM --}}
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-use-item"
|
||||
onclick="fillFormFromDeleted({{ json_encode($deleted) }})">
|
||||
Use
|
||||
</button>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
|
||||
{{-- Auto-fill button --}}
|
||||
<button type="button" class="btn btn-sm btn-use-item"
|
||||
onclick="fillFormFromDeleted({{ json_encode($deleted) }})">
|
||||
Use This
|
||||
{{-- RESTORE ITEM --}}
|
||||
<form action="{{ route('admin.orders.restoreItem', $deleted->id) }}" method="POST">
|
||||
@csrf
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-restore js-restore-item"
|
||||
data-id="{{ $deleted->id }}"
|
||||
data-url="{{ route('admin.orders.restoreItem', $deleted->id) }}">
|
||||
Restore
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{{-- Restore --}}
|
||||
<form action="{{ route('admin.orders.restoreItem', $deleted->id) }}" method="POST">
|
||||
@csrf
|
||||
<button class="btn btn-sm btn-restore">Restore</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="14" class="text-muted">No deleted items.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
@empty
|
||||
<li class="list-group-item text-muted">No deleted items.</li>
|
||||
@endforelse
|
||||
|
||||
</ul>
|
||||
|
||||
{{-- ADD FORM --}}
|
||||
<div class="row g-3">
|
||||
@@ -380,21 +544,62 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const token = '{{ csrf_token() }}';
|
||||
|
||||
document.querySelectorAll('.js-restore-item').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
if (!confirm('Restore this item?')) return;
|
||||
|
||||
const url = this.dataset.url;
|
||||
const row = this.closest('tr');
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': token,
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({})
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error('Restore failed');
|
||||
return response.json().catch(() => ({}));
|
||||
})
|
||||
.then(() => {
|
||||
row.remove();
|
||||
location.reload(); // 🔥 refresh UI so restored item appears
|
||||
})
|
||||
.catch(() => {
|
||||
alert('Could not restore item. Please try again.');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{{-- AUTO-FILL SCRIPT --}}
|
||||
<script>
|
||||
function fillFormFromDeleted(item) {
|
||||
document.querySelector('input[name="description"]').value = item.description;
|
||||
document.querySelector('input[name="ctn"]').value = item.ctn;
|
||||
document.querySelector('input[name="qty"]').value = item.qty;
|
||||
document.querySelector('input[name="ttl_qty"]').value = item.ttl_qty;
|
||||
document.querySelector('input[name="unit"]').value = item.unit;
|
||||
document.querySelector('input[name="price"]').value = item.price;
|
||||
document.querySelector('input[name="ttl_amount"]').value = item.ttl_amount;
|
||||
document.querySelector('input[name="cbm"]').value = item.cbm;
|
||||
document.querySelector('input[name="ttl_cbm"]').value = item.ttl_cbm;
|
||||
document.querySelector('input[name="kg"]').value = item.kg;
|
||||
document.querySelector('input[name="ttl_kg"]').value = item.ttl_kg;
|
||||
document.querySelector('input[name="shop_no"]').value = item.shop_no;
|
||||
let form = document.getElementById('addItemForm');
|
||||
|
||||
form.querySelector('input[name="description"]').value = item.description;
|
||||
form.querySelector('input[name="ctn"]').value = item.ctn;
|
||||
form.querySelector('input[name="qty"]').value = item.qty;
|
||||
form.querySelector('input[name="ttl_qty"]').value = item.ttl_qty;
|
||||
form.querySelector('input[name="unit"]').value = item.unit;
|
||||
form.querySelector('input[name="price"]').value = item.price;
|
||||
form.querySelector('input[name="ttl_amount"]').value = item.ttl_amount;
|
||||
form.querySelector('input[name="cbm"]').value = item.cbm;
|
||||
form.querySelector('input[name="ttl_cbm"]').value = item.ttl_cbm;
|
||||
form.querySelector('input[name="kg"]').value = item.kg;
|
||||
form.querySelector('input[name="ttl_kg"]').value = item.ttl_kg;
|
||||
form.querySelector('input[name="shop_no"]').value = item.shop_no;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -404,13 +609,15 @@ function fillFormFromDeleted(item) {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 12px 24px;
|
||||
padding: 6px 14px;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px 0 rgba(102, 126, 234, 0.3);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin-right: -800px;
|
||||
}
|
||||
|
||||
.btn-add-item:hover {
|
||||
|
||||
@@ -258,6 +258,14 @@
|
||||
line-height: 1.2 !important;
|
||||
}
|
||||
|
||||
/* Loading Status - PROPER */
|
||||
.badge-loading {
|
||||
background: linear-gradient(135deg, #e3f2fd, #90caf9) !important;
|
||||
color: #1565c0 !important;
|
||||
border-color: #2196f3 !important;
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
/* Pending Status - SAME SIZE */
|
||||
.badge-pending {
|
||||
background: linear-gradient(135deg, #fef3c7, #fde68a) !important;
|
||||
@@ -320,7 +328,30 @@
|
||||
padding: 6px 12px !important;
|
||||
}
|
||||
|
||||
/* NEW: Action Button Styles */
|
||||
/* Eye Button Style - PROPER */
|
||||
.btn-eye {
|
||||
background: linear-gradient(135deg, #4cc9f0, #4361ee);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 8px 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.btn-eye:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(76, 201, 240, 0.3);
|
||||
background: linear-gradient(135deg, #38bdf8, #3a56d4);
|
||||
}
|
||||
|
||||
/* Action Button Styles */
|
||||
.action-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
@@ -368,6 +399,16 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Loading Status Option - PROPER */
|
||||
.status-option.loading {
|
||||
background: rgba(33, 150, 243, 0.1);
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.status-option.loading:hover {
|
||||
background: rgba(33, 150, 243, 0.2);
|
||||
}
|
||||
|
||||
.status-option {
|
||||
padding: 10px 12px;
|
||||
border: none;
|
||||
@@ -430,6 +471,11 @@
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Loading Status Indicator - PROPER */
|
||||
.status-indicator.loading {
|
||||
background: #2196f3;
|
||||
}
|
||||
|
||||
.status-indicator.pending {
|
||||
background: #f8961e;
|
||||
@@ -1083,15 +1129,13 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- ============================= -->
|
||||
<!-- SEARCH BAR AND ADD BUTTON -->
|
||||
<!-- ============================= -->
|
||||
<div class="search-shipment-bar">
|
||||
<span class="search-icon">🔍</span>
|
||||
<input type="text" id="searchInput" placeholder="Search Shipments...">
|
||||
<div class="status-filter-container">
|
||||
<select id="statusFilter" class="status-filter-select">
|
||||
<option value="all">All Status</option>
|
||||
<option value="loading">Loading</option>
|
||||
<option value="pending">Pending</option>
|
||||
<option value="in_transit">In Transit</option>
|
||||
<option value="dispatched">Dispatched</option>
|
||||
@@ -1110,9 +1154,6 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ============================= -->
|
||||
<!-- CREATE SHIPMENT MODAL -->
|
||||
<!-- ============================= -->
|
||||
<div class="modal fade" id="createShipmentModal" tabindex="-1" aria-labelledby="createShipmentModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
@@ -1204,9 +1245,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================= -->
|
||||
<!-- SHIPMENT LIST TABLE -->
|
||||
<!-- ============================= -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-truck me-2"></i> Shipments List</h5>
|
||||
@@ -1227,6 +1265,7 @@
|
||||
<th>Total Amount</th>
|
||||
<th>Status</th>
|
||||
<th>Date</th>
|
||||
<th>View</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -1257,6 +1296,12 @@
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ \Carbon\Carbon::parse($ship->shipment_date)->format('d M Y') }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn-eye" onclick="openShipmentDetails({{ $ship->id }})" title="View Shipment">
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div class="action-container">
|
||||
<button type="button" class="btn-edit-status" onclick="toggleStatusDropdown(this, {{ $ship->id }})" title="Edit Status">
|
||||
@@ -1266,6 +1311,10 @@
|
||||
<form action="{{ route('admin.shipments.updateStatus') }}" method="POST" class="status-form">
|
||||
@csrf
|
||||
<input type="hidden" name="shipment_id" value="{{ $ship->id }}">
|
||||
<button type="submit" name="status" value="loading" class="status-option loading">
|
||||
<span class="status-indicator loading"></span>
|
||||
Loading
|
||||
</button>
|
||||
<button type="submit" name="status" value="pending" class="status-option pending">
|
||||
<span class="status-indicator pending"></span>
|
||||
Pending
|
||||
@@ -1289,7 +1338,7 @@
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="11" class="text-center py-5 text-muted">
|
||||
<td colspan="12" class="text-center py-5 text-muted">
|
||||
<i class="bi bi-inbox display-4 d-block mb-3"></i>
|
||||
No shipments found
|
||||
</td>
|
||||
@@ -1299,21 +1348,17 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination Controls -->
|
||||
<div class="pagination-container">
|
||||
<div class="pagination-info" id="pageInfo">Showing 1 to {{ $shipments->count() }} of {{ $shipments->count() }} entries</div>
|
||||
<div class="pagination-controls">
|
||||
<button class="pagination-img-btn" id="prevPageBtn" title="Previous page" disabled>
|
||||
<!-- Left arrow SVG -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="pagination-pages" id="paginationPages">
|
||||
<!-- Page numbers will be inserted here -->
|
||||
</div>
|
||||
</div>
|
||||
<button class="pagination-img-btn" id="nextPageBtn" title="Next page" disabled>
|
||||
<!-- Right arrow SVG -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
@@ -1323,9 +1368,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================= -->
|
||||
<!-- SHIPMENT DETAILS MODAL -->
|
||||
<!-- ============================= -->
|
||||
{{-- SHIPMENT DETAILS MODAL --}}
|
||||
<div class="modal fade" id="shipmentDetailsModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
@@ -1348,9 +1391,6 @@
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ========================= -->
|
||||
<!-- MODAL LOAD SCRIPT (AJAX) -->
|
||||
<!-- ========================= -->
|
||||
<script>
|
||||
// Pagination state
|
||||
let currentPage = 1;
|
||||
@@ -1503,7 +1543,7 @@ function renderTable() {
|
||||
if (filteredShipments.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="11" class="text-center py-5 text-muted">
|
||||
<td colspan="12" class="text-center py-5 text-muted">
|
||||
<i class="bi bi-search display-4 d-block mb-3"></i>
|
||||
No shipments found matching your criteria
|
||||
</td>
|
||||
@@ -1529,6 +1569,12 @@ function renderTable() {
|
||||
row.setAttribute('data-status', shipment.status);
|
||||
row.setAttribute('data-shipment-id', shipment.shipment_id);
|
||||
|
||||
// Function to format status string
|
||||
const formatStatus = (status) => {
|
||||
if (!status) return '';
|
||||
return status.charAt(0).toUpperCase() + status.slice(1).replace(/_/g, ' ');
|
||||
};
|
||||
|
||||
row.innerHTML = `
|
||||
<td class="fw-bold">${displayIndex}</td>
|
||||
<td>
|
||||
@@ -1544,10 +1590,19 @@ function renderTable() {
|
||||
<td class="fw-bold text-success">₹${parseFloat(shipment.total_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
|
||||
<td>
|
||||
<span class="badge badge-${shipment.status}">
|
||||
${shipment.status.charAt(0).toUpperCase() + shipment.status.slice(1).replace('_', ' ')}
|
||||
${formatStatus(shipment.status)}
|
||||
</span>
|
||||
</td>
|
||||
<td>${new Date(shipment.shipment_date).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })}</td>
|
||||
|
||||
<td>
|
||||
<a href="{{ route('admin.shipments.dummy', $ship->id) }}"
|
||||
class="btn-view-details">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
<div class="action-container">
|
||||
<button type="button" class="btn-edit-status" onclick="toggleStatusDropdown(this, ${shipment.id})" title="Edit Status">
|
||||
@@ -1557,6 +1612,10 @@ function renderTable() {
|
||||
<form action="/admin/shipments/update-status" method="POST" class="status-form">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="shipment_id" value="${shipment.id}">
|
||||
<button type="submit" name="status" value="loading" class="status-option loading">
|
||||
<span class="status-indicator loading"></span>
|
||||
Loading
|
||||
</button>
|
||||
<button type="submit" name="status" value="pending" class="status-option pending">
|
||||
<span class="status-indicator pending"></span>
|
||||
Pending
|
||||
@@ -1582,6 +1641,9 @@ function renderTable() {
|
||||
});
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Function: Open Consolidated Shipment Details Modal
|
||||
// --------------------------------------------------------------------
|
||||
function openShipmentDetails(id) {
|
||||
let modal = new bootstrap.Modal(document.getElementById('shipmentDetailsModal'));
|
||||
let content = document.getElementById('shipmentDetailsContent');
|
||||
@@ -1609,8 +1671,13 @@ function openShipmentDetails(id) {
|
||||
year: 'numeric'
|
||||
});
|
||||
|
||||
// Function to format status string
|
||||
const formatStatus = (status) => {
|
||||
if (!status) return 'N/A';
|
||||
return status.charAt(0).toUpperCase() + status.slice(1).replace(/_/g, ' ');
|
||||
};
|
||||
|
||||
let html = `
|
||||
<!-- Shipment Basic Info -->
|
||||
<div class="shipment-info-row">
|
||||
<div class="shipment-info-item">
|
||||
<div class="shipment-info-label">Shipment ID</div>
|
||||
@@ -1622,7 +1689,7 @@ function openShipmentDetails(id) {
|
||||
</div>
|
||||
<div class="shipment-info-item">
|
||||
<div class="shipment-info-label">Status</div>
|
||||
<div class="shipment-info-value">${data.shipment.status}</div>
|
||||
<div class="shipment-info-value">${formatStatus(data.shipment.status)}</div>
|
||||
</div>
|
||||
<div class="shipment-info-item">
|
||||
<div class="shipment-info-label">Date</div>
|
||||
@@ -1657,7 +1724,9 @@ function openShipmentDetails(id) {
|
||||
data.orders.forEach(order => {
|
||||
html += `
|
||||
<tr>
|
||||
<td class="fw-bold text-primary">${order.order_id}</td>
|
||||
<td class="fw-bold">
|
||||
${order.order_id}
|
||||
</td>
|
||||
<td>${order.origin || 'N/A'}</td>
|
||||
<td>${order.destination || 'N/A'}</td>
|
||||
<td>${order.mark_no || 'ITEM001'}</td>
|
||||
@@ -1679,7 +1748,6 @@ function openShipmentDetails(id) {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Totals Section -->
|
||||
<div class="shipment-totals">
|
||||
<div class="shipment-totals-row">
|
||||
<div class="shipment-total-item">
|
||||
|
||||
187
resources/views/admin/staff/create.blade.php
Normal file
187
resources/views/admin/staff/create.blade.php
Normal file
@@ -0,0 +1,187 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('page-title', 'Account Dashboard')
|
||||
|
||||
@section('content')
|
||||
<style>
|
||||
.card { background:#fff; border:1px solid #eaeaea; padding:1rem; border-radius:6px; }
|
||||
.field { margin-bottom:.8rem; }
|
||||
label { display:block; font-weight:600; margin-bottom:.25rem; }
|
||||
input[type="text"], input[type="email"], input[type="date"], input[type="password"], textarea, select {
|
||||
width:100%; padding:.5rem; border:1px solid #ddd; border-radius:4px;
|
||||
}
|
||||
.stages { display:flex; gap:.5rem; margin-bottom:1rem; }
|
||||
.stage-ind { padding:.35rem .6rem; border-radius:4px; background:#f3f3f3; }
|
||||
.stage { display:none; }
|
||||
.stage.active { display:block; }
|
||||
.btn { padding:.5rem .8rem; border-radius:5px; border:1px solid #ccc; background:#f6f6f6; cursor:pointer; }
|
||||
.btn.primary { background:#0b74de; color:#fff; border-color:#0b74de; }
|
||||
.perm-group { border:1px dashed #eee; padding:.6rem; margin-bottom:.6rem; border-radius:4px; }
|
||||
.perm-list { display:flex; flex-wrap:wrap; gap:.5rem; }
|
||||
.perm-item { min-width:200px; }
|
||||
.error { color:#b00020; font-size:.95rem; margin-top:.25rem; }
|
||||
</style>
|
||||
|
||||
<div class="card">
|
||||
<h3>Add Staff</h3>
|
||||
|
||||
@if($errors->any())
|
||||
<div style="background:#fff0f0; padding:.6rem; border:1px solid #f3c6c6; margin-bottom:1rem;">
|
||||
<strong>There were some problems with your input:</strong>
|
||||
<ul>
|
||||
@foreach($errors->all() as $err)
|
||||
<li>{{ $err }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('admin.staff.store') }}">
|
||||
@csrf
|
||||
|
||||
<div class="stages">
|
||||
<div class="stage-ind">1. Personal</div>
|
||||
<div class="stage-ind">2. Professional</div>
|
||||
<div class="stage-ind">3. System</div>
|
||||
<div class="stage-ind">4. Permissions</div>
|
||||
</div>
|
||||
|
||||
{{-- Stage 1 --}}
|
||||
<div id="stage-1" class="stage active">
|
||||
<div class="field">
|
||||
<label>Name *</label>
|
||||
<input type="text" name="name" value="{{ old('name') }}" required>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Email *</label>
|
||||
<input type="email" name="email" value="{{ old('email') }}" required>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Phone *</label>
|
||||
<input type="text" name="phone" value="{{ old('phone') }}" required>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Emergency Phone</label>
|
||||
<input type="text" name="emergency_phone" value="{{ old('emergency_phone') }}">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Address</label>
|
||||
<textarea name="address" rows="3">{{ old('address') }}</textarea>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; justify-content:flex-end; gap:.5rem;">
|
||||
<button type="button" class="btn" onclick="showStage(2)">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Stage 2 --}}
|
||||
<div id="stage-2" class="stage">
|
||||
<div class="field">
|
||||
<label>Role (Business role)</label>
|
||||
<input type="text" name="role" value="{{ old('role') }}">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Department</label>
|
||||
<input type="text" name="department" value="{{ old('department') }}">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Designation</label>
|
||||
<input type="text" name="designation" value="{{ old('designation') }}">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Joining Date</label>
|
||||
<input type="date" name="joining_date" value="{{ old('joining_date') }}">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Status</label>
|
||||
<select name="status">
|
||||
<option value="active" {{ old('status')=='active'?'selected':'' }}>Active</option>
|
||||
<option value="inactive" {{ old('status')=='inactive'?'selected':'' }}>Inactive</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Additional Info</label>
|
||||
<textarea name="additional_info" rows="3">{{ old('additional_info') }}</textarea>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; justify-content:space-between; gap:.5rem;">
|
||||
<button type="button" class="btn" onclick="showStage(1)">Back</button>
|
||||
<button type="button" class="btn" onclick="showStage(3)">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Stage 3 --}}
|
||||
<div id="stage-3" class="stage">
|
||||
<div class="field">
|
||||
<label>Username *</label>
|
||||
<input type="text" name="username" value="{{ old('username') }}" placeholder="leave blank to use EMPxxxx">
|
||||
<div class="muted" style="font-size:.9rem; margin-top:.25rem;">If left blank employee id will be used.</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Password *</label>
|
||||
<input type="password" name="password" required>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; justify-content:space-between; gap:.5rem;">
|
||||
<button type="button" class="btn" onclick="showStage(2)">Back</button>
|
||||
<button type="button" class="btn" onclick="showStage(4)">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Stage 4 --}}
|
||||
<div id="stage-4" class="stage">
|
||||
<div style="margin-bottom:.5rem; font-weight:700;">Permissions</div>
|
||||
|
||||
@foreach($permissions as $group => $groupPerms)
|
||||
<div class="perm-group">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<div style="font-weight:600;">{{ ucfirst($group) }}</div>
|
||||
<div>
|
||||
<button type="button" class="btn" onclick="toggleGroup('{{ $group }}')">Toggle group</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="perm-list" id="group-{{ $group }}">
|
||||
@foreach($groupPerms as $perm)
|
||||
<label class="perm-item">
|
||||
<input type="checkbox" name="permissions[]" value="{{ $perm->name }}"> {{ $perm->name }}
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<div style="display:flex; justify-content:space-between; gap:.5rem;">
|
||||
<button type="button" class="btn" onclick="showStage(3)">Back</button>
|
||||
<button type="submit" class="btn primary">Create Staff</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function showStage(n){
|
||||
document.querySelectorAll('.stage').forEach(s=>s.classList.remove('active'));
|
||||
document.getElementById('stage-'+n).classList.add('active');
|
||||
window.scrollTo({top:0, behavior:'smooth'});
|
||||
}
|
||||
|
||||
function toggleGroup(group){
|
||||
const el = document.getElementById('group-'+group);
|
||||
if(!el) return;
|
||||
const inputs = el.querySelectorAll('input[type="checkbox"]');
|
||||
const anyUnchecked = Array.from(inputs).some(i => !i.checked);
|
||||
inputs.forEach(i => i.checked = anyUnchecked);
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
156
resources/views/admin/staff/edit.blade.php
Normal file
156
resources/views/admin/staff/edit.blade.php
Normal file
@@ -0,0 +1,156 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('page-title', 'Account Dashboard')
|
||||
|
||||
@section('content')
|
||||
<style>
|
||||
.card { background:#fff; border:1px solid #eaeaea; padding:1rem; border-radius:6px; }
|
||||
.field { margin-bottom:.8rem; }
|
||||
label { display:block; font-weight:600; margin-bottom:.25rem; }
|
||||
input[type="text"], input[type="email"], input[type="date"], input[type="password"], textarea, select {
|
||||
width:100%; padding:.5rem; border:1px solid #ddd; border-radius:4px;
|
||||
}
|
||||
.perm-group { border:1px dashed #eee; padding:.6rem; margin-bottom:.6rem; border-radius:4px; }
|
||||
.perm-list { display:flex; flex-wrap:wrap; gap:.5rem; }
|
||||
.perm-item { min-width:200px; }
|
||||
.btn { padding:.5rem .8rem; border-radius:5px; border:1px solid #ccc; background:#f6f6f6; cursor:pointer; }
|
||||
.btn.primary { background:#0b74de; color:#fff; border-color:#0b74de; }
|
||||
</style>
|
||||
|
||||
<div class="card">
|
||||
<h3>Edit Staff — {{ $staff->display_name ?? $staff->name }}</h3>
|
||||
|
||||
@if($errors->any())
|
||||
<div style="background:#fff0f0; padding:.6rem; border:1px solid #f3c6c6; margin-bottom:1rem;">
|
||||
<strong>There were some problems with your input:</strong>
|
||||
<ul>
|
||||
@foreach($errors->all() as $err)
|
||||
<li>{{ $err }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('admin.staff.update', $staff->id) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="field">
|
||||
<label>Employee ID</label>
|
||||
<input type="text" value="{{ $staff->employee_id }}" disabled>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Name *</label>
|
||||
<input type="text" name="name" value="{{ old('name', $staff->name) }}" required>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Email *</label>
|
||||
<input type="email" name="email" value="{{ old('email', $staff->email) }}" required>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Phone *</label>
|
||||
<input type="text" name="phone" value="{{ old('phone', $staff->phone) }}" required>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Emergency Phone</label>
|
||||
<input type="text" name="emergency_phone" value="{{ old('emergency_phone', $staff->emergency_phone) }}">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Address</label>
|
||||
<textarea name="address" rows="3">{{ old('address', $staff->address) }}</textarea>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="field">
|
||||
<label>Role</label>
|
||||
<input type="text" name="role" value="{{ old('role', $staff->role) }}">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Department</label>
|
||||
<input type="text" name="department" value="{{ old('department', $staff->department) }}">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Designation</label>
|
||||
<input type="text" name="designation" value="{{ old('designation', $staff->designation) }}">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Joining Date</label>
|
||||
<input type="date" name="joining_date" value="{{ old('joining_date', optional($staff->joining_date)->format('Y-m-d')) }}">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Status</label>
|
||||
<select name="status">
|
||||
<option value="active" {{ old('status', $staff->status)=='active'?'selected':'' }}>Active</option>
|
||||
<option value="inactive" {{ old('status', $staff->status)=='inactive'?'selected':'' }}>Inactive</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Additional Info</label>
|
||||
<textarea name="additional_info" rows="3">{{ old('additional_info', $staff->additional_info) }}</textarea>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="field">
|
||||
<label>Username *</label>
|
||||
<input type="text" name="username" value="{{ old('username', $staff->username) }}" required>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>New Password (leave blank to keep existing)</label>
|
||||
<input type="password" name="password" autocomplete="new-password">
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div style="margin-bottom:.5rem; font-weight:700;">Permissions</div>
|
||||
|
||||
@foreach($permissions as $group => $groupPerms)
|
||||
<div class="perm-group">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<div style="font-weight:600;">{{ ucfirst($group) }}</div>
|
||||
<div>
|
||||
<button type="button" class="btn" onclick="toggleGroup('{{ $group }}')">Toggle group</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="perm-list" id="group-{{ $group }}">
|
||||
@foreach($groupPerms as $perm)
|
||||
<label class="perm-item">
|
||||
<input type="checkbox" name="permissions[]" value="{{ $perm->name }}"
|
||||
{{ in_array($perm->name, old('permissions', $staffPermissions)) ? 'checked' : '' }}>
|
||||
{{ $perm->name }}
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<div style="display:flex; justify-content:flex-end; gap:.5rem;">
|
||||
<a href="{{ route('admin.staff.index') }}" class="btn">Cancel</a>
|
||||
<button type="submit" class="btn primary">Update Staff</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleGroup(group){
|
||||
const el = document.getElementById('group-'+group);
|
||||
if(!el) return;
|
||||
const inputs = el.querySelectorAll('input[type="checkbox"]');
|
||||
const anyUnchecked = Array.from(inputs).some(i => !i.checked);
|
||||
inputs.forEach(i => i.checked = anyUnchecked);
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
65
resources/views/admin/staff/index.blade.php
Normal file
65
resources/views/admin/staff/index.blade.php
Normal file
@@ -0,0 +1,65 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('page-title', 'Account Dashboard')
|
||||
|
||||
@section('content')
|
||||
<style>
|
||||
.top-bar { display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem; }
|
||||
.card { background:#fff; border:1px solid #e4e4e4; border-radius:6px; padding:1rem; box-shadow:0 1px 3px rgba(0,0,0,.03); }
|
||||
table { width:100%; border-collapse:collapse; }
|
||||
th, td { padding:.6rem .75rem; border-bottom:1px solid #f1f1f1; text-align:left; }
|
||||
.btn { padding:.45rem .75rem; border-radius:4px; border:1px solid #ccc; background:#f7f7f7; cursor:pointer; }
|
||||
.btn.primary { background:#0b74de; color:#fff; border-color:#0b74de; }
|
||||
.actions a { margin-right:.5rem; color:#0b74de; text-decoration:none; }
|
||||
.muted { color:#666; font-size:.95rem; }
|
||||
</style>
|
||||
|
||||
<div class="top-bar">
|
||||
<h2>Staff</h2>
|
||||
<a href="{{ route('admin.staff.create') }}" class="btn primary">Add Staff</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
@if(session('success'))
|
||||
<div style="padding:.5rem; background:#e6ffed; border:1px solid #b6f0c6; margin-bottom:1rem;">{{ session('success') }}</div>
|
||||
@endif
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Employee ID</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Phone</th>
|
||||
<th>Role</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($staff as $s)
|
||||
<tr>
|
||||
<td>{{ $s->id }}</td>
|
||||
<td class="muted">{{ $s->employee_id }}</td>
|
||||
<td>{{ $s->name }}</td>
|
||||
<td>{{ $s->email }}</td>
|
||||
<td>{{ $s->phone }}</td>
|
||||
<td>{{ $s->role ?? '-' }}</td>
|
||||
<td>{{ ucfirst($s->status) }}</td>
|
||||
<td class="actions">
|
||||
<a href="{{ route('admin.staff.edit', $s->id) }}">Edit</a>
|
||||
<form action="{{ route('admin.staff.destroy', $s->id) }}" method="POST" style="display:inline" onsubmit="return confirm('Delete this staff?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button class="btn" type="submit">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="8" class="muted">No staff found.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endsection
|
||||
128
resources/views/admin/view_shipment.blade.php
Normal file
128
resources/views/admin/view_shipment.blade.php
Normal file
@@ -0,0 +1,128 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('page-title', 'Shipment Preview')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="container py-4">
|
||||
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-box-seam me-2"></i>
|
||||
Shipment Preview: {{ $shipment->shipment_id }}
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
<!-- Dummy Info -->
|
||||
<div class="alert alert-info">
|
||||
<strong>{{ $dummyData['title'] }}</strong><br>
|
||||
Generated On: {{ $dummyData['generated_on'] }} <br>
|
||||
Note: {{ $dummyData['note'] }}
|
||||
</div>
|
||||
|
||||
<!-- Shipment Basic Details -->
|
||||
<h5 class="fw-bold mt-3">Shipment Details</h5>
|
||||
<table class="table table-bordered">
|
||||
<tr>
|
||||
<th>Shipment ID</th>
|
||||
<td>{{ $shipment->shipment_id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Origin</th>
|
||||
<td>{{ $shipment->origin }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Destination</th>
|
||||
<td>{{ $shipment->destination }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<td>{{ ucfirst($shipment->status) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<td>{{ \Carbon\Carbon::parse($shipment->shipment_date)->format('d M Y') }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Orders in Shipment -->
|
||||
<h5 class="fw-bold mt-4">Orders Contained in Shipment</h5>
|
||||
|
||||
@if($shipment->orders->isEmpty())
|
||||
<p class="text-muted">No orders found.</p>
|
||||
@else
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-striped">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Order ID</th>
|
||||
<th>Origin</th>
|
||||
<th>Destination</th>
|
||||
<th>CTN</th>
|
||||
<th>QTY</th>
|
||||
<th>TTL/QTY</th>
|
||||
<th>KG</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($shipment->orders as $order)
|
||||
<tr>
|
||||
<td>{{ $order->order_id }}</td>
|
||||
<td>{{ $order->origin }}</td>
|
||||
<td>{{ $order->destination }}</td>
|
||||
<td>{{ $order->ctn }}</td>
|
||||
<td>{{ $order->qty }}</td>
|
||||
<td>{{ $order->ttl_qty }}</td>
|
||||
<td>{{ $order->ttl_kg }}</td>
|
||||
<td>₹{{ number_format($order->ttl_amount, 2) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Shipment Totals -->
|
||||
<h5 class="fw-bold mt-4">Shipment Totals</h5>
|
||||
<table class="table table-bordered">
|
||||
<tr>
|
||||
<th>Total CTN</th>
|
||||
<td>{{ $shipment->total_ctn }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total Quantity</th>
|
||||
<td>{{ $shipment->total_qty }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total TTL Quantity</th>
|
||||
<td>{{ $shipment->total_ttl_qty }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total CBM</th>
|
||||
<td>{{ $shipment->total_cbm }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total KG</th>
|
||||
<td>{{ $shipment->total_kg }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total Amount</th>
|
||||
<td class="fw-bold text-success">
|
||||
₹{{ number_format($shipment->total_amount, 2) }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<a href="{{ route('admin.shipments') }}" class="btn btn-secondary mt-3">
|
||||
← Back to Shipments
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
288
routes/web.php
288
routes/web.php
@@ -10,6 +10,7 @@ use App\Http\Controllers\Admin\AdminInvoiceController;
|
||||
use App\Http\Controllers\Admin\AdminCustomerController;
|
||||
use App\Http\Controllers\Admin\AdminAccountController;
|
||||
use App\Http\Controllers\Admin\AdminReportController;
|
||||
use App\Http\Controllers\Admin\AdminStaffController;
|
||||
|
||||
// ---------------------------
|
||||
// Public Front Page
|
||||
@@ -21,19 +22,11 @@ Route::get('/', function () {
|
||||
// ---------------------------
|
||||
// ADMIN LOGIN ROUTES
|
||||
// ---------------------------
|
||||
// login routes (public)
|
||||
Route::prefix('admin')->group(function () {
|
||||
|
||||
// MUST have route name "login" for session redirect
|
||||
Route::get('/login', [AdminAuthController::class, 'showLoginForm'])
|
||||
->name('admin.login');
|
||||
|
||||
Route::post('/login', [AdminAuthController::class, 'login'])
|
||||
->name('admin.login.submit');
|
||||
|
||||
Route::post('/logout', [AdminAuthController::class, 'logout'])
|
||||
->name('admin.logout');
|
||||
|
||||
|
||||
Route::get('/login', [AdminAuthController::class, 'showLoginForm'])->name('admin.login');
|
||||
Route::post('/login', [AdminAuthController::class, 'login'])->name('admin.login.submit');
|
||||
Route::post('/logout', [AdminAuthController::class, 'logout'])->name('admin.logout');
|
||||
});
|
||||
|
||||
|
||||
@@ -66,14 +59,14 @@ Route::prefix('admin')
|
||||
// ---------------------------
|
||||
// USER REQUESTS
|
||||
// ---------------------------
|
||||
Route::get('/requests', [UserRequestController::class, 'index'])
|
||||
->name('admin.requests');
|
||||
Route::get('/requests', [UserRequestController::class, 'index'])
|
||||
->name('admin.requests');
|
||||
|
||||
Route::get('/requests/approve/{id}', [UserRequestController::class, 'approve'])
|
||||
->name('admin.requests.approve');
|
||||
Route::get('/requests/approve/{id}', [UserRequestController::class, 'approve'])
|
||||
->name('admin.requests.approve');
|
||||
|
||||
Route::get('/requests/reject/{id}', [UserRequestController::class, 'reject'])
|
||||
->name('admin.requests.reject');
|
||||
Route::get('/requests/reject/{id}', [UserRequestController::class, 'reject'])
|
||||
->name('admin.requests.reject');
|
||||
|
||||
// PROFILE UPDATE REQUESTS
|
||||
Route::get('/profile-update-requests',
|
||||
@@ -93,110 +86,111 @@ Route::prefix('admin')
|
||||
// ---------------------------
|
||||
// MARK LIST
|
||||
// ---------------------------
|
||||
Route::get('/mark-list', [AdminMarkListController::class, 'index'])
|
||||
->name('admin.marklist.index');
|
||||
Route::get('/mark-list', [AdminMarkListController::class, 'index'])
|
||||
->name('admin.marklist.index');
|
||||
|
||||
Route::get('/mark-list/status/{id}', [AdminMarkListController::class, 'toggleStatus'])
|
||||
->name('admin.marklist.toggle');
|
||||
Route::get('/mark-list/status/{id}', [AdminMarkListController::class, 'toggleStatus'])
|
||||
->name('admin.marklist.toggle');
|
||||
|
||||
|
||||
// ---------------------------
|
||||
// ORDERS
|
||||
// ---------------------------
|
||||
Route::get('/orders', [AdminOrderController::class, 'orderShow'])
|
||||
->name('admin.orders');
|
||||
Route::get('/orders', [AdminOrderController::class, 'orderShow'])
|
||||
->name('admin.orders');
|
||||
|
||||
|
||||
Route::get('/orders/list', [AdminOrderController::class, 'index'])
|
||||
->name('admin.orders.index');
|
||||
Route::get('/orders/list', [AdminOrderController::class, 'index'])
|
||||
->name('admin.orders.index');
|
||||
|
||||
Route::get('/orders/{id}', [AdminOrderController::class, 'show'])
|
||||
->name('admin.orders.show');
|
||||
Route::get('/orders/{id}', [AdminOrderController::class, 'show'])
|
||||
->name('admin.orders.show');
|
||||
|
||||
Route::post('/orders/temp/add', [AdminOrderController::class, 'addTempItem'])
|
||||
->name('admin.orders.temp.add');
|
||||
Route::post('/orders/temp/add', [AdminOrderController::class, 'addTempItem'])
|
||||
->name('admin.orders.temp.add');
|
||||
|
||||
Route::post('/orders/temp/delete', [AdminOrderController::class, 'deleteTempItem'])
|
||||
->name('admin.orders.temp.delete');
|
||||
Route::post('/orders/temp/delete', [AdminOrderController::class, 'deleteTempItem'])
|
||||
->name('admin.orders.temp.delete');
|
||||
|
||||
Route::post('/orders/temp/reset', [AdminOrderController::class, 'resetTemp'])
|
||||
->name('admin.orders.temp.reset');
|
||||
Route::post('/orders/temp/reset', [AdminOrderController::class, 'resetTemp'])
|
||||
->name('admin.orders.temp.reset');
|
||||
|
||||
Route::post('/orders/finish', [AdminOrderController::class, 'finishOrder'])
|
||||
->name('admin.orders.finish');
|
||||
Route::post('/orders/finish', [AdminOrderController::class, 'finishOrder'])
|
||||
->name('admin.orders.finish');
|
||||
|
||||
Route::get('/orders/view/{id}', [AdminOrderController::class, 'popup'])
|
||||
->name('admin.orders.popup');
|
||||
Route::get('/orders/view/{id}', [AdminOrderController::class, 'popup'])
|
||||
->name('admin.orders.popup');
|
||||
|
||||
// ---------------------------
|
||||
// ORDERS (FIXED ROUTES)
|
||||
// ---------------------------
|
||||
|
||||
// ORDERS (FIXED ROUTES)
|
||||
// ---------------------------
|
||||
// Add item to order
|
||||
Route::post('/orders/{order}/item', [AdminOrderController::class, 'addItem'])
|
||||
->name('admin.orders.addItem');
|
||||
|
||||
// Delete item from order
|
||||
Route::delete('/orders/item/{id}', [AdminOrderController::class, 'deleteItem'])
|
||||
->name('admin.orders.deleteItem');
|
||||
|
||||
// Restore deleted item
|
||||
Route::post('/orders/item/{id}/restore', [AdminOrderController::class, 'restoreItem'])
|
||||
->name('admin.orders.restoreItem');
|
||||
|
||||
// Update main order fields
|
||||
Route::post('/orders/{id}/update', [AdminOrderController::class, 'update'])
|
||||
->name('admin.orders.update');
|
||||
|
||||
Route::put('/admin/orders/item/update/{id}', [AdminOrderController::class, 'updateItem'])
|
||||
->name('admin.orders.updateItem');
|
||||
// Delete full order
|
||||
Route::delete('/orders/{id}/delete', [AdminOrderController::class, 'destroy'])
|
||||
->name('admin.orders.destroy');
|
||||
|
||||
// ---------------------------
|
||||
// SHIPMENTS (FIXED ROUTES)
|
||||
// ---------------------------
|
||||
Route::get('/shipments', [ShipmentController::class, 'index'])
|
||||
->name('admin.shipments');
|
||||
|
||||
// ---------------------------
|
||||
// SHIPMENTS (FIXED ROUTES)
|
||||
// ---------------------------
|
||||
Route::get('/shipments', [ShipmentController::class, 'index'])
|
||||
->name('admin.shipments');
|
||||
|
||||
Route::post('/shipments', [ShipmentController::class, 'store'])
|
||||
->name('admin.shipments.store');
|
||||
|
||||
Route::post('/shipments/update-status', [ShipmentController::class, 'updateStatus'])
|
||||
->name('admin.shipments.updateStatus');
|
||||
|
||||
// Get shipment orders for modal (AJAX)
|
||||
Route::get('/shipments/{id}/orders', [ShipmentController::class, 'getShipmentOrders'])
|
||||
->name('admin.shipments.orders');
|
||||
|
||||
// Get shipment details for edit (AJAX)
|
||||
Route::get('/shipments/{id}', [ShipmentController::class, 'show'])
|
||||
->name('admin.shipments.show');
|
||||
|
||||
// Shipment Update
|
||||
Route::put('/shipments/{id}', [ShipmentController::class, 'update'])
|
||||
->name('admin.shipments.update');
|
||||
|
||||
// Shipment Delete
|
||||
Route::delete('/shipments/{id}', [ShipmentController::class, 'destroy'])
|
||||
->name('admin.shipments.destroy');
|
||||
|
||||
Route::post('/shipments', [ShipmentController::class, 'store'])
|
||||
->name('admin.shipments.store');
|
||||
|
||||
Route::post('/shipments/update-status', [ShipmentController::class, 'updateStatus'])
|
||||
->name('admin.shipments.updateStatus');
|
||||
|
||||
// Get shipment orders for modal (AJAX)
|
||||
Route::get('/shipments/{id}/orders', [ShipmentController::class, 'getShipmentOrders'])
|
||||
->name('admin.shipments.orders');
|
||||
|
||||
// Get shipment details for edit (AJAX)
|
||||
Route::get('/shipments/{id}', [ShipmentController::class, 'show'])
|
||||
->name('admin.shipments.show');
|
||||
|
||||
// Shipment Update
|
||||
Route::put('/shipments/{id}', [ShipmentController::class, 'update'])
|
||||
->name('admin.shipments.update');
|
||||
|
||||
// Shipment Delete
|
||||
Route::delete('/shipments/{id}', [ShipmentController::class, 'destroy'])
|
||||
->name('admin.shipments.destroy');
|
||||
Route::get('/shipment/dummy/{id}', [ShipmentController::class, 'dummy'])
|
||||
->name('admin.shipments.dummy');
|
||||
|
||||
|
||||
// ---------------------------
|
||||
// INVOICES
|
||||
// ---------------------------
|
||||
Route::get('/invoices', [AdminInvoiceController::class, 'index'])
|
||||
->name('admin.invoices.index');
|
||||
Route::get('/invoices', [AdminInvoiceController::class, 'index'])
|
||||
->name('admin.invoices.index');
|
||||
|
||||
Route::get('/invoices/{id}/popup', [AdminInvoiceController::class, 'popup'])
|
||||
->name('admin.invoices.popup');
|
||||
Route::get('/invoices/{id}/popup', [AdminInvoiceController::class, 'popup'])
|
||||
->name('admin.invoices.popup');
|
||||
|
||||
Route::get('/invoices/{id}/edit', [AdminInvoiceController::class, 'edit'])
|
||||
->name('admin.invoices.edit');
|
||||
Route::get('/invoices/{id}/edit', [AdminInvoiceController::class, 'edit'])
|
||||
->name('admin.invoices.edit');
|
||||
|
||||
Route::post('/invoices/{id}/update', [AdminInvoiceController::class, 'update'])
|
||||
->name('admin.invoices.update');
|
||||
Route::post('/invoices/{id}/update', [AdminInvoiceController::class, 'update'])
|
||||
->name('admin.invoices.update');
|
||||
|
||||
Route::post('/invoices/{invoice}/installments', [AdminInvoiceController::class, 'storeInstallment'])
|
||||
Route::post('/invoices/{invoice}/installments', [AdminInvoiceController::class, 'storeInstallment'])
|
||||
->name('admin.invoice.installment.store');
|
||||
|
||||
Route::post('/invoices/{id}/installment', [AdminInvoiceController::class, 'storeInstallment'])
|
||||
->name('admin.invoice.installment.store');
|
||||
|
||||
|
||||
@@ -205,81 +199,78 @@ Route::prefix('admin')
|
||||
->name('admin.invoice.installment.delete');
|
||||
|
||||
|
||||
|
||||
|
||||
//Add New Invoice
|
||||
Route::get('/admin/invoices/create', [InvoiceController::class, 'create'])->name('admin.invoices.create');
|
||||
|
||||
|
||||
//Add New Invoice
|
||||
Route::get('/admin/invoices/create', [InvoiceController::class, 'create'])->name('admin.invoices.create');
|
||||
|
||||
|
||||
// ---------------------------
|
||||
// CUSTOMERS
|
||||
// ---------------------------
|
||||
Route::get('/customers', [AdminCustomerController::class, 'index'])
|
||||
->name('admin.customers.index');
|
||||
Route::get('/customers', [AdminCustomerController::class, 'index'])
|
||||
->name('admin.customers.index');
|
||||
|
||||
Route::get('/customers/add', [AdminCustomerController::class, 'create'])
|
||||
->name('admin.customers.add');
|
||||
Route::get('/customers/add', [AdminCustomerController::class, 'create'])
|
||||
->name('admin.customers.add');
|
||||
|
||||
Route::post('/customers/store', [AdminCustomerController::class, 'store'])
|
||||
->name('admin.customers.store');
|
||||
Route::post('/customers/store', [AdminCustomerController::class, 'store'])
|
||||
->name('admin.customers.store');
|
||||
|
||||
Route::get('/customers/{id}/view', [AdminCustomerController::class, 'view'])
|
||||
->name('admin.customers.view');
|
||||
Route::get('/customers/{id}/view', [AdminCustomerController::class, 'view'])
|
||||
->name('admin.customers.view');
|
||||
|
||||
Route::post('/customers/{id}/status', [AdminCustomerController::class, 'toggleStatus'])
|
||||
->name('admin.customers.status');
|
||||
});
|
||||
Route::post('/customers/{id}/status', [AdminCustomerController::class, 'toggleStatus'])
|
||||
->name('admin.customers.status');
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// ADMIN ACCOUNT (AJAX) ROUTES
|
||||
// ==========================================
|
||||
Route::prefix('admin/account')
|
||||
->middleware('auth:admin')
|
||||
->name('admin.account.')
|
||||
->group(function () {
|
||||
|
||||
Route::get('/dashboard', [AdminAccountController::class, 'getDashboardData'])
|
||||
->name('dashboard');
|
||||
|
||||
Route::get('/available-orders', [AdminAccountController::class, 'getAvailableOrders'])
|
||||
->name('orders.available');
|
||||
|
||||
Route::post('/create-order', [AdminAccountController::class, 'accountCreateOrder'])
|
||||
->name('create');
|
||||
|
||||
Route::post('/toggle-payment', [AdminAccountController::class, 'togglePayment'])
|
||||
->name('toggle');
|
||||
|
||||
Route::post('/installment/create', [AdminAccountController::class, 'addInstallment'])
|
||||
->name('installment.create');
|
||||
|
||||
Route::post('/installment/update-status', [AdminAccountController::class, 'updateInstallmentStatus'])
|
||||
->name('installment.update');
|
||||
|
||||
Route::get('/entry/{entry_no}', [AdminAccountController::class, 'getEntryDetails'])
|
||||
->name('entry.details');
|
||||
|
||||
// ⬇⬇ NEW ROUTES FOR EDIT + DELETE ⬇⬇
|
||||
Route::post('/update-entry', [AdminAccountController::class, 'updateEntry'])
|
||||
->name('update.entry');
|
||||
|
||||
Route::post('/delete-entry', [AdminAccountController::class, 'deleteEntry'])
|
||||
->name('delete.entry');
|
||||
|
||||
|
||||
// ===== Associated Orders Routes (EDIT MODAL साठी) =====
|
||||
Route::post('/add-orders-to-entry', [AdminAccountController::class, 'addOrdersToEntry'])
|
||||
->name('add.orders.to.entry');
|
||||
Route::prefix('admin/account')
|
||||
->middleware('auth:admin')
|
||||
->name('admin.account.')
|
||||
->group(function () {
|
||||
|
||||
Route::get('/entry-orders/{entry_no}', [AdminAccountController::class, 'getEntryOrders'])
|
||||
->name('entry.orders');
|
||||
Route::get('/dashboard', [AdminAccountController::class, 'getDashboardData'])
|
||||
->name('dashboard');
|
||||
|
||||
Route::post('/remove-order-from-entry', [AdminAccountController::class, 'removeOrderFromEntry'])
|
||||
->name('remove.order.from.entry');
|
||||
Route::get('/available-orders', [AdminAccountController::class, 'getAvailableOrders'])
|
||||
->name('orders.available');
|
||||
|
||||
Route::post('/create-order', [AdminAccountController::class, 'accountCreateOrder'])
|
||||
->name('create');
|
||||
|
||||
Route::post('/toggle-payment', [AdminAccountController::class, 'togglePayment'])
|
||||
->name('toggle');
|
||||
|
||||
Route::post('/installment/create', [AdminAccountController::class, 'addInstallment'])
|
||||
->name('installment.create');
|
||||
|
||||
Route::post('/installment/update-status', [AdminAccountController::class, 'updateInstallmentStatus'])
|
||||
->name('installment.update');
|
||||
|
||||
Route::get('/entry/{entry_no}', [AdminAccountController::class, 'getEntryDetails'])
|
||||
->name('entry.details');
|
||||
|
||||
// ⬇⬇ NEW ROUTES FOR EDIT + DELETE ⬇⬇
|
||||
Route::post('/update-entry', [AdminAccountController::class, 'updateEntry'])
|
||||
->name('update.entry');
|
||||
|
||||
Route::post('/delete-entry', [AdminAccountController::class, 'deleteEntry'])
|
||||
->name('delete.entry');
|
||||
|
||||
|
||||
// ===== Associated Orders Routes (EDIT MODAL साठी) =====
|
||||
Route::post('/add-orders-to-entry', [AdminAccountController::class, 'addOrdersToEntry'])
|
||||
->name('add.orders.to.entry');
|
||||
|
||||
});
|
||||
Route::get('/entry-orders/{entry_no}', [AdminAccountController::class, 'getEntryOrders'])
|
||||
->name('entry.orders');
|
||||
|
||||
Route::post('/remove-order-from-entry', [AdminAccountController::class, 'removeOrderFromEntry'])
|
||||
->name('remove.order.from.entry');
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -293,8 +284,21 @@ Route::prefix('admin')
|
||||
Route::get('/admin/orders/download/excel', [AdminOrderController::class, 'downloadExcel'])
|
||||
->name('admin.orders.download.excel');
|
||||
|
||||
|
||||
Route::prefix('admin/account')->middleware('auth:admin')->name('admin.account.')->group(function () {
|
||||
Route::post('/toggle-payment', [AdminAccountController::class, 'togglePayment'])->name('toggle');
|
||||
});
|
||||
|
||||
//---------------------------
|
||||
//Edit Button Route
|
||||
//---------------------------
|
||||
// protected admin routes
|
||||
Route::middleware(['auth:admin'])
|
||||
->prefix('admin')
|
||||
->name('admin.')
|
||||
->group(function () {
|
||||
|
||||
|
||||
// staff resource
|
||||
Route::resource('staff', AdminStaffController::class);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user