diff --git a/Convoi.json b/Convoi.json new file mode 100644 index 0000000..703821f --- /dev/null +++ b/Convoi.json @@ -0,0 +1,291 @@ +{ + "defuntId": null, + "titreMission": "", + "clientId": null, + "typeConvoi": "local", + "lieuDepart": { + "modeSelection": "lieu", + "lieuId": null, + "nom": "", + "adresse": "", + "ville": "", + "codePostal": "", + "pays": "", + "latitude": null, + "longitude": null, + "detailsComplementaires": "" + }, + "modeTransport": "route", + "debutPrevu": "2026-04-15T10:57", + "finEstimee": null, + "statut": "planifie", + "emailFamille": "", + "notificationsAutomatiques": true, + "ongletsMission": { + "itineraire": { + "enabled": false + }, + "documentsLegaux": { + "enabled": false + }, + "equipeMoyens": { + "enabled": true + }, + "demarches": { + "enabled": true + }, + "suiviCouts": { + "enabled": true + }, + "carburant": { + "enabled": true + }, + "ceremonie": { + "enabled": true + }, + "thanatopraxie": { + "enabled": true + }, + "suiviGpsEtapes": { + "enabled": false + }, + "communication": { + "enabled": true + } + } +} + + +// VEHICULE +{ + "attributMissionConvoi": { + "defuntId": null, + "titreMission": "", + "clientId": null, + "typeConvoi": "local", + "lieuDepart": { + "modeSelection": "lieu", + "lieuId": null, + "nom": "", + "adresse": "", + "ville": "", + "codePostal": "", + "pays": "", + "latitude": null, + "longitude": null, + "detailsComplementaires": "" + }, + "modeTransport": "route", + "debutPrevu": "2026-04-15T10:57", + "finEstimee": null, + "statut": "planifie", + "emailFamille": "", + "notificationsAutomatiques": true, + "ongletsMission": { + "itineraire": false, + "documentsLegaux": false, + "equipeMoyens": true, + "demarches": true, + "suiviCouts": true, + "carburant": true, + "ceremonie": true, + "thanatopraxie": true, + "suiviGpsEtapes": false, + "communication": true + } + }, + "attributVehicule": { + "photoVehicule": { + "fileName": "", + "fileUrl": "", + "mimeType": "", + "size": null + }, + "marque": "", + "modele": "", + "immatriculation": "", + "typeVehicule": "utilitaire", + "carburant": "diesel", + "annee": 2026, + "utilisateurPrincipalId": null, + "statut": "actif", + "notes": "", + "ongletsVehicule": { + "informationsGenerales": true, + "entretienMaintenance": true, + "coutsAcquisition": true + } + } +} + +{ + "attributMissionConvoi": { + "defunt": { + "label": "Défunt", + "required": true, + "type": "select", + "value": null + }, + "titreMission": { + "label": "Titre de la mission", + "required": false, + "type": "string", + "value": "" + }, + "client": { + "label": "Client (Donneur d'ordre)", + "required": false, + "type": "select", + "value": null + }, + "typeConvoi": { + "label": "Type de convoi", + "required": false, + "type": "select", + "value": "Local" + }, + "lieuDepartConvoi": { + "label": "Lieu de Départ du Convoi", + "required": false, + "type": "object", + "value": { + "mode": "selection_lieu", + "recherche": "", + "lieuId": null, + "adresseManuelle": null + } + }, + "modeTransport": { + "label": "Mode de Transport", + "required": false, + "type": "select", + "value": "Route" + }, + "debutPrevu": { + "label": "Début Prévu", + "required": true, + "type": "datetime-local", + "value": "2026-04-15T10:57" + }, + "finEstimee": { + "label": "Fin Estimée", + "required": false, + "type": "datetime-local", + "value": null + }, + "statut": { + "label": "Statut", + "required": false, + "type": "select", + "value": "Planifié" + }, + "emailFamille": { + "label": "Email famille (pour notifications auto)", + "required": false, + "type": "email", + "value": "" + }, + "notificationsAutomatiques": { + "label": "Notifications automatiques (départ, arrivée, frontière)", + "required": false, + "type": "boolean", + "value": true + } + }, + "attributVehicule": { + "photoVehicule": { + "label": "Photo du véhicule", + "required": false, + "type": "file:image", + "value": null + }, + "marque": { + "label": "Marque", + "required": true, + "type": "select", + "value": null, + "options": [ + "Mercedes-Benz", + "Peugeot", + "Renault", + "Citroën", + "Volkswagen", + "Ford", + "Fiat", + "Opel", + "Toyota", + "Nissan", + "Volvo", + "BMW", + "Audi", + "Iveco", + "Autre" + ] + }, + "modele": { + "label": "Modèle", + "required": true, + "type": "string", + "value": "" + }, + "immatriculation": { + "label": "Immatriculation", + "required": true, + "type": "string", + "value": "" + }, + "typeVehicule": { + "label": "Type véhicule", + "required": false, + "type": "select", + "value": "utilitaire", + "options": [ + "corbillard", + "vehicule_transport", + "utilitaire", + "berline" + ] + }, + "carburant": { + "label": "Carburant", + "required": false, + "type": "select", + "value": "diesel", + "options": [ + "diesel", + "essence", + "electrique", + "hybride" + ] + }, + "annee": { + "label": "Année", + "required": false, + "type": "number", + "value": 2026 + }, + "utilisateurPrincipal": { + "label": "Utilisateur principal", + "required": false, + "type": "select", + "value": null + }, + "statutVehicule": { + "label": "Statut", + "required": false, + "type": "select", + "value": "actif", + "options": [ + "actif", + "en_maintenance", + "hors_service" + ] + }, + "notes": { + "label": "Notes", + "required": false, + "type": "textarea", + "value": "" + } + } +} \ No newline at end of file diff --git a/thanasoft-back/app/Http/Controllers/Api/ConvoyController.php b/thanasoft-back/app/Http/Controllers/Api/ConvoyController.php new file mode 100644 index 0000000..20eb42a --- /dev/null +++ b/thanasoft-back/app/Http/Controllers/Api/ConvoyController.php @@ -0,0 +1,144 @@ +convoyRepository->paginate( + (int) $request->integer('per_page', 15), + $request->only(['search', 'status', 'convoy_type', 'vehicle_id', 'deceased_id', 'sort_by', 'sort_direction']) + ); + + return response()->json([ + 'data' => ConvoyResource::collection($convoys->items()), + 'meta' => [ + 'current_page' => $convoys->currentPage(), + 'last_page' => $convoys->lastPage(), + 'per_page' => $convoys->perPage(), + 'total' => $convoys->total(), + ], + 'status' => 'success', + ]); + } catch (\Exception $e) { + Log::error('Error fetching convoys: ' . $e->getMessage()); + + return response()->json([ + 'message' => 'An error occurred while fetching convoys.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } + + public function store(StoreConvoyRequest $request): JsonResponse + { + try { + $convoy = $this->convoyRepository->create($request->validated()); + + return response()->json([ + 'data' => new ConvoyResource($convoy->load(['deceased', 'client', 'vehicle', 'departureLocation'])), + 'message' => 'Convoy created successfully.', + 'status' => 'success', + ], 201); + } catch (\Exception $e) { + Log::error('Error creating convoy: ' . $e->getMessage()); + + return response()->json([ + 'message' => 'An error occurred while creating the convoy.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } + + public function show(string $id): JsonResponse + { + try { + $convoy = $this->convoyRepository->find((int) $id); + + if (! $convoy) { + return response()->json(['message' => 'Convoy not found.'], 404); + } + + $convoy->load(['deceased', 'client', 'vehicle', 'departureLocation']); + + return response()->json([ + 'data' => new ConvoyResource($convoy), + 'status' => 'success', + ]); + } catch (\Exception $e) { + Log::error('Error fetching convoy: ' . $e->getMessage()); + + return response()->json([ + 'message' => 'An error occurred while fetching the convoy.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } + + public function update(UpdateConvoyRequest $request, string $id): JsonResponse + { + try { + $updated = $this->convoyRepository->update((int) $id, $request->validated()); + + if (! $updated) { + return response()->json(['message' => 'Convoy not found or update failed.'], 404); + } + + $convoy = $this->convoyRepository->find((int) $id); + + return response()->json([ + 'data' => new ConvoyResource($convoy->load(['deceased', 'client', 'vehicle', 'departureLocation'])), + 'message' => 'Convoy updated successfully.', + 'status' => 'success', + ]); + } catch (\Exception $e) { + Log::error('Error updating convoy: ' . $e->getMessage()); + + return response()->json([ + 'message' => 'An error occurred while updating the convoy.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } + + public function destroy(string $id): JsonResponse + { + try { + $deleted = $this->convoyRepository->delete((int) $id); + + if (! $deleted) { + return response()->json(['message' => 'Convoy not found or delete failed.'], 404); + } + + return response()->json([ + 'message' => 'Convoy deleted successfully.', + 'status' => 'success', + ]); + } catch (\Exception $e) { + Log::error('Error deleting convoy: ' . $e->getMessage()); + + return response()->json([ + 'message' => 'An error occurred while deleting the convoy.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } +} diff --git a/thanasoft-back/app/Http/Controllers/Api/VehicleController.php b/thanasoft-back/app/Http/Controllers/Api/VehicleController.php new file mode 100644 index 0000000..ac24ddb --- /dev/null +++ b/thanasoft-back/app/Http/Controllers/Api/VehicleController.php @@ -0,0 +1,144 @@ +vehicleRepository->paginate( + (int) $request->integer('per_page', 15), + $request->only(['search', 'status', 'vehicle_type', 'sort_by', 'sort_direction']) + ); + + return response()->json([ + 'data' => VehicleResource::collection($vehicles->items()), + 'meta' => [ + 'current_page' => $vehicles->currentPage(), + 'last_page' => $vehicles->lastPage(), + 'per_page' => $vehicles->perPage(), + 'total' => $vehicles->total(), + ], + 'status' => 'success', + ]); + } catch (\Exception $e) { + Log::error('Error fetching vehicles: ' . $e->getMessage()); + + return response()->json([ + 'message' => 'An error occurred while fetching vehicles.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } + + public function store(StoreVehicleRequest $request): JsonResponse + { + try { + $vehicle = $this->vehicleRepository->create($request->validated()); + + return response()->json([ + 'data' => new VehicleResource($vehicle->load('primaryUser')), + 'message' => 'Vehicle created successfully.', + 'status' => 'success', + ], 201); + } catch (\Exception $e) { + Log::error('Error creating vehicle: ' . $e->getMessage()); + + return response()->json([ + 'message' => 'An error occurred while creating the vehicle.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } + + public function show(string $id): JsonResponse + { + try { + $vehicle = $this->vehicleRepository->find((int) $id); + + if (! $vehicle) { + return response()->json(['message' => 'Vehicle not found.'], 404); + } + + $vehicle->load(['primaryUser', 'convoys']); + + return response()->json([ + 'data' => new VehicleResource($vehicle), + 'status' => 'success', + ]); + } catch (\Exception $e) { + Log::error('Error fetching vehicle: ' . $e->getMessage()); + + return response()->json([ + 'message' => 'An error occurred while fetching the vehicle.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } + + public function update(UpdateVehicleRequest $request, string $id): JsonResponse + { + try { + $updated = $this->vehicleRepository->update((int) $id, $request->validated()); + + if (! $updated) { + return response()->json(['message' => 'Vehicle not found or update failed.'], 404); + } + + $vehicle = $this->vehicleRepository->find((int) $id); + + return response()->json([ + 'data' => new VehicleResource($vehicle->load('primaryUser')), + 'message' => 'Vehicle updated successfully.', + 'status' => 'success', + ]); + } catch (\Exception $e) { + Log::error('Error updating vehicle: ' . $e->getMessage()); + + return response()->json([ + 'message' => 'An error occurred while updating the vehicle.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } + + public function destroy(string $id): JsonResponse + { + try { + $deleted = $this->vehicleRepository->delete((int) $id); + + if (! $deleted) { + return response()->json(['message' => 'Vehicle not found or delete failed.'], 404); + } + + return response()->json([ + 'message' => 'Vehicle deleted successfully.', + 'status' => 'success', + ]); + } catch (\Exception $e) { + Log::error('Error deleting vehicle: ' . $e->getMessage()); + + return response()->json([ + 'message' => 'An error occurred while deleting the vehicle.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } +} diff --git a/thanasoft-back/app/Http/Requests/StoreConvoyRequest.php b/thanasoft-back/app/Http/Requests/StoreConvoyRequest.php new file mode 100644 index 0000000..6faa002 --- /dev/null +++ b/thanasoft-back/app/Http/Requests/StoreConvoyRequest.php @@ -0,0 +1,41 @@ + ['required', 'exists:deceased,id'], + 'client_id' => ['nullable', 'exists:clients,id'], + 'vehicle_id' => ['nullable', 'exists:vehicles,id'], + 'mission_title' => ['nullable', 'string', 'max:255'], + 'convoy_type' => ['nullable', Rule::in(['local', 'national', 'international'])], + 'transport_mode' => ['nullable', Rule::in(['road', 'air', 'sea', 'rail'])], + 'status' => ['nullable', Rule::in(['planned', 'in_progress', 'completed', 'cancelled'])], + 'planned_start_at' => ['required', 'date'], + 'estimated_end_at' => ['nullable', 'date', 'after_or_equal:planned_start_at'], + 'family_email' => ['nullable', 'email', 'max:255'], + 'automatic_notifications' => ['nullable', 'boolean'], + 'departure_location_selection_mode' => ['nullable', Rule::in(['place', 'manual'])], + 'departure_location_id' => ['nullable', 'exists:client_locations,id'], + 'departure_name' => ['nullable', 'string', 'max:255'], + 'departure_address' => ['nullable', 'string', 'max:255'], + 'departure_city' => ['nullable', 'string', 'max:255'], + 'departure_postal_code' => ['nullable', 'string', 'max:20'], + 'departure_country_code' => ['nullable', 'string', 'size:2'], + 'departure_latitude' => ['nullable', 'numeric', 'between:-90,90'], + 'departure_longitude' => ['nullable', 'numeric', 'between:-180,180'], + 'departure_additional_details' => ['nullable', 'string'], + ]; + } +} diff --git a/thanasoft-back/app/Http/Requests/StoreVehicleRequest.php b/thanasoft-back/app/Http/Requests/StoreVehicleRequest.php new file mode 100644 index 0000000..a94f251 --- /dev/null +++ b/thanasoft-back/app/Http/Requests/StoreVehicleRequest.php @@ -0,0 +1,33 @@ + ['nullable', 'string', 'max:255'], + 'photo_file_url' => ['nullable', 'string', 'max:2048'], + 'photo_mime_type' => ['nullable', 'string', 'max:100'], + 'photo_size' => ['nullable', 'integer', 'min:0'], + 'brand' => ['required', 'string', 'max:255'], + 'model' => ['required', 'string', 'max:255'], + 'registration_number' => ['required', 'string', 'max:255', 'unique:vehicles,registration_number'], + 'vehicle_type' => ['nullable', Rule::in(['hearse', 'transport_vehicle', 'utility', 'sedan'])], + 'fuel_type' => ['nullable', Rule::in(['diesel', 'petrol', 'electric', 'hybrid'])], + 'year' => ['nullable', 'integer', 'min:1900', 'max:2100'], + 'primary_user_id' => ['nullable', 'exists:employees,id'], + 'status' => ['nullable', Rule::in(['active', 'maintenance', 'out_of_service'])], + 'notes' => ['nullable', 'string'], + ]; + } +} diff --git a/thanasoft-back/app/Http/Requests/UpdateConvoyRequest.php b/thanasoft-back/app/Http/Requests/UpdateConvoyRequest.php new file mode 100644 index 0000000..240064e --- /dev/null +++ b/thanasoft-back/app/Http/Requests/UpdateConvoyRequest.php @@ -0,0 +1,51 @@ + ['sometimes', 'required', 'exists:deceased,id'], + 'client_id' => ['nullable', 'exists:clients,id'], + 'vehicle_id' => ['nullable', 'exists:vehicles,id'], + 'mission_title' => ['nullable', 'string', 'max:255'], + 'convoy_type' => ['nullable', Rule::in(['local', 'national', 'international'])], + 'transport_mode' => ['nullable', Rule::in(['road', 'air', 'sea', 'rail'])], + 'status' => ['nullable', Rule::in(['planned', 'in_progress', 'completed', 'cancelled'])], + 'planned_start_at' => ['sometimes', 'required', 'date'], + 'estimated_end_at' => ['nullable', 'date'], + 'family_email' => ['nullable', 'email', 'max:255'], + 'automatic_notifications' => ['nullable', 'boolean'], + 'departure_location_selection_mode' => ['nullable', Rule::in(['place', 'manual'])], + 'departure_location_id' => ['nullable', 'exists:client_locations,id'], + 'departure_name' => ['nullable', 'string', 'max:255'], + 'departure_address' => ['nullable', 'string', 'max:255'], + 'departure_city' => ['nullable', 'string', 'max:255'], + 'departure_postal_code' => ['nullable', 'string', 'max:20'], + 'departure_country_code' => ['nullable', 'string', 'size:2'], + 'departure_latitude' => ['nullable', 'numeric', 'between:-90,90'], + 'departure_longitude' => ['nullable', 'numeric', 'between:-180,180'], + 'departure_additional_details' => ['nullable', 'string'], + 'tab_itinerary' => ['nullable', 'boolean'], + 'tab_legal_documents' => ['nullable', 'boolean'], + 'tab_team_resources' => ['nullable', 'boolean'], + 'tab_procedures' => ['nullable', 'boolean'], + 'tab_cost_tracking' => ['nullable', 'boolean'], + 'tab_fuel' => ['nullable', 'boolean'], + 'tab_ceremony' => ['nullable', 'boolean'], + 'tab_thanatopraxy' => ['nullable', 'boolean'], + 'tab_gps_tracking_steps' => ['nullable', 'boolean'], + 'tab_communication' => ['nullable', 'boolean'], + ]; + } +} diff --git a/thanasoft-back/app/Http/Requests/UpdateVehicleRequest.php b/thanasoft-back/app/Http/Requests/UpdateVehicleRequest.php new file mode 100644 index 0000000..44a1fd3 --- /dev/null +++ b/thanasoft-back/app/Http/Requests/UpdateVehicleRequest.php @@ -0,0 +1,35 @@ +route('vehicle') ?? $this->route('id'); + + return [ + 'photo_file_name' => ['nullable', 'string', 'max:255'], + 'photo_file_url' => ['nullable', 'string', 'max:2048'], + 'photo_mime_type' => ['nullable', 'string', 'max:100'], + 'photo_size' => ['nullable', 'integer', 'min:0'], + 'brand' => ['sometimes', 'required', 'string', 'max:255'], + 'model' => ['sometimes', 'required', 'string', 'max:255'], + 'registration_number' => ['sometimes', 'required', 'string', 'max:255', Rule::unique('vehicles', 'registration_number')->ignore($vehicleId)], + 'vehicle_type' => ['nullable', Rule::in(['hearse', 'transport_vehicle', 'utility', 'sedan'])], + 'fuel_type' => ['nullable', Rule::in(['diesel', 'petrol', 'electric', 'hybrid'])], + 'year' => ['nullable', 'integer', 'min:1900', 'max:2100'], + 'primary_user_id' => ['nullable', 'exists:employees,id'], + 'status' => ['nullable', Rule::in(['active', 'maintenance', 'out_of_service'])], + 'notes' => ['nullable', 'string'], + ]; + } +} diff --git a/thanasoft-back/app/Http/Resources/Convoy/ConvoyResource.php b/thanasoft-back/app/Http/Resources/Convoy/ConvoyResource.php new file mode 100644 index 0000000..3f8bae5 --- /dev/null +++ b/thanasoft-back/app/Http/Resources/Convoy/ConvoyResource.php @@ -0,0 +1,79 @@ + $this->id, + 'deceased_id' => $this->deceased_id, + 'client_id' => $this->client_id, + 'vehicle_id' => $this->vehicle_id, + 'mission_title' => $this->mission_title, + 'convoy_type' => $this->convoy_type, + 'transport_mode' => $this->transport_mode, + 'status' => $this->status, + 'planned_start_at' => $this->planned_start_at?->format('Y-m-d H:i:s'), + 'estimated_end_at' => $this->estimated_end_at?->format('Y-m-d H:i:s'), + 'family_email' => $this->family_email, + 'automatic_notifications' => $this->automatic_notifications, + 'departure' => [ + 'location_selection_mode' => $this->departure_location_selection_mode, + 'location_id' => $this->departure_location_id, + 'location' => $this->whenLoaded('departureLocation', function () { + return $this->departureLocation ? [ + 'id' => $this->departureLocation->id, + 'client_id' => $this->departureLocation->client_id, + 'name' => $this->departureLocation->name, + 'address_line1' => $this->departureLocation->address_line1, + 'address_line2' => $this->departureLocation->address_line2, + 'postal_code' => $this->departureLocation->postal_code, + 'city' => $this->departureLocation->city, + 'country_code' => $this->departureLocation->country_code, + 'gps_lat' => $this->departureLocation->gps_lat, + 'gps_lng' => $this->departureLocation->gps_lng, + ] : null; + }), + 'name' => $this->departure_name, + 'address' => $this->departure_address, + 'city' => $this->departure_city, + 'postal_code' => $this->departure_postal_code, + 'country_code' => $this->departure_country_code, + 'latitude' => $this->departure_latitude, + 'longitude' => $this->departure_longitude, + 'additional_details' => $this->departure_additional_details, + ], + 'tabs' => [ + 'itinerary' => $this->tab_itinerary, + 'legal_documents' => $this->tab_legal_documents, + 'team_resources' => $this->tab_team_resources, + 'procedures' => $this->tab_procedures, + 'cost_tracking' => $this->tab_cost_tracking, + 'fuel' => $this->tab_fuel, + 'ceremony' => $this->tab_ceremony, + 'thanatopraxy' => $this->tab_thanatopraxy, + 'gps_tracking_steps' => $this->tab_gps_tracking_steps, + 'communication' => $this->tab_communication, + ], + 'deceased' => $this->whenLoaded('deceased', function () { + return new DeceasedResource($this->deceased); + }), + 'client' => $this->whenLoaded('client', function () { + return $this->client ? new ClientResource($this->client) : null; + }), + 'vehicle' => $this->whenLoaded('vehicle', function () { + return $this->vehicle ? new VehicleResource($this->vehicle) : null; + }), + 'created_at' => $this->created_at?->format('Y-m-d H:i:s'), + 'updated_at' => $this->updated_at?->format('Y-m-d H:i:s'), + ]; + } +} diff --git a/thanasoft-back/app/Http/Resources/Vehicle/VehicleResource.php b/thanasoft-back/app/Http/Resources/Vehicle/VehicleResource.php new file mode 100644 index 0000000..002074a --- /dev/null +++ b/thanasoft-back/app/Http/Resources/Vehicle/VehicleResource.php @@ -0,0 +1,42 @@ + $this->id, + 'photo' => [ + 'file_name' => $this->photo_file_name, + 'file_url' => $this->photo_file_url, + 'mime_type' => $this->photo_mime_type, + 'size' => $this->photo_size, + ], + 'brand' => $this->brand, + 'model' => $this->model, + 'registration_number' => $this->registration_number, + 'vehicle_type' => $this->vehicle_type, + 'fuel_type' => $this->fuel_type, + 'year' => $this->year, + 'status' => $this->status, + 'notes' => $this->notes, + 'primary_user_id' => $this->primary_user_id, + 'primary_user' => $this->whenLoaded('primaryUser', function () { + return $this->primaryUser ? [ + 'id' => $this->primaryUser->id, + 'first_name' => $this->primaryUser->first_name, + 'last_name' => $this->primaryUser->last_name, + 'full_name' => $this->primaryUser->full_name, + 'email' => $this->primaryUser->email, + ] : null; + }), + 'created_at' => $this->created_at?->format('Y-m-d H:i:s'), + 'updated_at' => $this->updated_at?->format('Y-m-d H:i:s'), + ]; + } +} diff --git a/thanasoft-back/app/Models/Client.php b/thanasoft-back/app/Models/Client.php index 16e8b2e..2e7ad2f 100644 --- a/thanasoft-back/app/Models/Client.php +++ b/thanasoft-back/app/Models/Client.php @@ -66,6 +66,11 @@ class Client extends Model return $this->belongsTo(ClientGroup::class, 'group_id'); } + public function convoys() + { + return $this->hasMany(Convoy::class); + } + /** * Get the human-readable label for the client type. */ diff --git a/thanasoft-back/app/Models/Convoy.php b/thanasoft-back/app/Models/Convoy.php new file mode 100644 index 0000000..4683ced --- /dev/null +++ b/thanasoft-back/app/Models/Convoy.php @@ -0,0 +1,64 @@ + 'datetime', + 'estimated_end_at' => 'datetime', + 'automatic_notifications' => 'boolean', + 'departure_latitude' => 'decimal:7', + 'departure_longitude' => 'decimal:7', + ]; + + public function deceased(): BelongsTo + { + return $this->belongsTo(Deceased::class); + } + + public function client(): BelongsTo + { + return $this->belongsTo(Client::class); + } + + public function vehicle(): BelongsTo + { + return $this->belongsTo(Vehicle::class); + } + + public function departureLocation(): BelongsTo + { + return $this->belongsTo(ClientLocation::class, 'departure_location_id'); + } +} diff --git a/thanasoft-back/app/Models/Deceased.php b/thanasoft-back/app/Models/Deceased.php index 98f8515..51ce87e 100644 --- a/thanasoft-back/app/Models/Deceased.php +++ b/thanasoft-back/app/Models/Deceased.php @@ -57,6 +57,11 @@ class Deceased extends Model return $this->hasMany(Intervention::class); } + public function convoys(): HasMany + { + return $this->hasMany(Convoy::class); + } + /** * Get the file attachments for the deceased (polymorphic). */ diff --git a/thanasoft-back/app/Models/Employee.php b/thanasoft-back/app/Models/Employee.php index 8550797..5adb6f5 100644 --- a/thanasoft-back/app/Models/Employee.php +++ b/thanasoft-back/app/Models/Employee.php @@ -53,6 +53,11 @@ class Employee extends Model return $this->belongsTo(User::class); } + public function vehicles(): HasMany + { + return $this->hasMany(Vehicle::class, 'primary_user_id'); + } + /** * Get the full name of the employee. */ diff --git a/thanasoft-back/app/Models/Vehicle.php b/thanasoft-back/app/Models/Vehicle.php new file mode 100644 index 0000000..bbe7c0e --- /dev/null +++ b/thanasoft-back/app/Models/Vehicle.php @@ -0,0 +1,44 @@ + 'integer', + 'year' => 'integer', + ]; + + public function primaryUser(): BelongsTo + { + return $this->belongsTo(Employee::class, 'primary_user_id'); + } + + public function convoys(): HasMany + { + return $this->hasMany(Convoy::class); + } +} diff --git a/thanasoft-back/app/Providers/AppServiceProvider.php b/thanasoft-back/app/Providers/AppServiceProvider.php index 97ea76e..53eb5c6 100644 --- a/thanasoft-back/app/Providers/AppServiceProvider.php +++ b/thanasoft-back/app/Providers/AppServiceProvider.php @@ -116,6 +116,8 @@ class AppServiceProvider extends ServiceProvider $this->app->bind(\App\Repositories\PurchaseOrderRepositoryInterface::class, \App\Repositories\PurchaseOrderRepository::class); $this->app->bind(\App\Repositories\DeceasedDocumentRepositoryInterface::class, \App\Repositories\DeceasedDocumentRepository::class); + $this->app->bind(\App\Repositories\VehicleRepositoryInterface::class, \App\Repositories\VehicleRepository::class); + $this->app->bind(\App\Repositories\ConvoyRepositoryInterface::class, \App\Repositories\ConvoyRepository::class); } diff --git a/thanasoft-back/app/Repositories/ConvoyRepository.php b/thanasoft-back/app/Repositories/ConvoyRepository.php new file mode 100644 index 0000000..8e648ad --- /dev/null +++ b/thanasoft-back/app/Repositories/ConvoyRepository.php @@ -0,0 +1,51 @@ +model->newQuery()->with(['deceased', 'client', 'vehicle', 'departureLocation']); + + if (! empty($filters['search'])) { + $query->where(function ($q) use ($filters) { + $q->where('mission_title', 'like', '%' . $filters['search'] . '%') + ->orWhere('family_email', 'like', '%' . $filters['search'] . '%') + ->orWhere('departure_name', 'like', '%' . $filters['search'] . '%') + ->orWhere('departure_city', 'like', '%' . $filters['search'] . '%'); + }); + } + + if (! empty($filters['status'])) { + $query->where('status', $filters['status']); + } + + if (! empty($filters['convoy_type'])) { + $query->where('convoy_type', $filters['convoy_type']); + } + + if (! empty($filters['vehicle_id'])) { + $query->where('vehicle_id', $filters['vehicle_id']); + } + + if (! empty($filters['deceased_id'])) { + $query->where('deceased_id', $filters['deceased_id']); + } + + $sortField = $filters['sort_by'] ?? 'planned_start_at'; + $sortDirection = $filters['sort_direction'] ?? 'desc'; + + return $query->orderBy($sortField, $sortDirection)->paginate($perPage); + } +} diff --git a/thanasoft-back/app/Repositories/ConvoyRepositoryInterface.php b/thanasoft-back/app/Repositories/ConvoyRepositoryInterface.php new file mode 100644 index 0000000..7bd753c --- /dev/null +++ b/thanasoft-back/app/Repositories/ConvoyRepositoryInterface.php @@ -0,0 +1,12 @@ +model->newQuery()->with(['primaryUser', 'convoys']); + + if (! empty($filters['search'])) { + $query->where(function ($q) use ($filters) { + $q->where('brand', 'like', '%' . $filters['search'] . '%') + ->orWhere('model', 'like', '%' . $filters['search'] . '%') + ->orWhere('registration_number', 'like', '%' . $filters['search'] . '%'); + }); + } + + if (! empty($filters['status'])) { + $query->where('status', $filters['status']); + } + + if (! empty($filters['vehicle_type'])) { + $query->where('vehicle_type', $filters['vehicle_type']); + } + + $sortField = $filters['sort_by'] ?? 'created_at'; + $sortDirection = $filters['sort_direction'] ?? 'desc'; + + return $query->orderBy($sortField, $sortDirection)->paginate($perPage); + } +} diff --git a/thanasoft-back/app/Repositories/VehicleRepositoryInterface.php b/thanasoft-back/app/Repositories/VehicleRepositoryInterface.php new file mode 100644 index 0000000..5008812 --- /dev/null +++ b/thanasoft-back/app/Repositories/VehicleRepositoryInterface.php @@ -0,0 +1,12 @@ +id(); + $table->string('photo_file_name')->nullable(); + $table->string('photo_file_url')->nullable(); + $table->string('photo_mime_type')->nullable(); + $table->unsignedBigInteger('photo_size')->nullable(); + $table->string('brand'); + $table->string('model'); + $table->string('registration_number')->unique(); + $table->enum('vehicle_type', ['hearse', 'transport_vehicle', 'utility', 'sedan'])->default('utility'); + $table->enum('fuel_type', ['diesel', 'petrol', 'electric', 'hybrid'])->default('diesel'); + $table->unsignedSmallInteger('year')->nullable(); + $table->foreignId('primary_user_id')->nullable()->constrained('employees')->nullOnDelete(); + $table->enum('status', ['active', 'maintenance', 'out_of_service'])->default('active'); + $table->text('notes')->nullable(); + $table->timestamps(); + + $table->index(['brand']); + $table->index(['status']); + $table->index(['primary_user_id']); + }); + + Schema::create('convoys', function (Blueprint $table) { + $table->id(); + $table->foreignId('deceased_id')->constrained('deceased')->cascadeOnDelete(); + $table->foreignId('client_id')->nullable()->constrained('clients')->nullOnDelete(); + $table->foreignId('vehicle_id')->nullable()->constrained('vehicles')->nullOnDelete(); + $table->string('mission_title')->nullable(); + $table->enum('convoy_type', ['local', 'national', 'international'])->default('local'); + $table->enum('transport_mode', ['road', 'air', 'sea', 'rail'])->default('road'); + $table->enum('status', ['planned', 'in_progress', 'completed', 'cancelled'])->default('planned'); + $table->dateTime('planned_start_at'); + $table->dateTime('estimated_end_at')->nullable(); + $table->string('family_email')->nullable(); + $table->boolean('automatic_notifications')->default(true); + $table->enum('departure_location_selection_mode', ['place', 'manual'])->default('place'); + $table->unsignedBigInteger('departure_location_id')->nullable(); + $table->string('departure_name')->nullable(); + $table->string('departure_address')->nullable(); + $table->string('departure_city')->nullable(); + $table->string('departure_postal_code', 20)->nullable(); + $table->string('departure_country_code', 2)->nullable(); + $table->decimal('departure_latitude', 10, 7)->nullable(); + $table->decimal('departure_longitude', 10, 7)->nullable(); + $table->text('departure_additional_details')->nullable(); + $table->timestamps(); + + $table->index(['deceased_id']); + $table->index(['client_id']); + $table->index(['vehicle_id']); + $table->index(['status']); + $table->index(['planned_start_at']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('convoys'); + Schema::dropIfExists('vehicles'); + } +}; diff --git a/thanasoft-back/routes/api.php b/thanasoft-back/routes/api.php index 3795d90..33d4099 100644 --- a/thanasoft-back/routes/api.php +++ b/thanasoft-back/routes/api.php @@ -25,6 +25,8 @@ use App\Http\Controllers\Api\PriceListController; use App\Http\Controllers\Api\TvaRateController; use App\Http\Controllers\Api\GoodsReceiptController; use App\Http\Controllers\Api\UserController; +use App\Http\Controllers\Api\VehicleController; +use App\Http\Controllers\Api\ConvoyController; /* @@ -121,6 +123,8 @@ Route::middleware('auth:sanctum')->group(function () { // Goods Receipts management Route::apiResource('goods-receipts', GoodsReceiptController::class); + Route::apiResource('vehicles', VehicleController::class); + Route::apiResource('convoys', ConvoyController::class); // Product Category management Route::get('/product-categories/search', [ProductCategoryController::class, 'search']); diff --git a/thanasoft-front/src/components/Organism/CRM/lieux/ListeLieuxPresentation.vue b/thanasoft-front/src/components/Organism/CRM/lieux/ListeLieuxPresentation.vue index efa1d9d..d6cb9a2 100644 --- a/thanasoft-front/src/components/Organism/CRM/lieux/ListeLieuxPresentation.vue +++ b/thanasoft-front/src/components/Organism/CRM/lieux/ListeLieuxPresentation.vue @@ -1,7 +1,7 @@