migrate to gtea from bistbucket

This commit is contained in:
2026-03-15 17:08:23 +07:00
commit 129ca2260c
3716 changed files with 566316 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\Addon\AddonStoreRequest;
use Modules\Restaurant\Http\Requests\Addon\AddonUpdateRequest;
use Modules\Restaurant\Repositories\AddonRepository;
class AddonController extends Controller
{
use RequestSanitizerTrait;
public function __construct(private AddonRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'Addon has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(AddonStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'Addon has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'Addon has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(AddonUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'Addon has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'Addon has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\AddonFood\AddonFoodStoreRequest;
use Modules\Restaurant\Http\Requests\AddonFood\AddonFoodUpdateRequest;
use Modules\Restaurant\Repositories\AddonFoodRepository;
class AddonFoodController extends Controller
{
use RequestSanitizerTrait;
public function __construct(private AddonFoodRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'AddonFood has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(AddonFoodStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'AddonFood has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'AddonFood has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(AddonFoodUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'AddonFood has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'AddonFood has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\CustomerAddress\CustomerAddressStoreRequest;
use Modules\Restaurant\Http\Requests\CustomerAddress\CustomerAddressUpdateRequest;
use Modules\Restaurant\Repositories\CustomerAddressRepository;
class CustomerAddressController extends Controller
{
public function __construct(private CustomerAddressRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'CustomerAddress has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(CustomerAddressStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'CustomerAddress has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'CustomerAddress has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(CustomerAddressUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'CustomerAddress has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'CustomerAddress has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\Customer\CustomerStoreRequest;
use Modules\Restaurant\Http\Requests\Customer\CustomerUpdateRequest;
use Modules\Restaurant\Repositories\CustomerRepository;
class CustomerController extends Controller
{
use RequestSanitizerTrait;
public function __construct(private CustomerRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'Customer has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(CustomerStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'Customer has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'Customer has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(CustomerUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'Customer has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'Customer has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,234 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Modules\Restaurant\Models\Customer;
use Modules\Restaurant\Models\Ingredient;
use Modules\Restaurant\Models\Order;
use Modules\Restaurant\Models\OrderItem;
use Modules\Restaurant\Models\Reservation;
use Modules\Restaurant\Models\ReservationUnavailable;
use Modules\Restaurant\Models\Stock;
use Modules\Restaurant\Models\Supplier;
class DashboardController extends Controller
{
public function index(Request $request): JsonResponse
{
$restaurantId = $request->restaurant_id ?? getUserRestaurantId();
// 🗓 Convert date filters properly
$start = $request->filled('start_date') ? date('Y-m-d', strtotime($request->start_date)) : Carbon::now()->startOfMonth()->toDateString();
$end = $request->filled('end_date') ? date('Y-m-d', strtotime($request->end_date)) : Carbon::now()->toDateString();
/** =====================
* 🧾 1. Order & Sales Stats
* ===================== */
$orderQuery = Order::query()
->with(['customer:id,name', 'waiter:id,first_name'])
->when($restaurantId, fn ($q, $r) => $q->where('restaurant_id', $r))
->whereBetween(DB::raw('DATE(order_date)'), [$start, $end]);
$totalOrders = (clone $orderQuery)->count();
$totalSalesAmount = (clone $orderQuery)->sum('grand_total');
$completedOrders = (clone $orderQuery)->where('status', 'completed')->count();
$pendingOrders = (clone $orderQuery)->where('status', 'pending')->count();
/** =====================
* 🍽️ 2. Latest Orders (5)
* ===================== */
$latestOrders = (clone $orderQuery)
->latest('id')
->take(5)
->get([
'id',
'order_number',
'grand_total',
'status',
'order_date',
'customer_id',
'waiter_id',
])
->map(function ($order) {
return [
'id' => $order->id,
'order_number' => $order->order_number,
'grand_total' => $order->grand_total,
'status' => $order->status,
'order_date' => $order->order_date,
'customer_name' => $order->customer?->name,
'waiter_name' => $order->waiter?->first_name,
];
});
/** =====================
* 📦 3. Ingredients & Stock
* ===================== */
$totalIngredients = Ingredient::where('restaurant_id', $restaurantId)->count();
$lowStockIngredients = Ingredient::where('restaurant_id', $restaurantId)
->whereColumn('stock_quantity', '<=', 'low_stock_alert')
->count();
$totalStockValue = Ingredient::where('restaurant_id', $restaurantId)
->sum(DB::raw('stock_quantity * cost_per_unit'));
/** =====================
* 🧍 4. Customers & Suppliers
* ===================== */
$totalCustomers = Customer::when($restaurantId, fn ($q, $r) => $q->where('restaurant_id', $r))->count();
$totalSuppliers = Supplier::when($restaurantId, fn ($q, $r) => $q->where('restaurant_id', $r))->count();
/** =====================
* 📅 5. Reservations
* ===================== */
$latestReservations = Reservation::with(['customer:id,name,phone', 'table:id,name'])
->when($restaurantId, fn ($q, $r) => $q->where('restaurant_id', $r))
->latest('id')
->take(5)
->get(['id', 'customer_id', 'table_id', 'reservation_date', 'start_time', 'status'])
->map(function ($item) {
return [
'id' => $item->id,
'customer_id' => $item->customer_id,
'customer_name' => $item->customer?->name,
'customer_phone' => $item->customer?->phone,
'table_name' => $item->table?->name,
'reservation_date' => $item->reservation_date,
'start_time' => $item->start_time,
'status' => $item->status,
];
});
$latestUnavailable = ReservationUnavailable::with('table:id,name')
->when($restaurantId, fn ($q, $r) => $q->where('restaurant_id', $r))
->latest('id')
->take(5)
->get(['id', 'table_id', 'date', 'start_time', 'end_time', 'reason'])
->map(function ($item) {
return [
'id' => $item->id,
'table_name' => $item->table?->name,
'date' => $item->date,
'start_time' => $item->start_time,
'end_time' => $item->end_time,
'reason' => $item->reason,
];
});
/** =====================
* 📊 6. Stocks (movement summary)
* ===================== */
$stockSummary = Stock::select(
'type',
DB::raw('SUM(quantity) as total_qty'),
DB::raw('SUM(total_cost) as total_cost')
)
->when($restaurantId, fn ($q, $r) => $q->where('restaurant_id', $r))
->whereBetween(DB::raw('DATE(created_at)'), [$start, $end])
->groupBy('type')
->get()
->keyBy('type');
/** =====================
* 💹 7. Sales & Purchase Comparison
* ===================== */
$salesItems = OrderItem::when($restaurantId, fn ($q, $r) => $q->where('restaurant_id', $r))
->whereBetween(DB::raw('DATE(created_at)'), [$start, $end])
->select(DB::raw('SUM(quantity) as total_qty'), DB::raw('SUM(total) as total_sales'))
->first();
/** =====================
* 📈 8. Orders Chart Data (daily, weekly, monthly)
* ===================== */
// --- Daily orders (last 30 days) ---
$dailyOrders = Order::select(
DB::raw('DATE(order_date) as date'),
DB::raw('COUNT(*) as total')
)
->when($restaurantId, fn ($q, $r) => $q->where('restaurant_id', $r))
// ->where('status', 'completed')
->where('order_date', '>=', now()->subDays(30))
->groupBy(DB::raw('DATE(order_date)'))
->orderBy('date')
->get();
// --- Weekly orders (last 7 days) ---
$weeklyOrders = Order::select(
DB::raw('DATE(order_date) as date'),
DB::raw('COUNT(*) as total')
)
->when($restaurantId, fn ($q, $r) => $q->where('restaurant_id', $r))
// ->where('status', 'completed')
->where('order_date', '>=', now()->subDays(7))
->groupBy(DB::raw('DATE(order_date)'))
->orderBy('date')
->get();
// --- Monthly orders (last 12 months, always show all months) ---
$monthlyOrdersRaw = Order::select(
DB::raw('DATE_FORMAT(order_date, "%Y-%m") as month'),
DB::raw('COUNT(*) as total')
)
->when($restaurantId, fn ($q, $r) => $q->where('restaurant_id', $r))
->where('order_date', '>=', now()->subMonths(12))
->groupBy(DB::raw('DATE_FORMAT(order_date, "%Y-%m")'))
->orderBy('month')
->pluck('total', 'month'); // → key = month (YYYY-MM), value = total count
// --- Build full 12-month list ---
$months = collect(range(0, 11))
->map(function ($i) use ($monthlyOrdersRaw) {
$date = Carbon::now()->subMonths(11 - $i);
$monthKey = $date->format('Y-m');
return [
'month' => $date->format('F Y'), // Example: "Octobor 2025"
'total' => $monthlyOrdersRaw[$monthKey] ?? 0,
];
});
/** =====================
* 📈 9. Final Dashboard Response
* ===================== */
return $this->responseSuccess([
'filters' => [
'start_date' => $start,
'end_date' => $end,
'restaurant_id' => $restaurantId,
],
'stats' => [
'orders' => [
'total' => $totalOrders,
'completed' => $completedOrders,
'pending' => $pendingOrders,
'sales_amount' => round($totalSalesAmount, 2),
],
'ingredients' => [
'total' => $totalIngredients,
'low_stock' => $lowStockIngredients,
'stock_value' => round($totalStockValue, 2),
],
'customers' => $totalCustomers,
'suppliers' => $totalSuppliers,
],
'stock_summary' => $stockSummary,
'latest_orders' => $latestOrders,
'latest_reservations' => $latestReservations,
'latest_unavailable_tables' => $latestUnavailable,
'sales_summary' => [
'total_items_sold' => (int) ($salesItems->total_qty ?? 0),
'total_sales' => round($salesItems->total_sales ?? 0, 2),
],
'charts' => [
'daily_orders' => $dailyOrders,
'weekly_orders' => $weeklyOrders,
'monthly_orders' => $months,
],
], 'Dashboard data fetched successfully.');
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\DeliveryCharge\DeliveryChargeStoreRequest;
use Modules\Restaurant\Http\Requests\DeliveryCharge\DeliveryChargeUpdateRequest;
use Modules\Restaurant\Repositories\DeliveryChargeRepository;
class DeliveryChargeController extends Controller
{
public function __construct(private DeliveryChargeRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'DeliveryCharge has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(DeliveryChargeStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'DeliveryCharge has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'DeliveryCharge has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(DeliveryChargeUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'DeliveryCharge has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'DeliveryCharge has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,191 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Modules\Restaurant\Http\Requests\FoodAvailability\FoodAvailabilityStoreRequest;
use Modules\Restaurant\Http\Requests\FoodAvailability\FoodAvailabilityUpdateRequest;
use Modules\Restaurant\Models\FoodAvailability;
class FoodAvailabilityController extends Controller
{
public function index(Request $request): JsonResponse
{
try {
$perPage = $request->input('perPage', 15);
$sortBy = $request->input('sortBy', 'id');
$sortDir = $request->input('sortDir', 'desc');
$search = $request->input('search');
$foodItemId = $request->input('food_item_id');
$day = $request->input('day'); // Saturday, Sunday etc.
$startDate = $request->input('start_date');
$endDate = $request->input('end_date');
$query = FoodAvailability::query()
->with('foodItem:id,name')
->with('times:id,food_availability_id,open_time,close_time')
->where('restaurant_id', getUserRestaurantId());
// 🔍 SEARCH Filter
if (! empty($search)) {
$query->where(function ($q) use ($search) {
$q->where('day', 'LIKE', "%{$search}%")
->orWhereHas('foodItem', function ($fi) use ($search) {
$fi->where('name', 'LIKE', "%{$search}%");
});
});
}
// 🍽 Filter by food item
if (! empty($foodItemId)) {
$query->where('food_item_id', $foodItemId);
}
// 📅 Filter by specific day
if (! empty($day)) {
$query->where('day', $day);
}
// 📆 Filter by date range (start_date & end_date)
if (! empty($startDate) && ! empty($endDate)) {
$query->whereBetween('created_at', [$startDate, $endDate]);
}
// 🔽 Sorting
$query->orderBy($sortBy, $sortDir);
// 📄 Pagination
$data = $query->paginate($perPage)->appends($request->query());
return $this->responseSuccess($data, 'Availability list fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(FoodAvailabilityStoreRequest $request): JsonResponse
{
DB::beginTransaction();
try {
foreach ($request->availability as $item) {
$availability = FoodAvailability::updateOrCreate(
[
'restaurant_id' => getUserRestaurantId(),
'food_item_id' => $request->food_item_id,
'day' => $item['day'],
],
[
'is_available' => true,
]
);
// Remove old times for fresh update
$availability->times()->delete();
foreach ($item['times'] as $slot) {
$availability->times()->create([
'open_time' => $slot['open_time'],
'close_time' => $slot['close_time'],
]);
}
}
DB::commit();
return $this->responseSuccess([], 'Food availability created successfully.');
} catch (Exception $e) {
DB::rollBack();
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
$data = FoodAvailability::with('times')->findOrFail($id);
return $this->responseSuccess($data, 'Availability fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(FoodAvailabilityUpdateRequest $request, $foodItemId): JsonResponse
{
DB::beginTransaction();
try {
$restaurantId = getUserRestaurantId();
foreach ($request->availability as $item) {
// Find existing day or create new
$availability = FoodAvailability::firstOrCreate(
[
'restaurant_id' => $restaurantId,
'food_item_id' => $foodItemId,
'day' => $item['day'],
],
['is_available' => true]
);
// Current time slots in DB
$existingTimes = $availability->times()->get();
$newTimes = collect($item['times']);
// Delete removed slots
$existingTimes->each(function ($slot) use ($newTimes) {
if (! $newTimes->contains(fn ($t) => $t['open_time'] == $slot->open_time && $t['close_time'] == $slot->close_time)) {
$slot->delete();
}
});
// Add or update new slots
foreach ($item['times'] as $slot) {
// Use food_availability_id in the condition to prevent duplicates
$availability->times()->updateOrCreate(
[
'food_availability_id' => $availability->id,
'open_time' => $slot['open_time'],
'close_time' => $slot['close_time'],
],
[
'open_time' => $slot['open_time'],
'close_time' => $slot['close_time'],
]
);
}
}
DB::commit();
return $this->responseSuccess([], 'Food availability updated successfully (attach/detach).');
} catch (Exception $e) {
DB::rollBack();
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
$item = FoodAvailability::findOrFail($id);
$item->times()->delete();
$item->delete();
return $this->responseSuccess([], 'Availability deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\FoodCategory\FoodCategoryStoreRequest;
use Modules\Restaurant\Http\Requests\FoodCategory\FoodCategoryUpdateRequest;
use Modules\Restaurant\Repositories\FoodCategoryRepository;
class FoodCategoryController extends Controller
{
use RequestSanitizerTrait;
public function __construct(private FoodCategoryRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'FoodCategory has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(FoodCategoryStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'FoodCategory has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'FoodCategory has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(FoodCategoryUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'FoodCategory has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'FoodCategory has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Modules\Restaurant\Http\Requests\FoodItem\FoodItemStoreRequest;
use Modules\Restaurant\Http\Requests\FoodItem\FoodItemUpdateRequest;
use Modules\Restaurant\Models\FoodItem;
use Modules\Restaurant\Repositories\FoodItemRepository;
class FoodItemController extends Controller
{
use RequestSanitizerTrait;
public function __construct(private FoodItemRepository $repo) {}
public function index(Request $request): JsonResponse
{
try {
// Base query with eager loading
$query = FoodItem::with([
'category',
'menuCategory',
'menuSection',
'variants',
'defaultVariant',
'availabilities',
'addons',
]);
// Optional: Search by name or description
if ($request->filled('search')) {
$search = $request->get('search');
$query->where(function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('description', 'like', "%{$search}%");
});
}
// Optional: Filter by category
if ($request->filled('category_id')) {
$query->where('category_id', $request->get('category_id'));
}
// Optional: Filter by restaurant
if ($request->filled('restaurant_id')) {
$query->where('restaurant_id', $request->get('restaurant_id'));
}
// Optional: Filter by food type (e.g. veg/non-veg)
if ($request->filled('food_type')) {
$query->where('food_type', $request->get('food_type'));
}
// Optional: Filter by is_party / is_dinner / is_lunch
foreach (['is_party', 'is_dinner', 'is_lunch', 'is_chef_special', 'is_popular_item'] as $flag) {
if ($request->filled($flag)) {
$query->where($flag, $request->boolean($flag));
}
}
// Pagination: default 20 per page
$perPage = $request->get('perPage', 20);
$results = $query->latest()->paginate($perPage);
return $this->responseSuccess($results, 'Food items fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(FoodItemStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'FoodItem has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'FoodItem has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(FoodItemUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'FoodItem has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'FoodItem has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,199 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Modules\Restaurant\Http\Requests\FoodReview\FoodReviewStoreRequest;
use Modules\Restaurant\Http\Requests\FoodReview\FoodReviewUpdateRequest;
use Modules\Restaurant\Models\FoodReview;
class FoodReviewController extends Controller
{
public function index(Request $request): JsonResponse
{
try {
$restaurantId = getUserRestaurantId();
$perPage = $request->get('per_page', 10);
$reviews = FoodReview::with([
'replies' => function ($q) {
$q->where('status', 1)->latest();
},
])
->where('restaurant_id', $restaurantId)
->orderBy('is_featured', 'desc')
->orderBy('position', 'asc')
->latest()
->paginate($perPage);
return $this->responseSuccess($reviews, 'Food reviews fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(FoodReviewStoreRequest $request): JsonResponse
{
try {
$restaurantId = getUserRestaurantId();
$customerId = getUserId();
$exists = FoodReview::where([
'restaurant_id' => $restaurantId,
'food_item_id' => $request->food_item_id,
'customer_id' => $customerId,
])->exists();
if ($exists) {
return $this->responseError([], 'You have already reviewed this food.');
}
$data = $request->only(['rating', 'review', 'video_url']);
$data['restaurant_id'] = $restaurantId;
$data['food_item_id'] = $request->food_item_id;
$data['customer_id'] = $customerId;
$data['status'] = 'pending';
/** IMAGE UPLOAD (JSON) */
if ($request->hasFile('images')) {
$images = [];
foreach ($request->file('images') as $img) {
$images[] = fileUploader('food_reviews/images/', 'png', $img);
}
$data['images'] = $images;
}
/** VIDEO FILE */
if ($request->hasFile('video_file')) {
$data['video_file'] = fileUploader(
'food_reviews/videos/',
'mp4',
$request->file('video_file')
);
}
$review = FoodReview::create($data);
return $this->responseSuccess($review, 'Review submitted successfully. Awaiting approval.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show($id): JsonResponse
{
try {
$restaurantId = getUserRestaurantId();
$review = FoodReview::with([
'replies' => function ($q) {
$q->where('status', 1)->latest();
},
])
->where('restaurant_id', $restaurantId)
->findOrFail($id);
return $this->responseSuccess($review, 'Food review fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(FoodReviewUpdateRequest $request, $id): JsonResponse
{
try {
$restaurantId = getUserRestaurantId();
$customerId = getUserId();
$review = FoodReview::where([
'id' => $id,
'restaurant_id' => $restaurantId,
'customer_id' => $customerId,
])->firstOrFail();
if ($review->status === 'approved') {
return $this->responseError([], 'Approved reviews cannot be edited.');
}
$data = $request->only(['rating', 'review', 'video_url']);
/** IMAGE JSON UPDATE */
$images = $review->images ?? [];
// Remove selected images
if ($request->filled('remove_images')) {
foreach ($request->remove_images as $removeImg) {
if (in_array($removeImg, $images)) {
fileRemover('food_reviews/images/', $removeImg);
$images = array_values(array_diff($images, [$removeImg]));
}
}
}
// Add new images
if ($request->hasFile('images')) {
foreach ($request->file('images') as $img) {
$images[] = fileUploader('food_reviews/images/', 'png', $img);
}
}
$data['images'] = $images;
/** VIDEO FILE UPDATE */
if ($request->hasFile('video_file')) {
$data['video_file'] = fileUploader(
'food_reviews/videos/',
'mp4',
$request->file('video_file'),
$review->video_file
);
}
$review->update($data);
return $this->responseSuccess($review, 'Review updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy($id): JsonResponse
{
try {
$restaurantId = getUserRestaurantId();
$customerId = getUserId();
$review = FoodReview::where([
'id' => $id,
'restaurant_id' => $restaurantId,
'customer_id' => $customerId,
])->firstOrFail();
if ($review->status === 'approved') {
return $this->responseError([], 'Approved reviews cannot be deleted.');
}
// Delete images
if (! empty($review->images)) {
foreach ($review->images as $img) {
fileRemover('food_reviews/images/', $img);
}
}
// Delete video
if ($review->video_file) {
fileRemover('food_reviews/videos/', $review->video_file);
}
$review->delete();
return $this->responseSuccess([], 'Review deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,166 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Modules\Restaurant\Http\Requests\FoodReviewReply\FoodReviewReplyStoreRequest;
use Modules\Restaurant\Http\Requests\FoodReviewReply\FoodReviewReplyUpdateRequest;
use Modules\Restaurant\Models\FoodReviewReply;
class FoodReviewReplyController extends Controller
{
/**
* List replies for a review
*/
public function index(Request $request, $reviewId): JsonResponse
{
try {
$perPage = $request->get('per_page', 10);
$replies = FoodReviewReply::where('review_id', $reviewId)
->where('status', 1)
->latest()
->paginate($perPage);
return $this->responseSuccess($replies, 'Replies fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
/**
* Store a reply
*/
public function store(FoodReviewReplyStoreRequest $request): JsonResponse
{
try {
$restaurantId = getUserRestaurantId();
$userId = getUserId();
$userType = $request->user_type ?? 'customer'; // default customer
$data = [
'restaurant_id' => $restaurantId,
'review_id' => $request->review_id,
'user_type' => $userType,
'message' => $request->message,
];
// Assign correct ID based on type
if ($userType === 'admin') {
$data['user_id'] = $userId;
} else {
$data['customer_id'] = $userId;
}
/** Attachments upload */
if ($request->hasFile('attachments')) {
$attachments = [];
foreach ($request->file('attachments') as $file) {
$attachments[] = fileUploader('review_replies/attachments/', 'png', $file);
}
$data['attachments'] = $attachments;
}
$reply = FoodReviewReply::create($data);
return $this->responseSuccess($reply, 'Reply created successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
/**
* Show single reply
*/
public function show($id): JsonResponse
{
try {
$reply = FoodReviewReply::findOrFail($id);
return $this->responseSuccess($reply, 'Reply fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
/**
* Update a reply
*/
public function update(FoodReviewReplyUpdateRequest $request, $id): JsonResponse
{
try {
$userId = getUserId();
$reply = FoodReviewReply::findOrFail($id);
// Permission: Only the owner (admin/moderator or customer) can edit
if (($reply->user_type === 'admin' && $reply->user_id !== $userId) ||
($reply->user_type === 'customer' && $reply->customer_id !== $userId)
) {
return $this->responseError([], 'You are not authorized to edit this reply.');
}
$data = $request->only(['message']);
$attachments = $reply->attachments ?? [];
// Remove selected attachments
if ($request->filled('remove_attachments')) {
foreach ($request->remove_attachments as $removeFile) {
if (in_array($removeFile, $attachments)) {
fileRemover('review_replies/attachments/', $removeFile);
$attachments = array_values(array_diff($attachments, [$removeFile]));
}
}
}
// Add new attachments
if ($request->hasFile('attachments')) {
foreach ($request->file('attachments') as $file) {
$attachments[] = fileUploader('review_replies/attachments/', 'png', $file);
}
}
$data['attachments'] = $attachments;
$reply->update($data);
return $this->responseSuccess($reply, 'Reply updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
/**
* Delete a reply
*/
public function destroy($id): JsonResponse
{
try {
$userId = getUserId();
$reply = FoodReviewReply::findOrFail($id);
// Permission: Only owner can delete
if (($reply->user_type === 'admin' && $reply->user_id !== $userId) ||
($reply->user_type === 'customer' && $reply->customer_id !== $userId)
) {
return $this->responseError([], 'You are not authorized to delete this reply.');
}
// Delete attachments
if (! empty($reply->attachments)) {
foreach ($reply->attachments as $file) {
fileRemover('review_replies/attachments/', $file);
}
}
$reply->delete();
return $this->responseSuccess([], 'Reply deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\FoodVariant\FoodVariantStoreRequest;
use Modules\Restaurant\Http\Requests\FoodVariant\FoodVariantUpdateRequest;
use Modules\Restaurant\Repositories\FoodVariantRepository;
class FoodVariantController extends Controller
{
use RequestSanitizerTrait;
public function __construct(private FoodVariantRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'FoodVariant has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(FoodVariantStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'FoodVariant has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'FoodVariant has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(FoodVariantUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'FoodVariant has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'FoodVariant has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,252 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\Authenticatable;
use Exception;
use Illuminate\Http\Request;
use Modules\Restaurant\Http\Requests\FoodVariantIngredient\FoodVariantIngredientStoreRequest;
use Modules\Restaurant\Models\FoodVariant;
use Modules\Restaurant\Models\FoodVariantIngredient;
class FoodVariantIngredientController extends Controller
{
use Authenticatable;
public function index(Request $request)
{
try {
$perPage = $request->get('perPage', 10);
$variants = FoodVariant::with(['ingredients.ingredient'])
->when($request->search, fn ($q) => $q->where('name', 'like', "%{$request->search}%"))
->whereHas('ingredients') // only variants that have ingredients
->paginate($perPage);
return $this->responseSuccess([
'current_page' => $variants->currentPage(),
'data' => $variants->map(function ($variant) {
return [
'variant_id' => $variant->id,
'variant_name' => $variant->name,
'ingredients' => $variant->ingredients->map(function ($fvIng) {
return [
'id' => $fvIng->ingredient->id ?? null,
'ingredient_name' => $fvIng->ingredient->name ?? null,
'quantity' => $fvIng->quantity,
'wastage_percentage' => $fvIng->wastage_percentage,
'unit_conversion_factor' => $fvIng->unit_conversion_factor,
'optional' => $fvIng->optional,
'notes' => $fvIng->notes,
'status' => $fvIng->status,
];
}),
];
}),
'first_page_url' => $variants->url(1),
'from' => $variants->firstItem(),
'last_page' => $variants->lastPage(),
'last_page_url' => $variants->url($variants->lastPage()),
'links' => $variants->linkCollection()->toArray(),
'next_page_url' => $variants->nextPageUrl(),
'path' => $request->url(),
'per_page' => $variants->perPage(),
'prev_page_url' => $variants->previousPageUrl(),
'to' => $variants->lastItem(),
'total' => $variants->total(),
], 'Variant list fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(FoodVariantIngredientStoreRequest $request)
{
try {
$food_variant_id = $request->food_variant_id;
$ingredients = $request->ingredients;
$restaurant_id = $this->getCurrentRestaurantId();
$created = [];
foreach ($ingredients as $item) {
$ingredient_id = $item['ingredient_id'];
// Check if this combination already exists
$exists = FoodVariantIngredient::where([
'restaurant_id' => $restaurant_id,
'food_variant_id' => $food_variant_id,
'ingredient_id' => $ingredient_id,
])->exists();
if ($exists) {
// Skip duplicate
continue;
}
// Prepare data
$item['restaurant_id'] = $restaurant_id;
$item['food_variant_id'] = $food_variant_id;
$created[] = FoodVariantIngredient::create($item);
}
return $this->responseSuccess([
'status' => true,
'message' => 'FoodVariantIngredients created successfully (duplicates skipped).',
'data' => $created,
'errors' => null,
], 'Ingredients processed successfully.');
} catch (Exception $e) {
return $this->responseError([
'status' => false,
'message' => 'Failed to create.',
'data' => null,
'errors' => $e->getMessage(),
], $e->getMessage());
}
}
public function show($id)
{
try {
$variant = FoodVariant::with(['ingredients.ingredient'])->findOrFail($id);
$data = [
'variant_id' => $variant->id,
'variant_name' => $variant->name,
'ingredients' => $variant->ingredients->map(function ($fvIng) {
return [
'id' => $fvIng->ingredient->id ?? null,
'ingredient_name' => $fvIng->ingredient->name ?? null,
'quantity' => $fvIng->quantity,
'wastage_percentage' => $fvIng->wastage_percentage,
'unit_conversion_factor' => $fvIng->unit_conversion_factor,
'optional' => $fvIng->optional,
'notes' => $fvIng->notes,
'status' => $fvIng->status,
];
}),
];
return $this->responseSuccess($data, 'FoodVariant with ingredients fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], 'FoodVariant not found.');
}
}
// ✅ Update
public function update(Request $request, $food_variant_id)
{
try {
$ingredients = $request->ingredients ?? [];
// Fetch the variant with its current ingredients
$variant = FoodVariant::with('ingredients')->findOrFail($food_variant_id);
$existingIds = $variant->ingredients->pluck('ingredient_id')->toArray();
$incomingIds = collect($ingredients)->pluck('ingredient_id')->toArray();
// Delete removed ingredients
$toDelete = array_diff($existingIds, $incomingIds);
if (! empty($toDelete)) {
FoodVariantIngredient::where('food_variant_id', $food_variant_id)
->whereIn('ingredient_id', $toDelete)
->delete();
}
$updatedOrCreated = [];
// Add or update ingredients
foreach ($ingredients as $item) {
$item['restaurant_id'] = $this->getCurrentRestaurantId();
$item['food_variant_id'] = $food_variant_id;
$fvIng = FoodVariantIngredient::updateOrCreate(
[
'food_variant_id' => $food_variant_id,
'ingredient_id' => $item['ingredient_id'],
],
$item
);
$updatedOrCreated[] = $fvIng;
}
// Fetch updated variant data
$variant = FoodVariant::with('ingredients.ingredient')->find($food_variant_id);
$data = [
'variant_id' => $variant->id,
'variant_name' => $variant->name,
'ingredients' => $variant->ingredients->map(function ($fvIng) {
return [
'id' => $fvIng->ingredient->id ?? null,
'ingredient_name' => $fvIng->ingredient->name ?? null,
'quantity' => $fvIng->quantity,
'wastage_percentage' => $fvIng->wastage_percentage,
'unit_conversion_factor' => $fvIng->unit_conversion_factor,
'optional' => $fvIng->optional,
'notes' => $fvIng->notes,
'status' => $fvIng->status,
];
}),
];
return $this->responseSuccess($data, 'FoodVariant ingredients updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
// ✅ Delete
public function destroy(Request $request)
{
try {
$food_variant_id = $request->food_variant_id;
$ingredient_id = $request->ingredient_id ?? null;
if (! $food_variant_id) {
return response()->json([
'status' => false,
'message' => 'Food Variant ID is required.',
'data' => null,
'errors' => null,
]);
}
$query = FoodVariantIngredient::where('food_variant_id', $food_variant_id);
// If ingredient_id is provided, delete only that ingredient
if ($ingredient_id) {
$query->where('ingredient_id', $ingredient_id);
}
$deleted = $query->delete();
if ($deleted) {
return response()->json([
'status' => true,
'message' => $ingredient_id ?
'Single ingredient removed successfully.' :
'All ingredients of the variant removed successfully.',
'data' => null,
'errors' => null,
]);
}
return response()->json([
'status' => false,
'message' => 'No matching record found.',
'data' => null,
'errors' => null,
]);
} catch (Exception $e) {
return response()->json([
'status' => false,
'message' => 'Failed to delete.',
'data' => null,
'errors' => $e->getMessage(),
]);
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\FoodWaste\FoodWasteStoreRequest;
use Modules\Restaurant\Http\Requests\FoodWaste\FoodWasteUpdateRequest;
use Modules\Restaurant\Repositories\FoodWasteRepository;
class FoodWasteController extends Controller
{
public function __construct(private FoodWasteRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'FoodWaste has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(FoodWasteStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'FoodWaste has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'FoodWaste has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(FoodWasteUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'FoodWaste has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'FoodWaste has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\Ingredient\IngredientStoreRequest;
use Modules\Restaurant\Http\Requests\Ingredient\IngredientUpdateRequest;
use Modules\Restaurant\Repositories\IngredientRepository;
class IngredientController extends Controller
{
use RequestSanitizerTrait;
public function __construct(private IngredientRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'Ingredient has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(IngredientStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'Ingredient has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'Ingredient has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(IngredientUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'Ingredient has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'Ingredient has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,128 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Helper\IngredientDamageHelper;
use App\Http\Controllers\Controller;
use App\Traits\Authenticatable;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Modules\Restaurant\Http\Requests\IngredientDamage\IngredientDamageStoreRequest;
use Modules\Restaurant\Http\Requests\IngredientDamage\IngredientDamageUpdateRequest;
use Modules\Restaurant\Models\IngredientDamage;
use Modules\Restaurant\Repositories\IngredientDamageRepository;
class IngredientDamageController extends Controller
{
use Authenticatable, RequestSanitizerTrait;
public function __construct(private IngredientDamageRepository $repo) {}
public function index(Request $request): JsonResponse
{
try {
$query = IngredientDamage::query()
->with([
'ingredient:id,name,unit_id',
'reportedBy:id,first_name',
])
->when(
$request->restaurant_id,
fn ($q) => $q->where('restaurant_id', $request->restaurant_id)
);
// 🔍 Search by ingredient name or reason
if ($search = $request->input('search')) {
$query->where(function ($q) use ($search) {
$q->where('reason', 'like', "%{$search}%")
->orWhereHas('ingredient', fn ($i) => $i->where('name', 'like', "%{$search}%"));
});
}
// 🗓️ Filter by date range
if ($start = $request->input('start_date') and $end = $request->input('end_date')) {
$query->whereBetween(DB::raw('DATE(created_at)'), [$start, $end]);
}
// 🔁 Sorting
$sortBy = $request->input('sort_by', 'created_at');
$sortOrder = $request->input('sort_order', 'desc');
$query->orderBy($sortBy, $sortOrder);
// 📄 Pagination
$perPage = (int) $request->input('perPage', 15);
$damageList = $query->paginate($perPage);
// 📊 Metadata (total damaged qty & value)
$meta = [
'total_damage_qty' => (float) $query->clone()->sum('quantity'),
'total_damage_value' => (float) $query->clone()->sum(DB::raw('quantity * unit_cost')),
'total_records' => $damageList->total(),
];
return $this->responseSuccess([
'data' => $damageList,
'meta' => $meta,
], 'IngredientDamage has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(IngredientDamageStoreRequest $request): JsonResponse
{
$data = $request->validated();
try {
$data['restaurant_id'] = $this->getCurrentRestaurantId();
$damage = IngredientDamageHelper::createDamage($data);
return response()->json([
'status' => true,
'message' => 'Ingredient damage has been recorded successfully.',
'data' => $damage,
]);
} catch (\Illuminate\Database\QueryException $exception) {
return response()->json([
'status' => false,
'message' => 'Database error: '.$exception->getMessage(),
'data' => null,
], 500);
} catch (Exception $e) {
return response()->json([
'status' => false,
'message' => $e->getMessage(),
'data' => null,
], 500);
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess([], 'IngredientDamage has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(IngredientDamageUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess([], 'IngredientDamage has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess([], 'IngredientDamage has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\KitchenAssign\KitchenAssignStoreRequest;
use Modules\Restaurant\Http\Requests\KitchenAssign\KitchenAssignUpdateRequest;
use Modules\Restaurant\Repositories\KitchenAssignRepository;
class KitchenAssignController extends Controller
{
public function __construct(private KitchenAssignRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'KitchenAssign has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(KitchenAssignStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'KitchenAssign has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'KitchenAssign has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(KitchenAssignUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'KitchenAssign has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'KitchenAssign has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\Kitchen\KitchenStoreRequest;
use Modules\Restaurant\Http\Requests\Kitchen\KitchenUpdateRequest;
use Modules\Restaurant\Repositories\KitchenRepository;
class KitchenController extends Controller
{
public function __construct(private KitchenRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'Kitchen has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(KitchenStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'Kitchen has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'Kitchen has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(KitchenUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'Kitchen has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'Kitchen has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\LoyaltyPoint\LoyaltyPointStoreRequest;
use Modules\Restaurant\Http\Requests\LoyaltyPoint\LoyaltyPointUpdateRequest;
use Modules\Restaurant\Repositories\LoyaltyPointRepository;
class LoyaltyPointController extends Controller
{
public function __construct(private LoyaltyPointRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'LoyaltyPoint has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(LoyaltyPointStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'LoyaltyPoint has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'LoyaltyPoint has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(LoyaltyPointUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'LoyaltyPoint has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'LoyaltyPoint has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\LoyaltyPointSetting\LoyaltyPointSettingStoreRequest;
use Modules\Restaurant\Http\Requests\LoyaltyPointSetting\LoyaltyPointSettingUpdateRequest;
use Modules\Restaurant\Repositories\LoyaltyPointSettingRepository;
class LoyaltyPointSettingController extends Controller
{
public function __construct(private LoyaltyPointSettingRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'LoyaltyPointSetting has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(LoyaltyPointSettingStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'LoyaltyPointSetting has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'LoyaltyPointSetting has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(LoyaltyPointSettingUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'LoyaltyPointSetting has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'LoyaltyPointSetting has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\LoyaltyRedemption\LoyaltyRedemptionStoreRequest;
use Modules\Restaurant\Http\Requests\LoyaltyRedemption\LoyaltyRedemptionUpdateRequest;
use Modules\Restaurant\Repositories\LoyaltyRedemptionRepository;
class LoyaltyRedemptionController extends Controller
{
public function __construct(private LoyaltyRedemptionRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'LoyaltyRedemption has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(LoyaltyRedemptionStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'LoyaltyRedemption has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'LoyaltyRedemption has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(LoyaltyRedemptionUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'LoyaltyRedemption has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'LoyaltyRedemption has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\MenuCategory\MenuCategoryStoreRequest;
use Modules\Restaurant\Http\Requests\MenuCategory\MenuCategoryUpdateRequest;
use Modules\Restaurant\Repositories\MenuCategoryRepository;
class MenuCategoryController extends Controller
{
public function __construct(private MenuCategoryRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'MenuCategory has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(MenuCategoryStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'MenuCategory has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'MenuCategory has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(MenuCategoryUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'MenuCategory has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'MenuCategory has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\MenuSection\MenuSectionStoreRequest;
use Modules\Restaurant\Http\Requests\MenuSection\MenuSectionUpdateRequest;
use Modules\Restaurant\Repositories\MenuSectionRepository;
class MenuSectionController extends Controller
{
public function __construct(private MenuSectionRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'MenuSection has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(MenuSectionStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'MenuSection has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'MenuSection has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(MenuSectionUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'MenuSection has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'MenuSection has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\MenuType\MenuTypeStoreRequest;
use Modules\Restaurant\Http\Requests\MenuType\MenuTypeUpdateRequest;
use Modules\Restaurant\Repositories\MenuTypeRepository;
class MenuTypeController extends Controller
{
use RequestSanitizerTrait;
public function __construct(private MenuTypeRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'MenuType has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(MenuTypeStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'MenuType has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'MenuType has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(MenuTypeUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'MenuType has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'MenuType has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,339 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Enum\ActionStatus;
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\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Modules\Authentication\Models\User;
use Modules\Restaurant\Http\Requests\Order\OrderStoreRequest;
use Modules\Restaurant\Http\Requests\Order\OrderUpdateRequest;
use Modules\Restaurant\Http\Resources\OrderResource;
use Modules\Restaurant\Models\Order;
class OrderController extends Controller
{
use Authenticatable, RequestSanitizerTrait, Trackable;
public function index(): JsonResponse
{
try {
$query = Order::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']);
if ($search = request('search')) {
$query->where(function ($q) use ($search) {
$search = strtolower($search); // ensure lowercase
$q->whereRaw('LOWER(orders.order_type) like ?', ["%{$search}%"])
->orWhereRaw('LOWER(orders.status) like ?', ["%{$search}%"])
->orWhereRaw('LOWER(orders.order_number) like ?', ["%{$search}%"])
->orWhereRaw('LOWER(customers.name) like ?', ["%{$search}%"])
->orWhereRaw('LOWER(customers.phone) like ?', ["%{$search}%"])
->orWhereRaw('LOWER(waiters.first_name) like ?', ["%{$search}%"]);
});
}
if (request('from_date') && request('to_date')) {
$query->whereBetween('orders.created_at', [
request('from_date').' 00:00:00',
request('to_date').' 23:59:59',
]);
}
// Paginate the query
$orders = $query->orderBy('orders.created_at', 'desc')
->paginate(request('perPage', 10));
// Convert to resource collection **with pagination meta**
$ordersResource = OrderResource::collection($orders)->response()->getData(true);
return response()->json([
'status' => true,
'message' => 'Orders have been fetched successfully.',
'data' => $ordersResource['data'], // resource items
'meta' => $ordersResource['meta'], // pagination info
'links' => $ordersResource['links'], // pagination links
]);
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(OrderStoreRequest $request, FirebaseService $firebase): JsonResponse
{
DB::beginTransaction();
try {
$restaurantId = $this->getCurrentRestaurantId();
$orderNumber = OrderHelper::generateOrderNumber($restaurantId);
// =========================
// 🔹 Calculate Subtotal With Addons
// =========================
$subtotal = 0;
foreach ($request->items as $item) {
// Base price calculation
$baseTotal = $item['price'] * $item['quantity'];
// Addon calculation
$addonTotal = 0;
if (! empty($item['addons'])) {
foreach ($item['addons'] as $addon) {
$addonTotal += $addon['price'];
}
// Multiply addons by quantity
$addonTotal = $addonTotal * $item['quantity'];
}
// Add item total to subtotal
$subtotal += ($baseTotal + $addonTotal);
}
// Discount & Tax
$discount = $request->discount ?? 0;
$tax = $request->tax ?? 0;
// Grand Total
$grandTotal = $subtotal - $discount + $tax;
// =========================
// 🔹 Create Order
// =========================
$order = Order::create([
'restaurant_id' => $restaurantId,
'order_number' => $orderNumber,
'order_type' => $request->order_type,
'status' => $request->status,
'table_id' => $request->table_id,
'waiter_id' => $request->waiter_id,
'customer_id' => $request->customer_id,
'payment_method_id' => $request->payment_method_id,
'payment_status' => $request->payment_status ?? false,
'subtotal' => $subtotal,
'discount' => $discount,
'tax' => $tax,
'grand_total' => $grandTotal,
'order_date' => Carbon::now(),
'added_by' => auth()->id(),
]);
// =========================
// 🔹 Create Order Items
// =========================
foreach ($request->items as $item) {
$addonTotal = 0;
if (! empty($item['addons'])) {
foreach ($item['addons'] as $addon) {
$addonTotal += $addon['price'];
}
}
$itemTotal =
($item['price'] * $item['quantity']) +
$addonTotal;
$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' => $itemTotal,
'addons' => ! empty($item['addons'])
? json_encode($item['addons'])
: null,
]);
/*
// 🔽 Ingredient deduction (optional)
foreach ($item['ingredients'] ?? [] as $ingredient) {
$ingredientModel = Ingredient::find($ingredient['id']);
if ($ingredientModel) {
$deductQty = $ingredient['quantity_required'] * $item['quantity'];
$ingredientModel->decrement('available_quantity', $deductQty);
}
}
*/
}
// =========================
// 🔹 Action Log
// =========================
$user = auth()->user();
$this->trackAction(ActionStatus::OrderCreate, User::class, $user->id, $order);
DB::commit();
// Push Notification
// if ($user?->fcm_token) {
// $result = $firebase->sendNotification(
// $user->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());
// }
// }
// =========================
// 🔹 Load Response Data
// =========================
$ordersResource = new OrderResource($order);
return $this->responseSuccess($ordersResource, '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',
'items.variation', // add this relation
'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(OrderUpdateRequest $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' => Carbon::now(),
]);
$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());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\OrderTracking\OrderTrackingStoreRequest;
use Modules\Restaurant\Http\Requests\OrderTracking\OrderTrackingUpdateRequest;
use Modules\Restaurant\Repositories\OrderTrackingRepository;
class OrderTrackingController extends Controller
{
public function __construct(private OrderTrackingRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'OrderTracking has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(OrderTrackingStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'OrderTracking has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'OrderTracking has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(OrderTrackingUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'OrderTracking has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'OrderTracking has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\PaymentMethod\PaymentMethodStoreRequest;
use Modules\Restaurant\Http\Requests\PaymentMethod\PaymentMethodUpdateRequest;
use Modules\Restaurant\Repositories\PaymentMethodRepository;
class PaymentMethodController extends Controller
{
use RequestSanitizerTrait;
public function __construct(private PaymentMethodRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'PaymentMethod has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(PaymentMethodStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'PaymentMethod has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'PaymentMethod has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(PaymentMethodUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'PaymentMethod has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'PaymentMethod has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,303 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Helper\PurchaseHelper;
use App\Http\Controllers\Controller;
use App\Traits\Authenticatable;
use App\Traits\RequestSanitizerTrait;
use App\Traits\Trackable;
use Carbon\Carbon;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use Modules\Restaurant\Http\Requests\Purchase\PurchaseStoreRequest;
use Modules\Restaurant\Http\Requests\Purchase\PurchaseUpdateRequest;
use Modules\Restaurant\Models\Purchase;
use Modules\Restaurant\Models\PurchaseItem;
use Modules\Restaurant\Models\Stock;
use Modules\Restaurant\Repositories\PurchaseRepository;
class PurchaseController extends Controller
{
use Authenticatable, RequestSanitizerTrait, Trackable;
public function __construct(private PurchaseRepository $repo) {}
public function index(): JsonResponse
{
try {
$filters = request()->all();
$data = $this->repo->getAll($filters);
return $this->responseSuccess($data, 'Purchase list fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(PurchaseStoreRequest $request): JsonResponse
{
$data = $request->validated();
DB::beginTransaction();
try {
// 1⃣ Create Purchase
$restaurantId = $this->getCurrentRestaurantId();
$invoiceNo = PurchaseHelper::generateInvoiceNumber($restaurantId);
$purchase = Purchase::create([
'restaurant_id' => $restaurantId,
'supplier_id' => $data['supplier_id'] ?? null,
'invoice_no' => $invoiceNo,
'purchase_date' => $data['purchase_date'] ?? Carbon::now(),
'sub_total' => $data['sub_total'],
'discount_amount' => $data['discount_amount'] ?? 0,
'tax_amount' => $data['tax_amount'] ?? 0,
'total_amount' => $data['total_amount'],
'payment_status' => $data['payment_status'] ?? 'unpaid',
'payment_method' => $data['payment_method'] ?? null,
'purchase_type' => $data['purchase_type'] ?? 'ingredient',
'created_by' => $data['created_by'] ?? null,
'notes' => $data['notes'] ?? null,
]);
// 2⃣ Loop through items and create Purchase Items + Stock
foreach ($data['items'] as $item) {
$purchaseItem = PurchaseItem::create([
'restaurant_id' => $restaurantId,
'purchase_id' => $purchase->id,
'ingredient_id' => $item['ingredient_id'],
'food_variant_id' => $item['food_variant_id'] ?? null,
'quantity' => $item['quantity'],
'unit_price' => $item['unit_price'],
'total_cost' => $item['total_cost'],
'tax_amount' => $item['tax_amount'] ?? 0,
'discount_amount' => $item['discount_amount'] ?? 0,
'batch_no' => $item['batch_no'] ?? null,
'expiry_date' => $item['expiry_date'] ?? null,
'received_quantity' => $item['received_quantity'] ?? $item['quantity'],
'wastage_quantity' => $item['wastage_quantity'] ?? 0,
]);
// 3⃣ Update Stock
Stock::create([
'restaurant_id' => $restaurantId,
'ingredient_id' => $item['ingredient_id'],
'type' => 'purchase',
'quantity' => $item['received_quantity'] ?? $item['quantity'],
'unit_cost' => $item['unit_price'],
'total_cost' => $item['total_cost'],
'reference_type' => 'PurchaseItem',
'purchase_id' => $purchaseItem->id,
'batch_no' => $item['batch_no'] ?? null,
'expiry_date' => $item['expiry_date'] ?? null,
'added_by' => $data['created_by'] ?? null,
'remarks' => 'Initial purchase stock',
]);
}
DB::commit();
return $this->responseSuccess($purchase->load('items'), 'Purchase has been created successfully.');
} catch (Exception $e) {
DB::rollBack();
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
$restaurantId = getUserRestaurantId();
$purchase = Purchase::with([
'supplier:id,name,phone',
'creator:id,first_name,last_name',
'items' => function ($q) {
$q->with([
'ingredient:id,name,unit_id',
'ingredient.unit:id,name',
]);
},
])
->where('restaurant_id', $restaurantId)
->where('id', $id)
->first();
if (! $purchase) {
return $this->responseError([], 'Purchase not found.', 404);
}
return $this->responseSuccess($purchase, 'Purchase fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(PurchaseUpdateRequest $request, int $id): JsonResponse
{
$data = $request->validated();
DB::beginTransaction();
try {
$restaurantId = $this->getCurrentRestaurantId();
// 1⃣ Fetch Purchase
$purchase = Purchase::with('items')->findOrFail($id);
// 2⃣ Update Purchase fields
$purchase->update([
'supplier_id' => $data['supplier_id'] ?? null,
'purchase_date' => $data['purchase_date'] ?? $purchase->purchase_date,
'sub_total' => $data['sub_total'],
'discount_amount' => $data['discount_amount'] ?? 0,
'tax_amount' => $data['tax_amount'] ?? 0,
'total_amount' => $data['total_amount'],
'payment_status' => $data['payment_status'] ?? $purchase->payment_status,
'payment_method' => $data['payment_method'] ?? $purchase->payment_method,
'purchase_type' => $data['purchase_type'] ?? $purchase->purchase_type,
'created_by' => $data['created_by'] ?? $purchase->created_by,
'notes' => $data['notes'] ?? null,
]);
// Collect item IDs included in request
$incomingItemIds = collect($data['items'])
->pluck('id')
->filter()
->toArray();
// 3⃣ Remove items that are NOT in request
$itemsToDelete = $purchase->items()
->whereNotIn('id', $incomingItemIds)
->get();
foreach ($itemsToDelete as $oldItem) {
// Delete stock related to this item
Stock::where('reference_type', 'PurchaseItem')
->where('purchase_id', $oldItem->id)
->delete();
// Delete purchase item
$oldItem->delete();
}
// 4⃣ Update or Create items
foreach ($data['items'] as $item) {
// If item has ID → update
if (! empty($item['id'])) {
$purchaseItem = PurchaseItem::find($item['id']);
$purchaseItem->update([
'ingredient_id' => $item['ingredient_id'],
'food_variant_id' => $item['food_variant_id'] ?? null,
'quantity' => $item['quantity'],
'unit_price' => $item['unit_price'],
'total_cost' => $item['total_cost'],
'tax_amount' => $item['tax_amount'] ?? 0,
'discount_amount' => $item['discount_amount'] ?? 0,
'batch_no' => $item['batch_no'] ?? null,
'expiry_date' => $item['expiry_date'] ?? null,
'received_quantity' => $item['received_quantity'] ?? $item['quantity'],
'wastage_quantity' => $item['wastage_quantity'] ?? 0,
]);
// Update stock row
Stock::where('reference_type', 'PurchaseItem')
->where('purchase_id', $purchaseItem->id)
->update([
'ingredient_id' => $item['ingredient_id'],
'quantity' => $item['received_quantity'] ?? $item['quantity'],
'unit_cost' => $item['unit_price'],
'total_cost' => $item['total_cost'],
'batch_no' => $item['batch_no'] ?? null,
'expiry_date' => $item['expiry_date'] ?? null,
'added_by' => $data['created_by'] ?? $purchase->created_by,
'remarks' => 'Updated purchase stock',
]);
} else {
// No ID → Create new item
$purchaseItem = PurchaseItem::create([
'restaurant_id' => $restaurantId,
'purchase_id' => $purchase->id,
'ingredient_id' => $item['ingredient_id'],
'food_variant_id' => $item['food_variant_id'] ?? null,
'quantity' => $item['quantity'],
'unit_price' => $item['unit_price'],
'total_cost' => $item['total_cost'],
'tax_amount' => $item['tax_amount'] ?? 0,
'discount_amount' => $item['discount_amount'] ?? 0,
'batch_no' => $item['batch_no'] ?? null,
'expiry_date' => $item['expiry_date'] ?? null,
'received_quantity' => $item['received_quantity'] ?? $item['quantity'],
'wastage_quantity' => $item['wastage_quantity'] ?? 0,
]);
// Create stock for new item
Stock::create([
'restaurant_id' => $restaurantId,
'ingredient_id' => $item['ingredient_id'],
'type' => 'purchase',
'quantity' => $item['received_quantity'] ?? $item['quantity'],
'unit_cost' => $item['unit_price'],
'total_cost' => $item['total_cost'],
'reference_type' => 'PurchaseItem',
'purchase_id' => $purchaseItem->id,
'batch_no' => $item['batch_no'] ?? null,
'expiry_date' => $item['expiry_date'] ?? null,
'added_by' => $data['created_by'] ?? null,
'remarks' => 'Newly added purchase item',
]);
}
}
DB::commit();
return $this->responseSuccess(
$purchase->load('items'),
'Purchase has been updated successfully.'
);
} catch (Exception $e) {
DB::rollBack();
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
DB::beginTransaction();
try {
$restaurantId = getUserRestaurantId();
$purchase = Purchase::where('restaurant_id', $restaurantId)
->where('id', $id)
->first();
if (! $purchase) {
return $this->responseError([], 'Purchase not found.', 404);
}
// 1⃣ Delete related Purchase Items + Stock
foreach ($purchase->items as $item) {
// Delete stock entries created from this item
Stock::where('reference_type', 'PurchaseItem')
->where('purchase_id', $item->id)
->delete();
// Delete purchase item
$item->delete();
}
// 2⃣ Delete Main Purchase
$purchase->delete();
DB::commit();
return $this->responseSuccess([], 'Purchase deleted successfully.');
} catch (Exception $e) {
DB::rollBack();
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,179 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Helper\PurchaseReturnHelper;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Modules\Restaurant\Models\Purchase;
use Modules\Restaurant\Models\PurchaseReturn;
class PurchaseReturnController extends Controller
{
/**
* List purchase returns with pagination, search, and metadata
*/
public function index(Request $request): JsonResponse
{
try {
$filters = $request->all();
$data = $this->getAll($filters);
return $this->responseSuccess($data, 'Purchase returns fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
/**
* Store a new purchase return
*/
public function store(Request $request): JsonResponse
{
$request->validate([
'purchase_id' => 'required|exists:purchases,id',
'items' => 'required|array|min:1',
'items.*.purchase_item_id' => 'required|exists:purchase_items,id',
'items.*.quantity' => 'required|numeric|min:0.01',
'items.*.reason' => 'nullable|string|max:255',
]);
try {
$purchaseId = $request->purchase_id;
$items = $request->items;
$userId = auth()->id();
// Use the helper to process returns
$result = PurchaseReturnHelper::processReturn($purchaseId, $items, $userId);
if (! $result['status']) {
return response()->json([
'status' => false,
'message' => $result['message'],
'data' => [],
], 400);
}
// Build response with purchase and item details
$purchase = Purchase::with(['supplier', 'items.ingredient', 'items.returns.processedBy'])->findOrFail($purchaseId);
$totals = [
'sub_total' => $purchase->items->sum(fn ($i) => $i->quantity * $i->unit_price),
'total_returned_quantity' => $purchase->items->sum(fn ($i) => $i->returned_quantity),
'total_returned_value' => $purchase->items->sum(fn ($i) => $i->returns->sum(fn ($r) => $r->total_cost)),
];
return response()->json([
'status' => true,
'message' => 'Purchase return processed successfully.',
'data' => [
'purchase_id' => $purchase->id,
'supplier' => $purchase->supplier,
'restaurant' => $purchase->restaurant,
'items' => $purchase->items->map(function ($item) {
return [
'purchase_item_id' => $item->id,
'ingredient' => $item->ingredient,
'quantity' => $item->quantity,
'unit_price' => $item->unit_price,
'total_cost' => $item->quantity * $item->unit_price,
'received_quantity' => $item->received_quantity,
'wastage_quantity' => $item->wastage_quantity,
'returned_quantity' => $item->returned_quantity,
'batch_no' => $item->batch_no,
'expiry_date' => $item->expiry_date,
'returns' => $item->returns->map(function ($r) {
return [
'return_id' => $r->id,
'quantity' => $r->quantity,
'unit_cost' => $r->unit_cost,
'total_cost' => $r->total_cost,
'reason' => $r->reason,
'return_date' => $r->created_at->toDateString(),
'processed_by' => $r->processedBy,
];
}),
];
}),
'totals' => $totals,
],
]);
} catch (Exception $e) {
return response()->json([
'status' => false,
'message' => $e->getMessage(),
'data' => null,
], 500);
}
}
/**
* Show single purchase return
*/
public function show(int $id): JsonResponse
{
$return = PurchaseReturn::with(['restaurant', 'purchase', 'purchaseItem', 'ingredient', 'processedBy'])
->findOrFail($id);
return response()->json([
'status' => true,
'message' => 'Purchase return fetched successfully.',
'data' => $return,
]);
}
public function getAll(array $filters)
{
$query = PurchaseReturn::with(['purchase', 'purchaseItem', 'ingredient', 'processedBy']);
// === Filters ===
if (! empty($filters['ingredient_id'])) {
$query->where('ingredient_id', $filters['ingredient_id']);
}
if (! empty($filters['purchase_id'])) {
$query->where('purchase_id', $filters['purchase_id']);
}
if (! empty($filters['restaurant_id'])) {
$query->where('restaurant_id', $filters['restaurant_id']);
}
if (! empty($filters['search'])) {
$search = $filters['search'];
$query->where(function ($q) use ($search) {
$q->where('reason', 'like', "%$search%")
->orWhere('batch_no', 'like', "%$search%");
});
}
// === Sorting ===
$sortBy = $filters['sort_by'] ?? 'return_date';
$sortOrder = $filters['sort_order'] ?? 'desc';
$query->orderBy($sortBy, $sortOrder);
// === Pagination ===
$perPage = $filters['perPage'] ?? 10;
$paginator = $query->paginate($perPage);
// === Return same structure as Purchase list ===
return [
'current_page' => $paginator->currentPage(),
'data' => $paginator->items(),
'first_page_url' => $paginator->url(1),
'from' => $paginator->firstItem(),
'last_page' => $paginator->lastPage(),
'last_page_url' => $paginator->url($paginator->lastPage()),
'links' => $paginator->linkCollection(),
'next_page_url' => $paginator->nextPageUrl(),
'path' => $paginator->path(),
'perPage' => $paginator->perPage(),
'prev_page_url' => $paginator->previousPageUrl(),
'to' => $paginator->lastItem(),
'total' => $paginator->total(),
];
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\QRMenuSetting\QRMenuSettingStoreRequest;
use Modules\Restaurant\Http\Requests\QRMenuSetting\QRMenuSettingUpdateRequest;
use Modules\Restaurant\Models\QRMenuSetting;
use Modules\Restaurant\Repositories\QRMenuSettingRepository;
class QRMenuSettingController extends Controller
{
public function __construct(private QRMenuSettingRepository $repo) {}
public function index(): JsonResponse
{
try {
$qrCode = QRMenuSetting::where('restaurant_id', getUserRestaurantId())->first();
return $this->responseSuccess($qrCode, 'QRMenuSetting has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(QRMenuSettingStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'QRMenuSetting has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'QRMenuSetting has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(QRMenuSettingUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'QRMenuSetting has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'QRMenuSetting has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,394 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Modules\Restaurant\Models\Ingredient;
use Modules\Restaurant\Models\Purchase;
use Modules\Restaurant\Models\PurchaseItem;
use Modules\Restaurant\Models\Stock;
class ReportController extends Controller
{
/**
* Ingredient report (restaurant-wise) with date range and search/filter
* GET /api/reports/ingredients
* params: restaurant_id, start_date, end_date, search, page, perPage
*/
public function ingredients(Request $request): JsonResponse
{
$v = Validator::make($request->all(), [
'restaurant_id' => 'nullable|integer|exists:restaurants,id',
'start_date' => 'nullable|date',
'end_date' => 'nullable|date',
'search' => 'nullable|string',
'page' => 'nullable|integer',
'perPage' => 'nullable|integer',
]);
if ($v->fails()) {
return response()->json(['errors' => $v->errors()], 422);
}
$start = $request->input('start_date');
$end = $request->input('end_date');
$restaurantId = getUserRestaurantId();
$search = $request->input('search');
$perPage = $request->input('perPage', 25);
$query = Ingredient::query()
->leftJoin('units', 'units.id', '=', 'ingredients.unit_id') // join units
->select([
'ingredients.id',
'ingredients.name',
'ingredients.unit_id',
'units.name as unit_name', // include unit name
'ingredients.cost_per_unit',
'ingredients.last_purchase_price',
'ingredients.restaurant_id',
DB::raw('IFNULL(SUM(stocks_in.quantity),0) as total_in'),
DB::raw('IFNULL(SUM(stocks_out.quantity),0) as total_out'),
DB::raw('IFNULL(SUM(stocks_in.total_cost),0) as total_in_cost'),
])
->leftJoin('stocks as stocks_in', function ($join) use ($start, $end, $restaurantId) {
$join->on('stocks_in.ingredient_id', '=', 'ingredients.id')
->whereIn('stocks_in.type', ['purchase', 'return', 'transfer_in'])
->whereNull('stocks_in.deleted_at');
if ($start) {
$join->whereDate('stocks_in.created_at', '>=', $start);
}
if ($end) {
$join->whereDate('stocks_in.created_at', '<=', $end);
}
if ($restaurantId) {
$join->where('stocks_in.restaurant_id', $restaurantId);
}
})
->leftJoin('stocks as stocks_out', function ($join) use ($start, $end, $restaurantId) {
$join->on('stocks_out.ingredient_id', '=', 'ingredients.id')
->whereIn('stocks_out.type', ['usage', 'damage', 'transfer_out'])
->whereNull('stocks_out.deleted_at');
if ($start) {
$join->whereDate('stocks_out.created_at', '>=', $start);
}
if ($end) {
$join->whereDate('stocks_out.created_at', '<=', $end);
}
if ($restaurantId) {
$join->where('stocks_out.restaurant_id', $restaurantId);
}
})
->groupBy([
'ingredients.id',
'ingredients.name',
'ingredients.unit_id',
'units.name', // include in groupBy
'ingredients.cost_per_unit',
'ingredients.last_purchase_price',
'ingredients.restaurant_id',
]);
if ($restaurantId) {
$query->where('ingredients.restaurant_id', $restaurantId);
}
if ($search) {
$query->where('ingredients.name', 'like', "%{$search}%");
}
$paginated = $query->paginate($perPage);
// Enrich each item with computed fields
$paginated->getCollection()->transform(function ($item) use ($start, $restaurantId) {
$currentStock = $this->currentStockForIngredient($item->id, $restaurantId);
$openingStock = $this->openingStockForIngredient($item->id, $start, $restaurantId);
$unitCost = $item->cost_per_unit
?: $item->last_purchase_price
?: $this->avgUnitCostFromStocks($item->id, $restaurantId);
$marketValue = round($currentStock * ($unitCost ?? 0), 2);
return array_merge($item->toArray(), [
'opening_stock' => round($openingStock, 2),
'current_stock' => round($currentStock, 2),
'estimated_market_value' => $marketValue,
]);
});
return $this->responseSuccess($paginated, 'Ingredients report fetched successfully.');
}
/**
* Stock report (stock movements) date-to-date
* GET /api/reports/stocks
* params: restaurant_id, start_date, end_date, ingredient_id, type, page, perPage
*/
public function stocks(Request $request): JsonResponse
{
$v = Validator::make($request->all(), [
'restaurant_id' => 'nullable|integer|exists:restaurants,id',
'start_date' => 'nullable|date',
'end_date' => 'nullable|date',
'ingredient_id' => 'nullable|integer|exists:ingredients,id',
'type' => 'nullable|in:purchase,usage,return,damage,transfer_in,transfer_out',
'page' => 'nullable|integer',
'perPage' => 'nullable|integer',
]);
if ($v->fails()) {
return response()->json(['errors' => $v->errors()], 422);
}
$q = Stock::query()->with(['ingredient'])
->when($request->restaurant_id ?? getUserRestaurantId(), fn ($q, $r) => $q->where('restaurant_id', $r))
->when($request->ingredient_id, fn ($q, $id) => $q->where('ingredient_id', $id))
->when($request->type, fn ($q, $t) => $q->where('type', $t))
->when($request->start_date, fn ($q, $s) => $q->whereDate('created_at', '>=', $s))
->when($request->end_date, fn ($q, $e) => $q->whereDate('created_at', '<=', $e))
->orderBy('created_at', 'desc');
$perPage = $request->input('perPage', 25);
$result = $q->paginate($perPage);
return $this->responseSuccess($result, 'Stock reports successfully.');
}
/**
* Purchase report date-to-date
* GET /api/reports/purchases
* params: restaurant_id, start_date, end_date, supplier_id, payment_status
*/
public function purchases(Request $request): JsonResponse
{
$v = Validator::make($request->all(), [
'restaurant_id' => 'nullable|integer|exists:restaurants,id',
'supplier_id' => 'nullable|integer|exists:suppliers,id',
'start_date' => 'nullable|date',
'end_date' => 'nullable|date',
'payment_status' => 'nullable|in:paid,unpaid,partial',
'page' => 'nullable|integer',
'perPage' => 'nullable|integer',
]);
if ($v->fails()) {
return response()->json(['errors' => $v->errors()], 422);
}
$q = Purchase::with(['supplier', 'items.ingredient'])
->when($request->restaurant_id ?? getUserRestaurantId(), fn ($q, $r) => $q->where('restaurant_id', $r))
->when($request->supplier_id, fn ($q, $s) => $q->where('supplier_id', $s))
->when($request->payment_status, fn ($q, $p) => $q->where('payment_status', $p))
->when($request->start_date, fn ($q, $s) => $q->whereDate('purchase_date', '>=', $s))
->when($request->end_date, fn ($q, $e) => $q->whereDate('purchase_date', '<=', $e))
->orderBy('purchase_date', 'desc');
$perPage = $request->input('perPage', 25);
$res = $q->paginate($perPage);
return $this->responseSuccess($res, 'Purchase reports successfully.');
}
/**
* Purchase ingredient with sales items countable for sales-estimate report
* Combines purchases and sold menu items to estimate ingredient usage & profitability
* GET /api/reports/purchase-estimate
* params: restaurant_id, start_date, end_date
*/
public function purchaseEstimate(Request $request): JsonResponse
{
$v = Validator::make($request->all(), [
'restaurant_id' => 'nullable|integer|exists:restaurants,id',
'start_date' => 'required|date',
'end_date' => 'required|date',
]);
if ($v->fails()) {
return response()->json(['errors' => $v->errors()], 422);
}
$start = date('Y-m-d', strtotime($request->start_date));
$end = date('Y-m-d', strtotime($request->end_date));
$restaurantId = $request->restaurant_id ?? getUserRestaurantId();
// 🛒 Purchased quantities per ingredient
$purchases = PurchaseItem::select(
'ingredient_id',
DB::raw('SUM(received_quantity) as purchased_qty'),
DB::raw('SUM(total_cost) as purchased_cost')
)
->when($restaurantId, fn ($q, $r) => $q->where('restaurant_id', $r))
->whereBetween(DB::raw('DATE(created_at)'), [$start, $end])
->groupBy('ingredient_id')
->get()
->keyBy('ingredient_id');
// 🍽️ Estimate ingredient usage from sales
$salesEstimate = DB::table('order_items')
->select(
'food_variant_ingredients.ingredient_id',
DB::raw('SUM(order_items.quantity * food_variant_ingredients.quantity * (1 + food_variant_ingredients.wastage_percentage / 100)) as estimated_used')
)
->join('food_variants', 'order_items.food_variant_id', '=', 'food_variants.id')
->join('food_variant_ingredients', 'food_variant_ingredients.food_variant_id', '=', 'food_variants.id')
->when($restaurantId, fn ($q, $r) => $q->where('order_items.restaurant_id', $r))
->whereBetween(DB::raw('DATE(order_items.created_at)'), [$start, $end])
->groupBy('food_variant_ingredients.ingredient_id')
->get()
->keyBy('ingredient_id');
// 🧾 Merge purchase + estimated usage
$ingredientIds = collect(array_unique(array_merge(
$purchases->keys()->toArray(),
$salesEstimate->keys()->toArray()
)));
$report = [];
foreach ($ingredientIds as $ingId) {
$ingredient = Ingredient::find($ingId);
$p = $purchases->get($ingId);
$s = $salesEstimate->get($ingId);
$pQty = $p->purchased_qty ?? 0;
$pCost = $p->purchased_cost ?? 0;
$used = $s->estimated_used ?? 0;
$remaining = max(0, $pQty - $used);
$unitCost = $ingredient->cost_per_unit
?: ($pQty ? ($pCost / $pQty) : ($ingredient->last_purchase_price ?? 0));
$report[] = [
'ingredient_id' => $ingId,
'ingredient_name' => $ingredient->name ?? 'Unknown',
'purchased_qty' => (float) $pQty,
'purchased_cost' => (float) $pCost,
'estimated_used' => (float) $used,
'estimated_remaining' => (float) $remaining,
'unit_cost_used' => round($unitCost, 2),
'estimated_remaining_value' => round($remaining * $unitCost, 2),
];
}
return $this->responseSuccess([
'start' => $start,
'end' => $end,
'data' => $report,
], 'Purchase Estimate reports successfully.');
}
/**
* Opening stock (date-to-date): returns opening stock at start_date, current stock and market value
* GET /api/reports/opening-stock
* params: restaurant_id, start_date, end_date, ingredient_id
*/
public function openingStock(Request $request): JsonResponse
{
$v = Validator::make($request->all(), [
'restaurant_id' => 'nullable|integer|exists:restaurants,id',
'ingredient_id' => 'nullable|integer|exists:ingredients,id',
'start_date' => 'required|date',
'end_date' => 'nullable|date',
]);
if ($v->fails()) {
return response()->json(['errors' => $v->errors()], 422);
}
$start = $request->start_date;
$restaurantId = $request->restaurant_id ?? getUserRestaurantId();
$ingQuery = Ingredient::query()->when($request->ingredient_id, fn ($q, $id) => $q->where('id', $id))
->when($restaurantId, fn ($q, $r) => $q->where('restaurant_id', $r));
$results = $ingQuery->get()->map(function ($ingredient) use ($start, $restaurantId) {
$opening = $this->openingStockForIngredient($ingredient->id, $start, $restaurantId);
$current = $this->currentStockForIngredient($ingredient->id, $restaurantId);
$unitCost = $ingredient->cost_per_unit ?: $ingredient->last_purchase_price ?: $this->avgUnitCostFromStocks($ingredient->id, $restaurantId);
$marketValue = round($current * ($unitCost ?? 0), 2);
return [
'ingredient_id' => $ingredient->id,
'ingredient_name' => $ingredient->name,
'opening_stock' => round($opening, 2),
'current_stock' => round($current, 2),
'unit_cost' => round($unitCost ?? 0, 2),
'market_value' => $marketValue,
];
});
return $this->responseSuccess(['start_date' => $start, 'data' => $results], 'Opening Stock reports successfully.');
}
// ---------------- Helper methods ----------------
/**
* Calculate opening stock for ingredient before the given date (exclusive)
*/
protected function openingStockForIngredient($ingredientId, $startDate = null, $restaurantId = null): float|int
{
if (! $startDate) {
return 0;
}
$pos = ['purchase', 'return', 'transfer_in'];
$neg = ['usage', 'damage', 'transfer_out'];
$q = Stock::where('ingredient_id', $ingredientId)->whereDate('created_at', '<', $startDate)->whereNull('deleted_at');
if ($restaurantId) {
$q->where('restaurant_id', $restaurantId);
}
$in = (float) $q->whereIn('type', $pos)->sum('quantity');
$out = (float) $q->whereIn('type', $neg)->sum('quantity');
return $in - $out;
}
/**
* Current stock calculated from stocks table (fallback to ingredients.stock_quantity)
*/
protected function currentStockForIngredient($ingredientId, $restaurantId = null): float|int
{
$pos = ['purchase', 'return', 'transfer_in'];
$neg = ['usage', 'damage', 'transfer_out'];
$q = Stock::where('ingredient_id', $ingredientId)->whereNull('deleted_at');
if ($restaurantId) {
$q->where('restaurant_id', $restaurantId);
}
$in = (float) $q->whereIn('type', $pos)->sum('quantity');
$out = (float) $q->whereIn('type', $neg)->sum('quantity');
$delta = $in - $out;
// fallback to ingredients.stock_quantity if stocks table is sparse
$ingredient = Ingredient::find($ingredientId);
$ingQty = $ingredient ? (float) $ingredient->stock_quantity : 0;
// Use the most reliable value: if ingredient.stock_quantity > 0, assume it's authoritative
if ($ingQty > 0) {
return $ingQty;
}
return $delta;
}
protected function avgUnitCostFromStocks($ingredientId, $restaurantId = null): float|int|null
{
$q = Stock::where('ingredient_id', $ingredientId)->whereNotNull('unit_cost')->whereNull('deleted_at');
if ($restaurantId) {
$q->where('restaurant_id', $restaurantId);
}
$totalQty = (float) $q->sum('quantity');
$totalCost = (float) $q->sum('total_cost');
if ($totalQty <= 0) {
return null;
}
return $totalCost / $totalQty;
}
}

View File

@@ -0,0 +1,136 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\Authenticatable;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Modules\Restaurant\Models\Reservation;
use Modules\Restaurant\Repositories\ReservationRepository;
class ReservationController extends Controller
{
public function __construct(private ReservationRepository $repo) {}
use Authenticatable;
public function index(Request $request): JsonResponse
{
try {
$filters = $request->all();
$data = $this->repo->getAll($filters);
return $this->responseSuccess($data, 'Reservation list fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(Request $request): JsonResponse
{
try {
$validated = $request->validate([
'table_id' => 'required|integer|exists:tables,id',
'customer_id' => 'nullable|integer|exists:customers,id',
'reservation_date' => 'required|date',
'start_time' => 'required|date_format:H:i',
'end_time' => 'required|date_format:H:i|after:start_time',
'people_count' => 'required|integer|min:1',
'status' => 'nullable|in:booked,free,upcoming,cancelled',
'note' => 'nullable|string',
]);
$validated['restaurant_id'] = $this->getCurrentRestaurantId();
$data = $this->repo->store($validated);
// TODO
// $user = auth()->user();
// // Action Log info
// $this->trackAction(ActionStatus::OrderCreate, User::class, $user->id, $order);
// // Push Notification
// if ($user?->fcm_token) {
// $result = $firebase->sendNotification(
// $user->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);
// }
// }
// Prepare data for markdown email
// $data = [
// 'customer_name' => $order->customer->name ?? 'Valued Customer',
// 'order_id' => $order->tracking_number,
// 'order_date' => Carbon::parse($order->created_at)->format('d-m-Y'),
// 'payment_method' => $order->payment_method ?? 'N/A',
// 'order_status' => $order->order_status ?? 'Pending',
// 'products' => $products,
// 'subtotal' => $order->amount ?? 0,
// 'shipping_cost' => $order->delivery_fee ?? 0,
// 'total_amount' => $order->paid_total ?? 0,
// 'customer_address' => $order->shipping_address ?? 'N/A',
// 'store_name' => get_setting('company_name', $restaurant_id) ?? 'Our Store',
// ];
// Directly send email
// $result = shop_mailer_send($order->restaurant_id, $order->customer?->email, new OrderInvoiceMail($data));
// if (! $result['status']) {
// Log::warning("Order invoice email not sent for shop {$order->restaurant_id}: " . $result['message']);
// }
// Mail::to($email)->send(new OrderInvoiceMail($data));
return $this->responseSuccess($data, 'Reservation created successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
$reservation = Reservation::with(['table', 'customer'])->findOrFail($id);
return $this->responseSuccess($reservation, 'Reservation details fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(Request $request, int $id): JsonResponse
{
try {
$reservation = Reservation::findOrFail($id);
$validated = $request->validate([
'status' => 'nullable|in:booked,free,upcoming,cancelled',
'note' => 'nullable|string',
]);
$reservation->update($validated);
return $this->responseSuccess($reservation, 'Reservation updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
$reservation = Reservation::findOrFail($id);
$reservation->delete();
return $this->responseSuccess([], 'Reservation deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\ReservationSetting\ReservationSettingStoreRequest;
use Modules\Restaurant\Http\Requests\ReservationSetting\ReservationSettingUpdateRequest;
use Modules\Restaurant\Repositories\ReservationSettingRepository;
class ReservationSettingController extends Controller
{
public function __construct(private ReservationSettingRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'ReservationSetting has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(ReservationSettingStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'ReservationSetting has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'ReservationSetting has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(ReservationSettingUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'ReservationSetting has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'ReservationSetting has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\Authenticatable;
use Exception;
use Illuminate\Http\Request;
use Modules\Restaurant\Models\ReservationUnavailable;
class ReservationUnavailableController extends Controller
{
use Authenticatable;
/**
* Display a listing of unavailable tables.
*/
public function index(Request $request)
{
try {
$query = ReservationUnavailable::with('table');
if ($request->filled('table_id')) {
$query->where('table_id', $request->table_id);
}
if ($request->filled('date')) {
$query->where('date', $request->date);
}
$data = $query->latest()->paginate($request->get('perPage', 10));
return $this->responseSuccess($data, 'Unavailable table list fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
/**
* Store a new unavailable record.
*/
public function store(Request $request)
{
try {
$validated = $request->validate([
'table_id' => 'required|exists:tables,id',
'date' => 'required|date',
'start_time' => 'required|date_format:H:i',
'end_time' => 'required|date_format:H:i|after:start_time',
'reason' => 'nullable|string|max:255',
'is_recurring' => 'boolean',
]);
$validated['restaurant_id'] = $this->getCurrentRestaurantId();
$data = ReservationUnavailable::create($validated);
return $this->responseSuccess($data, 'Table marked as unavailable successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
/**
* Show unavailable table details.
*/
public function show($id)
{
try {
$record = ReservationUnavailable::with('table')->findOrFail($id);
return $this->responseSuccess($record, 'Unavailable record details fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
/**
* Update unavailable record.
*/
public function update(Request $request, $id)
{
try {
$record = ReservationUnavailable::findOrFail($id);
$validated = $request->validate([
'table_id' => 'sometimes|exists:tables,id',
'date' => 'sometimes|date',
'start_time' => 'sometimes|date_format:H:i',
'end_time' => 'sometimes|date_format:H:i|after:start_time',
'reason' => 'nullable|string|max:255',
'is_recurring' => 'boolean',
]);
$record->update($validated);
return $this->responseSuccess($record, 'Unavailable record updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
/**
* Delete unavailable record.
*/
public function destroy($id)
{
try {
$record = ReservationUnavailable::findOrFail($id);
$record->delete();
return $this->responseSuccess([], 'Unavailable record removed successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,155 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Modules\Restaurant\Http\Requests\RestaurantSchedule\RestaurantScheduleStoreRequest;
use Modules\Restaurant\Http\Requests\RestaurantSchedule\RestaurantScheduleUpdateRequest;
use Modules\Restaurant\Models\RestaurantSchedule;
class RestaurantScheduleController extends Controller
{
public function index(Request $request): JsonResponse
{
try {
$perPage = $request->input('perPage', 15);
$sortBy = $request->input('sortBy', 'id');
$sortDir = $request->input('sortDir', 'desc');
$search = $request->input('search');
$query = RestaurantSchedule::query()
->with('times:id,restaurant_schedule_id,open_time,close_time')
->where('restaurant_id', getUserRestaurantId());
// 🔍 Search Filter
if (! empty($search)) {
$query->where(function ($q) use ($search) {
$q->where('day', 'LIKE', "%{$search}%")
->orWhere('start_date', 'LIKE', "%{$search}%")
->orWhere('end_date', 'LIKE', "%{$search}%");
});
}
// 🔽 Sorting
$query->orderBy($sortBy, $sortDir);
// 📄 Pagination
$data = $query->paginate($perPage)->appends($request->query());
return $this->responseSuccess($data, 'Restaurant schedule retrieved successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(RestaurantScheduleStoreRequest $request): JsonResponse
{
try {
foreach ($request->schedules as $item) {
// Create / Update
$schedule = RestaurantSchedule::updateOrCreate([
'restaurant_id' => getUserRestaurantId(),
'day' => $item['day'],
], [
'is_open' => $item['is_open'] ?? true,
]);
// Clean old times
$schedule->times()->delete();
// Add new slots
foreach ($item['times'] as $slot) {
$schedule->times()->create([
'open_time' => $slot['open_time'],
'close_time' => $slot['close_time'],
]);
}
}
return $this->responseSuccess([], 'Restaurant schedule added successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
$data = RestaurantSchedule::with('times')->findOrFail($id);
return $this->responseSuccess($data, 'Schedule retrieved successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(RestaurantScheduleUpdateRequest $request, $restaurantId): JsonResponse
{
DB::beginTransaction();
try {
foreach ($request->schedules as $item) {
// Find existing schedule day or create new
$schedule = RestaurantSchedule::firstOrCreate(
[
'restaurant_id' => $restaurantId,
'day' => $item['day'],
],
['is_open' => $item['is_open'] ?? true]
);
// Update is_open if changed
$schedule->update(['is_open' => $item['is_open'] ?? true]);
// Existing time slots
$existingTimes = $schedule->times()->get();
$newTimes = collect($item['times']);
// Delete removed slots
$existingTimes->each(function ($slot) use ($newTimes) {
if (! $newTimes->contains(fn ($t) => $t['open_time'] == $slot->open_time && $t['close_time'] == $slot->close_time)) {
$slot->delete();
}
});
// Add or update new slots
foreach ($item['times'] as $slot) {
$schedule->times()->updateOrCreate(
[
'open_time' => $slot['open_time'],
'close_time' => $slot['close_time'],
]
);
}
}
DB::commit();
return $this->responseSuccess([], 'Restaurant schedule updated successfully (attach/detach).');
} catch (Exception $e) {
DB::rollBack();
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
$schedule = RestaurantSchedule::findOrFail($id);
$schedule->times()->delete();
$schedule->delete();
return $this->responseSuccess([], 'Schedule deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\Supplier\SupplierStoreRequest;
use Modules\Restaurant\Http\Requests\Supplier\SupplierUpdateRequest;
use Modules\Restaurant\Repositories\SupplierRepository;
class SupplierController extends Controller
{
use RequestSanitizerTrait;
public function __construct(private SupplierRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'Supplier has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(SupplierStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'Supplier has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'Supplier has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(SupplierUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'Supplier has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'Supplier has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Traits\RequestSanitizerTrait;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\Table\TableStoreRequest;
use Modules\Restaurant\Http\Requests\Table\TableUpdateRequest;
use Modules\Restaurant\Repositories\TableRepository;
class TableController extends Controller
{
use RequestSanitizerTrait;
public function __construct(private TableRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'Table has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(TableStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'Table has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'Table has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(TableUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'Table has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'Table has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\TaxSetting\TaxSettingStoreRequest;
use Modules\Restaurant\Http\Requests\TaxSetting\TaxSettingUpdateRequest;
use Modules\Restaurant\Repositories\TaxSettingRepository;
class TaxSettingController extends Controller
{
public function __construct(private TaxSettingRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'TaxSetting has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(TaxSettingStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'TaxSetting has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'TaxSetting has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(TaxSettingUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'TaxSetting has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'TaxSetting has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\Unit\UnitStoreRequest;
use Modules\Restaurant\Http\Requests\Unit\UnitUpdateRequest;
use Modules\Restaurant\Repositories\UnitRepository;
class UnitController extends Controller
{
public function __construct(private UnitRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'Unit has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(UnitStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'Unit has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'Unit has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(UnitUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'Unit has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'Unit has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Restaurant\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Restaurant\Http\Requests\Zone\ZoneStoreRequest;
use Modules\Restaurant\Http\Requests\Zone\ZoneUpdateRequest;
use Modules\Restaurant\Repositories\ZoneRepository;
class ZoneController extends Controller
{
public function __construct(private ZoneRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'Zone has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(ZoneStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'Zone has been created successfully.');
} catch (\Illuminate\Database\QueryException $exception) {
return $this->responseError([], 'Database error: '.$exception->getMessage());
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function show(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'Zone has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(ZoneUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'Zone has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'Zone has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}