diff --git a/app/Exports/OrdersExport.php b/app/Exports/OrdersExport.php index 44d5abf..486dd0e 100644 --- a/app/Exports/OrdersExport.php +++ b/app/Exports/OrdersExport.php @@ -6,10 +6,11 @@ use App\Models\Order; use Illuminate\Http\Request; use Maatwebsite\Excel\Concerns\FromCollection; use Maatwebsite\Excel\Concerns\WithHeadings; +use Carbon\Carbon; class OrdersExport implements FromCollection, WithHeadings { - protected $request; + protected Request $request; public function __construct(Request $request) { @@ -18,61 +19,99 @@ class OrdersExport implements FromCollection, WithHeadings private function buildQuery() { - $query = Order::with(['markList', 'invoice', 'shipments']); + $query = Order::query()->with([ + 'markList', + 'invoice', + 'shipments', + ]); + // SEARCH if ($this->request->filled('search')) { - $search = $this->request->search; - $query->where(function($q) use ($search) { - $q->where('order_id', 'like', "%{$search}%") - ->orWhereHas('markList', function($q2) use ($search) { + $search = trim($this->request->search); + + $query->where(function ($q) use ($search) { + $q->where('orders.order_id', 'like', "%{$search}%") + ->orWhereHas('markList', function ($q2) use ($search) { $q2->where('company_name', 'like', "%{$search}%") - ->orWhere('customer_id', 'like', "%{$search}%"); + ->orWhere('customer_id', 'like', "%{$search}%") + ->orWhere('origin', 'like', "%{$search}%") + ->orWhere('destination', 'like', "%{$search}%"); }) - ->orWhereHas('invoice', function($q3) use ($search) { + ->orWhereHas('invoice', function ($q3) use ($search) { $q3->where('invoice_number', 'like', "%{$search}%"); + }) + ->orWhereHas('shipments', function ($q4) use ($search) { + // ✅ FIXED + $q4->where('shipments.shipment_id', 'like', "%{$search}%"); }); }); } + // INVOICE STATUS + // INVOICE STATUS (FIXED) if ($this->request->filled('status')) { - $query->whereHas('invoice', function($q) { - $q->where('status', $this->request->status); + $query->where(function ($q) { + $q->whereHas('invoice', function ($q2) { + $q2->where('status', $this->request->status); + }) + ->orWhereDoesntHave('invoice'); }); } + + // SHIPMENT STATUS (FIXED) if ($this->request->filled('shipment')) { - $query->whereHas('shipments', function($q) { - $q->where('status', $this->request->shipment); + $query->where(function ($q) { + $q->whereHas('shipments', function ($q2) { + $q2->where('status', $this->request->shipment); + }) + ->orWhereDoesntHave('shipments'); }); } - return $query->latest('id'); + + // DATE RANGE + if ($this->request->filled('from_date')) { + $query->whereDate('orders.created_at', '>=', $this->request->from_date); + } + + if ($this->request->filled('to_date')) { + $query->whereDate('orders.created_at', '<=', $this->request->to_date); + } + + return $query->latest('orders.id'); } public function collection() { - $orders = $this->buildQuery()->get(); + return $this->buildQuery()->get()->map(function ($order) { - // Map to simple array rows suitable for Excel - return $orders->map(function($order) { - $mark = $order->markList; - $invoice = $order->invoice; - $shipment = $order->shipments->first() ?? null; + $mark = $order->markList; + $invoice = $order->invoice; + $shipment = $order->shipments->first(); return [ - 'Order ID' => $order->order_id, - 'Shipment ID' => $shipment->shipment_id ?? '-', - 'Customer ID' => $mark->customer_id ?? '-', - 'Company' => $mark->company_name ?? '-', - 'Origin' => $mark->origin ?? $order->origin ?? '-', - 'Destination' => $mark->destination ?? $order->destination ?? '-', - 'Order Date' => $order->created_at ? $order->created_at->format('d-m-Y') : '-', - 'Invoice No' => $invoice->invoice_number ?? '-', - 'Invoice Date' => $invoice?->invoice_date ? \Carbon\Carbon::parse($invoice->invoice_date)->format('d-m-Y') : '-', - 'Amount' => $invoice?->final_amount ? number_format($invoice->final_amount, 2) : '-', - 'Amount + GST' => $invoice?->final_amount_with_gst ? number_format($invoice->final_amount_with_gst, 2) : '-', - 'Invoice Status' => $invoice->status ? ucfirst($invoice->status) : 'Pending', - 'Shipment Status' => $shipment?->status ? ucfirst(str_replace('_', ' ', $shipment->status)) : 'Pending', + 'Order ID' => $order->order_id ?? '-', + 'Shipment ID' => $shipment?->shipment_id ?? '-', + 'Customer ID' => $mark?->customer_id ?? '-', + 'Company' => $mark?->company_name ?? '-', + 'Origin' => $mark?->origin ?? $order->origin ?? '-', + 'Destination' => $mark?->destination ?? $order->destination ?? '-', + 'Order Date' => $order->created_at + ? $order->created_at->format('d-m-Y') + : '-', + 'Invoice No' => $invoice?->invoice_number ?? '-', + 'Invoice Date' => $invoice?->invoice_date + ? Carbon::parse($invoice->invoice_date)->format('d-m-Y') + : '-', + 'Amount' => $invoice?->final_amount !== null + ? number_format($invoice->final_amount, 2) + : '0.00', + 'Amount + GST' => $invoice?->final_amount_with_gst !== null + ? number_format($invoice->final_amount_with_gst, 2) + : '0.00', + 'Invoice Status' => ucfirst($invoice?->status ?? 'pending'), + 'Shipment Status' => ucfirst(str_replace('_', ' ', $shipment?->status ?? 'pending')), ]; }); } diff --git a/app/Http/Controllers/Admin/AdminChatController.php b/app/Http/Controllers/Admin/AdminChatController.php index ed8fee1..3ae2c30 100644 --- a/app/Http/Controllers/Admin/AdminChatController.php +++ b/app/Http/Controllers/Admin/AdminChatController.php @@ -3,14 +3,22 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; +<<<<<<< HEAD use App\Models\SupportTicket; use App\Models\ChatMessage; use App\Events\NewChatMessage; use Illuminate\Http\Request; +======= +use Illuminate\Http\Request; +use App\Models\SupportTicket; +use App\Models\ChatMessage; +use App\Events\NewChatMessage; +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 class AdminChatController extends Controller { /** +<<<<<<< HEAD * Page 1: List all customer chat tickets */ public function index() @@ -23,11 +31,31 @@ class AdminChatController extends Controller return view('admin.chat_support', compact('tickets')); } +======= + * Page 1: List all active user chats + */ + public function index() +{ + $tickets = SupportTicket::with('user') + ->withCount([ + 'messages as unread_count' => function ($q) { + $q->where('sender_type', \App\Models\User::class) + ->where('read_by_admin', false); + } + ]) + ->orderBy('updated_at', 'desc') + ->get(); + + return view('admin.chat_support', compact('tickets')); +} + +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 /** * Page 2: Open chat window for a specific user */ public function openChat($ticketId) +<<<<<<< HEAD { $ticket = SupportTicket::with('user')->findOrFail($ticketId); $messages = ChatMessage::where('ticket_id', $ticketId) @@ -40,6 +68,28 @@ class AdminChatController extends Controller /** * Admin sends a message to the user (FIXED - LIVE CHAT) +======= +{ + $ticket = SupportTicket::with('user')->findOrFail($ticketId); + + // ✅ MARK USER MESSAGES AS READ FOR ADMIN + ChatMessage::where('ticket_id', $ticketId) + ->where('sender_type', \App\Models\User::class) + ->where('read_by_admin', false) + ->update(['read_by_admin' => true]); + + $messages = ChatMessage::where('ticket_id', $ticketId) + ->orderBy('created_at', 'asc') + ->with('sender') + ->get(); + + return view('admin.chat_window', compact('ticket', 'messages')); +} + + + /** + * Admin sends a message to the user +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 */ public function sendMessage(Request $request, $ticketId) { @@ -56,6 +106,12 @@ class AdminChatController extends Controller 'sender_id' => $admin->id, 'sender_type' => \App\Models\Admin::class, 'message' => $request->message, +<<<<<<< HEAD +======= + + 'read_by_admin' => true, + 'read_by_user' => false, +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 ]; // File Upload @@ -69,14 +125,28 @@ class AdminChatController extends Controller $message = ChatMessage::create($data); $message->load('sender'); +<<<<<<< HEAD \Log::info("DEBUG: ChatController sendMessage called", [ +======= + \Log::info("DEBUG: ChatController sendMessage called", [ +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 'ticket_id' => $ticketId, 'payload' => $request->all() ]); +<<<<<<< HEAD // 🔥 LIVE CHAT - Queue bypass (100% working) broadcast(new NewChatMessage($message))->toOthers(); +======= + // Broadcast real-time + broadcast(new NewChatMessage($message)); + + \Log::info("DEBUG: ChatController sendMessage called 79", [ + 'ticket_id' => $ticketId, + 'payload' => $request->all() + ]); +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 return response()->json([ 'success' => true, 'message' => $message diff --git a/app/Http/Controllers/Admin/AdminCustomerController.php b/app/Http/Controllers/Admin/AdminCustomerController.php index aab2cd1..4afccda 100644 --- a/app/Http/Controllers/Admin/AdminCustomerController.php +++ b/app/Http/Controllers/Admin/AdminCustomerController.php @@ -19,32 +19,37 @@ class AdminCustomerController extends Controller $search = $request->search; $status = $request->status; - $query = User::with(['marks', 'orders'])->orderBy('id', 'desc'); + $query = User::with([ + 'marks', + 'orders', + 'invoices.installments' // 🔥 IMPORTANT + ])->orderBy('id', 'desc'); - // SEARCH FILTER if (!empty($search)) { $query->where(function ($q) use ($search) { $q->where('customer_name', 'like', "%$search%") - ->orWhere('email', 'like', "%$search%") - ->orWhere('mobile_no', 'like', "%$search%") - ->orWhere('customer_id', 'like', "%$search%"); + ->orWhere('email', 'like', "%$search%") + ->orWhere('mobile_no', 'like', "%$search%") + ->orWhere('customer_id', 'like', "%$search%"); }); } - // STATUS FILTER if (!empty($status) && in_array($status, ['active', 'inactive'])) { $query->where('status', $status); } - // Get all customers for statistics (without pagination) $allCustomers = $query->get(); - - // Get paginated customers for the table (10 per page) $customers = $query->paginate(10); - return view('admin.customers', compact('customers', 'allCustomers', 'search', 'status')); + return view('admin.customers', compact( + 'customers', + 'allCustomers', + 'search', + 'status' + )); } + // --------------------------------------------------------- // SHOW ADD CUSTOMER FORM // --------------------------------------------------------- @@ -106,20 +111,36 @@ class AdminCustomerController extends Controller // VIEW CUSTOMER FULL DETAILS // --------------------------------------------------------- public function view($id) - { - $customer = User::with(['marks', 'orders'])->findOrFail($id); +{ + $customer = User::with([ + 'marks', + 'orders', + 'invoices.installments' + ])->findOrFail($id); - $totalOrders = $customer->orders->count(); - $totalAmount = $customer->orders->sum('ttl_amount'); - $recentOrders = $customer->orders()->latest()->take(5)->get(); + // Orders + $totalOrders = $customer->orders->count(); + $totalOrderAmount = $customer->orders->sum('ttl_amount'); + + // Invoices (PAYABLE) + $totalPayable = $customer->invoices->sum('final_amount_with_gst'); + + // Paid via installments + $totalPaid = $customer->invoiceInstallments->sum('amount'); + + // Remaining + $totalRemaining = max($totalPayable - $totalPaid, 0); + + return view('admin.customers_view', compact( + 'customer', + 'totalOrders', + 'totalOrderAmount', + 'totalPayable', + 'totalPaid', + 'totalRemaining' + )); +} - return view('admin.customers_view', compact( - 'customer', - 'totalOrders', - 'totalAmount', - 'recentOrders' - )); - } // --------------------------------------------------------- // TOGGLE STATUS ACTIVE / INACTIVE diff --git a/app/Http/Controllers/Admin/AdminInvoiceController.php b/app/Http/Controllers/Admin/AdminInvoiceController.php index 422482b..6dbab61 100644 --- a/app/Http/Controllers/Admin/AdminInvoiceController.php +++ b/app/Http/Controllers/Admin/AdminInvoiceController.php @@ -32,7 +32,7 @@ class AdminInvoiceController extends Controller $invoice = Invoice::with(['items', 'customer', 'container'])->findOrFail($id); $shipment = null; - return view('admin.popup_invoice', compact('invoice', 'shipment')); + return view('admin.popup_invoice', compact('invoice', 'shipment')); } // ------------------------------------------------------------- @@ -43,7 +43,14 @@ class AdminInvoiceController extends Controller $invoice = Invoice::with(['items', 'customer', 'container'])->findOrFail($id); $shipment = null; - return view('admin.invoice_edit', compact('invoice', 'shipment')); + // ADD THIS SECTION: Calculate customer's total due across all invoices + $customerTotalDue = Invoice::where('customer_id', $invoice->customer_id) + ->where('status', '!=', 'cancelled') + ->where('status', '!=', 'void') + ->sum('final_amount_with_gst'); + + // Pass the new variable to the view + return view('admin.invoice_edit', compact('invoice', 'shipment', 'customerTotalDue')); } // ------------------------------------------------------------- @@ -250,6 +257,17 @@ class AdminInvoiceController extends Controller $invoice->update(['pdf_path' => 'invoices/' . $fileName]); } + public function downloadInvoice($id) +{ + $invoice = Invoice::findOrFail($id); + + // ALWAYS regenerate to reflect latest HTML/CSS + $this->generateInvoicePDF($invoice); + $invoice->refresh(); + + return response()->download(public_path($invoice->pdf_path)); +} + // ------------------------------------------------------------- // INSTALLMENTS (ADD) // ------------------------------------------------------------- @@ -285,6 +303,8 @@ class AdminInvoiceController extends Controller if ($newPaid >= $invoice->final_amount_with_gst) { $invoice->update(['status' => 'paid']); + + $this->generateInvoicePDF($invoice); } return response()->json([ @@ -315,6 +335,8 @@ class AdminInvoiceController extends Controller if ($remaining > 0 && $invoice->status === 'paid') { $invoice->update(['status' => 'pending']); + + $this->generateInvoicePDF($invoice); } return response()->json([ diff --git a/app/Http/Controllers/Admin/AdminOrderController.php b/app/Http/Controllers/Admin/AdminOrderController.php index aa2734f..e9d6187 100644 --- a/app/Http/Controllers/Admin/AdminOrderController.php +++ b/app/Http/Controllers/Admin/AdminOrderController.php @@ -10,47 +10,33 @@ use App\Models\MarkList; use App\Models\Invoice; use App\Models\InvoiceItem; use App\Models\User; -use PDF; // barryvdh/laravel-dompdf facade +use PDF; use Maatwebsite\Excel\Facades\Excel; use App\Exports\OrdersExport; +use App\Imports\OrderItemsPreviewImport; + + +use Illuminate\Validation\ValidationException; class AdminOrderController extends Controller { - // --------------------------- - // LIST / DASHBOARD - // --------------------------- + /* --------------------------- + * LIST / DASHBOARD + * ---------------------------*/ public function index() { - // raw list for admin dashboard (simple) - $orders = Order::latest()->get(); + $orders = Order::latest()->get(); $markList = MarkList::where('status', 'active')->get(); return view('admin.dashboard', compact('orders', 'markList')); } - /** - * Orders list (detailed) - */ - // public function orderShow() - // { - // $orders = Order::with(['markList', 'shipments', 'invoice']) - // ->latest('id') - // ->get(); - - // return view('admin.orders', compact('orders')); - // } - - // --------------------------- - // CREATE NEW ORDER (simple DB flow) - // --------------------------- - /** - * Show create form (you can place create UI on separate view or dashboard) - */ + /* --------------------------- + * CREATE NEW ORDER (simple page) + * ---------------------------*/ public function create() { - // return a dedicated create view - create it at resources/views/admin/orders_create.blade.php - // If you prefer create UI on dashboard, change this to redirect route('admin.orders.index') etc. $markList = MarkList::where('status', 'active')->get(); return view('admin.orders_create', compact('markList')); } @@ -104,43 +90,19 @@ class AdminOrderController extends Controller public function show($id) { $order = Order::with('items', 'markList')->findOrFail($id); - $user = $this->getCustomerFromOrder($order); + $user = $this->getCustomerFromOrder($order); return view('admin.orders_show', compact('order', 'user')); } - // public function popup($id) - // { - // $order = Order::with(['items', 'markList'])->findOrFail($id); - // $user = $this->getCustomerFromOrder($order); - - // return view('admin.popup', compact('order', 'user')); - // } - - // --------------------------- - // ORDER ITEM MANAGEMENT (DB) - // --------------------------- - /** - * Add an item to an existing order - */ - public function addItem(Request $request, $orderId) + public function popup($id) { - $order = Order::findOrFail($orderId); + $order = Order::with(['items', 'markList'])->findOrFail($id); - $data = $request->validate([ - 'description' => 'required|string', - 'ctn' => 'nullable|numeric', - 'qty' => 'nullable|numeric', - 'ttl_qty' => 'nullable|numeric', - 'unit' => 'nullable|string', - 'price' => 'nullable|numeric', - 'ttl_amount' => 'nullable|numeric', - 'cbm' => 'nullable|numeric', - 'ttl_cbm' => 'nullable|numeric', - 'kg' => 'nullable|numeric', - 'ttl_kg' => 'nullable|numeric', - 'shop_no' => 'nullable|string', - ]); + $user = null; + if ($order->markList && $order->markList->customer_id) { + $user = User::where('customer_id', $order->markList->customer_id)->first(); + } $data['order_id'] = $order->id; @@ -153,171 +115,187 @@ class AdminOrderController extends Controller return redirect()->back()->with('success', 'Item added and totals updated.'); } - /** - * Soft-delete an order item and recalc totals - */ + /* --------------------------- + * ORDER ITEM MANAGEMENT (existing orders) + * ---------------------------*/ + public function addItem(Request $request, $orderId) +{ + $order = Order::findOrFail($orderId); + + $data = $request->validate([ + 'description' => 'required|string', + 'ctn' => 'nullable|numeric', + 'qty' => 'nullable|numeric', + 'unit' => 'nullable|string', + 'price' => 'nullable|numeric', + 'cbm' => 'nullable|numeric', + 'kg' => 'nullable|numeric', + 'shop_no' => 'nullable|string', + ]); + + // ✅ BACKEND CALCULATION + $ctn = (float) ($data['ctn'] ?? 0); + $qty = (float) ($data['qty'] ?? 0); + $price = (float) ($data['price'] ?? 0); + $cbm = (float) ($data['cbm'] ?? 0); + $kg = (float) ($data['kg'] ?? 0); + + $data['ttl_qty'] = $ctn * $qty; + $data['ttl_amount'] = $data['ttl_qty'] * $price; + $data['ttl_cbm'] = $cbm * $ctn; + $data['ttl_kg'] = $ctn * $kg; + + $data['order_id'] = $order->id; + + OrderItem::create($data); + + $this->recalcTotals($order); + $this->updateInvoiceFromOrder($order); + + return redirect()->back()->with('success', 'Item added and totals updated.'); +} + + public function deleteItem($id) { - $item = OrderItem::findOrFail($id); + $item = OrderItem::findOrFail($id); $order = $item->order; - $item->delete(); // soft delete + $item->delete(); - // recalc totals $this->recalcTotals($order); $this->updateInvoiceFromOrder($order); - return redirect()->back()->with('success', 'Item deleted and totals updated.'); } - /** - * Restore soft-deleted item and recalc totals - */ public function restoreItem($id) { - $item = OrderItem::withTrashed()->findOrFail($id); + $item = OrderItem::withTrashed()->findOrFail($id); $order = Order::findOrFail($item->order_id); $item->restore(); - // recalc totals $this->recalcTotals($order); $this->updateInvoiceFromOrder($order); - return redirect()->back()->with('success', 'Item restored and totals updated.'); } - // --------------------------- - // ORDER CRUD: update / destroy - // --------------------------- + /* --------------------------- + * ORDER CRUD: update / destroy + * ---------------------------*/ public function update(Request $request, $id) { $order = Order::findOrFail($id); $data = $request->validate([ - 'mark_no' => 'required|string', - 'origin' => 'nullable|string', + 'mark_no' => 'required|string', + 'origin' => 'nullable|string', 'destination' => 'nullable|string', ]); $order->update([ - 'mark_no' => $data['mark_no'], - 'origin' => $data['origin'] ?? null, + 'mark_no' => $data['mark_no'], + 'origin' => $data['origin'] ?? null, 'destination' => $data['destination'] ?? null, ]); - // optionally recalc totals (not necessary unless you change item-level fields here) $this->recalcTotals($order); return redirect()->route('admin.orders.show', $order->id) - ->with('success', 'Order updated successfully.'); + ->with('success', 'Order updated successfully.'); } - /** - * Soft-delete whole order and its items (soft-delete items first then order) - */ public function destroy($id) { $order = Order::findOrFail($id); - // soft-delete items first (so they show up in onlyTrashed for restore) OrderItem::where('order_id', $order->id)->delete(); - - // then soft-delete order $order->delete(); return redirect()->route('admin.orders.index') - ->with('success', 'Order deleted successfully.'); + ->with('success', 'Order deleted successfully.'); } - // --------------------------- - // HELPERS - // --------------------------- - /** - * Recalculate totals for the order from current (non-deleted) items - */ + /* --------------------------- + * HELPERS + * ---------------------------*/ private function recalcTotals(Order $order) { - // make sure we re-query live items (non-deleted) $items = $order->items()->get(); $order->update([ - 'ctn' => (int) $items->sum(fn($i) => (int) ($i->ctn ?? 0)), - 'qty' => (int) $items->sum(fn($i) => (int) ($i->qty ?? 0)), - 'ttl_qty' => (int) $items->sum(fn($i) => (int) ($i->ttl_qty ?? 0)), - 'ttl_amount'=> (float) $items->sum(fn($i) => (float) ($i->ttl_amount ?? 0)), - 'cbm' => (float) $items->sum(fn($i) => (float) ($i->cbm ?? 0)), - 'ttl_cbm' => (float) $items->sum(fn($i) => (float) ($i->ttl_cbm ?? 0)), - 'kg' => (float) $items->sum(fn($i) => (float) ($i->kg ?? 0)), - 'ttl_kg' => (float) $items->sum(fn($i) => (float) ($i->ttl_kg ?? 0)), + 'ctn' => (int) $items->sum(fn($i) => (int) ($i->ctn ?? 0)), + 'qty' => (int) $items->sum(fn($i) => (int) ($i->qty ?? 0)), + 'ttl_qty' => (int) $items->sum(fn($i) => (int) ($i->ttl_qty ?? 0)), + 'ttl_amount' => (float) $items->sum(fn($i) => (float) ($i->ttl_amount ?? 0)), + 'cbm' => (float) $items->sum(fn($i) => (float) ($i->cbm ?? 0)), + 'ttl_cbm' => (float) $items->sum(fn($i) => (float) ($i->ttl_cbm ?? 0)), + 'kg' => (float) $items->sum(fn($i) => (float) ($i->kg ?? 0)), + 'ttl_kg' => (float) $items->sum(fn($i) => (float) ($i->ttl_kg ?? 0)), ]); } - /** - * Generate order id (keeps old format) - */ private function generateOrderId() { - $year = date('y'); + $year = date('y'); $prefix = "KNT-$year-"; - $lastOrder = Order::latest('id')->first(); + $lastOrder = Order::latest('id')->first(); $nextNumber = $lastOrder ? intval(substr($lastOrder->order_id, -8)) + 1 : 1; return $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT); } - // --------------------------- - // INVOICE CREATION (optional helper used by store/finish) - // --------------------------- + /* --------------------------- + * INVOICE CREATION HELPERS + * ---------------------------*/ private function createInvoice(Order $order) { $invoiceNumber = $this->generateInvoiceNumber(); - $customer = $this->getCustomerFromMarkList($order->mark_no); - $totalAmount = $order->ttl_amount; + $customer = $this->getCustomerFromMarkList($order->mark_no); + $totalAmount = $order->ttl_amount; $invoice = Invoice::create([ - 'order_id' => $order->id, - 'customer_id' => $customer->id ?? null, - 'mark_no' => $order->mark_no, - 'invoice_number' => $invoiceNumber, - 'invoice_date' => now(), - 'due_date' => now()->addDays(10), - 'payment_method' => null, - 'reference_no' => null, - 'status' => 'pending', - 'final_amount' => $totalAmount, - 'gst_percent' => 0, - 'gst_amount' => 0, + 'order_id' => $order->id, + 'customer_id' => $customer->id ?? null, + 'mark_no' => $order->mark_no, + 'invoice_number' => $invoiceNumber, + 'invoice_date' => now(), + 'due_date' => now()->addDays(10), + 'payment_method' => null, + 'reference_no' => null, + 'status' => 'pending', + 'final_amount' => $totalAmount, + 'gst_percent' => 0, + 'gst_amount' => 0, 'final_amount_with_gst' => $totalAmount, - 'customer_name' => $customer->customer_name ?? null, - 'company_name' => $customer->company_name ?? null, - 'customer_email' => $customer->email ?? null, - 'customer_mobile' => $customer->mobile_no ?? null, - 'customer_address' => $customer->address ?? null, - 'pincode' => $customer->pincode ?? null, - 'notes' => null, - 'pdf_path' => null, + 'customer_name' => $customer->customer_name ?? null, + 'company_name' => $customer->company_name ?? null, + 'customer_email' => $customer->email ?? null, + 'customer_mobile' => $customer->mobile_no ?? null, + 'customer_address' => $customer->address ?? null, + 'pincode' => $customer->pincode ?? null, + 'notes' => null, + 'pdf_path' => null, ]); - // clone order items into invoice items foreach ($order->items as $item) { InvoiceItem::create([ - 'invoice_id' => $invoice->id, + 'invoice_id' => $invoice->id, 'description' => $item->description, - 'ctn' => $item->ctn, - 'qty' => $item->qty, - 'ttl_qty' => $item->ttl_qty, - 'unit' => $item->unit, - 'price' => $item->price, - 'ttl_amount' => $item->ttl_amount, - 'cbm' => $item->cbm, - 'ttl_cbm' => $item->ttl_cbm, - 'kg' => $item->kg, - 'ttl_kg' => $item->ttl_kg, - 'shop_no' => $item->shop_no, + 'ctn' => $item->ctn, + 'qty' => $item->qty, + 'ttl_qty' => $item->ttl_qty, + 'unit' => $item->unit, + 'price' => $item->price, + 'ttl_amount' => $item->ttl_amount, + 'cbm' => $item->cbm, + 'ttl_cbm' => $item->ttl_cbm, + 'kg' => $item->kg, + 'ttl_kg' => $item->ttl_kg, + 'shop_no' => $item->shop_no, ]); } } @@ -350,248 +328,327 @@ class AdminOrderController extends Controller return null; } - public function popup($id) + /* --------------------------- + * SEE (detailed) + * ---------------------------*/ + public function see($id) { - // Load order with items + markList - $order = Order::with(['items', 'markList'])->findOrFail($id); + $order = Order::with([ + 'markList', + 'items', + 'invoice.items', + 'shipments' => function ($q) use ($id) { + $q->whereHas('orders', function ($oq) use ($id) { + $oq->where('orders.id', $id) + ->whereNull('orders.deleted_at'); + })->with([ + 'orders' => function ($oq) use ($id) { + $oq->where('orders.id', $id) + ->whereNull('orders.deleted_at') + ->with('items'); + } + ]); + } + ])->findOrFail($id); - // Fetch user based on markList customer_id (same as show method) - $user = null; - if ($order->markList && $order->markList->customer_id) { - $user = \App\Models\User::where('customer_id', $order->markList->customer_id)->first(); + $orderData = [ + 'order_id' => $order->order_id, + 'status' => $order->status, + 'totals' => [ + 'ctn' => $order->ctn, + 'qty' => $order->qty, + 'ttl_qty' => $order->ttl_qty, + 'cbm' => $order->cbm, + 'ttl_cbm' => $order->ttl_cbm, + 'kg' => $order->kg, + 'ttl_kg' => $order->ttl_kg, + 'amount' => $order->ttl_amount, + ], + 'items' => $order->items, + ]; + + $shipmentsData = []; + foreach ($order->shipments as $shipment) { + $shipmentOrders = []; + $totals = [ + 'ctn' => 0, 'qty' => 0, 'ttl_qty' => 0, + 'cbm' => 0, 'ttl_cbm' => 0, + 'kg' => 0, 'ttl_kg' => 0, + 'amount' => 0, + ]; + + foreach ($shipment->orders as $shipOrder) { + foreach ($shipOrder->items as $item) { + $shipmentOrders[] = [ + 'order_id' => $shipOrder->order_id, + 'origin' => $shipOrder->origin, + 'destination' => $shipOrder->destination, + 'description' => $item->description, + 'ctn' => $item->ctn, + 'qty' => $item->qty, + 'ttl_qty' => $item->ttl_qty, + 'amount' => $item->ttl_amount, + ]; + + $totals['ctn'] += $item->ctn; + $totals['qty'] += $item->qty; + $totals['ttl_qty'] += $item->ttl_qty; + $totals['cbm'] += $item->cbm; + $totals['ttl_cbm'] += $item->ttl_cbm; + $totals['kg'] += $item->kg; + $totals['ttl_kg'] += $item->ttl_kg; + $totals['amount'] += $item->ttl_amount; + } + } + + if (empty($shipmentOrders)) { + continue; + } + + $shipmentsData[] = [ + 'shipment_id' => $shipment->shipment_id, + 'status' => $shipment->status, + 'date' => $shipment->shipment_date, + 'total_orders' => 1, + 'orders' => $shipmentOrders, + 'totals' => $totals, + ]; } - return view('admin.popup', compact('order', 'user')); - } - - - - - public function resetTemp() - { - session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']); - - return redirect()->to(route('admin.orders.index') . '#createOrderForm') - ->with('success', 'Order reset successfully.'); + $invoiceData = null; + if ($order->invoice) { + $invoice = $order->invoice; + $invoiceData = [ + 'invoice_no' => $invoice->invoice_number, + 'status' => $invoice->status, + 'invoice_date' => $invoice->invoice_date, + 'due_date' => $invoice->due_date, + 'customer' => [ + 'name' => $invoice->customer_name, + 'mobile' => $invoice->customer_mobile, + 'email' => $invoice->customer_email, + 'address' => $invoice->customer_address, + 'pincode' => $invoice->pincode, + ], + 'items' => $invoice->items, + 'summary' => [ + 'amount' => $invoice->final_amount, + 'cgst' => 0, + 'sgst' => 0, + 'total' => $invoice->final_amount_with_gst, + ], + ]; + } + + return view('admin.see_order', compact( + 'order', + 'orderData', + 'shipmentsData', + 'invoiceData' + )); } + /* --------------------------- + * FILTERED LIST + EXPORTS + * ---------------------------*/ public function orderShow() { $orders = Order::with([ - 'markList', // company, customer, origin, destination, date - 'shipments', // shipment_id, shipment_date, status - 'invoice' // invoice number, dates, amounts, status - ]) - ->latest('id') // show latest orders first - ->get(); + 'markList', + 'shipments', + 'invoice' + ])->latest('id')->get(); return view('admin.orders', compact('orders')); } - - // inside AdminOrderController private function buildOrdersQueryFromRequest(Request $request) { - $query = Order::with(['markList', 'invoice', 'shipments']); + $query = Order::query() + ->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 = trim($request->search); + + $query->where(function ($q) use ($search) { + $q->where('orders.order_id', 'like', "%{$search}%") + ->orWhereHas('markList', function ($q2) use ($search) { + $q2->where('company_name', 'like', "%{$search}%") + ->orWhere('customer_id', 'like', "%{$search}%") + ->orWhere('origin', 'like', "%{$search}%") + ->orWhere('destination', 'like', "%{$search}%"); + }) + ->orWhereHas('invoice', function ($q3) use ($search) { + $q3->where('invoice_number', 'like', "%{$search}%"); + }) + ->orWhereHas('shipments', function ($q4) use ($search) { + $q4->where('shipments.shipment_id', 'like', "%{$search}%"); + }); }); } - // Invoice status filter if ($request->filled('status')) { - $query->whereHas('invoice', function($q) use ($request) { - $q->where('status', $request->status); + $query->where(function ($q) use ($request) { + $q->whereHas('invoice', function ($q2) use ($request) { + $q2->where('status', $request->status); + })->orWhereDoesntHave('invoice'); }); } - // Shipment status filter if ($request->filled('shipment')) { - $query->whereHas('shipments', function($q) use ($request) { - $q->where('status', $request->shipment); + $query->where(function ($q) use ($request) { + $q->whereHas('shipments', function ($q2) use ($request) { + $q2->where('status', $request->shipment); + })->orWhereDoesntHave('shipments'); }); } - // optional ordering - $query->latest('id'); + if ($request->filled('from_date')) { + $query->whereDate('orders.created_at', '>=', $request->from_date); + } - return $query; + if ($request->filled('to_date')) { + $query->whereDate('orders.created_at', '<=', $request->to_date); + } + + return $query->latest('orders.id'); } 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 + $orders = $this->buildOrdersQueryFromRequest($request)->get(); $filters = [ - 'search' => $request->search ?? null, - 'status' => $request->status ?? null, - 'shipment' => $request->shipment ?? null, + 'search' => $request->search, + 'status' => $request->status, + 'shipment' => $request->shipment, + 'from' => $request->from_date, + 'to' => $request->to_date, ]; $pdf = PDF::loadView('admin.orders.pdf', compact('orders', 'filters')) - ->setPaper('a4', 'landscape'); // adjust if needed + ->setPaper('a4', 'landscape'); - $fileName = 'orders-report' - . ($filters['status'] ? "-{$filters['status']}" : '') - . '-' . date('Y-m-d') . '.pdf'; - - return $pdf->download($fileName); + return $pdf->download( + 'orders-report-' . now()->format('Y-m-d') . '.pdf' + ); } public function downloadExcel(Request $request) { - // pass request to OrdersExport which will build Filtered query internally - return Excel::download(new OrdersExport($request), 'orders-report-' . date('Y-m-d') . '.xlsx'); + return Excel::download( + new OrdersExport($request), + 'orders-report-' . now()->format('Y-m-d') . '.xlsx' + ); } - + /* -------------------------------------------------- + * NEW: Create Order + Invoice directly from popup + * route: admin.orders.temp.add (Create New Order form) + * --------------------------------------------------*/ public function addTempItem(Request $request) { - // Validate item fields - $item = $request->validate([ - 'mark_no' => 'required', - 'origin' => 'required', - 'destination' => 'required', - 'description' => 'required|string', - 'ctn' => 'nullable|numeric', - 'qty' => 'nullable|numeric', - 'ttl_qty' => 'nullable|numeric', - 'unit' => 'nullable|string', - 'price' => 'nullable|numeric', - 'ttl_amount' => 'nullable|numeric', - 'cbm' => 'nullable|numeric', - 'ttl_cbm' => 'nullable|numeric', - 'kg' => 'nullable|numeric', - 'ttl_kg' => 'nullable|numeric', - 'shop_no' => 'nullable|string', - ]); - - // ❌ Prevent changing mark_no once first item added - if (session()->has('mark_no') && session('mark_no') != $request->mark_no) { - return redirect()->to(route('admin.orders.index') . '#createOrderForm') - ->with('error', 'You must finish or clear the current order before changing Mark No.'); - } - - // Save mark, origin, destination ONLY ONCE - if (!session()->has('mark_no')) { - session([ - 'mark_no' => $request->mark_no, - 'origin' => $request->origin, - 'destination' => $request->destination - ]); - } - - // ❌ DO NOT overwrite these values again - // session(['mark_no' => $request->mark_no]); - // session(['origin' => $request->origin]); - // session(['destination' => $request->destination]); - - // Add new sub-item into session - session()->push('temp_order_items', $item); - - return redirect()->to(route('admin.orders.index') . '#createOrderForm') - - ->with('success', 'Item added.'); - } - - // ------------------------------------------------------------------------- - // STEP 3 : FINISH ORDER - // ------------------------------------------------------------------------- - - public function finishOrder(Request $request) - { + // 1) order-level fields $request->validate([ - 'mark_no' => 'required', - 'origin' => 'required', + 'mark_no' => 'required', + 'origin' => 'required', 'destination' => 'required', ]); - $items = session('temp_order_items', []); + // 2) multi-row items + $items = $request->validate([ + 'items' => 'required|array', + 'items.*.description' => 'required|string', + 'items.*.ctn' => 'nullable|numeric', + 'items.*.qty' => 'nullable|numeric', + + 'items.*.unit' => 'nullable|string', + 'items.*.price' => 'nullable|numeric', + + 'items.*.cbm' => 'nullable|numeric', + + 'items.*.kg' => 'nullable|numeric', + + 'items.*.shop_no' => 'nullable|string', + ])['items']; + + // रिकामे rows काढा + $items = array_filter($items, function ($row) { + return trim($row['description'] ?? '') !== ''; + }); if (empty($items)) { - return redirect()->to(route('admin.orders.index') . '#createOrderForm') - ->with('error', 'Add at least one item before finishing.'); + return back()->with('error', 'Add at least one item.'); } - // ======================= - // GENERATE ORDER ID - // ======================= - $year = date('y'); - $prefix = "KNT-$year-"; + // ✅ BACKEND CALCULATION (DO NOT TRUST FRONTEND) + foreach ($items as &$item) { - $lastOrder = Order::latest('id')->first(); - $nextNumber = $lastOrder ? intval(substr($lastOrder->order_id, -8)) + 1 : 1; + $ctn = (float) ($item['ctn'] ?? 0); + $qty = (float) ($item['qty'] ?? 0); + $price = (float) ($item['price'] ?? 0); + $cbm = (float) ($item['cbm'] ?? 0); + $kg = (float) ($item['kg'] ?? 0); - $orderId = $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT); + // Calculated fields + $item['ttl_qty'] = $ctn * $qty; + $item['ttl_amount'] = $item['ttl_qty'] * $price; + $item['ttl_cbm'] = $cbm * $ctn; + $item['ttl_kg'] = $ctn * $kg; + } + unset($item); // VERY IMPORTANT - // ======================= - // 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 - // ======================= + // 3) totals + $total_ctn = array_sum(array_column($items, 'ctn')); + $total_qty = array_sum(array_column($items, 'qty')); + $total_ttl_qty = array_sum(array_column($items, 'ttl_qty')); + $total_amount = array_sum(array_column($items, 'ttl_amount')); + $total_cbm = array_sum(array_column($items, 'cbm')); + $total_ttl_cbm = array_sum(array_column($items, 'ttl_cbm')); + $total_kg = array_sum(array_column($items, 'kg')); + $total_ttl_kg = array_sum(array_column($items, 'ttl_kg')); + + // 4) order id generate + $orderId = $this->generateOrderId(); + + // 5) order create $order = Order::create([ - 'order_id' => $orderId, - 'mark_no' => $request->mark_no, - 'origin' => $request->origin, - 'destination' => $request->destination, - 'ctn' => $total_ctn, - 'qty' => $total_qty, - 'ttl_qty' => $total_ttl_qty, + 'order_id' => $orderId, + 'mark_no' => $request->mark_no, + 'origin' => $request->origin, + 'destination'=> $request->destination, + 'ctn' => $total_ctn, + 'qty' => $total_qty, + 'ttl_qty' => $total_ttl_qty, 'ttl_amount' => $total_amount, - 'cbm' => $total_cbm, - 'ttl_cbm' => $total_ttl_cbm, - 'kg' => $total_kg, - 'ttl_kg' => $total_ttl_kg, - 'status' => 'pending', + 'cbm' => $total_cbm, + 'ttl_cbm' => $total_ttl_cbm, + 'kg' => $total_kg, + 'ttl_kg' => $total_ttl_kg, + 'status' => 'order_placed', ]); - // SAVE ORDER ITEMS + // 6) order items foreach ($items as $item) { OrderItem::create([ - 'order_id' => $order->id, - 'description' => $item['description'], - 'ctn' => $item['ctn'], - 'qty' => $item['qty'], - 'ttl_qty' => $item['ttl_qty'], - 'unit' => $item['unit'], - 'price' => $item['price'], + 'order_id' => $order->id, + 'description'=> $item['description'], + 'ctn' => $item['ctn'], + 'qty' => $item['qty'], + 'ttl_qty' => $item['ttl_qty'], + 'unit' => $item['unit'], + 'price' => $item['price'], 'ttl_amount' => $item['ttl_amount'], - 'cbm' => $item['cbm'], - 'ttl_cbm' => $item['ttl_cbm'], - 'kg' => $item['kg'], - 'ttl_kg' => $item['ttl_kg'], - 'shop_no' => $item['shop_no'], + 'cbm' => $item['cbm'], + 'ttl_cbm' => $item['ttl_cbm'], + 'kg' => $item['kg'], + 'ttl_kg' => $item['ttl_kg'], + 'shop_no' => $item['shop_no'], ]); } - // ======================= - // INVOICE CREATION START - // ======================= + // 7) invoice number + $invoiceNumber = $this->generateInvoiceNumber(); // 1. Auto-generate invoice number // $lastInvoice = \App\Models\Invoice::latest()->first(); @@ -667,37 +724,55 @@ class AdminOrderController extends Controller session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']); return redirect()->route('admin.orders.index') - ->with('success', 'Order + Invoice created successfully.'); + ->with('success', 'Order + Invoice created successfully.'); } - // --------------------------- - // ORDER CRUD: update / destroy - // --------------------------- + /* --------------------------- + * UPDATE ORDER ITEM (existing orders) + * ---------------------------*/ public function updateItem(Request $request, $id) - { - $item = OrderItem::findOrFail($id); - $order = $item->order; +{ + $item = OrderItem::findOrFail($id); + $order = $item->order; - $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, - ]); + $request->validate([ + 'description' => 'required|string', + 'ctn' => 'nullable|numeric', + 'qty' => 'nullable|numeric', + 'unit' => 'nullable|string', + 'price' => 'nullable|numeric', + 'cbm' => 'nullable|numeric', + 'kg' => 'nullable|numeric', + 'shop_no' => 'nullable|string', + ]); - $this->recalcTotals($order); - $this->updateInvoiceFromOrder($order); // <-- NEW + // ✅ BACKEND CALCULATION + $ctn = (float) ($request->ctn ?? 0); + $qty = (float) ($request->qty ?? 0); + $price = (float) ($request->price ?? 0); + $cbm = (float) ($request->cbm ?? 0); + $kg = (float) ($request->kg ?? 0); - return back()->with('success', 'Item updated successfully'); - } + $item->update([ + 'description' => $request->description, + 'ctn' => $ctn, + 'qty' => $qty, + 'ttl_qty' => $ctn * $qty, + 'unit' => $request->unit, + 'price' => $price, + 'ttl_amount' => ($ctn * $qty) * $price, + 'cbm' => $cbm, + 'ttl_cbm' => $cbm * $ctn, + 'kg' => $kg, + 'ttl_kg' => $ctn * $kg, + 'shop_no' => $request->shop_no, + ]); + + $this->recalcTotals($order); + $this->updateInvoiceFromOrder($order); + + return back()->with('success', 'Item updated successfully'); +} private function updateInvoiceFromOrder(Order $order) @@ -705,20 +780,17 @@ class AdminOrderController extends Controller $invoice = Invoice::where('order_id', $order->id)->first(); if (!$invoice) { - return; // No invoice exists (should not happen normally) + return; } - // Update invoice totals - $invoice->final_amount = $order->ttl_amount; - $invoice->gst_percent = 0; - $invoice->gst_amount = 0; + $invoice->final_amount = $order->ttl_amount; + $invoice->gst_percent = 0; + $invoice->gst_amount = 0; $invoice->final_amount_with_gst = $order->ttl_amount; $invoice->save(); - // Delete old invoice items InvoiceItem::where('invoice_id', $invoice->id)->delete(); - // Re-create invoice items from updated order items foreach ($order->items as $item) { InvoiceItem::create([ 'invoice_id' => $invoice->id, @@ -738,4 +810,35 @@ class AdminOrderController extends Controller } } -} \ No newline at end of file + + +public function uploadExcelPreview(Request $request) +{ + try { + $request->validate([ + 'excel' => 'required|file|mimes:xlsx,xls' + ]); + + $import = new OrderItemsPreviewImport(); + Excel::import($import, $request->file('excel')); + + return response()->json([ + 'success' => true, + 'items' => $import->rows + ]); + } catch (ValidationException $e) { + return response()->json([ + 'success' => false, + 'message' => 'Invalid Excel file format' + ], 422); + } catch (\Throwable $e) { + \Log::error($e); + return response()->json([ + 'success' => false, + 'message' => 'Server error' + ], 500); + } +} + + +} diff --git a/app/Http/Controllers/Admin/AdminStaffController.php b/app/Http/Controllers/Admin/AdminStaffController.php index 020239e..f7d8ddb 100644 --- a/app/Http/Controllers/Admin/AdminStaffController.php +++ b/app/Http/Controllers/Admin/AdminStaffController.php @@ -55,6 +55,7 @@ class AdminStaffController extends Controller DB::beginTransaction(); try { + // 1️⃣ Create staff WITHOUT employee_id (ID not available yet) $admin = Admin::create([ 'name' => $request->name, 'email' => $request->email, @@ -69,23 +70,33 @@ class AdminStaffController extends Controller 'status' => $request->status, 'additional_info' => $request->additional_info, - 'username' => $request->username, + // username may be NULL here + 'username' => $request->username ?: null, 'password' => Hash::make($request->password), 'type' => 'staff', ]); - // Generate EMPLOYEE ID using admin ID (safe) + // 2️⃣ Generate EMPLOYEE ID $employeeId = 'EMP' . str_pad($admin->id, 4, '0', STR_PAD_LEFT); - $admin->update(['employee_id' => $employeeId]); - // Assign permissions (if any) + // 3️⃣ Auto-generate username if left blank + $username = $request->username ?: strtolower($employeeId); + + // 4️⃣ Update employee_id + username together + $admin->update([ + 'employee_id' => $employeeId, + 'username' => $username, + ]); + + // 5️⃣ Assign permissions (if any) if ($request->permissions) { $admin->givePermissionTo($request->permissions); } DB::commit(); - return redirect()->route('admin.staff.index') + return redirect() + ->route('admin.staff.index') ->with('success', 'Staff created successfully.'); } catch (\Exception $e) { @@ -94,6 +105,7 @@ class AdminStaffController extends Controller } } + public function edit($id) { $staff = Admin::where('type', 'staff')->findOrFail($id); diff --git a/app/Http/Controllers/Admin/ShipmentController.php b/app/Http/Controllers/Admin/ShipmentController.php index 0eab511..781507e 100644 --- a/app/Http/Controllers/Admin/ShipmentController.php +++ b/app/Http/Controllers/Admin/ShipmentController.php @@ -20,7 +20,11 @@ class ShipmentController extends Controller $usedOrderIds = ShipmentItem::pluck('order_id')->toArray(); // 2) Load available orders (not used in any shipment) - $availableOrders = Order::whereNotIn('id', $usedOrderIds)->get(); + $availableOrders = Order::whereNotIn('id', $usedOrderIds) + ->where('status', '!=', 'order_placed') + ->get(); + + // 3) Load all shipments for listing $shipments = Shipment::latest()->get(); @@ -65,6 +69,16 @@ class ShipmentController extends Controller // CALCULATE TOTALS // ----------------------------- $orders = Order::whereIn('id', $request->order_ids)->get(); + foreach ($orders as $order) { + if ($order->status === 'order_placed') { + return back()->with( + 'error', + "Order {$order->order_id} is not ready for shipment" + ); + } + } + + $total_ctn = $orders->sum('ctn'); $total_qty = $orders->sum('qty'); @@ -82,7 +96,7 @@ class ShipmentController extends Controller 'shipment_id' => $newShipmentId, 'origin' => $request->origin, 'destination' => $request->destination, - 'status' => Shipment::STATUS_PENDING, + 'status' => Shipment::STATUS_SHIPMENT_READY, 'shipment_date' => $request->shipment_date, 'total_ctn' => $total_ctn, @@ -135,29 +149,35 @@ class ShipmentController extends Controller * Update Shipment status from action button */ public function updateStatus(Request $request) - { - $request->validate([ - 'shipment_id' => 'required|exists:shipments,id', - 'status' => 'required|string' - ]); +{ + $request->validate([ + 'shipment_id' => 'required|exists:shipments,id', + 'status' => 'required|string' + ]); - // 1) Update shipment status - $shipment = Shipment::findOrFail($request->shipment_id); - $shipment->status = $request->status; - $shipment->save(); + $shipment = Shipment::findOrFail($request->shipment_id); + $shipment->status = $request->status; + $shipment->save(); - // 2) Update ALL related orders' status - foreach ($shipment->orders as $order) { - $order->status = $shipment->status; // status is string: pending, in_transit, dispatched, delivered - $order->save(); + // ✅ Sync shipment status to orders ONLY after shipment exists + foreach ($shipment->orders as $order) { + + // Prevent rollback or overwrite + if ($order->status === 'delivered') { + continue; } - return redirect()->back()->with( - 'success', - "Shipment status updated to {$shipment->statusLabel()} and related orders updated." - ); + $order->status = $shipment->status; + $order->save(); } + return redirect()->back()->with( + 'success', + "Shipment status updated to {$shipment->statusLabel()}." + ); +} + + /** * Update shipment details */ @@ -224,5 +244,95 @@ class ShipmentController extends Controller return view('admin.view_shipment', compact('shipment', 'dummyData')); } + // App\Models\Shipment.php + +public function orders() +{ + return $this->belongsToMany(\App\Models\Order::class, 'shipment_items', 'shipment_id', 'order_id'); +} + +public function removeOrder(Shipment $shipment, Order $order) +{ + // Remove row from pivot table shipment_items + ShipmentItem::where('shipment_id', $shipment->id) + ->where('order_id', $order->id) + ->delete(); // removes link shipment <-> order [web:41][web:45] + + // Recalculate totals on this shipment (optional but recommended) + $orders = Order::whereIn( + 'id', + ShipmentItem::where('shipment_id', $shipment->id)->pluck('order_id') + )->get(); + + $shipment->total_ctn = $orders->sum('ctn'); + $shipment->total_qty = $orders->sum('qty'); + $shipment->total_ttl_qty = $orders->sum('ttl_qty'); + $shipment->total_cbm = $orders->sum('cbm'); + $shipment->total_ttl_cbm = $orders->sum('ttl_cbm'); + $shipment->total_kg = $orders->sum('kg'); + $shipment->total_ttl_kg = $orders->sum('ttl_kg'); + $shipment->total_amount = $orders->sum('ttl_amount'); + $shipment->save(); + + // Redirect back to preview page where your blade is loaded + return redirect() + ->route('admin.shipments.dummy', $shipment->id) + ->with('success', 'Order removed from shipment successfully.'); +} + +public function addOrders(Request $request, Shipment $shipment) +{ + $request->validate([ + 'order_ids' => 'required|array|min:1', + ]); + + $orders = Order::whereIn('id', $request->order_ids)->get(); + + foreach ($orders as $order) { + + if ($order->status === 'order_placed') { + return back()->with( + 'error', + "Order {$order->order_id} is not ready for shipment" + ); + } + + + // Prevent duplicates + if (ShipmentItem::where('order_id', $order->id)->exists()) { + continue; + } + + ShipmentItem::create([ + 'shipment_id' => $shipment->id, + 'order_id' => $order->id, + 'order_ctn' => $order->ctn, + 'order_qty' => $order->qty, + 'order_ttl_qty' => $order->ttl_qty, + 'order_ttl_amount' => $order->ttl_amount, + 'order_ttl_kg' => $order->ttl_kg, + ]); + } + + // Recalculate totals + $orderIds = ShipmentItem::where('shipment_id', $shipment->id)->pluck('order_id'); + $allOrders = Order::whereIn('id', $orderIds)->get(); + + $shipment->update([ + 'total_ctn' => $allOrders->sum('ctn'), + 'total_qty' => $allOrders->sum('qty'), + 'total_ttl_qty' => $allOrders->sum('ttl_qty'), + 'total_cbm' => $allOrders->sum('cbm'), + 'total_ttl_cbm' => $allOrders->sum('ttl_cbm'), + 'total_kg' => $allOrders->sum('kg'), + 'total_ttl_kg' => $allOrders->sum('ttl_kg'), + 'total_amount' => $allOrders->sum('ttl_amount'), + ]); + + return redirect() + ->route('admin.shipments.dummy', $shipment->id) + ->with('success', 'Orders added to shipment successfully.'); +} + } \ No newline at end of file diff --git a/app/Http/Controllers/Admin/UserRequestController.php b/app/Http/Controllers/Admin/UserRequestController.php index e09c56f..eb977c6 100644 --- a/app/Http/Controllers/Admin/UserRequestController.php +++ b/app/Http/Controllers/Admin/UserRequestController.php @@ -15,7 +15,12 @@ class UserRequestController extends Controller public function index() { $requests = CustomerRequest::orderBy('id', 'desc')->get(); - return view('admin.requests', compact('requests')); + $pendingProfileUpdates = \App\Models\UpdateRequest::where('status', 'pending')->count(); + + return view('admin.requests', compact( + 'requests', + 'pendingProfileUpdates' + )); } // Approve user request diff --git a/app/Http/Controllers/UserAuthController.php b/app/Http/Controllers/UserAuthController.php index 256f98a..9d407b2 100644 --- a/app/Http/Controllers/UserAuthController.php +++ b/app/Http/Controllers/UserAuthController.php @@ -6,76 +6,48 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; use PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth; use App\Models\User; +use Illuminate\Support\Facades\Log; class UserAuthController extends Controller { - public function refreshToken() - { - \Log::info('🔄 refreshToken() called'); +public function refreshToken() +{ + Log::info('🔄 [JWT-REFRESH] called'); - try { - // Get current token - $currentToken = JWTAuth::getToken(); + try { + $newToken = JWTAuth::parseToken()->refresh(); - if (!$currentToken) { - \Log::warning('⚠ No token provided in refreshToken()'); - return response()->json([ - 'success' => false, - 'message' => 'Token not provided', - ], 401); - } + Log::info('✅ [JWT-REFRESH] Token refreshed'); - \Log::info('📥 Current Token:', ['token' => (string) $currentToken]); + return response()->json([ + 'success' => true, + 'token' => $newToken, + ]); - // Try refreshing token - $newToken = JWTAuth::refresh($currentToken); + } catch (\PHPOpenSourceSaver\JWTAuth\Exceptions\TokenExpiredException $e) { + Log::warning('⛔ [JWT-REFRESH] Refresh TTL expired'); - \Log::info('✅ Token refreshed successfully', ['new_token' => $newToken]); + return response()->json([ + 'success' => false, + 'message' => 'Refresh expired. Please login again.', + ], 401); - return response()->json([ - 'success' => true, - 'token' => $newToken, - ]); + } catch (\Exception $e) { + Log::error('🔥 [JWT-REFRESH] Exception', [ + 'error' => $e->getMessage(), + ]); - } catch (\Tymon\JWTAuth\Exceptions\TokenExpiredException $e) { - \Log::error('❌ TokenExpiredException in refreshToken()', [ - 'message' => $e->getMessage(), - ]); - return response()->json([ - 'success' => false, - 'message' => 'Token expired, cannot refresh.', - ], 401); - - } catch (\Tymon\JWTAuth\Exceptions\TokenInvalidException $e) { - \Log::error('❌ TokenInvalidException in refreshToken()', [ - 'message' => $e->getMessage(), - ]); - return response()->json([ - 'success' => false, - 'message' => 'Invalid token.', - ], 401); - - } catch (\Tymon\JWTAuth\Exceptions\JWTException $e) { - \Log::error('❌ JWTException in refreshToken()', [ - 'message' => $e->getMessage(), - ]); - return response()->json([ - 'success' => false, - 'message' => 'Could not refresh token.', - ], 401); - - } catch (\Exception $e) { - \Log::error('❌ General Exception in refreshToken()', [ - 'message' => $e->getMessage(), - 'trace' => $e->getTraceAsString(), - ]); - return response()->json([ - 'success' => false, - 'message' => 'Unexpected error while refreshing token.', - ], 500); - } + return response()->json([ + 'success' => false, + 'message' => 'Unable to refresh token.', + ], 401); } +} + + + + /** * User Login diff --git a/app/Http/Controllers/user/ChatController.php b/app/Http/Controllers/user/ChatController.php index 87b3591..547b92f 100644 --- a/app/Http/Controllers/user/ChatController.php +++ b/app/Http/Controllers/user/ChatController.php @@ -67,6 +67,12 @@ class ChatController extends Controller 'sender_id' => auth()->id(), 'sender_type' => \App\Models\User::class, 'message' => $request->message, +<<<<<<< HEAD +======= + 'client_id' => $request->client_id, // ✅ ADD + 'read_by_admin' => false, + 'read_by_user' => true, +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 ]; // Handle file upload @@ -83,7 +89,11 @@ class ChatController extends Controller $message->load('sender'); // Fire real-time event +<<<<<<< HEAD broadcast(new NewChatMessage($message))->toOthers(); +======= + broadcast(new NewChatMessage($message)); +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 return response()->json([ 'success' => true, diff --git a/app/Http/Controllers/user/UserOrderController.php b/app/Http/Controllers/user/UserOrderController.php index d18f7ae..da77e68 100644 --- a/app/Http/Controllers/user/UserOrderController.php +++ b/app/Http/Controllers/user/UserOrderController.php @@ -289,6 +289,44 @@ public function invoiceDetails($invoice_id) ]); } +public function confirmOrder($order_id) +{ + $user = JWTAuth::parseToken()->authenticate(); + + if (! $user) { + return response()->json([ + 'success' => false, + 'message' => 'Unauthorized' + ], 401); + } + + $order = $user->orders() + ->where('order_id', $order_id) + ->first(); + + if (! $order) { + return response()->json([ + 'success' => false, + 'message' => 'Order not found' + ], 404); + } + + // 🚫 Only allow confirm from order_placed + if ($order->status !== 'order_placed') { + return response()->json([ + 'success' => false, + 'message' => 'Order cannot be confirmed' + ], 422); + } + + $order->status = 'order_confirmed'; + $order->save(); + + return response()->json([ + 'success' => true, + 'message' => 'Order confirmed successfully' + ]); +} diff --git a/app/Http/Middleware/JwtRefreshMiddleware.php b/app/Http/Middleware/JwtRefreshMiddleware.php index 5d625e2..5eb2be6 100644 --- a/app/Http/Middleware/JwtRefreshMiddleware.php +++ b/app/Http/Middleware/JwtRefreshMiddleware.php @@ -11,7 +11,8 @@ use Tymon\JWTAuth\Exceptions\JWTException; class JwtRefreshMiddleware { public function handle($request, Closure $next) - { + { + try { JWTAuth::parseToken()->authenticate(); } catch (TokenExpiredException $e) { diff --git a/app/Imports/OrderItemsPreviewImport.php b/app/Imports/OrderItemsPreviewImport.php new file mode 100644 index 0000000..0865b6f --- /dev/null +++ b/app/Imports/OrderItemsPreviewImport.php @@ -0,0 +1,26 @@ +first()->map(fn ($h) => strtolower(trim($h)))->toArray(); + + foreach ($collection->skip(1) as $row) { + $item = []; + foreach ($header as $i => $key) { + $item[$key] = $row[$i] ?? null; + } + + if (!empty($item['description'])) { + $this->rows[] = $item; + } + } + } +} diff --git a/app/Models/Admin.php b/app/Models/Admin.php index 118f371..c897aeb 100644 --- a/app/Models/Admin.php +++ b/app/Models/Admin.php @@ -18,7 +18,7 @@ class Admin extends Authenticatable 'name', 'email', 'password', 'username', 'phone', 'emergency_phone', 'address', 'role', 'department', 'designation', 'joining_date', - 'status', 'additional_info', 'type', // admin/staff indicator + 'status', 'additional_info', 'type','employee_id', // admin/staff indicator ]; protected $hidden = [ diff --git a/app/Models/ChatMessage.php b/app/Models/ChatMessage.php index 363b5d4..2029998 100644 --- a/app/Models/ChatMessage.php +++ b/app/Models/ChatMessage.php @@ -10,6 +10,7 @@ class ChatMessage extends Model use HasFactory; protected $fillable = [ +<<<<<<< HEAD 'ticket_id', 'sender_id', 'sender_type', // user OR admin @@ -17,6 +18,19 @@ class ChatMessage extends Model 'file_path', 'file_type', ]; +======= + 'ticket_id', + 'sender_id', + 'sender_type', + 'message', + 'file_path', + 'file_type', + 'read_by_admin', + 'read_by_user', + 'client_id', +]; + +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 /** * The ticket this message belongs to. diff --git a/app/Models/Order.php b/app/Models/Order.php index 029cf35..9362328 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -64,5 +64,25 @@ class Order extends Model } + const STATUS_LABELS = [ + 'order_placed' => 'Order Placed', + 'order_confirmed' => 'Order Confirmed', + 'supplier_warehouse' => 'Supplier Warehouse', + 'consolidate_warehouse'=> 'Consolidate Warehouse', + 'export_custom' => 'Export Custom', + 'international_transit'=> 'International Transit', + 'arrived_india' => 'Arrived at India', + 'import_custom' => 'Import Custom', + 'warehouse' => 'Warehouse', + 'domestic_distribution'=> 'Domestic Distribution', + 'out_for_delivery' => 'Out for Delivery', + 'delivered' => 'Delivered', + ]; + + public function getStatusLabelAttribute() + { + return self::STATUS_LABELS[$this->status] + ?? ucfirst(str_replace('_', ' ', $this->status)); + } } diff --git a/app/Models/Shipment.php b/app/Models/Shipment.php index 95bb5d5..3c5cacd 100644 --- a/app/Models/Shipment.php +++ b/app/Models/Shipment.php @@ -45,25 +45,6 @@ class Shipment extends Model return $this->belongsToMany(Order::class, 'shipment_items', 'shipment_id', 'order_id'); } - // --------------------------- - // STATUS CONSTANTS - // --------------------------- - - const STATUS_PENDING = 'pending'; - const STATUS_IN_TRANSIT = 'in_transit'; - const STATUS_DISPATCHED = 'dispatched'; - const STATUS_DELIVERED = 'delivered'; - - public static function statusOptions() - { - return [ - self::STATUS_PENDING => 'Pending', - self::STATUS_IN_TRANSIT => 'In Transit', - self::STATUS_DISPATCHED => 'Dispatched', - self::STATUS_DELIVERED => 'Delivered', - ]; - } - // --------------------------- // HELPERS // --------------------------- @@ -73,8 +54,38 @@ class Shipment extends Model return $this->items()->count(); } + // --------------------------- + // STATUS CONSTANTS (LOGISTICS FLOW) + // --------------------------- + const STATUS_SHIPMENT_READY = 'shipment_ready'; + const STATUS_EXPORT_CUSTOM = 'export_custom'; + const STATUS_INTERNATIONAL_TRANSIT= 'international_transit'; + const STATUS_ARRIVED_INDIA = 'arrived_india'; + const STATUS_IMPORT_CUSTOM = 'import_custom'; + const STATUS_WAREHOUSE = 'warehouse'; + const STATUS_DOMESTIC_DISTRIBUTION= 'domestic_distribution'; + const STATUS_OUT_FOR_DELIVERY = 'out_for_delivery'; + const STATUS_DELIVERED = 'delivered'; + + public static function statusOptions() + { + return [ + self::STATUS_SHIPMENT_READY => 'Shipment Ready', + self::STATUS_EXPORT_CUSTOM => 'Export Custom', + self::STATUS_INTERNATIONAL_TRANSIT => 'International Transit', + self::STATUS_ARRIVED_INDIA => 'Arrived at India', + self::STATUS_IMPORT_CUSTOM => 'Import Custom', + self::STATUS_WAREHOUSE => 'Warehouse', + self::STATUS_DOMESTIC_DISTRIBUTION => 'Domestic Distribution', + self::STATUS_OUT_FOR_DELIVERY => 'Out for Delivery', + self::STATUS_DELIVERED => 'Delivered', + ]; + } + public function statusLabel() { - return self::statusOptions()[$this->status] ?? ucfirst($this->status); + return self::statusOptions()[$this->status] + ?? ucfirst(str_replace('_', ' ', $this->status)); } + } diff --git a/app/Models/User.php b/app/Models/User.php index e67ea2f..48c963c 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -94,6 +94,19 @@ public function invoices() return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'id'); } +// App\Models\User.php + + +public function invoiceInstallments() +{ + return $this->hasManyThrough( + InvoiceInstallment::class, + Invoice::class, + 'customer_id', // FK on invoices + 'invoice_id' // FK on installments + ); +} + } diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php new file mode 100644 index 0000000..fed8005 --- /dev/null +++ b/app/Providers/BroadcastServiceProvider.php @@ -0,0 +1,22 @@ + ['web'], + ]); + + // 👇 FORCE admin guard for broadcasting + Auth::shouldUse('admin'); + + require base_path('routes/channels.php'); + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 116b7e2..e2064f5 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -6,13 +6,18 @@ use Illuminate\Foundation\Configuration\Middleware; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( - web: __DIR__.'/../routes/web.php', + web: [ + __DIR__.'/../routes/web.php', + __DIR__.'/../routes/channels.php', + ], + api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) - ->withMiddleware(function (Middleware $middleware): void { + ->withMiddleware(function (Middleware $middleware) { // }) - ->withExceptions(function (Exceptions $exceptions): void { + ->withExceptions(function (Exceptions $exceptions) { // - })->create(); + }) + ->create(); diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 8e6dce1..6e5e701 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -4,4 +4,6 @@ return [ App\Providers\AppServiceProvider::class, App\Providers\RouteServiceProvider::class, App\Providers\AuthServiceProvider::class, + App\Providers\BroadcastServiceProvider::class, + ]; diff --git a/composer.lock b/composer.lock index f710ccf..5c487e0 100644 --- a/composer.lock +++ b/composer.lock @@ -2880,6 +2880,7 @@ }, { "name": "maennchen/zipstream-php", +<<<<<<< HEAD "version": "3.2.1", "source": { "type": "git", @@ -2890,21 +2891,45 @@ "type": "zip", "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/682f1098a8fddbaf43edac2306a691c7ad508ec5", "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5", +======= + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f", + "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f", +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 "shasum": "" }, "require": { "ext-mbstring": "*", "ext-zlib": "*", +<<<<<<< HEAD "php-64bit": "^8.3" +======= + "php-64bit": "^8.2" +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 }, "require-dev": { "brianium/paratest": "^7.7", "ext-zip": "*", +<<<<<<< HEAD "friendsofphp/php-cs-fixer": "^3.86", "guzzlehttp/guzzle": "^7.5", "mikey179/vfsstream": "^1.6", "php-coveralls/php-coveralls": "^2.5", "phpunit/phpunit": "^12.0", +======= + "friendsofphp/php-cs-fixer": "^3.16", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^11.0", +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 "vimeo/psalm": "^6.0" }, "suggest": { @@ -2946,7 +2971,11 @@ ], "support": { "issues": "https://github.com/maennchen/ZipStream-PHP/issues", +<<<<<<< HEAD "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.1" +======= + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2" +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 }, "funding": [ { @@ -2954,7 +2983,11 @@ "type": "github" } ], +<<<<<<< HEAD "time": "2025-12-10T09:58:31+00:00" +======= + "time": "2025-01-27T12:07:53+00:00" +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 }, { "name": "markbaker/complex", @@ -4181,6 +4214,7 @@ }, { "name": "phpoffice/phpspreadsheet", +<<<<<<< HEAD "version": "1.30.2", "source": { "type": "git", @@ -4191,6 +4225,18 @@ "type": "zip", "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/09cdde5e2f078b9a3358dd217e2c8cb4dac84be2", "reference": "09cdde5e2f078b9a3358dd217e2c8cb4dac84be2", +======= + "version": "1.30.1", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "fa8257a579ec623473eabfe49731de5967306c4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fa8257a579ec623473eabfe49731de5967306c4c", + "reference": "fa8257a579ec623473eabfe49731de5967306c4c", +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 "shasum": "" }, "require": { @@ -4213,11 +4259,19 @@ "markbaker/complex": "^3.0", "markbaker/matrix": "^3.0", "php": ">=7.4.0 <8.5.0", +<<<<<<< HEAD +======= + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "dev-main", +<<<<<<< HEAD "doctrine/instantiator": "^1.5", +======= +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 "dompdf/dompdf": "^1.0 || ^2.0 || ^3.0", "friendsofphp/php-cs-fixer": "^3.2", "mitoteam/jpgraph": "^10.3", @@ -4264,9 +4318,12 @@ }, { "name": "Adrien Crivelli" +<<<<<<< HEAD }, { "name": "Owen Leibman" +======= +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 } ], "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", @@ -4283,9 +4340,15 @@ ], "support": { "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", +<<<<<<< HEAD "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.2" }, "time": "2026-01-11T05:58:24+00:00" +======= + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.1" + }, + "time": "2025-10-26T16:01:04+00:00" +>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043 }, { "name": "phpoption/phpoption", diff --git a/config/auth.php b/config/auth.php index 3015769..e0705e5 100644 --- a/config/auth.php +++ b/config/auth.php @@ -89,6 +89,8 @@ return [ 'driver' => 'eloquent', 'model' => App\Models\Staff::class, ], + + ], diff --git a/config/jwt.php b/config/jwt.php index adfab4d..3119694 100644 --- a/config/jwt.php +++ b/config/jwt.php @@ -89,7 +89,7 @@ return [ | */ - 'ttl' => (int) env('JWT_TTL', 60), + 'ttl' => (int) env('JWT_TTL', 1440), /* |-------------------------------------------------------------------------- @@ -108,7 +108,7 @@ return [ | */ - 'refresh_ttl' => (int) env('JWT_REFRESH_TTL', 20160), + 'refresh_ttl' => (int) env('JWT_REFRESH_TTL', 64800), /* |-------------------------------------------------------------------------- diff --git a/database/migrations/2025_12_14_073425_add_read_flags_to_chat_messages.php b/database/migrations/2025_12_14_073425_add_read_flags_to_chat_messages.php new file mode 100644 index 0000000..ade7935 --- /dev/null +++ b/database/migrations/2025_12_14_073425_add_read_flags_to_chat_messages.php @@ -0,0 +1,30 @@ +boolean('read_by_admin')->default(false)->after('file_type'); + $table->boolean('read_by_user')->default(false)->after('read_by_admin'); +}); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('chat_messages', function (Blueprint $table) { + // + }); + } +}; diff --git a/database/migrations/2025_12_17_053233_add_client_id_to_chat_messages.php b/database/migrations/2025_12_17_053233_add_client_id_to_chat_messages.php new file mode 100644 index 0000000..ad2e653 --- /dev/null +++ b/database/migrations/2025_12_17_053233_add_client_id_to_chat_messages.php @@ -0,0 +1,25 @@ +string('client_id') + ->nullable() + ->after('sender_type') + ->index(); + }); + } + + public function down(): void + { + Schema::table('chat_messages', function (Blueprint $table) { + $table->dropColumn('client_id'); + }); + } +}; diff --git a/public/invoices/invoice-INV-2025-000001.pdf b/public/invoices/invoice-INV-2025-000001.pdf deleted file mode 100644 index 6e98cc4..0000000 Binary files a/public/invoices/invoice-INV-2025-000001.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000002.pdf b/public/invoices/invoice-INV-2025-000002.pdf deleted file mode 100644 index dde03a9..0000000 Binary files a/public/invoices/invoice-INV-2025-000002.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000003.pdf b/public/invoices/invoice-INV-2025-000003.pdf deleted file mode 100644 index e17839c..0000000 Binary files a/public/invoices/invoice-INV-2025-000003.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000004.pdf b/public/invoices/invoice-INV-2025-000004.pdf deleted file mode 100644 index a443c78..0000000 Binary files a/public/invoices/invoice-INV-2025-000004.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000005.pdf b/public/invoices/invoice-INV-2025-000005.pdf deleted file mode 100644 index f30024c..0000000 Binary files a/public/invoices/invoice-INV-2025-000005.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000006.pdf b/public/invoices/invoice-INV-2025-000006.pdf deleted file mode 100644 index f1d586a..0000000 Binary files a/public/invoices/invoice-INV-2025-000006.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000007.pdf b/public/invoices/invoice-INV-2025-000007.pdf deleted file mode 100644 index 4425ec2..0000000 Binary files a/public/invoices/invoice-INV-2025-000007.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000008.pdf b/public/invoices/invoice-INV-2025-000008.pdf deleted file mode 100644 index e6c8df1..0000000 Binary files a/public/invoices/invoice-INV-2025-000008.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000009.pdf b/public/invoices/invoice-INV-2025-000009.pdf deleted file mode 100644 index 362d2f4..0000000 Binary files a/public/invoices/invoice-INV-2025-000009.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000010.pdf b/public/invoices/invoice-INV-2025-000010.pdf deleted file mode 100644 index 435c32b..0000000 Binary files a/public/invoices/invoice-INV-2025-000010.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000011.pdf b/public/invoices/invoice-INV-2025-000011.pdf deleted file mode 100644 index 5c37efa..0000000 Binary files a/public/invoices/invoice-INV-2025-000011.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000012.pdf b/public/invoices/invoice-INV-2025-000012.pdf deleted file mode 100644 index 188bf50..0000000 Binary files a/public/invoices/invoice-INV-2025-000012.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000013.pdf b/public/invoices/invoice-INV-2025-000013.pdf deleted file mode 100644 index 350715c..0000000 Binary files a/public/invoices/invoice-INV-2025-000013.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000014.pdf b/public/invoices/invoice-INV-2025-000014.pdf deleted file mode 100644 index a2f5b70..0000000 Binary files a/public/invoices/invoice-INV-2025-000014.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000017.pdf b/public/invoices/invoice-INV-2025-000017.pdf deleted file mode 100644 index 23bd004..0000000 Binary files a/public/invoices/invoice-INV-2025-000017.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000019.pdf b/public/invoices/invoice-INV-2025-000019.pdf deleted file mode 100644 index 431b377..0000000 Binary files a/public/invoices/invoice-INV-2025-000019.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000020.pdf b/public/invoices/invoice-INV-2025-000020.pdf deleted file mode 100644 index 89c69e8..0000000 Binary files a/public/invoices/invoice-INV-2025-000020.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000022.pdf b/public/invoices/invoice-INV-2025-000022.pdf deleted file mode 100644 index 663e06a..0000000 Binary files a/public/invoices/invoice-INV-2025-000022.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000023.pdf b/public/invoices/invoice-INV-2025-000023.pdf deleted file mode 100644 index 1519813..0000000 Binary files a/public/invoices/invoice-INV-2025-000023.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000024.pdf b/public/invoices/invoice-INV-2025-000024.pdf deleted file mode 100644 index 68bc8aa..0000000 Binary files a/public/invoices/invoice-INV-2025-000024.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000025.pdf b/public/invoices/invoice-INV-2025-000025.pdf deleted file mode 100644 index b7d7a56..0000000 Binary files a/public/invoices/invoice-INV-2025-000025.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000026.pdf b/public/invoices/invoice-INV-2025-000026.pdf deleted file mode 100644 index 619708c..0000000 Binary files a/public/invoices/invoice-INV-2025-000026.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000028.pdf b/public/invoices/invoice-INV-2025-000028.pdf deleted file mode 100644 index 2778e4d..0000000 Binary files a/public/invoices/invoice-INV-2025-000028.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000029.pdf b/public/invoices/invoice-INV-2025-000029.pdf deleted file mode 100644 index f5a8e87..0000000 Binary files a/public/invoices/invoice-INV-2025-000029.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000030.pdf b/public/invoices/invoice-INV-2025-000030.pdf deleted file mode 100644 index 605f767..0000000 Binary files a/public/invoices/invoice-INV-2025-000030.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000032.pdf b/public/invoices/invoice-INV-2025-000032.pdf deleted file mode 100644 index 7ffbb92..0000000 Binary files a/public/invoices/invoice-INV-2025-000032.pdf and /dev/null differ diff --git a/public/invoices/invoice-INV-2025-000033.pdf b/public/invoices/invoice-INV-2025-000033.pdf index 7cd4e84..11e266b 100644 Binary files a/public/invoices/invoice-INV-2025-000033.pdf and b/public/invoices/invoice-INV-2025-000033.pdf differ diff --git a/public/profile_upload/profile_1764645094.jpg b/public/profile_upload/profile_1764645094.jpg deleted file mode 100644 index 3c6a162..0000000 Binary files a/public/profile_upload/profile_1764645094.jpg and /dev/null differ diff --git a/public/profile_upload/profile_1766120292.jpg b/public/profile_upload/profile_1766120292.jpg new file mode 100644 index 0000000..3923309 Binary files /dev/null and b/public/profile_upload/profile_1766120292.jpg differ diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js index b11f617..8c9cdf4 100644 --- a/resources/js/bootstrap.js +++ b/resources/js/bootstrap.js @@ -1,4 +1,9 @@ import axios from 'axios'; window.axios = axios; -window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; +axios.defaults.withCredentials = true; +axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest"; +axios.defaults.headers.common["X-CSRF-TOKEN"] = document.querySelector( + 'meta[name="csrf-token"]' +).content; + diff --git a/resources/js/echo.js b/resources/js/echo.js index 5e9691a..235ab4c 100644 --- a/resources/js/echo.js +++ b/resources/js/echo.js @@ -28,4 +28,4 @@ window.Echo = new Echo({ } }); -console.log('%c[ECHO] Initialized!', 'color: green; font-weight: bold;', window.Echo); \ No newline at end of file +console.log('%c[ECHO] Initialized!', 'color: green; font-weight: bold;', window.Echo); diff --git a/resources/views/admin/account.blade.php b/resources/views/admin/account.blade.php index c584ca9..db330cf 100644 --- a/resources/views/admin/account.blade.php +++ b/resources/views/admin/account.blade.php @@ -6,6 +6,7 @@
@@ -1166,8 +1547,8 @@ tr:hover td{ background:#fbfdff; }
- -