From a51e05559a1b77a59d6b1a381c6df34ea775dbdd Mon Sep 17 00:00:00 2001 From: Nyavokevin <42602932+nyavokevin@users.noreply.github.com> Date: Fri, 21 Nov 2025 17:26:43 +0300 Subject: [PATCH] assigner thanato --- .../Api/InterventionController.php | 62 +++++ .../Api/ThanatopractitionerController.php | 35 +++ .../Requests/StoreInterventionRequest.php | 12 +- .../StoreInterventionWithAllDataRequest.php | 12 +- .../Requests/UpdateInterventionRequest.php | 12 +- .../Intervention/InterventionResource.php | 8 +- thanasoft-back/app/Models/Intervention.php | 40 ++- .../app/Models/Thanatopractitioner.php | 33 +++ .../Repositories/InterventionRepository.php | 2 +- .../ThanatopractitionerRepository.php | 16 ++ ...ThanatopractitionerRepositoryInterface.php | 9 + thanasoft-back/config/cors.php | 2 +- ...create_intervention_practitioner_table.php | 38 +++ ...actitioner_id_from_interventions_table.php | 33 +++ thanasoft-back/routes/api.php | 2 + .../InterventionDetailPresentation.vue | 22 +- .../intervention/AddPractitionerModal.vue | 112 ++++++++ .../InterventionDetailContent.vue | 43 ++-- .../InterventionDetailSidebar.vue | 2 +- .../Interventions/interventionDetails.vue | 2 +- .../intervention/AssignPractitionerModal.vue | 241 ++++++++++++++++++ .../PractitionerSearchInput.vue | 131 ++++++++++ thanasoft-front/src/services/intervention.ts | 52 +++- .../src/services/thanatopractitioner.ts | 4 +- .../src/stores/interventionStore.ts | 82 +++++- .../Interventions/InterventionDetails.vue | 32 ++- thanasoft-front/src/views/pages/Login.vue | 6 +- 27 files changed, 985 insertions(+), 60 deletions(-) create mode 100644 thanasoft-back/database/migrations/2025_11_19_131200_create_intervention_practitioner_table.php create mode 100644 thanasoft-back/database/migrations/2025_11_19_131500_remove_assigned_practitioner_id_from_interventions_table.php create mode 100644 thanasoft-front/src/components/Organism/Interventions/intervention/AddPractitionerModal.vue create mode 100644 thanasoft-front/src/components/molecules/intervention/AssignPractitionerModal.vue create mode 100644 thanasoft-front/src/components/molecules/thanatopractitioner/PractitionerSearchInput.vue diff --git a/thanasoft-back/app/Http/Controllers/Api/InterventionController.php b/thanasoft-back/app/Http/Controllers/Api/InterventionController.php index cec39f5..3080c46 100644 --- a/thanasoft-back/app/Http/Controllers/Api/InterventionController.php +++ b/thanasoft-back/app/Http/Controllers/Api/InterventionController.php @@ -359,4 +359,66 @@ class InterventionController extends Controller ], Response::HTTP_INTERNAL_SERVER_ERROR); } } + + /** + * Assign a practitioner to an intervention + * + * @param Request $request + * @param int $id + * @return JsonResponse + */ + public function assignPractitioner(Request $request, int $id): JsonResponse + { + try { + $validated = $request->validate([ + 'principal_practitioner_id' => 'nullable|integer|exists:thanatopractitioners,id', + 'assistant_practitioner_ids' => 'nullable|array', + 'assistant_practitioner_ids.*' => 'integer|exists:thanatopractitioners,id', + ]); + + $intervention = $this->interventionRepository->findById($id); + + if (!$intervention) { + return response()->json([ + 'message' => 'Intervention non trouvée.' + ], Response::HTTP_NOT_FOUND); + } + + // Sync practitioners with their roles + $practitioners = []; + + if (isset($validated['principal_practitioner_id'])) { + $practitioners[$validated['principal_practitioner_id']] = ['role' => 'principal']; + } + + if (isset($validated['assistant_practitioner_ids'])) { + foreach ($validated['assistant_practitioner_ids'] as $assistantId) { + $practitioners[$assistantId] = ['role' => 'assistant']; + } + } + + // Sync the practitioners (this will replace existing assignments) + $intervention->practitioners()->sync($practitioners); + + // Reload the intervention with relationships + $intervention = $this->interventionRepository->findById($id); + + return response()->json([ + 'data' => new InterventionResource($intervention), + 'message' => 'Praticien(s) assigné(s) avec succès.' + ], Response::HTTP_OK); + + } catch (\Exception $e) { + Log::error('Error assigning practitioner to intervention: ' . $e->getMessage(), [ + 'intervention_id' => $id, + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return response()->json([ + 'message' => 'Une erreur est survenue lors de l\'assignation du praticien.', + 'error' => $e->getMessage() + ], Response::HTTP_INTERNAL_SERVER_ERROR); + } + } } diff --git a/thanasoft-back/app/Http/Controllers/Api/ThanatopractitionerController.php b/thanasoft-back/app/Http/Controllers/Api/ThanatopractitionerController.php index 2d06d98..00b5c9d 100644 --- a/thanasoft-back/app/Http/Controllers/Api/ThanatopractitionerController.php +++ b/thanasoft-back/app/Http/Controllers/Api/ThanatopractitionerController.php @@ -258,6 +258,41 @@ class ThanatopractitionerController extends Controller } } + /** + * Search thanatopractitioners by employee name. + */ + public function searchByEmployeeName(Request $request): JsonResponse + { + try { + $query = $request->get('query', ''); + + if (strlen($query) < 2) { + return response()->json([ + 'data' => [], + 'message' => 'Veuillez entrer au moins 2 caractères pour la recherche.', + ], 200); + } + + $thanatopractitioners = $this->thanatopractitionerRepository->searchByEmployeeName($query); + + return response()->json([ + 'data' => new ThanatopractitionerCollection($thanatopractitioners), + 'message' => 'Recherche effectuée avec succès.', + ], 200); + } catch (\Exception $e) { + Log::error('Error searching thanatopractitioners by employee name: ' . $e->getMessage(), [ + 'exception' => $e, + 'trace' => $e->getTraceAsString(), + 'query' => $request->get('query'), + ]); + + return response()->json([ + 'message' => 'Une erreur est survenue lors de la recherche.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } + /** * Update the specified thanatopractitioner. */ diff --git a/thanasoft-back/app/Http/Requests/StoreInterventionRequest.php b/thanasoft-back/app/Http/Requests/StoreInterventionRequest.php index 3298451..a63a6a7 100644 --- a/thanasoft-back/app/Http/Requests/StoreInterventionRequest.php +++ b/thanasoft-back/app/Http/Requests/StoreInterventionRequest.php @@ -45,7 +45,11 @@ class StoreInterventionRequest extends FormRequest 'termine', 'annule' ])], - 'assigned_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'], + 'practitioners' => ['nullable', 'array'], + 'practitioners.*' => ['exists:thanatopractitioners,id'], + 'principal_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'], + 'assistant_practitioner_ids' => ['nullable', 'array'], + 'assistant_practitioner_ids.*' => ['exists:thanatopractitioners,id'], 'notes' => ['nullable', 'string'], 'created_by' => ['nullable', 'exists:users,id'] ]; @@ -68,7 +72,11 @@ class StoreInterventionRequest extends FormRequest 'duration_min.integer' => 'La durée doit être un nombre entier.', 'duration_min.min' => 'La durée ne peut pas être négative.', 'status.in' => 'Le statut de l\'intervention est invalide.', - 'assigned_practitioner_id.exists' => 'Le praticien sélectionné est invalide.', + 'practitioners.array' => 'Les praticiens doivent être un tableau.', + 'practitioners.*.exists' => 'Un des praticiens sélectionnés est invalide.', + 'principal_practitioner_id.exists' => 'Le praticien principal sélectionné est invalide.', + 'assistant_practitioner_ids.array' => 'Les praticiens assistants doivent être un tableau.', + 'assistant_practitioner_ids.*.exists' => 'Un des praticiens assistants est invalide.', 'created_by.exists' => 'L\'utilisateur créateur est invalide.' ]; } diff --git a/thanasoft-back/app/Http/Requests/StoreInterventionWithAllDataRequest.php b/thanasoft-back/app/Http/Requests/StoreInterventionWithAllDataRequest.php index ac0dffd..940e878 100644 --- a/thanasoft-back/app/Http/Requests/StoreInterventionWithAllDataRequest.php +++ b/thanasoft-back/app/Http/Requests/StoreInterventionWithAllDataRequest.php @@ -83,7 +83,11 @@ class StoreInterventionWithAllDataRequest extends FormRequest 'termine', 'annule' ])], - 'intervention.assigned_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'], + 'intervention.practitioners' => ['nullable', 'array'], + 'intervention.practitioners.*' => ['exists:thanatopractitioners,id'], + 'intervention.principal_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'], + 'intervention.assistant_practitioner_ids' => ['nullable', 'array'], + 'intervention.assistant_practitioner_ids.*' => ['exists:thanatopractitioners,id'], 'intervention.order_giver' => ['nullable', 'string', 'max:255'], 'intervention.notes' => ['nullable', 'string'], 'intervention.created_by' => ['nullable', 'exists:users,id'] @@ -131,7 +135,11 @@ class StoreInterventionWithAllDataRequest extends FormRequest 'intervention.duration_min.integer' => 'La durée doit être un nombre entier.', 'intervention.duration_min.min' => 'La durée ne peut pas être négative.', 'intervention.status.in' => 'Le statut de l\'intervention est invalide.', - 'intervention.assigned_practitioner_id.exists' => 'Le praticien sélectionné est invalide.', + 'intervention.practitioners.array' => 'Les praticiens doivent être un tableau.', + 'intervention.practitioners.*.exists' => 'Un des praticiens sélectionnés est invalide.', + 'intervention.principal_practitioner_id.exists' => 'Le praticien principal sélectionné est invalide.', + 'intervention.assistant_practitioner_ids.array' => 'Les praticiens assistants doivent être un tableau.', + 'intervention.assistant_practitioner_ids.*.exists' => 'Un des praticiens assistants est invalide.', 'intervention.order_giver.max' => 'Le donneur d\'ordre ne peut pas dépasser 255 caractères.', 'intervention.created_by.exists' => 'L\'utilisateur créateur est invalide.' ]; diff --git a/thanasoft-back/app/Http/Requests/UpdateInterventionRequest.php b/thanasoft-back/app/Http/Requests/UpdateInterventionRequest.php index 4f5a99a..b9e27e1 100644 --- a/thanasoft-back/app/Http/Requests/UpdateInterventionRequest.php +++ b/thanasoft-back/app/Http/Requests/UpdateInterventionRequest.php @@ -45,7 +45,11 @@ class UpdateInterventionRequest extends FormRequest 'termine', 'annule' ])], - 'assigned_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'], + 'practitioners' => ['nullable', 'array'], + 'practitioners.*' => ['exists:thanatopractitioners,id'], + 'principal_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'], + 'assistant_practitioner_ids' => ['nullable', 'array'], + 'assistant_practitioner_ids.*' => ['exists:thanatopractitioners,id'], 'notes' => ['nullable', 'string'], 'created_by' => ['nullable', 'exists:users,id'] ]; @@ -68,7 +72,11 @@ class UpdateInterventionRequest extends FormRequest 'duration_min.integer' => 'La durée doit être un nombre entier.', 'duration_min.min' => 'La durée ne peut pas être négative.', 'status.in' => 'Le statut de l\'intervention est invalide.', - 'assigned_practitioner_id.exists' => 'Le praticien sélectionné est invalide.', + 'practitioners.array' => 'Les praticiens doivent être un tableau.', + 'practitioners.*.exists' => 'Un des praticiens sélectionnés est invalide.', + 'principal_practitioner_id.exists' => 'Le praticien principal sélectionné est invalide.', + 'assistant_practitioner_ids.array' => 'Les praticiens assistants doivent être un tableau.', + 'assistant_practitioner_ids.*.exists' => 'Un des praticiens assistants est invalide.', 'created_by.exists' => 'L\'utilisateur créateur est invalide.' ]; } diff --git a/thanasoft-back/app/Http/Resources/Intervention/InterventionResource.php b/thanasoft-back/app/Http/Resources/Intervention/InterventionResource.php index 0b9a040..17dbda5 100644 --- a/thanasoft-back/app/Http/Resources/Intervention/InterventionResource.php +++ b/thanasoft-back/app/Http/Resources/Intervention/InterventionResource.php @@ -36,8 +36,12 @@ class InterventionResource extends JsonResource 'scheduled_at' => $this->scheduled_at ? $this->scheduled_at->format('Y-m-d H:i:s') : null, 'duration_min' => $this->duration_min, 'status' => $this->status, - 'assigned_practitioner' => $this->whenLoaded('assignedPractitioner', function () { - return new ThanatopractitionerResource($this->assignedPractitioner); + 'practitioners' => $this->whenLoaded('practitioners', function () { + return ThanatopractitionerResource::collection($this->practitioners); + }), + 'principal_practitioner' => $this->whenLoaded('practitioners', function () { + $principal = $this->practitioners->where('pivot.role', 'principal')->first(); + return $principal ? new ThanatopractitionerResource($principal) : null; }), 'attachments_count' => $this->attachments_count, 'notes' => $this->notes, diff --git a/thanasoft-back/app/Models/Intervention.php b/thanasoft-back/app/Models/Intervention.php index 7470b99..bb40846 100644 --- a/thanasoft-back/app/Models/Intervention.php +++ b/thanasoft-back/app/Models/Intervention.php @@ -5,6 +5,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; class Intervention extends Model @@ -25,7 +26,6 @@ class Intervention extends Model 'scheduled_at', 'duration_min', 'status', - 'assigned_practitioner_id', 'attachments_count', 'notes', 'created_by' @@ -66,11 +66,43 @@ class Intervention extends Model } /** - * Get the practitioner assigned to the intervention. + * Get the practitioners assigned to the intervention. */ - public function assignedPractitioner(): BelongsTo + public function practitioners(): BelongsToMany { - return $this->belongsTo(Thanatopractitioner::class, 'assigned_practitioner_id'); + return $this->belongsToMany(Thanatopractitioner::class, 'intervention_practitioner', 'intervention_id', 'practitioner_id') + ->withPivot('role', 'assigned_at') + ->withTimestamps(); + } + + /** + * Alias for practitioners relationship (for backward compatibility). + */ + public function assignedPractitioner(): BelongsToMany + { + return $this->practitioners(); + } + + /** + * Get the principal practitioner assigned to the intervention. + */ + public function principalPractitioner(): BelongsToMany + { + return $this->belongsToMany(Thanatopractitioner::class, 'intervention_practitioner', 'intervention_id', 'practitioner_id') + ->wherePivot('role', 'principal') + ->withPivot('role', 'assigned_at') + ->withTimestamps(); + } + + /** + * Get the assistant practitioners assigned to the intervention. + */ + public function assistantPractitioners(): BelongsToMany + { + return $this->belongsToMany(Thanatopractitioner::class, 'intervention_practitioner', 'intervention_id', 'practitioner_id') + ->wherePivot('role', 'assistant') + ->withPivot('role', 'assigned_at') + ->withTimestamps(); } /** diff --git a/thanasoft-back/app/Models/Thanatopractitioner.php b/thanasoft-back/app/Models/Thanatopractitioner.php index df6ca07..80e8267 100644 --- a/thanasoft-back/app/Models/Thanatopractitioner.php +++ b/thanasoft-back/app/Models/Thanatopractitioner.php @@ -5,6 +5,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; class Thanatopractitioner extends Model @@ -55,6 +56,38 @@ class Thanatopractitioner extends Model return $this->hasMany(PractitionerDocument::class, 'practitioner_id'); } + /** + * Get the interventions assigned to the thanatopractitioner. + */ + public function interventions(): BelongsToMany + { + return $this->belongsToMany(Intervention::class, 'intervention_practitioner') + ->withPivot('role', 'assigned_at') + ->withTimestamps(); + } + + /** + * Get the interventions where this practitioner is the principal. + */ + public function principalInterventions(): BelongsToMany + { + return $this->belongsToMany(Intervention::class, 'intervention_practitioner') + ->wherePivot('role', 'principal') + ->withPivot('role', 'assigned_at') + ->withTimestamps(); + } + + /** + * Get the interventions where this practitioner is an assistant. + */ + public function assistantInterventions(): BelongsToMany + { + return $this->belongsToMany(Intervention::class, 'intervention_practitioner') + ->wherePivot('role', 'assistant') + ->withPivot('role', 'assigned_at') + ->withTimestamps(); + } + /** * Scope a query to only include practitioners with valid authorization. */ diff --git a/thanasoft-back/app/Repositories/InterventionRepository.php b/thanasoft-back/app/Repositories/InterventionRepository.php index b1e8c9d..6dd756c 100644 --- a/thanasoft-back/app/Repositories/InterventionRepository.php +++ b/thanasoft-back/app/Repositories/InterventionRepository.php @@ -73,7 +73,7 @@ class InterventionRepository implements InterventionRepositoryInterface 'client', 'deceased', 'location', - 'assignedPractitioner', + 'practitioners', 'attachments', 'notifications' ])->findOrFail($id); diff --git a/thanasoft-back/app/Repositories/ThanatopractitionerRepository.php b/thanasoft-back/app/Repositories/ThanatopractitionerRepository.php index f199753..c73cbd9 100644 --- a/thanasoft-back/app/Repositories/ThanatopractitionerRepository.php +++ b/thanasoft-back/app/Repositories/ThanatopractitionerRepository.php @@ -132,4 +132,20 @@ class ThanatopractitionerRepository extends BaseRepository implements Thanatopra 'with_documents' => $this->model->newQuery()->has('documents')->count(), ]; } + + /** + * Search thanatopractitioners by employee name. + */ + public function searchByEmployeeName(string $query): Collection + { + return $this->model->newQuery() + ->with(['employee']) + ->whereHas('employee', function ($q) use ($query) { + $q->where('first_name', 'LIKE', "%{$query}%") + ->orWhere('last_name', 'LIKE', "%{$query}%") + ->orWhereRaw("CONCAT(first_name, ' ', last_name) LIKE ?", ["%{$query}%"]); + }) + ->limit(10) + ->get(); + } } diff --git a/thanasoft-back/app/Repositories/ThanatopractitionerRepositoryInterface.php b/thanasoft-back/app/Repositories/ThanatopractitionerRepositoryInterface.php index 4e12187..01849f9 100644 --- a/thanasoft-back/app/Repositories/ThanatopractitionerRepositoryInterface.php +++ b/thanasoft-back/app/Repositories/ThanatopractitionerRepositoryInterface.php @@ -70,5 +70,14 @@ interface ThanatopractitionerRepositoryInterface * * @return array */ + + /** + * Search thanatopractitioners by employee name. + * + * @param string $query + * @return Collection + */ + public function searchByEmployeeName(string $query): Collection; + public function getStatistics(): array; } diff --git a/thanasoft-back/config/cors.php b/thanasoft-back/config/cors.php index 9c420ff..c718056 100644 --- a/thanasoft-back/config/cors.php +++ b/thanasoft-back/config/cors.php @@ -9,7 +9,7 @@ return [ // IMPORTANT: Do NOT use '*' when sending credentials. List explicit origins. // Set FRONTEND_URL in .env to override the default if needed. - 'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:8080', 'http://localhost:8081')], + 'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:8081')], // Alternatively, use patterns (kept empty for clarity) 'allowed_origins_patterns' => [], diff --git a/thanasoft-back/database/migrations/2025_11_19_131200_create_intervention_practitioner_table.php b/thanasoft-back/database/migrations/2025_11_19_131200_create_intervention_practitioner_table.php new file mode 100644 index 0000000..508a028 --- /dev/null +++ b/thanasoft-back/database/migrations/2025_11_19_131200_create_intervention_practitioner_table.php @@ -0,0 +1,38 @@ +id(); + $table->foreignId('intervention_id')->constrained('interventions')->onDelete('cascade'); + $table->foreignId('practitioner_id')->constrained('thanatopractitioners')->onDelete('cascade'); + $table->enum('role', ['principal', 'assistant'])->default('principal'); + $table->timestamp('assigned_at')->useCurrent(); + $table->timestamps(); + + // Unique constraint to prevent duplicate assignments + $table->unique(['intervention_id', 'practitioner_id']); + + // Indexes for better query performance + $table->index('practitioner_id', 'idx_intervention_practitioner_practitioner'); + $table->index('intervention_id', 'idx_intervention_practitioner_intervention'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('intervention_practitioner'); + } +}; diff --git a/thanasoft-back/database/migrations/2025_11_19_131500_remove_assigned_practitioner_id_from_interventions_table.php b/thanasoft-back/database/migrations/2025_11_19_131500_remove_assigned_practitioner_id_from_interventions_table.php new file mode 100644 index 0000000..9ef927b --- /dev/null +++ b/thanasoft-back/database/migrations/2025_11_19_131500_remove_assigned_practitioner_id_from_interventions_table.php @@ -0,0 +1,33 @@ +dropForeign(['assigned_practitioner_id']); + $table->dropColumn('assigned_practitioner_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // This migration is not easily reversible since we'd lose the many-to-many relationship + // In a real scenario, you'd want to handle this differently + Schema::table('interventions', function (Blueprint $table) { + $table->foreignId('assigned_practitioner_id')->nullable()->constrained('thanatopractitioners')->nullOnDelete(); + }); + } +}; diff --git a/thanasoft-back/routes/api.php b/thanasoft-back/routes/api.php index d1dfb71..ae8a44e 100644 --- a/thanasoft-back/routes/api.php +++ b/thanasoft-back/routes/api.php @@ -87,6 +87,7 @@ Route::middleware('auth:sanctum')->group(function () { Route::apiResource('employees', EmployeeController::class); // Thanatopractitioner management + Route::get('/thanatopractitioners/search', [ThanatopractitionerController::class, 'searchByEmployeeName']); Route::apiResource('thanatopractitioners', ThanatopractitionerController::class); Route::get('employees/{employeeId}/thanatopractitioners', [ThanatopractitionerController::class, 'getByEmployee']); Route::get('/thanatopractitioners/{id}/documents', [PractitionerDocumentController::class, 'getByThanatopractitioner']); @@ -130,6 +131,7 @@ Route::middleware('auth:sanctum')->group(function () { Route::put('/{intervention}', [InterventionController::class, 'update']); Route::delete('/{intervention}', [InterventionController::class, 'destroy']); Route::patch('/{intervention}/status', [InterventionController::class, 'changeStatus']); + Route::patch('/{intervention}/assign', [InterventionController::class, 'assignPractitioner']); }); }); diff --git a/thanasoft-front/src/components/Organism/Interventions/InterventionDetailPresentation.vue b/thanasoft-front/src/components/Organism/Interventions/InterventionDetailPresentation.vue index 51c924a..e5613b6 100644 --- a/thanasoft-front/src/components/Organism/Interventions/InterventionDetailPresentation.vue +++ b/thanasoft-front/src/components/Organism/Interventions/InterventionDetailPresentation.vue @@ -41,6 +41,7 @@ @change-tab="activeTab = $event" @update-intervention="handleUpdateIntervention" @cancel="handleCancel" + @assign-practitioner="handleAssignPractitioner" /> @@ -119,16 +120,19 @@ const mappedIntervention = computed(() => { "Non disponible" : "Non disponible", prestationsSupplementaires: "À définir", - members: props.intervention.practitioner - ? [ - { - name: `${props.intervention.practitioner.first_name || ""} ${ - props.intervention.practitioner.last_name || "" - }`.trim(), + members: + props.intervention.practitioners && + props.intervention.practitioners.length > 0 + ? props.intervention.practitioners.map((p) => ({ + name: p.employee + ? `${p.employee.first_name || ""} ${ + p.employee.last_name || "" + }`.trim() + : `${p.first_name || ""} ${p.last_name || ""}`.trim(), image: "/images/avatar-default.png", - }, - ] - : [], + role: p.pivot?.role || "assistant", + })) + : [], // Map status from API string to expected object format status: props.intervention.status diff --git a/thanasoft-front/src/components/Organism/Interventions/intervention/AddPractitionerModal.vue b/thanasoft-front/src/components/Organism/Interventions/intervention/AddPractitionerModal.vue new file mode 100644 index 0000000..920833c --- /dev/null +++ b/thanasoft-front/src/components/Organism/Interventions/intervention/AddPractitionerModal.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/thanasoft-front/src/components/Organism/Interventions/intervention/InterventionDetailContent.vue b/thanasoft-front/src/components/Organism/Interventions/intervention/InterventionDetailContent.vue index 348c480..fad22ab 100644 --- a/thanasoft-front/src/components/Organism/Interventions/intervention/InterventionDetailContent.vue +++ b/thanasoft-front/src/components/Organism/Interventions/intervention/InterventionDetailContent.vue @@ -239,26 +239,30 @@
-
-
-
- Image placeholder -
-
{{ member.name }}
-

Praticien

-
+
+
+ {{ + practitioner.employee?.full_name || + (practitioner.employee?.first_name && + practitioner.employee?.last_name + ? practitioner.employee.first_name + + " " + + practitioner.employee.last_name + : "Praticien " + (index + 1)) + }} +
+

Praticien

@@ -375,7 +379,12 @@ const props = defineProps({ }, }); -const emit = defineEmits(["change-tab", "update-intervention", "cancel"]); +const emit = defineEmits([ + "change-tab", + "update-intervention", + "cancel", + "assign-practitioner", +]); // État local pour l'édition const editMode = ref(false); diff --git a/thanasoft-front/src/components/Organism/Interventions/intervention/InterventionDetailSidebar.vue b/thanasoft-front/src/components/Organism/Interventions/intervention/InterventionDetailSidebar.vue index e158449..daad456 100644 --- a/thanasoft-front/src/components/Organism/Interventions/intervention/InterventionDetailSidebar.vue +++ b/thanasoft-front/src/components/Organism/Interventions/intervention/InterventionDetailSidebar.vue @@ -16,7 +16,7 @@
-
+
+
+ + +
+ + + + + + + + + diff --git a/thanasoft-front/src/components/molecules/thanatopractitioner/PractitionerSearchInput.vue b/thanasoft-front/src/components/molecules/thanatopractitioner/PractitionerSearchInput.vue new file mode 100644 index 0000000..9ea4334 --- /dev/null +++ b/thanasoft-front/src/components/molecules/thanatopractitioner/PractitionerSearchInput.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/thanasoft-front/src/services/intervention.ts b/thanasoft-front/src/services/intervention.ts index 977e5ea..ac189ff 100644 --- a/thanasoft-front/src/services/intervention.ts +++ b/thanasoft-front/src/services/intervention.ts @@ -10,14 +10,14 @@ export interface Intervention { scheduled_at?: string; duration_min?: number; status?: string; - assigned_practitioner_id?: number; attachments_count?: number; notes?: string; created_by?: number; // Relations client?: any; deceased?: any; - practitioner?: any; + practitioners?: any[]; + principal_practitioner?: any; location?: any; // Timestamps created_at?: string; @@ -47,7 +47,9 @@ export interface CreateInterventionPayload { scheduled_at?: string; duration_min?: number; status?: string; - assigned_practitioner_id?: number; + practitioners?: number[]; + principal_practitioner_id?: number; + assistant_practitioner_ids?: number[]; notes?: string; created_by?: number; } @@ -233,16 +235,54 @@ export const InterventionService = { }, /** - * Assign practitioner to intervention + * Assign practitioner(s) to intervention */ async assignPractitioner( id: number, - practitionerId: number + practitionerData: { + practitioners?: number[]; + principal_practitioner_id?: number; + assistant_practitioner_ids?: number[]; + } ): Promise { const response = await request({ url: `/api/interventions/${id}/assign`, method: "patch", - data: { assigned_practitioner_id: practitionerId }, + data: practitionerData, + }); + + return response; + }, + + /** + * Assign multiple practitioners to intervention + */ + async assignPractitioners( + id: number, + practitionerIds: number[], + principalPractitionerId?: number + ): Promise { + return this.assignPractitioner(id, { + practitioners: practitionerIds, + principal_practitioner_id: principalPractitionerId, + }); + }, + + /** + * Update practitioners for intervention (replace all existing) + */ + async updatePractitioners( + id: number, + practitionerData: { + practitioners?: number[]; + principal_practitioner_id?: number; + assistant_practitioner_ids?: number[]; + } + ): Promise { + const response = await request({ + url: `/api/interventions/${id}/practitioners`, + method: "patch", + data: practitionerData, }); return response; diff --git a/thanasoft-front/src/services/thanatopractitioner.ts b/thanasoft-front/src/services/thanatopractitioner.ts index 1e4b3f6..59fb128 100644 --- a/thanasoft-front/src/services/thanatopractitioner.ts +++ b/thanasoft-front/src/services/thanatopractitioner.ts @@ -263,10 +263,10 @@ export const ThanatopractitionerService = { data: Thanatopractitioner[]; }; }>({ - url: "/api/thanatopractitioners", + url: "/api/thanatopractitioners/search", method: "get", params: { - search: query, + query: query, }, }); return response.data.data; diff --git a/thanasoft-front/src/stores/interventionStore.ts b/thanasoft-front/src/stores/interventionStore.ts index ba9acff..02b8e19 100644 --- a/thanasoft-front/src/stores/interventionStore.ts +++ b/thanasoft-front/src/stores/interventionStore.ts @@ -444,9 +444,16 @@ export const useInterventionStore = defineStore("intervention", () => { }; /** - * Assign practitioner to intervention + * Assign practitioner(s) to intervention */ - const assignPractitioner = async (id: number, practitionerId: number) => { + const assignPractitioner = async ( + id: number, + practitionerData: { + practitioners?: number[]; + principal_practitioner_id?: number; + assistant_practitioner_ids?: number[]; + } + ) => { setLoading(true); setError(null); setSuccess(false); @@ -454,7 +461,7 @@ export const useInterventionStore = defineStore("intervention", () => { try { const intervention = await InterventionService.assignPractitioner( id, - practitionerId + practitionerData ); // Update in the interventions list @@ -479,7 +486,72 @@ export const useInterventionStore = defineStore("intervention", () => { const errorMessage = err.response?.data?.message || err.message || - "Échec de l'assignation du praticien"; + "Échec de l'assignation des praticiens"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Assign multiple practitioners to intervention + */ + const assignPractitioners = async ( + id: number, + practitionerIds: number[], + principalPractitionerId?: number + ) => { + return assignPractitioner(id, { + practitioners: practitionerIds, + principal_practitioner_id: principalPractitionerId, + }); + }; + + /** + * Update practitioners for intervention (replace all existing) + */ + const updatePractitioners = async ( + id: number, + practitionerData: { + practitioners?: number[]; + principal_practitioner_id?: number; + assistant_practitioner_ids?: number[]; + } + ) => { + setLoading(true); + setError(null); + setSuccess(false); + + try { + const intervention = await InterventionService.updatePractitioners( + id, + practitionerData + ); + + // Update in the interventions list + const index = interventions.value.findIndex( + (i) => i.id === intervention.id + ); + if (index !== -1) { + interventions.value[index] = intervention; + } + + // Update current intervention if it's the one being updated + if ( + currentIntervention.value && + currentIntervention.value.id === intervention.id + ) { + setCurrentIntervention(intervention); + } + + setSuccess(true); + return intervention; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Échec de la mise à jour des praticiens"; setError(errorMessage); throw err; } finally { @@ -577,6 +649,8 @@ export const useInterventionStore = defineStore("intervention", () => { searchInterventions, updateInterventionStatus, assignPractitioner, + assignPractitioners, + updatePractitioners, fetchInterventionsByMonth, resetState, }; diff --git a/thanasoft-front/src/views/pages/Interventions/InterventionDetails.vue b/thanasoft-front/src/views/pages/Interventions/InterventionDetails.vue index 7013c51..1c1edb0 100644 --- a/thanasoft-front/src/views/pages/Interventions/InterventionDetails.vue +++ b/thanasoft-front/src/views/pages/Interventions/InterventionDetails.vue @@ -8,7 +8,14 @@ :practitioners="practitioners" @update-intervention="handleUpdate" @cancel="handleCancel" - @assign-practitioner="handleAssignPractitioner" + @assign-practitioner="openAssignModal" + /> + + + @@ -16,6 +23,7 @@ import { ref, onMounted, watch } from "vue"; import { useRoute } from "vue-router"; import InterventionDetailPresentation from "@/components/Organism/Interventions/InterventionDetailPresentation.vue"; +import AssignPractitionerModal from "@/components/molecules/intervention/AssignPractitionerModal.vue"; import { useInterventionStore } from "@/stores/interventionStore"; import { useNotificationStore } from "@/stores/notification"; @@ -27,6 +35,7 @@ const notificationStore = useNotificationStore(); const intervention = ref(null); const activeTab = ref("overview"); const practitioners = ref([]); +const isModalOpen = ref(false); // Fetch intervention data const fetchIntervention = async () => { @@ -47,18 +56,37 @@ const fetchIntervention = async () => { } }; +// Open assign modal +const openAssignModal = () => { + isModalOpen.value = true; +}; + +// Close assign modal +const closeAssignModal = () => { + isModalOpen.value = false; +}; + // Handle practitioner assignment const handleAssignPractitioner = async (practitionerData) => { try { if (intervention.value?.id) { + // Build the assignment payload based on role + const payload = {}; + if (practitionerData.role === "principal") { + payload.principal_practitioner_id = practitionerData.practitionerId; + } else { + payload.assistant_practitioner_ids = [practitionerData.practitionerId]; + } + await interventionStore.assignPractitioner( intervention.value.id, - practitionerData.practitionerId + payload ); // Refresh intervention data to get updated practitioner info await fetchIntervention(); notificationStore.created("Praticien assigné"); + closeAssignModal(); } } catch (error) { console.error("Error assigning practitioner:", error); diff --git a/thanasoft-front/src/views/pages/Login.vue b/thanasoft-front/src/views/pages/Login.vue index 6baccf7..a58b722 100644 --- a/thanasoft-front/src/views/pages/Login.vue +++ b/thanasoft-front/src/views/pages/Login.vue @@ -30,23 +30,21 @@