Resolve merge conflicts

This commit is contained in:
Utkarsh Khedkar
2026-02-17 14:44:47 +05:30
84 changed files with 16116 additions and 5486 deletions

View File

@@ -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')),
];
});
}

View File

@@ -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

View File

@@ -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

View File

@@ -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([

File diff suppressed because it is too large Load Diff

View File

@@ -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);

View File

@@ -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.');
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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'
]);
}

View File

@@ -11,7 +11,8 @@ use Tymon\JWTAuth\Exceptions\JWTException;
class JwtRefreshMiddleware
{
public function handle($request, Closure $next)
{
{
try {
JWTAuth::parseToken()->authenticate();
} catch (TokenExpiredException $e) {

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Imports;
use Maatwebsite\Excel\Concerns\ToCollection;
use Illuminate\Support\Collection;
class OrderItemsPreviewImport implements ToCollection
{
public array $rows = [];
public function collection(Collection $collection)
{
$header = $collection->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;
}
}
}
}

View File

@@ -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 = [

View File

@@ -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.

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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
);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider
{
public function boot(): void
{
Broadcast::routes([
'middleware' => ['web'],
]);
// 👇 FORCE admin guard for broadcasting
Auth::shouldUse('admin');
require base_path('routes/channels.php');
}
}