initial commit
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Supervisor;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Supervisor\LoginRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
public function create(): Response
|
||||
{
|
||||
return Inertia::render('Supervisor/Auth/Login', [
|
||||
'status' => session('status'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(LoginRequest $request): RedirectResponse
|
||||
{
|
||||
$request->authenticate();
|
||||
|
||||
$request->session()->regenerate();
|
||||
|
||||
$supervisor = Auth::guard('supervisor')->user();
|
||||
$supervisor?->update(['last_login_at' => now()]);
|
||||
|
||||
return redirect()->intended(route('supervisor.dashboard'));
|
||||
}
|
||||
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
Auth::guard('supervisor')->logout();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect()->route('login');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Supervisor;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Supervisor\Concerns\ScopesSupervisorResources;
|
||||
use App\Models\Booking;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class BookingPageController extends Controller
|
||||
{
|
||||
use ScopesSupervisorResources;
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$query = Booking::query()
|
||||
->with(['user:id,first_name,last_name,email', 'machine:id,uuid,name,establishment_id', 'machine.establishment:id,name']);
|
||||
|
||||
$this->scopeMachineRelationQuery($query);
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->string('status'));
|
||||
}
|
||||
|
||||
if ($request->filled('date')) {
|
||||
$query->whereDate('slot_start', $request->string('date'));
|
||||
}
|
||||
|
||||
$bookings = $query
|
||||
->orderByDesc('slot_start')
|
||||
->paginate(20)
|
||||
->withQueryString()
|
||||
->through(fn (Booking $booking) => [
|
||||
'uuid' => $booking->uuid,
|
||||
'status' => $booking->status,
|
||||
'status_label' => $this->statusLabel($booking->status),
|
||||
'slot_start' => $booking->slot_start?->toIso8601String(),
|
||||
'slot_end' => $booking->slot_end?->toIso8601String(),
|
||||
'booking_fee' => (float) $booking->booking_fee,
|
||||
'reserved_amount' => (float) $booking->reserved_amount,
|
||||
'penalty_amount' => (float) $booking->penalty_amount,
|
||||
'user' => $booking->user ? [
|
||||
'name' => trim($booking->user->first_name.' '.$booking->user->last_name),
|
||||
'email' => $booking->user->email,
|
||||
] : null,
|
||||
'machine' => $booking->machine ? [
|
||||
'uuid' => $booking->machine->uuid,
|
||||
'name' => $booking->machine->name,
|
||||
'establishment_name' => $booking->machine->establishment?->name,
|
||||
] : null,
|
||||
]);
|
||||
|
||||
return Inertia::render('Supervisor/Bookings/Index', [
|
||||
'bookings' => $bookings,
|
||||
'filters' => [
|
||||
'status' => $request->string('status')->toString() ?: null,
|
||||
'date' => $request->string('date')->toString() ?: null,
|
||||
],
|
||||
'statusOptions' => $this->statusOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function statusOptions(): array
|
||||
{
|
||||
return [
|
||||
'pending' => 'En attente',
|
||||
'confirmed' => 'Confirmée',
|
||||
'cancelled' => 'Annulée',
|
||||
'expired' => 'Expirée',
|
||||
'active' => 'Active',
|
||||
'completed' => 'Terminée',
|
||||
'no_show' => 'Absent',
|
||||
];
|
||||
}
|
||||
|
||||
private function statusLabel(string $status): string
|
||||
{
|
||||
return $this->statusOptions()[$status] ?? $status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Supervisor\Concerns;
|
||||
|
||||
use App\Models\Supervisor;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
trait ScopesSupervisorResources
|
||||
{
|
||||
protected function supervisor(): Supervisor
|
||||
{
|
||||
/** @var Supervisor $supervisor */
|
||||
$supervisor = Auth::guard('supervisor')->user();
|
||||
|
||||
return $supervisor;
|
||||
}
|
||||
|
||||
protected function scopeEstablishmentQuery(Builder $query, string $establishmentColumn = 'establishment_id'): Builder
|
||||
{
|
||||
$supervisor = $this->supervisor();
|
||||
|
||||
$query->whereHas('establishment', function (Builder $query) use ($supervisor) {
|
||||
$query->where('organization_id', $supervisor->organization_id);
|
||||
|
||||
if ($supervisor->establishment_id !== null) {
|
||||
$query->where('id', $supervisor->establishment_id);
|
||||
}
|
||||
});
|
||||
|
||||
if ($supervisor->establishment_id !== null && $establishmentColumn !== '') {
|
||||
$query->where($establishmentColumn, $supervisor->establishment_id);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function scopeMachineRelationQuery(Builder $query): Builder
|
||||
{
|
||||
$supervisor = $this->supervisor();
|
||||
|
||||
return $query->whereHas('machine.establishment', function (Builder $query) use ($supervisor) {
|
||||
$query->where('organization_id', $supervisor->organization_id);
|
||||
|
||||
if ($supervisor->establishment_id !== null) {
|
||||
$query->where('id', $supervisor->establishment_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Supervisor;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Supervisor\Concerns\ScopesSupervisorResources;
|
||||
use App\Services\DashboardService;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
use ScopesSupervisorResources;
|
||||
|
||||
public function index(DashboardService $dashboardService): Response
|
||||
{
|
||||
$supervisor = $this->supervisor();
|
||||
|
||||
return Inertia::render('Supervisor/Dashboard', [
|
||||
'kpis' => $dashboardService->getKpis($supervisor),
|
||||
'alerts' => $dashboardService->getAlerts($supervisor),
|
||||
'machinesOverview' => $dashboardService->getMachinesOverview($supervisor),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Supervisor;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Supervisor\Concerns\ScopesSupervisorResources;
|
||||
use App\Models\Machine;
|
||||
use App\Models\MachineEvent;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class MachinePageController extends Controller
|
||||
{
|
||||
use ScopesSupervisorResources;
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$query = Machine::query()
|
||||
->with('establishment:id,name,uuid');
|
||||
|
||||
$this->scopeEstablishmentQuery($query);
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->string('status'));
|
||||
}
|
||||
|
||||
if ($request->filled('type')) {
|
||||
$query->where('type', $request->string('type'));
|
||||
}
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->string('search');
|
||||
$query->where(function ($query) use ($search) {
|
||||
$query->where('name', 'like', "%{$search}%")
|
||||
->orWhere('qr_code', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
$machines = $query
|
||||
->orderBy('name')
|
||||
->paginate(20)
|
||||
->withQueryString()
|
||||
->through(fn (Machine $machine) => $this->formatMachineListItem($machine));
|
||||
|
||||
return Inertia::render('Supervisor/Machines/Index', [
|
||||
'machines' => $machines,
|
||||
'filters' => [
|
||||
'status' => $request->string('status')->toString() ?: null,
|
||||
'type' => $request->string('type')->toString() ?: null,
|
||||
'search' => $request->string('search')->toString() ?: null,
|
||||
],
|
||||
'statusOptions' => $this->statusOptions(),
|
||||
'typeOptions' => $this->typeOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(string $uuid): Response
|
||||
{
|
||||
$query = Machine::query()->where('uuid', $uuid);
|
||||
$this->scopeEstablishmentQuery($query);
|
||||
|
||||
$machine = $query
|
||||
->with(['establishment:id,name,uuid,address,city', 'machineIntegration', 'currentUser:id,first_name,last_name,email'])
|
||||
->firstOrFail();
|
||||
|
||||
$recentEvents = MachineEvent::query()
|
||||
->where('machine_id', $machine->id)
|
||||
->orderByDesc('occurred_at')
|
||||
->limit(20)
|
||||
->get()
|
||||
->map(fn (MachineEvent $event) => [
|
||||
'event_type' => $event->event_type,
|
||||
'event_type_label' => $this->eventTypeLabel($event->event_type),
|
||||
'occurred_at' => $event->occurred_at?->toIso8601String(),
|
||||
'processing_status' => $event->processing_status,
|
||||
]);
|
||||
|
||||
return Inertia::render('Supervisor/Machines/Show', [
|
||||
'machine' => [
|
||||
'uuid' => $machine->uuid,
|
||||
'name' => $machine->name,
|
||||
'type' => $machine->type,
|
||||
'type_label' => $this->typeLabel($machine->type),
|
||||
'qr_code' => $machine->qr_code,
|
||||
'status' => $machine->status,
|
||||
'status_label' => $this->statusLabel($machine->status),
|
||||
'cycle_started_at' => $machine->cycle_started_at?->toIso8601String(),
|
||||
'cycle_ends_at' => $machine->cycle_ends_at?->toIso8601String(),
|
||||
'last_heartbeat_at' => $machine->last_heartbeat_at?->toIso8601String(),
|
||||
'establishment' => $machine->establishment ? [
|
||||
'uuid' => $machine->establishment->uuid,
|
||||
'name' => $machine->establishment->name,
|
||||
'address' => $machine->establishment->address,
|
||||
'city' => $machine->establishment->city,
|
||||
] : null,
|
||||
'current_user' => $machine->currentUser ? [
|
||||
'name' => trim($machine->currentUser->first_name.' '.$machine->currentUser->last_name),
|
||||
'email' => $machine->currentUser->email,
|
||||
] : null,
|
||||
'integration' => $machine->machineIntegration ? [
|
||||
'provider' => $machine->machineIntegration->provider,
|
||||
'mode' => $machine->machineIntegration->mode,
|
||||
'is_active' => $machine->machineIntegration->is_active,
|
||||
] : null,
|
||||
],
|
||||
'recentEvents' => $recentEvents,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function formatMachineListItem(Machine $machine): array
|
||||
{
|
||||
return [
|
||||
'uuid' => $machine->uuid,
|
||||
'name' => $machine->name,
|
||||
'type' => $machine->type,
|
||||
'type_label' => $this->typeLabel($machine->type),
|
||||
'status' => $machine->status,
|
||||
'status_label' => $this->statusLabel($machine->status),
|
||||
'establishment_name' => $machine->establishment?->name,
|
||||
'last_heartbeat_at' => $machine->last_heartbeat_at?->toIso8601String(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function statusOptions(): array
|
||||
{
|
||||
return [
|
||||
'available' => 'Disponible',
|
||||
'reserved' => 'Réservée',
|
||||
'running' => 'En cours',
|
||||
'maintenance' => 'Maintenance',
|
||||
'offline' => 'Hors ligne',
|
||||
'error' => 'Erreur',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function typeOptions(): array
|
||||
{
|
||||
return [
|
||||
'washer_small' => 'Lave-linge petit',
|
||||
'washer_large' => 'Lave-linge grand',
|
||||
'dryer_small' => 'Sèche-linge petit',
|
||||
'dryer_large' => 'Sèche-linge grand',
|
||||
];
|
||||
}
|
||||
|
||||
private function statusLabel(string $status): string
|
||||
{
|
||||
return $this->statusOptions()[$status] ?? $status;
|
||||
}
|
||||
|
||||
private function typeLabel(string $type): string
|
||||
{
|
||||
return $this->typeOptions()[$type] ?? $type;
|
||||
}
|
||||
|
||||
private function eventTypeLabel(string $type): string
|
||||
{
|
||||
return match ($type) {
|
||||
'heartbeat' => 'Signal de vie',
|
||||
'machine_online' => 'En ligne',
|
||||
'machine_offline' => 'Hors ligne',
|
||||
'cycle_started' => 'Cycle démarré',
|
||||
'cycle_completed' => 'Cycle terminé',
|
||||
'cycle_failed' => 'Cycle échoué',
|
||||
'error_reported' => 'Erreur signalée',
|
||||
'status_changed' => 'Changement de statut',
|
||||
default => $type,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Supervisor;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Supervisor\Concerns\ScopesSupervisorResources;
|
||||
use App\Models\Establishment;
|
||||
use App\Models\PricingRule;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class PricingPageController extends Controller
|
||||
{
|
||||
use ScopesSupervisorResources;
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$query = PricingRule::query()
|
||||
->with(['establishment:id,name', 'machine:id,uuid,name']);
|
||||
|
||||
$this->scopeEstablishmentQuery($query);
|
||||
|
||||
$rules = $query
|
||||
->orderBy('priority')
|
||||
->orderBy('slot_start')
|
||||
->get()
|
||||
->map(fn (PricingRule $rule) => $this->formatRule($rule));
|
||||
|
||||
return Inertia::render('Supervisor/Pricing/Index', [
|
||||
'rules' => $rules,
|
||||
'establishments' => $this->establishmentOptions(),
|
||||
'machineTypes' => $this->machineTypeOptions(),
|
||||
'dayTypes' => $this->dayTypeOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $this->validateRule($request);
|
||||
$this->assertEstablishmentAccessible((int) $validated['establishment_id']);
|
||||
|
||||
PricingRule::query()->create($validated);
|
||||
|
||||
return redirect()->route('supervisor.pricing.index')
|
||||
->with('success', 'Règle tarifaire créée.');
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id): RedirectResponse
|
||||
{
|
||||
$rule = $this->findAccessibleRule($id);
|
||||
$validated = $this->validateRule($request);
|
||||
$this->assertEstablishmentAccessible((int) $validated['establishment_id']);
|
||||
|
||||
$rule->update($validated);
|
||||
|
||||
return redirect()->route('supervisor.pricing.index')
|
||||
->with('success', 'Règle tarifaire mise à jour.');
|
||||
}
|
||||
|
||||
public function destroy(int $id): RedirectResponse
|
||||
{
|
||||
$rule = $this->findAccessibleRule($id);
|
||||
$rule->delete();
|
||||
|
||||
return redirect()->route('supervisor.pricing.index')
|
||||
->with('success', 'Règle tarifaire supprimée.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function validateRule(Request $request): array
|
||||
{
|
||||
return $request->validate([
|
||||
'establishment_id' => ['required', 'integer', 'exists:establishments,id'],
|
||||
'machine_id' => ['nullable', 'integer', 'exists:machines,id'],
|
||||
'machine_type' => ['nullable', 'in:washer_small,washer_large,dryer_small,dryer_large'],
|
||||
'day_type' => ['required', 'in:weekday,weekend,holiday,all'],
|
||||
'slot_start' => ['required', 'date_format:H:i'],
|
||||
'slot_end' => ['required', 'date_format:H:i', 'after:slot_start'],
|
||||
'price' => ['required', 'numeric', 'min:0'],
|
||||
'label' => ['nullable', 'string', 'max:100'],
|
||||
'requires_app' => ['boolean'],
|
||||
'priority' => ['integer', 'min:0'],
|
||||
'is_active' => ['boolean'],
|
||||
]);
|
||||
}
|
||||
|
||||
private function findAccessibleRule(int $id): PricingRule
|
||||
{
|
||||
$query = PricingRule::query()->where('id', $id);
|
||||
$this->scopeEstablishmentQuery($query);
|
||||
|
||||
return $query->firstOrFail();
|
||||
}
|
||||
|
||||
private function assertEstablishmentAccessible(int $establishmentId): void
|
||||
{
|
||||
$supervisor = $this->supervisor();
|
||||
|
||||
$exists = Establishment::query()
|
||||
->where('id', $establishmentId)
|
||||
->where('organization_id', $supervisor->organization_id)
|
||||
->when($supervisor->establishment_id, fn ($q) => $q->where('id', $supervisor->establishment_id))
|
||||
->exists();
|
||||
|
||||
abort_unless($exists, 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function formatRule(PricingRule $rule): array
|
||||
{
|
||||
return [
|
||||
'id' => $rule->id,
|
||||
'establishment_id' => $rule->establishment_id,
|
||||
'establishment_name' => $rule->establishment?->name,
|
||||
'machine_id' => $rule->machine_id,
|
||||
'machine_name' => $rule->machine?->name,
|
||||
'machine_type' => $rule->machine_type,
|
||||
'machine_type_label' => $rule->machine_type ? ($this->machineTypeOptions()[$rule->machine_type] ?? $rule->machine_type) : null,
|
||||
'day_type' => $rule->day_type,
|
||||
'day_type_label' => $this->dayTypeOptions()[$rule->day_type] ?? $rule->day_type,
|
||||
'slot_start' => $rule->slot_start ? substr((string) $rule->slot_start, 0, 5) : null,
|
||||
'slot_end' => $rule->slot_end ? substr((string) $rule->slot_end, 0, 5) : null,
|
||||
'price' => (float) $rule->price,
|
||||
'label' => $rule->label,
|
||||
'requires_app' => $rule->requires_app,
|
||||
'priority' => $rule->priority,
|
||||
'is_active' => $rule->is_active,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{id: int, name: string}>
|
||||
*/
|
||||
private function establishmentOptions(): array
|
||||
{
|
||||
$supervisor = $this->supervisor();
|
||||
|
||||
return Establishment::query()
|
||||
->where('organization_id', $supervisor->organization_id)
|
||||
->when($supervisor->establishment_id, fn ($q) => $q->where('id', $supervisor->establishment_id))
|
||||
->orderBy('name')
|
||||
->get(['id', 'name'])
|
||||
->map(fn (Establishment $e) => ['id' => $e->id, 'name' => $e->name])
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function machineTypeOptions(): array
|
||||
{
|
||||
return [
|
||||
'washer_small' => 'Lave-linge petit',
|
||||
'washer_large' => 'Lave-linge grand',
|
||||
'dryer_small' => 'Sèche-linge petit',
|
||||
'dryer_large' => 'Sèche-linge grand',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function dayTypeOptions(): array
|
||||
{
|
||||
return [
|
||||
'all' => 'Tous les jours',
|
||||
'weekday' => 'Semaine',
|
||||
'weekend' => 'Week-end',
|
||||
'holiday' => 'Jours fériés',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Supervisor;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Supervisor\Concerns\ScopesSupervisorResources;
|
||||
use App\Models\Establishment;
|
||||
use App\Models\Promotion;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class PromotionPageController extends Controller
|
||||
{
|
||||
use ScopesSupervisorResources;
|
||||
|
||||
public function index(): Response
|
||||
{
|
||||
$query = Promotion::query()->with('establishment:id,name');
|
||||
$this->scopeEstablishmentQuery($query);
|
||||
|
||||
$promotions = $query
|
||||
->orderByDesc('starts_at')
|
||||
->get()
|
||||
->map(fn (Promotion $promotion) => $this->formatPromotion($promotion));
|
||||
|
||||
return Inertia::render('Supervisor/Promotions/Index', [
|
||||
'promotions' => $promotions,
|
||||
'establishments' => $this->establishmentOptions(),
|
||||
'machineTypes' => $this->machineTypeOptions(),
|
||||
'discountTypes' => $this->discountTypeOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $this->validatePromotion($request);
|
||||
$this->assertEstablishmentAccessible((int) $validated['establishment_id']);
|
||||
|
||||
Promotion::query()->create($validated);
|
||||
|
||||
return redirect()->route('supervisor.promotions.index')
|
||||
->with('success', 'Promotion créée.');
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id): RedirectResponse
|
||||
{
|
||||
$promotion = $this->findAccessiblePromotion($id);
|
||||
$validated = $this->validatePromotion($request);
|
||||
$this->assertEstablishmentAccessible((int) $validated['establishment_id']);
|
||||
|
||||
$promotion->update($validated);
|
||||
|
||||
return redirect()->route('supervisor.promotions.index')
|
||||
->with('success', 'Promotion mise à jour.');
|
||||
}
|
||||
|
||||
public function destroy(int $id): RedirectResponse
|
||||
{
|
||||
$promotion = $this->findAccessiblePromotion($id);
|
||||
$promotion->delete();
|
||||
|
||||
return redirect()->route('supervisor.promotions.index')
|
||||
->with('success', 'Promotion supprimée.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function validatePromotion(Request $request): array
|
||||
{
|
||||
return $request->validate([
|
||||
'establishment_id' => ['required', 'integer', 'exists:establishments,id'],
|
||||
'machine_type' => ['required', 'in:washer_small,washer_large,dryer_small,dryer_large,all'],
|
||||
'discount_type' => ['required', 'in:percent,fixed'],
|
||||
'discount_value' => ['required', 'numeric', 'min:0'],
|
||||
'starts_at' => ['required', 'date'],
|
||||
'ends_at' => ['required', 'date', 'after:starts_at'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'is_active' => ['boolean'],
|
||||
]);
|
||||
}
|
||||
|
||||
private function findAccessiblePromotion(int $id): Promotion
|
||||
{
|
||||
$query = Promotion::query()->where('id', $id);
|
||||
$this->scopeEstablishmentQuery($query);
|
||||
|
||||
return $query->firstOrFail();
|
||||
}
|
||||
|
||||
private function assertEstablishmentAccessible(int $establishmentId): void
|
||||
{
|
||||
$supervisor = $this->supervisor();
|
||||
|
||||
$exists = Establishment::query()
|
||||
->where('id', $establishmentId)
|
||||
->where('organization_id', $supervisor->organization_id)
|
||||
->when($supervisor->establishment_id, fn ($q) => $q->where('id', $supervisor->establishment_id))
|
||||
->exists();
|
||||
|
||||
abort_unless($exists, 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function formatPromotion(Promotion $promotion): array
|
||||
{
|
||||
return [
|
||||
'id' => $promotion->id,
|
||||
'establishment_id' => $promotion->establishment_id,
|
||||
'establishment_name' => $promotion->establishment?->name,
|
||||
'machine_type' => $promotion->machine_type,
|
||||
'machine_type_label' => $this->machineTypeOptions()[$promotion->machine_type] ?? $promotion->machine_type,
|
||||
'discount_type' => $promotion->discount_type,
|
||||
'discount_type_label' => $this->discountTypeOptions()[$promotion->discount_type] ?? $promotion->discount_type,
|
||||
'discount_value' => (float) $promotion->discount_value,
|
||||
'starts_at' => $promotion->starts_at?->format('Y-m-d\TH:i'),
|
||||
'ends_at' => $promotion->ends_at?->format('Y-m-d\TH:i'),
|
||||
'description' => $promotion->description,
|
||||
'is_active' => $promotion->is_active,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{id: int, name: string}>
|
||||
*/
|
||||
private function establishmentOptions(): array
|
||||
{
|
||||
$supervisor = $this->supervisor();
|
||||
|
||||
return Establishment::query()
|
||||
->where('organization_id', $supervisor->organization_id)
|
||||
->when($supervisor->establishment_id, fn ($q) => $q->where('id', $supervisor->establishment_id))
|
||||
->orderBy('name')
|
||||
->get(['id', 'name'])
|
||||
->map(fn (Establishment $e) => ['id' => $e->id, 'name' => $e->name])
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function machineTypeOptions(): array
|
||||
{
|
||||
return [
|
||||
'all' => 'Toutes les machines',
|
||||
'washer_small' => 'Lave-linge petit',
|
||||
'washer_large' => 'Lave-linge grand',
|
||||
'dryer_small' => 'Sèche-linge petit',
|
||||
'dryer_large' => 'Sèche-linge grand',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function discountTypeOptions(): array
|
||||
{
|
||||
return [
|
||||
'percent' => 'Pourcentage',
|
||||
'fixed' => 'Montant fixe',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Supervisor;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Supervisor\Concerns\ScopesSupervisorResources;
|
||||
use App\Models\Wash;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class WashPageController extends Controller
|
||||
{
|
||||
use ScopesSupervisorResources;
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$query = Wash::query()
|
||||
->with(['user:id,first_name,last_name,email', 'machine:id,uuid,name,establishment_id', 'machine.establishment:id,name']);
|
||||
|
||||
$this->scopeMachineRelationQuery($query);
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->string('status'));
|
||||
}
|
||||
|
||||
if ($request->filled('date')) {
|
||||
$query->whereDate('started_at', $request->string('date'));
|
||||
}
|
||||
|
||||
$washes = $query
|
||||
->orderByDesc('started_at')
|
||||
->paginate(20)
|
||||
->withQueryString()
|
||||
->through(fn (Wash $wash) => [
|
||||
'uuid' => $wash->uuid,
|
||||
'status' => $wash->status,
|
||||
'status_label' => $this->statusLabel($wash->status),
|
||||
'program' => $wash->program,
|
||||
'trigger_method' => $wash->trigger_method,
|
||||
'trigger_method_label' => $this->triggerMethodLabel($wash->trigger_method),
|
||||
'started_at' => $wash->started_at?->toIso8601String(),
|
||||
'ended_at' => $wash->ended_at?->toIso8601String(),
|
||||
'duration_minutes' => $wash->duration_minutes,
|
||||
'cost' => (float) $wash->cost,
|
||||
'user' => $wash->user ? [
|
||||
'name' => trim($wash->user->first_name.' '.$wash->user->last_name),
|
||||
'email' => $wash->user->email,
|
||||
] : null,
|
||||
'machine' => $wash->machine ? [
|
||||
'uuid' => $wash->machine->uuid,
|
||||
'name' => $wash->machine->name,
|
||||
'establishment_name' => $wash->machine->establishment?->name,
|
||||
] : null,
|
||||
]);
|
||||
|
||||
return Inertia::render('Supervisor/Washes/Index', [
|
||||
'washes' => $washes,
|
||||
'filters' => [
|
||||
'status' => $request->string('status')->toString() ?: null,
|
||||
'date' => $request->string('date')->toString() ?: null,
|
||||
],
|
||||
'statusOptions' => $this->statusOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function statusOptions(): array
|
||||
{
|
||||
return [
|
||||
'pending_start' => 'En attente',
|
||||
'running' => 'En cours',
|
||||
'completed' => 'Terminé',
|
||||
'failed' => 'Échoué',
|
||||
'cancelled' => 'Annulé',
|
||||
];
|
||||
}
|
||||
|
||||
private function statusLabel(string $status): string
|
||||
{
|
||||
return $this->statusOptions()[$status] ?? $status;
|
||||
}
|
||||
|
||||
private function triggerMethodLabel(string $method): string
|
||||
{
|
||||
return match ($method) {
|
||||
'qr_code' => 'QR code',
|
||||
'booking' => 'Réservation',
|
||||
'supervisor' => 'Superviseur',
|
||||
'system' => 'Système',
|
||||
default => $method,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user