initial commit

This commit is contained in:
bastien
2026-06-28 12:29:53 +02:00
parent 03e85d2f9d
commit b2443b654c
220 changed files with 24969 additions and 1 deletions
@@ -0,0 +1,74 @@
<?php
namespace App\Http\Controllers\Api\V1\Supervisor;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Concerns\RespondsWithJson;
use App\Http\Requests\Api\V1\Supervisor\LoginRequest;
use App\Http\Resources\SupervisorResource;
use App\Models\Supervisor;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
class AuthController extends Controller
{
use RespondsWithJson;
public function login(LoginRequest $request): JsonResponse
{
$supervisor = Supervisor::query()
->where('email', $request->string('email')->toString())
->first();
if ($supervisor === null || ! Hash::check($request->string('password')->toString(), $supervisor->password)) {
return $this->error('Identifiants invalides.', 401);
}
if (! $supervisor->is_active) {
return $this->error('Compte superviseur désactivé.', 403);
}
$supervisor->update(['last_login_at' => now()]);
$deviceName = $request->input('device_name', 'backoffice');
$accessMinutes = (int) config('laverie.auth.access_token_ttl_minutes', 60);
$refreshDays = (int) config('laverie.auth.refresh_token_ttl_days', 30);
$accessToken = $supervisor->createToken(
"{$deviceName}-access",
['supervisor', 'access'],
now()->addMinutes($accessMinutes),
);
$refreshToken = $supervisor->createToken(
"{$deviceName}-refresh",
['supervisor', 'refresh'],
now()->addDays($refreshDays),
);
Auth::guard('supervisor')->login($supervisor);
return $this->success([
'supervisor' => new SupervisorResource($supervisor),
'access_token' => $accessToken->plainTextToken,
'refresh_token' => $refreshToken->plainTextToken,
'token_type' => 'Bearer',
'expires_in' => $accessMinutes * 60,
], 'Connexion superviseur réussie.');
}
public function logout(Request $request): JsonResponse
{
$user = $request->user();
if ($user instanceof Supervisor) {
$user->currentAccessToken()?->delete();
}
Auth::guard('supervisor')->logout();
return $this->success(message: 'Déconnexion superviseur réussie.');
}
}
@@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\Api\V1\Supervisor;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Concerns\RespondsWithJson;
use App\Http\Middleware\EnsureSupervisorScope;
use App\Http\Resources\BookingResource;
use App\Models\Supervisor;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class BookingController extends Controller
{
use RespondsWithJson;
public function index(Request $request): JsonResponse
{
/** @var Supervisor $supervisor */
$supervisor = $request->attributes->get('supervisor') ?? $request->user();
$bookings = EnsureSupervisorScope::bookingsQuery($supervisor)
->when($request->filled('status'), fn ($q) => $q->where('status', $request->string('status')->toString()))
->when($request->filled('date'), fn ($q) => $q->whereDate('slot_start', $request->string('date')->toString()))
->orderByDesc('slot_start')
->paginate($request->integer('per_page', 50));
return $this->success([
'bookings' => BookingResource::collection($bookings),
'meta' => [
'current_page' => $bookings->currentPage(),
'last_page' => $bookings->lastPage(),
'per_page' => $bookings->perPage(),
'total' => $bookings->total(),
],
]);
}
}
@@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers\Api\V1\Supervisor;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Concerns\RespondsWithJson;
use App\Http\Middleware\EnsureSupervisorScope;
use App\Models\Supervisor;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class DashboardController extends Controller
{
use RespondsWithJson;
public function index(Request $request): JsonResponse
{
/** @var Supervisor $supervisor */
$supervisor = $request->attributes->get('supervisor') ?? $request->user();
$today = Carbon::today();
$statsQuery = EnsureSupervisorScope::dailyStatsQuery($supervisor)
->where('stat_date', $today);
$todayStats = $statsQuery->get();
$machinesQuery = EnsureSupervisorScope::machinesQuery($supervisor);
$offlineThreshold = now()->subMinutes((int) config('laverie.machine.offline_threshold_minutes', 10));
return $this->success([
'summary' => [
'machines_total' => (clone $machinesQuery)->count(),
'machines_available' => (clone $machinesQuery)->where('status', 'available')->count(),
'machines_running' => (clone $machinesQuery)->where('status', 'running')->count(),
'machines_offline' => (clone $machinesQuery)->where(function ($q) use ($offlineThreshold) {
$q->where('status', 'offline')
->orWhere(fn ($q) => $q->where('last_heartbeat_at', '<', $offlineThreshold));
})->count(),
'bookings_today' => EnsureSupervisorScope::bookingsQuery($supervisor)
->whereDate('slot_start', $today)
->count(),
'washes_today' => EnsureSupervisorScope::washesQuery($supervisor)
->whereDate('started_at', $today)
->count(),
'revenue_today' => (float) $todayStats->sum('total_revenue'),
'top_up_today' => (float) $todayStats->sum('top_up_amount'),
],
'establishments' => $todayStats->map(fn ($stat) => [
'establishment_id' => $stat->establishment_id,
'establishment_name' => $stat->establishment?->name,
'total_washes' => $stat->total_washes,
'total_revenue' => (float) $stat->total_revenue,
'bookings_count' => $stat->bookings_count,
'no_show_count' => $stat->no_show_count,
'occupancy_rate' => (float) $stat->occupancy_rate,
]),
]);
}
}
@@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\Api\V1\Supervisor;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Concerns\RespondsWithJson;
use App\Http\Middleware\EnsureSupervisorScope;
use App\Http\Resources\MachineResource;
use App\Models\Supervisor;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class MachineController extends Controller
{
use RespondsWithJson;
public function index(Request $request): JsonResponse
{
/** @var Supervisor $supervisor */
$supervisor = $request->attributes->get('supervisor') ?? $request->user();
$machines = EnsureSupervisorScope::machinesQuery($supervisor)
->when($request->filled('status'), fn ($q) => $q->where('status', $request->string('status')->toString()))
->when($request->filled('establishment_id'), fn ($q) => $q->where('establishment_id', $request->integer('establishment_id')))
->orderBy('name')
->paginate($request->integer('per_page', 50));
return $this->success([
'machines' => MachineResource::collection($machines),
'meta' => [
'current_page' => $machines->currentPage(),
'last_page' => $machines->lastPage(),
'per_page' => $machines->perPage(),
'total' => $machines->total(),
],
]);
}
}
@@ -0,0 +1,91 @@
<?php
namespace App\Http\Controllers\Api\V1\Supervisor;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Concerns\RespondsWithJson;
use App\Http\Middleware\EnsureSupervisorScope;
use App\Http\Requests\Api\V1\Supervisor\StorePricingRequest;
use App\Http\Requests\Api\V1\Supervisor\UpdatePricingRequest;
use App\Http\Resources\PricingRuleResource;
use App\Models\Establishment;
use App\Models\PricingRule;
use App\Models\Supervisor;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class PricingController extends Controller
{
use RespondsWithJson;
public function index(Request $request): JsonResponse
{
/** @var Supervisor $supervisor */
$supervisor = $request->attributes->get('supervisor') ?? $request->user();
$rules = EnsureSupervisorScope::pricingRulesQuery($supervisor)
->when($request->filled('establishment_id'), fn ($q) => $q->where('establishment_id', $request->integer('establishment_id')))
->orderBy('priority')
->paginate($request->integer('per_page', 50));
return $this->success([
'pricing_rules' => PricingRuleResource::collection($rules),
'meta' => [
'current_page' => $rules->currentPage(),
'last_page' => $rules->lastPage(),
'per_page' => $rules->perPage(),
'total' => $rules->total(),
],
]);
}
public function store(StorePricingRequest $request): JsonResponse
{
/** @var Supervisor $supervisor */
$supervisor = $request->attributes->get('supervisor') ?? $request->user();
$establishment = Establishment::query()->findOrFail($request->integer('establishment_id'));
if ($establishment->organization_id !== $supervisor->organization_id) {
return $this->error('Établissement hors périmètre.', 403);
}
if ($supervisor->establishment_id !== null && $supervisor->establishment_id !== $establishment->id) {
return $this->error('Établissement hors périmètre.', 403);
}
$rule = PricingRule::query()->create([
'establishment_id' => $establishment->id,
'machine_id' => $request->input('machine_id'),
'machine_type' => $request->input('machine_type'),
'day_type' => $request->string('day_type')->toString(),
'slot_start' => $request->string('slot_start')->toString(),
'slot_end' => $request->string('slot_end')->toString(),
'price' => $request->input('price'),
'label' => $request->input('label'),
'requires_app' => $request->boolean('requires_app'),
'priority' => $request->integer('priority', 100),
'is_active' => $request->boolean('is_active', true),
]);
return $this->created([
'pricing_rule' => new PricingRuleResource($rule->load(['establishment', 'machine'])),
], 'Règle tarifaire créée.');
}
public function update(UpdatePricingRequest $request, int $id): JsonResponse
{
/** @var Supervisor $supervisor */
$supervisor = $request->attributes->get('supervisor') ?? $request->user();
$rule = EnsureSupervisorScope::pricingRulesQuery($supervisor)
->whereKey($id)
->firstOrFail();
$rule->update($request->validated());
return $this->success([
'pricing_rule' => new PricingRuleResource($rule->fresh(['establishment', 'machine'])),
], 'Règle tarifaire mise à jour.');
}
}
@@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\Api\V1\Supervisor;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Concerns\RespondsWithJson;
use App\Http\Middleware\EnsureSupervisorScope;
use App\Http\Resources\PromotionResource;
use App\Models\Supervisor;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class PromotionController extends Controller
{
use RespondsWithJson;
public function index(Request $request): JsonResponse
{
/** @var Supervisor $supervisor */
$supervisor = $request->attributes->get('supervisor') ?? $request->user();
$promotions = EnsureSupervisorScope::promotionsQuery($supervisor)
->when($request->filled('establishment_id'), fn ($q) => $q->where('establishment_id', $request->integer('establishment_id')))
->when($request->boolean('active_only'), fn ($q) => $q->where('is_active', true))
->orderByDesc('starts_at')
->paginate($request->integer('per_page', 50));
return $this->success([
'promotions' => PromotionResource::collection($promotions),
'meta' => [
'current_page' => $promotions->currentPage(),
'last_page' => $promotions->lastPage(),
'per_page' => $promotions->perPage(),
'total' => $promotions->total(),
],
]);
}
}
@@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\Api\V1\Supervisor;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Concerns\RespondsWithJson;
use App\Http\Middleware\EnsureSupervisorScope;
use App\Http\Resources\WashResource;
use App\Models\Supervisor;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class WashController extends Controller
{
use RespondsWithJson;
public function index(Request $request): JsonResponse
{
/** @var Supervisor $supervisor */
$supervisor = $request->attributes->get('supervisor') ?? $request->user();
$washes = EnsureSupervisorScope::washesQuery($supervisor)
->when($request->filled('status'), fn ($q) => $q->where('status', $request->string('status')->toString()))
->when($request->filled('date'), fn ($q) => $q->whereDate('started_at', $request->string('date')->toString()))
->orderByDesc('started_at')
->paginate($request->integer('per_page', 50));
return $this->success([
'washes' => WashResource::collection($washes),
'meta' => [
'current_page' => $washes->currentPage(),
'last_page' => $washes->lastPage(),
'per_page' => $washes->perPage(),
'total' => $washes->total(),
],
]);
}
}