569 lines
21 KiB
PHP
569 lines
21 KiB
PHP
|
|
<?php
|
|||
|
|
|
|||
|
|
namespace Modules\Customer\Http\Controllers;
|
|||
|
|
|
|||
|
|
use App\Enum\ActionStatus;
|
|||
|
|
use App\Enum\OrderStatus;
|
|||
|
|
use App\Helper\OrderHelper;
|
|||
|
|
use App\Http\Controllers\Controller;
|
|||
|
|
use App\Mail\OrderInvoiceMail;
|
|||
|
|
use App\Services\Firebase\FirebaseService;
|
|||
|
|
use App\Traits\Authenticatable;
|
|||
|
|
use App\Traits\RequestSanitizerTrait;
|
|||
|
|
use App\Traits\Trackable;
|
|||
|
|
use Carbon\Carbon;
|
|||
|
|
use Exception;
|
|||
|
|
use Illuminate\Http\JsonResponse;
|
|||
|
|
use Illuminate\Http\Request;
|
|||
|
|
use Illuminate\Support\Facades\DB;
|
|||
|
|
use Illuminate\Support\Facades\Log;
|
|||
|
|
use Illuminate\Support\Facades\Mail;
|
|||
|
|
use Modules\Booking\Http\Requests\Booking\CustomerBookingStoreRequest;
|
|||
|
|
use Modules\Booking\Models\Booking;
|
|||
|
|
use Modules\Booking\Models\BookingItem;
|
|||
|
|
use Modules\Restaurant\Http\Requests\Order\CustomerOrderStoreRequest;
|
|||
|
|
use Modules\Restaurant\Http\Requests\Order\CustomerOrderUpdateRequest;
|
|||
|
|
use Modules\Restaurant\Http\Resources\OrderResource;
|
|||
|
|
use Modules\Restaurant\Models\Customer;
|
|||
|
|
use Modules\Restaurant\Models\Order;
|
|||
|
|
|
|||
|
|
class CustomerController extends Controller
|
|||
|
|
{
|
|||
|
|
use Authenticatable, RequestSanitizerTrait, Trackable;
|
|||
|
|
|
|||
|
|
public function index(): JsonResponse
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$query = Order::query()
|
|||
|
|
->select('orders.*')
|
|||
|
|
->leftJoin('customers', 'orders.customer_id', '=', 'customers.id')
|
|||
|
|
->leftJoin('users as waiters', 'orders.waiter_id', '=', 'waiters.id')
|
|||
|
|
->with([
|
|||
|
|
'items.variation',
|
|||
|
|
'items.food',
|
|||
|
|
'customer',
|
|||
|
|
'table',
|
|||
|
|
'paymentMethod',
|
|||
|
|
'waiter',
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 🔍 GLOBAL SEARCH
|
|||
|
|
*/
|
|||
|
|
if ($search = request('search')) {
|
|||
|
|
$search = strtolower(trim($search));
|
|||
|
|
|
|||
|
|
$query->where(function ($q) use ($search) {
|
|||
|
|
$q->whereRaw('LOWER(orders.order_number) LIKE ?', ["%{$search}%"])
|
|||
|
|
->orWhereRaw('LOWER(orders.order_type) LIKE ?', ["%{$search}%"])
|
|||
|
|
->orWhereRaw('LOWER(orders.status) LIKE ?', ["%{$search}%"])
|
|||
|
|
->orWhereRaw('LOWER(customers.name) LIKE ?', ["%{$search}%"])
|
|||
|
|
->orWhereRaw('LOWER(customers.phone) LIKE ?', ["%{$search}%"])
|
|||
|
|
->orWhereRaw('LOWER(waiters.first_name) LIKE ?', ["%{$search}%"]);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 📌 STATUS FILTER
|
|||
|
|
*/
|
|||
|
|
if ($status = request('status')) {
|
|||
|
|
$query->where('orders.status', $status);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 🍽 ORDER TYPE FILTER
|
|||
|
|
*/
|
|||
|
|
if ($orderType = request('order_type')) {
|
|||
|
|
$query->where('orders.order_type', $orderType);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 👨🍳 WAITER FILTER
|
|||
|
|
*/
|
|||
|
|
if ($waiterId = request('waiter_id')) {
|
|||
|
|
$query->where('orders.waiter_id', $waiterId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 👤 CUSTOMER FILTER
|
|||
|
|
*/
|
|||
|
|
if ($customerId = request('customer_id')) {
|
|||
|
|
$query->where('orders.customer_id', $customerId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 📆 DATE RANGE FILTER
|
|||
|
|
*/
|
|||
|
|
if (request('from_date') && request('to_date')) {
|
|||
|
|
$query->whereBetween('orders.created_at', [
|
|||
|
|
request('from_date').' 00:00:00',
|
|||
|
|
request('to_date').' 23:59:59',
|
|||
|
|
]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 🔃 SORTING (SAFE WHITELIST)
|
|||
|
|
*/
|
|||
|
|
$allowedSorts = [
|
|||
|
|
'created_at',
|
|||
|
|
'order_number',
|
|||
|
|
'status',
|
|||
|
|
'order_type',
|
|||
|
|
'grand_total',
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
$sortBy = request('sort_by', 'created_at');
|
|||
|
|
$sortOrder = request('sort_order', 'desc');
|
|||
|
|
|
|||
|
|
if (! in_array($sortBy, $allowedSorts)) {
|
|||
|
|
$sortBy = 'created_at';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (! in_array($sortOrder, ['asc', 'desc'])) {
|
|||
|
|
$sortOrder = 'desc';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 📄 PAGINATION
|
|||
|
|
*/
|
|||
|
|
$orders = $query
|
|||
|
|
->orderBy("orders.$sortBy", $sortOrder)
|
|||
|
|
->paginate(request('perPage', 10));
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 📦 APPLY RESOURCE (KEEP PAGINATION STRUCTURE)
|
|||
|
|
*/
|
|||
|
|
$orders->getCollection()->transform(function ($order) {
|
|||
|
|
return new OrderResource($order);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return $this->responseSuccess([
|
|||
|
|
$orders,
|
|||
|
|
], 'Order fetch successfully.');
|
|||
|
|
} catch (\Throwable $e) {
|
|||
|
|
return $this->responseError([], 'Order fetch failed: '.$e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function store(CustomerOrderStoreRequest $request, FirebaseService $firebase): JsonResponse
|
|||
|
|
{
|
|||
|
|
DB::beginTransaction();
|
|||
|
|
try {
|
|||
|
|
$restaurantId = $this->getCurrentRestaurantId();
|
|||
|
|
$orderNumber = OrderHelper::generateOrderNumber($restaurantId);
|
|||
|
|
|
|||
|
|
// 🔹 Calculate totals
|
|||
|
|
$subtotal = collect($request->items)->sum(fn ($i) => $i['quantity'] * $i['price']);
|
|||
|
|
$grandTotal = $subtotal - ($request->discount ?? 0) + ($request->tax ?? 0);
|
|||
|
|
|
|||
|
|
// 🔹 Create Order
|
|||
|
|
$order = Order::create([
|
|||
|
|
'restaurant_id' => $restaurantId,
|
|||
|
|
'order_number' => $orderNumber,
|
|||
|
|
'order_type' => $request->order_type,
|
|||
|
|
'status' => 'pending',
|
|||
|
|
'table_id' => $request->table_id,
|
|||
|
|
// 'waiter_id' => $request->waiter_id,
|
|||
|
|
'customer_id' => getUserId(),
|
|||
|
|
'payment_method_id' => $request->payment_method_id,
|
|||
|
|
'payment_status' => $request->payment_status ?? false,
|
|||
|
|
'subtotal' => $subtotal,
|
|||
|
|
'discount' => $request->discount ?? 0,
|
|||
|
|
'tax' => $request->tax ?? 0,
|
|||
|
|
'grand_total' => $grandTotal,
|
|||
|
|
'order_date' => Carbon::now(),
|
|||
|
|
'added_by' => getUserId(),
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
// 🔹 Create Order Items
|
|||
|
|
foreach ($request->items as $item) {
|
|||
|
|
$orderItem = $order->items()->create([
|
|||
|
|
'restaurant_id' => $restaurantId,
|
|||
|
|
'food_item_id' => $item['food_item_id'],
|
|||
|
|
'food_variant_id' => $item['food_variant_id'] ?? null,
|
|||
|
|
'quantity' => $item['quantity'],
|
|||
|
|
'price' => $item['price'],
|
|||
|
|
'total' => $item['quantity'] * $item['price'],
|
|||
|
|
'addons' => isset($item['addons']) ? json_encode($item['addons']) : null,
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 🔽 Ingredient deduction (Commented out for reference)
|
|||
|
|
*
|
|||
|
|
* foreach ($item['ingredients'] ?? [] as $ingredient) {
|
|||
|
|
* $ingredientModel = Ingredient::find($ingredient['id']);
|
|||
|
|
* if ($ingredientModel) {
|
|||
|
|
* $deductQty = $ingredient['quantity_required'] * $item['quantity'];
|
|||
|
|
* $ingredientModel->decrement('available_quantity', $deductQty);
|
|||
|
|
* }
|
|||
|
|
* }
|
|||
|
|
*/
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$customer = auth()->user();
|
|||
|
|
// Action Log info
|
|||
|
|
$this->trackAction(ActionStatus::OrderCreate, Customer::class, $customer->id, $order);
|
|||
|
|
|
|||
|
|
DB::commit();
|
|||
|
|
|
|||
|
|
// Push Notification
|
|||
|
|
if ($customer?->fcm_token) {
|
|||
|
|
$result = $firebase->sendNotification(
|
|||
|
|
$customer->fcm_token,
|
|||
|
|
'Order Confirmed',
|
|||
|
|
"Your order #{$order->id} has been confirmed!",
|
|||
|
|
['order_id' => $order->id]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if ($result === true) {
|
|||
|
|
return response()->json(['message' => 'Notification sent successfully']);
|
|||
|
|
} else {
|
|||
|
|
return response()->json(['message' => 'Failed to send notification', 'error' => $result], 500);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Directly send email
|
|||
|
|
if ($order->customer?->email) {
|
|||
|
|
try {
|
|||
|
|
$result = shop_mailer_send($order->restaurant_id, $order->customer?->email, new OrderInvoiceMail($order));
|
|||
|
|
if (! $result['status']) {
|
|||
|
|
Log::warning("Order invoice email not sent for shop {$order->restaurant_id}: ".$result['message']);
|
|||
|
|
}
|
|||
|
|
Mail::to($order->customer?->email)->send(new OrderInvoiceMail($order));
|
|||
|
|
} catch (Exception $ex) {
|
|||
|
|
Log::error("Invoice email failed for order {$order->id}: ".$ex->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $this->responseSuccess([
|
|||
|
|
'order' => $order->load('items'),
|
|||
|
|
], 'Order created successfully.');
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
DB::rollBack();
|
|||
|
|
|
|||
|
|
return $this->responseError([], 'Order creation failed: '.$e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function show(int $id): JsonResponse
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$order = Order::with([
|
|||
|
|
'items.food', // If you have relation: OrderItem → Food
|
|||
|
|
'customer',
|
|||
|
|
'table',
|
|||
|
|
'paymentMethod',
|
|||
|
|
])->findOrFail($id);
|
|||
|
|
|
|||
|
|
return $this->responseSuccess($order, 'Order has been fetched successfully.');
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
return $this->responseError([], $e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function update(CustomerOrderUpdateRequest $request, int $id): JsonResponse
|
|||
|
|
{
|
|||
|
|
DB::beginTransaction();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$order = Order::with('items')->findOrFail($id);
|
|||
|
|
|
|||
|
|
// Recalculate total
|
|||
|
|
$totalAmount = collect($request->items)->sum(function ($item) {
|
|||
|
|
return $item['quantity'] * $item['price'];
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Update order (order_number stays untouched)
|
|||
|
|
$order->update([
|
|||
|
|
'order_type' => $request->order_type,
|
|||
|
|
// 'status' => $request->status,
|
|||
|
|
'table_id' => $request->table_id,
|
|||
|
|
'payment_method_id' => $request->payment_method_id,
|
|||
|
|
'payment_status' => $request->payment_status ?? false,
|
|||
|
|
'waiter_id' => $request->waiter_id,
|
|||
|
|
// 'customer_id' => $request->customer_id,
|
|||
|
|
'grand_total' => $totalAmount,
|
|||
|
|
'updated_by' => getUserId(),
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
$existingItemIds = $order->items->pluck('id')->toArray();
|
|||
|
|
$requestItemIds = collect($request->items)->pluck('id')->filter()->toArray();
|
|||
|
|
|
|||
|
|
// 1️⃣ Remove items that are not in request anymore
|
|||
|
|
$itemsToDelete = array_diff($existingItemIds, $requestItemIds);
|
|||
|
|
if (! empty($itemsToDelete)) {
|
|||
|
|
$order->items()->whereIn('id', $itemsToDelete)->delete();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2️⃣ Add or update items
|
|||
|
|
foreach ($request->items as $item) {
|
|||
|
|
if (isset($item['id']) && in_array($item['id'], $existingItemIds)) {
|
|||
|
|
// Update existing item
|
|||
|
|
$order->items()->where('id', $item['id'])->update([
|
|||
|
|
'restaurant_id' => $order->restaurant_id,
|
|||
|
|
'food_item_id' => $item['food_item_id'],
|
|||
|
|
'food_variant_id' => $item['food_variant_id'] ?? null,
|
|||
|
|
'quantity' => $item['quantity'],
|
|||
|
|
'price' => $item['price'],
|
|||
|
|
'total' => $item['quantity'] * $item['price'],
|
|||
|
|
]);
|
|||
|
|
} else {
|
|||
|
|
// Add new item
|
|||
|
|
$order->items()->create([
|
|||
|
|
'restaurant_id' => $order->restaurant_id,
|
|||
|
|
'food_item_id' => $item['food_item_id'],
|
|||
|
|
'food_variant_id' => $item['food_variant_id'] ?? null,
|
|||
|
|
'quantity' => $item['quantity'],
|
|||
|
|
'price' => $item['price'],
|
|||
|
|
'total' => $item['quantity'] * $item['price'],
|
|||
|
|
]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
DB::commit();
|
|||
|
|
|
|||
|
|
return $this->responseSuccess($order->load('items'), 'Order has been updated successfully.');
|
|||
|
|
} catch (\Throwable $e) {
|
|||
|
|
DB::rollBack();
|
|||
|
|
|
|||
|
|
return $this->responseError([], 'Failed to update order: '.$e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function destroy(int $id): JsonResponse
|
|||
|
|
{
|
|||
|
|
DB::beginTransaction();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$order = Order::with('items')->findOrFail($id);
|
|||
|
|
|
|||
|
|
// Delete related order items
|
|||
|
|
foreach ($order->items as $item) {
|
|||
|
|
$item->delete();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Delete the order
|
|||
|
|
$order->delete();
|
|||
|
|
|
|||
|
|
DB::commit();
|
|||
|
|
|
|||
|
|
return $this->responseSuccess([], 'Order and related items have been deleted successfully.');
|
|||
|
|
} catch (\Throwable $e) {
|
|||
|
|
DB::rollBack();
|
|||
|
|
|
|||
|
|
return $this->responseError([], 'Failed to delete order: '.$e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function cancel(Request $request, int $orderId, FirebaseService $firebase): JsonResponse
|
|||
|
|
{
|
|||
|
|
$request->validate([
|
|||
|
|
'cancel_reason' => 'nullable|string|max:255',
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
DB::beginTransaction();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$restaurantId = $this->getCurrentRestaurantId();
|
|||
|
|
|
|||
|
|
$order = Order::where('restaurant_id', $restaurantId)
|
|||
|
|
->where('id', $orderId)
|
|||
|
|
->first();
|
|||
|
|
|
|||
|
|
if (! $order) {
|
|||
|
|
return $this->responseError([], 'Order not found.');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 🚫 Block if already cancelled or completed
|
|||
|
|
if (in_array($order->status, ['cancelled', 'completed'])) {
|
|||
|
|
return $this->responseError([], "This order is already {$order->status} and cannot be cancelled.");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 🕒 Check time difference (5 minutes)
|
|||
|
|
$minutesSinceOrder = now()->diffInMinutes($order->created_at);
|
|||
|
|
$cancelLimit = env('ORDER_CANCEL_LIMIT', 5);
|
|||
|
|
if ($minutesSinceOrder > $cancelLimit) {
|
|||
|
|
return $this->responseError([], "You can only cancel an order within {$cancelLimit} minutes of placement.");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 🔹 Update order cancellation info
|
|||
|
|
$order->update([
|
|||
|
|
'status' => OrderStatus::CANCELLED,
|
|||
|
|
'cancelled_at' => now(),
|
|||
|
|
'cancel_reason' => $request->cancel_reason ?? 'No reason provided',
|
|||
|
|
'cancelled_by' => getUserId(),
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
$customer = auth()->user();
|
|||
|
|
// Action Log info
|
|||
|
|
$this->trackAction(ActionStatus::OrderCreate, Customer::class, $customer->id, $order);
|
|||
|
|
|
|||
|
|
DB::commit();
|
|||
|
|
|
|||
|
|
// TODO
|
|||
|
|
// Order Cancel Mail & Notification
|
|||
|
|
// // Push Notification
|
|||
|
|
// if ($customer?->fcm_token) {
|
|||
|
|
// $result = $firebase->sendNotification(
|
|||
|
|
// $customer->fcm_token,
|
|||
|
|
// 'Order Confirmed',
|
|||
|
|
// "Your order #{$order->id} has been confirmed!",
|
|||
|
|
// ['order_id' => $order->id]
|
|||
|
|
// );
|
|||
|
|
|
|||
|
|
// if ($result === true) {
|
|||
|
|
// return response()->json(['message' => 'Notification sent successfully']);
|
|||
|
|
// } else {
|
|||
|
|
// return response()->json(['message' => 'Failed to send notification', 'error' => $result], 500);
|
|||
|
|
// }
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
// // Directly send email
|
|||
|
|
// if ($order->customer?->email) {
|
|||
|
|
// try {
|
|||
|
|
// $result = shop_mailer_send($order->restaurant_id, $order->customer?->email, new OrderInvoiceMail($order));
|
|||
|
|
// if (! $result['status']) {
|
|||
|
|
// Log::warning("Order invoice email not sent for shop {$order->restaurant_id}: " . $result['message']);
|
|||
|
|
// }
|
|||
|
|
// Mail::to($order->customer?->email)->send(new OrderInvoiceMail($order));
|
|||
|
|
// } catch (Exception $ex) {
|
|||
|
|
// Log::error("Invoice email failed for order {$order->id}: " . $ex->getMessage());
|
|||
|
|
// }
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
return $this->responseSuccess([
|
|||
|
|
'order' => $order->fresh(),
|
|||
|
|
], 'Order cancelled successfully.');
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
DB::rollBack();
|
|||
|
|
|
|||
|
|
return $this->responseError([], 'Order cancellation failed: '.$e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function myBooking(): JsonResponse
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$filters = request()->all();
|
|||
|
|
|
|||
|
|
$query = Booking::where('customer_id', getUserId())
|
|||
|
|
->with([
|
|||
|
|
'customer',
|
|||
|
|
'hotel',
|
|||
|
|
'bookingItems.room',
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
// -----------------------------
|
|||
|
|
// 🔍 SEARCHING
|
|||
|
|
// -----------------------------
|
|||
|
|
if (! empty($filters['search'])) {
|
|||
|
|
$search = $filters['search'];
|
|||
|
|
$query->whereHas('customer', function ($q) use ($search) {
|
|||
|
|
$q->where('name', 'LIKE', "%$search%")
|
|||
|
|
->orWhere('phone', 'LIKE', "%$search%");
|
|||
|
|
})->orWhere('id', $search);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// -----------------------------
|
|||
|
|
// 🎯 FILTERS
|
|||
|
|
// -----------------------------
|
|||
|
|
if (isset($filters['status'])) {
|
|||
|
|
$query->where('status', $filters['status']);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (! empty($filters['customer_id'])) {
|
|||
|
|
$query->where('customer_id', $filters['customer_id']);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (! empty($filters['hotel_id'])) {
|
|||
|
|
$query->where('hotel_id', $filters['hotel_id']);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (! empty($filters['check_in_from']) && ! empty($filters['check_in_to'])) {
|
|||
|
|
$query->whereBetween('check_in', [$filters['check_in_from'], $filters['check_in_to']]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (! empty($filters['check_out_from']) && ! empty($filters['check_out_to'])) {
|
|||
|
|
$query->whereBetween('check_out', [$filters['check_out_from'], $filters['check_out_to']]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// -----------------------------
|
|||
|
|
// 🔽 SORTING
|
|||
|
|
// -----------------------------
|
|||
|
|
$sortBy = $filters['sort_by'] ?? 'check_in';
|
|||
|
|
$sortOrder = $filters['sort_order'] ?? 'desc';
|
|||
|
|
$allowedSortColumns = ['id', 'check_in', 'check_out', 'status', 'created_at'];
|
|||
|
|
|
|||
|
|
if (! in_array($sortBy, $allowedSortColumns)) {
|
|||
|
|
$sortBy = 'check_in';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$query->orderBy($sortBy, $sortOrder);
|
|||
|
|
|
|||
|
|
// -----------------------------
|
|||
|
|
// 📄 PAGINATION
|
|||
|
|
// -----------------------------
|
|||
|
|
$perPage = $filters['per_page'] ?? 20;
|
|||
|
|
$bookings = $query->paginate($perPage);
|
|||
|
|
|
|||
|
|
return $this->responseSuccess($bookings, 'Bookings fetched successfully.');
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
return $this->responseError([], $e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function booking(CustomerBookingStoreRequest $request): JsonResponse
|
|||
|
|
{
|
|||
|
|
$data = $request->validated();
|
|||
|
|
|
|||
|
|
DB::beginTransaction();
|
|||
|
|
try {
|
|||
|
|
// Create main booking
|
|||
|
|
$booking = Booking::create([
|
|||
|
|
'restaurant_id' => getUserRestaurantId(),
|
|||
|
|
'hotel_id' => $data['hotel_id'],
|
|||
|
|
'customer_id' => getUserId(),
|
|||
|
|
'check_in' => $data['check_in'],
|
|||
|
|
'check_out' => $data['check_out'],
|
|||
|
|
'check_in_time' => $data['check_in_time'] ?? null,
|
|||
|
|
'check_out_time' => $data['check_out_time'] ?? null,
|
|||
|
|
'total_adults' => $data['total_adults'],
|
|||
|
|
'total_children' => $data['total_children'],
|
|||
|
|
'subtotal_amount' => $data['subtotal_amount'],
|
|||
|
|
'tax_amount' => $data['tax_amount'] ?? 0,
|
|||
|
|
'discount_amount' => $data['discount_amount'] ?? 0,
|
|||
|
|
'total_amount' => $data['total_amount'],
|
|||
|
|
'payment_status' => $data['payment_status'],
|
|||
|
|
'payment_method' => $data['payment_method'] ?? null,
|
|||
|
|
'channel' => $data['channel'] ?? null,
|
|||
|
|
'status' => $data['status'],
|
|||
|
|
'remarks' => $data['remarks'] ?? null,
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
// Create booking items
|
|||
|
|
foreach ($data['booking_items'] as $item) {
|
|||
|
|
BookingItem::create([
|
|||
|
|
'restaurant_id' => getUserRestaurantId(),
|
|||
|
|
'booking_id' => $booking->id,
|
|||
|
|
'room_id' => $item['room_id'],
|
|||
|
|
'adults' => $item['adults'],
|
|||
|
|
'children' => $item['children'],
|
|||
|
|
'room_price' => $item['room_price'],
|
|||
|
|
'nights' => $item['nights'],
|
|||
|
|
'tax_amount' => $item['tax_amount'] ?? 0,
|
|||
|
|
'total_amount' => $item['total_amount'],
|
|||
|
|
'status' => $item['status'],
|
|||
|
|
]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
DB::commit();
|
|||
|
|
|
|||
|
|
return $this->responseSuccess($booking->load('bookingItems'), 'Booking has been created successfully.');
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
DB::rollBack();
|
|||
|
|
|
|||
|
|
return $this->responseError([], $e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|