ajout table thanato dans intervention

This commit is contained in:
Nyavokevin 2025-11-26 17:53:17 +03:00
parent a51e05559a
commit 496b427e13
15 changed files with 968 additions and 274 deletions

View File

@ -4,11 +4,12 @@ namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\StoreInterventionRequest; use App\Http\Requests\StoreInterventionRequest;
use App\Http\Requests\StoreInterventionWithAllDataRequest; use App\HttpRequests\StoreInterventionWithAllDataRequest;
use App\Http\Requests\UpdateInterventionRequest; use App\Http\Requests\UpdateInterventionRequest;
use App\Http\Resources\Intervention\InterventionResource; use App\Http\Resources\Intervention\InterventionResource;
use App\Http\Resources\Intervention\InterventionCollection; use App\Http\Resources\Intervention\InterventionCollection;
use App\Repositories\InterventionRepositoryInterface; use App\Repositories\InterventionRepositoryInterface;
use App\Repositories\InterventionPractitionerRepositoryInterface;
use App\Repositories\ClientRepositoryInterface; use App\Repositories\ClientRepositoryInterface;
use App\Repositories\ContactRepositoryInterface; use App\Repositories\ContactRepositoryInterface;
use App\Repositories\DeceasedRepositoryInterface; use App\Repositories\DeceasedRepositoryInterface;
@ -25,6 +26,11 @@ class InterventionController extends Controller
*/ */
protected $interventionRepository; protected $interventionRepository;
/**
* @var InterventionPractitionerRepositoryInterface
*/
protected $interventionPractitionerRepository;
/** /**
* @var ClientRepositoryInterface * @var ClientRepositoryInterface
*/ */
@ -44,17 +50,20 @@ class InterventionController extends Controller
* InterventionController constructor. * InterventionController constructor.
* *
* @param InterventionRepositoryInterface $interventionRepository * @param InterventionRepositoryInterface $interventionRepository
* @param InterventionPractitionerRepositoryInterface $interventionPractitionerRepository
* @param ClientRepositoryInterface $clientRepository * @param ClientRepositoryInterface $clientRepository
* @param ContactRepositoryInterface $contactRepository * @param ContactRepositoryInterface $contactRepository
* @param DeceasedRepositoryInterface $deceasedRepository * @param DeceasedRepositoryInterface $deceasedRepository
*/ */
public function __construct( public function __construct(
InterventionRepositoryInterface $interventionRepository, InterventionRepositoryInterface $interventionRepository,
InterventionPractitionerRepositoryInterface $interventionPractitionerRepository,
ClientRepositoryInterface $clientRepository, ClientRepositoryInterface $clientRepository,
ContactRepositoryInterface $contactRepository, ContactRepositoryInterface $contactRepository,
DeceasedRepositoryInterface $deceasedRepository DeceasedRepositoryInterface $deceasedRepository
) { ) {
$this->interventionRepository = $interventionRepository; $this->interventionRepository = $interventionRepository;
$this->interventionPractitionerRepository = $interventionPractitionerRepository;
$this->clientRepository = $clientRepository; $this->clientRepository = $clientRepository;
$this->contactRepository = $contactRepository; $this->contactRepository = $contactRepository;
$this->deceasedRepository = $deceasedRepository; $this->deceasedRepository = $deceasedRepository;
@ -361,13 +370,13 @@ class InterventionController extends Controller
} }
/** /**
* Assign a practitioner to an intervention * Create assignment of practitioners to an intervention
* *
* @param Request $request * @param Request $request
* @param int $id * @param int $id
* @return JsonResponse * @return JsonResponse
*/ */
public function assignPractitioner(Request $request, int $id): JsonResponse public function createAssignment(Request $request, int $id): JsonResponse
{ {
try { try {
$validated = $request->validate([ $validated = $request->validate([
@ -376,6 +385,7 @@ class InterventionController extends Controller
'assistant_practitioner_ids.*' => 'integer|exists:thanatopractitioners,id', 'assistant_practitioner_ids.*' => 'integer|exists:thanatopractitioners,id',
]); ]);
$intervention = $this->interventionRepository->findById($id); $intervention = $this->interventionRepository->findById($id);
if (!$intervention) { if (!$intervention) {
@ -384,41 +394,164 @@ class InterventionController extends Controller
], Response::HTTP_NOT_FOUND); ], Response::HTTP_NOT_FOUND);
} }
// Sync practitioners with their roles // Remove existing principal practitioner first
$practitioners = [];
if (isset($validated['principal_practitioner_id'])) { if (isset($validated['principal_practitioner_id'])) {
$practitioners[$validated['principal_practitioner_id']] = ['role' => 'principal']; $principalId = $validated['principal_practitioner_id'];
$this->interventionPractitionerRepository->createAssignment($id, $principalId, 'principal');
} }
if (isset($validated['assistant_practitioner_ids'])) { // Handle assistant practitioners
if (isset($validated['assistant_practitioner_ids']) && is_array($validated['assistant_practitioner_ids'])) {
foreach ($validated['assistant_practitioner_ids'] as $assistantId) { foreach ($validated['assistant_practitioner_ids'] as $assistantId) {
$practitioners[$assistantId] = ['role' => 'assistant']; $this->interventionPractitionerRepository->createAssignment($id, $assistantId, 'assistant');
} }
} }
// Sync the practitioners (this will replace existing assignments) // Load the intervention with practitioners to return updated data
$intervention->practitioners()->sync($practitioners); $intervention->load('practitioners');
$practitioners = $intervention->practitioners;
// Reload the intervention with relationships
$intervention = $this->interventionRepository->findById($id);
return response()->json([ return response()->json([
'data' => new InterventionResource($intervention), 'data' => new InterventionResource($intervention),
'message' => 'Praticien(s) assigné(s) avec succès.' 'message' => 'Assignment(s) créé(s) avec succès.',
'practitioners_count' => $practitioners->count(),
'practitioners' => $practitioners->map(function($p) {
return [
'id' => $p->id,
'full_name' => $p->full_name ?? ($p->first_name . ' ' . $p->last_name),
'role' => $p->pivot->role ?? 'unknown'
];
})->toArray()
], Response::HTTP_OK); ], Response::HTTP_OK);
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error('Error assigning practitioner to intervention: ' . $e->getMessage(), [
'intervention_id' => $id, return response()->json([
'message' => 'Une erreur est survenue lors de la création de l\'assignment.',
'error' => $e->getMessage()
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Unassign a practitioner from an intervention
*
* @param Request $request
* @param int $interventionId
* @param int $practitionerId
* @return JsonResponse
*/
public function unassignPractitioner(Request $request, int $interventionId, int $practitionerId): JsonResponse
{
try {
Log::info('Unassigning practitioner from intervention', [
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId
]);
// Validate that the intervention exists
$intervention = $this->interventionRepository->findById($interventionId);
if (!$intervention) {
return response()->json([
'message' => 'Intervention non trouvée.'
], Response::HTTP_NOT_FOUND);
}
// Check if the practitioner is actually assigned to this intervention
$isAssigned = $this->interventionPractitionerRepository->isPractitionerAssigned($interventionId, $practitionerId);
if (!$isAssigned) {
return response()->json([
'message' => 'Le praticien n\'est pas assigné à cette intervention.'
], Response::HTTP_NOT_FOUND);
}
// Remove the practitioner assignment
$deleted = $this->interventionPractitionerRepository->removeAssignment($interventionId, $practitionerId);
if ($deleted > 0) {
Log::info('Practitioner unassigned successfully', [
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId,
'deleted_records' => $deleted
]);
// Reload intervention with remaining practitioners
$intervention->load('practitioners');
$remainingPractitioners = $intervention->practitioners;
return response()->json([
'data' => new InterventionResource($intervention),
'message' => 'Praticien désassigné avec succès.',
'remaining_practitioners_count' => $remainingPractitioners->count(),
'remaining_practitioners' => $remainingPractitioners->map(function($p) {
return [
'id' => $p->id,
'employee_name' => $p->employee->full_name ?? ($p->employee->first_name . ' ' . $p->employee->last_name),
'role' => $p->pivot->role ?? 'unknown'
];
})->toArray()
], Response::HTTP_OK);
} else {
Log::warning('No practitioner assignment found to delete', [
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId
]);
return response()->json([
'message' => 'Aucun assignment de praticien trouvé à supprimer.'
], Response::HTTP_NOT_FOUND);
}
} catch (\Exception $e) {
Log::error('Error unassigning practitioner from intervention: ' . $e->getMessage(), [
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId,
'request_data' => $request->all(),
'error' => $e->getMessage(), 'error' => $e->getMessage(),
'trace' => $e->getTraceAsString() 'trace' => $e->getTraceAsString()
]); ]);
return response()->json([ return response()->json([
'message' => 'Une erreur est survenue lors de l\'assignation du praticien.', 'message' => 'Une erreur est survenue lors de la désassignation du praticien.',
'error' => $e->getMessage() 'error' => $e->getMessage()
], Response::HTTP_INTERNAL_SERVER_ERROR); ], Response::HTTP_INTERNAL_SERVER_ERROR);
} }
} }
/**
* Debug endpoint to check practitioners in database
*/
public function debugPractitioners(int $id): JsonResponse
{
try {
$intervention = $this->interventionRepository->findById($id);
// Direct database query
$dbPractitioners = DB::table('intervention_practitioner')
->where('intervention_id', $id)
->get();
// Eager loaded practitioners
$eagerPractitioners = $intervention->practitioners()->get();
return response()->json([
'intervention_id' => $id,
'database_records' => $dbPractitioners,
'eager_loaded_count' => $eagerPractitioners->count(),
'eager_loaded_data' => $eagerPractitioners->map(function($p) {
return [
'id' => $p->id,
'full_name' => $p->full_name ?? ($p->first_name . ' ' . $p->last_name),
'role' => $p->pivot->role ?? 'unknown'
];
})->toArray()
]);
} catch (\Exception $e) {
Log::error('Error in debug practitioners: ' . $e->getMessage());
return response()->json(['error' => $e->getMessage()], 500);
}
}
} }

View File

@ -3,6 +3,8 @@
namespace App\Http\Resources\Employee; namespace App\Http\Resources\Employee;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;
use App\Http\Resources\Employee\EmployeeResource;
use App\Http\Resources\Employee\PractitionerDocumentResource;
class ThanatopractitionerResource extends JsonResource class ThanatopractitionerResource extends JsonResource
{ {
@ -17,6 +19,10 @@ class ThanatopractitionerResource extends JsonResource
return [ return [
'id' => $this->id, 'id' => $this->id,
'employee_id' => $this->employee_id, 'employee_id' => $this->employee_id,
'employee_name' => $this->when(
$this->relationLoaded('employee'),
$this->employee->full_name ?? ($this->employee->first_name . ' ' . $this->employee->last_name)
),
'diploma_number' => $this->diploma_number, 'diploma_number' => $this->diploma_number,
'diploma_date' => $this->diploma_date?->format('Y-m-d'), 'diploma_date' => $this->diploma_date?->format('Y-m-d'),
'authorization_number' => $this->authorization_number, 'authorization_number' => $this->authorization_number,

View File

@ -37,11 +37,44 @@ class InterventionResource extends JsonResource
'duration_min' => $this->duration_min, 'duration_min' => $this->duration_min,
'status' => $this->status, 'status' => $this->status,
'practitioners' => $this->whenLoaded('practitioners', function () { 'practitioners' => $this->whenLoaded('practitioners', function () {
return ThanatopractitionerResource::collection($this->practitioners); return $this->practitioners->map(function ($practitioner) {
return [
'id' => $practitioner->id,
'employee_id' => $practitioner->employee_id,
'employee_name' => $practitioner->employee->full_name ?? ($practitioner->employee->first_name . ' ' . $practitioner->employee->last_name),
'diploma_number' => $practitioner->diploma_number,
'diploma_date' => $practitioner->diploma_date?->format('Y-m-d'),
'authorization_number' => $practitioner->authorization_number,
'authorization_issue_date' => $practitioner->authorization_issue_date?->format('Y-m-d'),
'authorization_expiry_date' => $practitioner->authorization_expiry_date?->format('Y-m-d'),
'notes' => $practitioner->notes,
'is_authorization_valid' => $practitioner->is_authorization_valid,
'created_at' => $practitioner->created_at?->format('Y-m-d H:i:s'),
'updated_at' => $practitioner->updated_at?->format('Y-m-d H:i:s'),
'role' => $practitioner->pivot->role ?? null,
];
});
}), }),
'principal_practitioner' => $this->whenLoaded('practitioners', function () { 'principal_practitioner' => $this->whenLoaded('practitioners', function () {
$principal = $this->practitioners->where('pivot.role', 'principal')->first(); $principal = $this->practitioners->where('pivot.role', 'principal')->first();
return $principal ? new ThanatopractitionerResource($principal) : null; if (!$principal) {
return null;
}
return [
'id' => $principal->id,
'employee_id' => $principal->employee_id,
'employee_name' => $principal->employee->full_name ?? ($principal->employee->first_name . ' ' . $principal->employee->last_name),
'diploma_number' => $principal->diploma_number,
'diploma_date' => $principal->diploma_date?->format('Y-m-d'),
'authorization_number' => $principal->authorization_number,
'authorization_issue_date' => $principal->authorization_issue_date?->format('Y-m-d'),
'authorization_expiry_date' => $principal->authorization_expiry_date?->format('Y-m-d'),
'notes' => $principal->notes,
'is_authorization_valid' => $principal->is_authorization_valid,
'created_at' => $principal->created_at?->format('Y-m-d H:i:s'),
'updated_at' => $principal->updated_at?->format('Y-m-d H:i:s'),
'role' => $principal->pivot->role ?? null,
];
}), }),
'attachments_count' => $this->attachments_count, 'attachments_count' => $this->attachments_count,
'notes' => $this->notes, 'notes' => $this->notes,

View File

@ -0,0 +1,72 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class InterventionPractitioner extends Model
{
use HasFactory;
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'intervention_practitioner';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'intervention_id',
'practitioner_id',
'role',
'assigned_at'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'assigned_at' => 'datetime'
];
/**
* Get the intervention that owns the practitioner assignment.
*/
public function intervention(): BelongsTo
{
return $this->belongsTo(Intervention::class);
}
/**
* Get the practitioner assigned to the intervention.
*/
public function practitioner(): BelongsTo
{
return $this->belongsTo(Thanatopractitioner::class, 'practitioner_id');
}
/**
* Scope to get principal practitioners.
*/
public function scopePrincipal($query)
{
return $query->where('role', 'principal');
}
/**
* Scope to get assistant practitioners.
*/
public function scopeAssistant($query)
{
return $query->where('role', 'assistant');
}
}

View File

@ -60,6 +60,8 @@ class AppServiceProvider extends ServiceProvider
$this->app->bind(\App\Repositories\InterventionRepositoryInterface::class, \App\Repositories\InterventionRepository::class); $this->app->bind(\App\Repositories\InterventionRepositoryInterface::class, \App\Repositories\InterventionRepository::class);
$this->app->bind(\App\Repositories\DeceasedRepositoryInterface::class, \App\Repositories\DeceasedRepository::class); $this->app->bind(\App\Repositories\DeceasedRepositoryInterface::class, \App\Repositories\DeceasedRepository::class);
$this->app->bind(\App\Repositories\InterventionPractitionerRepositoryInterface::class, \App\Repositories\InterventionPractitionerRepository::class);
} }
/** /**

View File

@ -0,0 +1,151 @@
<?php
namespace App\Repositories;
use App\Models\InterventionPractitioner;
use Illuminate\Support\Facades\Log;
class InterventionPractitionerRepository implements InterventionPractitionerRepositoryInterface
{
/**
* Create a new intervention-practitioner assignment.
*/
public function createAssignment(int $interventionId, int $practitionerId, string $role, ?string $assignedAt = null): InterventionPractitioner
{
try {
// Check if assignment already exists
if ($this->isPractitionerAssigned($interventionId, $practitionerId)) {
// If exists, update the role
$this->updatePractitionerRole($interventionId, $practitionerId, $role);
// Return the updated record
return InterventionPractitioner::where('intervention_id', $interventionId)
->where('practitioner_id', $practitionerId)
->first();
}
$assignment = InterventionPractitioner::create([
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId,
'role' => $role,
'assigned_at' => $assignedAt ?: now()
]);
Log::info('Intervention-practitioner assignment created', [
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId,
'role' => $role,
'assignment_id' => $assignment->id
]);
return $assignment;
} catch (\Exception $e) {
Log::error('Error creating intervention-practitioner assignment', [
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId,
'role' => $role,
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* Remove all practitioner assignments for an intervention.
*/
public function removeAllAssignments(int $interventionId): int
{
$deleted = InterventionPractitioner::where('intervention_id', $interventionId)->delete();
Log::info('Removed all practitioner assignments for intervention', [
'intervention_id' => $interventionId,
'deleted_count' => $deleted
]);
return $deleted;
}
/**
* Remove specific practitioner assignment.
*/
public function removeAssignment(int $interventionId, int $practitionerId): int
{
$deleted = InterventionPractitioner::where('intervention_id', $interventionId)
->where('practitioner_id', $practitionerId)
->delete();
if ($deleted > 0) {
Log::info('Removed intervention-practitioner assignment', [
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId
]);
}
return $deleted;
}
/**
* Check if a practitioner is already assigned to an intervention.
*/
public function isPractitionerAssigned(int $interventionId, int $practitionerId): bool
{
return InterventionPractitioner::where('intervention_id', $interventionId)
->where('practitioner_id', $practitionerId)
->exists();
}
/**
* Get all practitioner assignments for an intervention.
*/
public function getAssignmentsForIntervention(int $interventionId)
{
return InterventionPractitioner::with('practitioner')
->where('intervention_id', $interventionId)
->get();
}
/**
* Get principal practitioners for an intervention.
*/
public function getPrincipalPractitioners(int $interventionId)
{
return InterventionPractitioner::with('practitioner')
->where('intervention_id', $interventionId)
->principal()
->get();
}
/**
* Get assistant practitioners for an intervention.
*/
public function getAssistantPractitioners(int $interventionId)
{
return InterventionPractitioner::with('practitioner')
->where('intervention_id', $interventionId)
->assistant()
->get();
}
/**
* Update practitioner role for an intervention.
*/
public function updatePractitionerRole(int $interventionId, int $practitionerId, string $role): bool
{
$updated = InterventionPractitioner::where('intervention_id', $interventionId)
->where('practitioner_id', $practitionerId)
->update([
'role' => $role,
'assigned_at' => now()
]);
if ($updated > 0) {
Log::info('Updated practitioner role for intervention', [
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId,
'new_role' => $role
]);
}
return $updated > 0;
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Repositories;
use App\Models\InterventionPractitioner;
interface InterventionPractitionerRepositoryInterface
{
/**
* Create a new intervention-practitioner assignment.
*
* @param int $interventionId
* @param int $practitionerId
* @param string $role
* @param string|null $assignedAt
* @return InterventionPractitioner
*/
public function createAssignment(int $interventionId, int $practitionerId, string $role, ?string $assignedAt = null): InterventionPractitioner;
/**
* Remove all practitioner assignments for an intervention.
*
* @param int $interventionId
* @return int
*/
public function removeAllAssignments(int $interventionId): int;
/**
* Remove specific practitioner assignment.
*
* @param int $interventionId
* @param int $practitionerId
* @return int
*/
public function removeAssignment(int $interventionId, int $practitionerId): int;
/**
* Check if a practitioner is already assigned to an intervention.
*
* @param int $interventionId
* @param int $practitionerId
* @return bool
*/
public function isPractitionerAssigned(int $interventionId, int $practitionerId): bool;
/**
* Get all practitioner assignments for an intervention.
*
* @param int $interventionId
* @return \Illuminate\Database\Eloquent\Collection
*/
public function getAssignmentsForIntervention(int $interventionId);
/**
* Get principal practitioners for an intervention.
*
* @param int $interventionId
* @return \Illuminate\Database\Eloquent\Collection
*/
public function getPrincipalPractitioners(int $interventionId);
/**
* Get assistant practitioners for an intervention.
*
* @param int $interventionId
* @return \Illuminate\Database\Eloquent\Collection
*/
public function getAssistantPractitioners(int $interventionId);
/**
* Update practitioner role for an intervention.
*
* @param int $interventionId
* @param int $practitionerId
* @param string $role
* @return bool
*/
public function updatePractitionerRole(int $interventionId, int $practitionerId, string $role): bool;
}

View File

@ -55,7 +55,7 @@ class InterventionRepository implements InterventionRepositoryInterface
'client', 'client',
'deceased', 'deceased',
'location', 'location',
'assignedPractitioner' 'practitioners'
]); ]);
return $query->paginate($perPage); return $query->paginate($perPage);
@ -149,8 +149,43 @@ class InterventionRepository implements InterventionRepositoryInterface
return Intervention::query() return Intervention::query()
->whereBetween('scheduled_at', [$startDate . ' 00:00:00', $endDate . ' 23:59:59']) ->whereBetween('scheduled_at', [$startDate . ' 00:00:00', $endDate . ' 23:59:59'])
->with(['client', 'deceased', 'location', 'assignedPractitioner']) ->with(['client', 'deceased', 'location', 'practitioners'])
->orderBy('scheduled_at', 'asc') ->orderBy('scheduled_at', 'asc')
->get(); ->get();
} }
/**
* Add practitioners to an intervention
*
* @param Intervention $intervention
* @param array $practitionerData
* @return array Array with 'principal' and 'assistant' results
*/
public function addPractitioners(Intervention $intervention, array $practitionerData): array
{
// This method is deprecated in favor of using InterventionPractitionerRepository directly
// Implementation kept for interface compatibility
$results = [
'principal' => null,
'assistant' => []
];
if (isset($practitionerData['principal_practitioner_id'])) {
$results['principal'] = [
'id' => $practitionerData['principal_practitioner_id'],
'status' => 'handled_by_practitioner_repository'
];
}
if (isset($practitionerData['assistant_practitioner_ids']) && is_array($practitionerData['assistant_practitioner_ids'])) {
foreach ($practitionerData['assistant_practitioner_ids'] as $assistantId) {
$results['assistant'][] = [
'id' => $assistantId,
'status' => 'handled_by_practitioner_repository'
];
}
}
return $results;
}
} }

View File

@ -66,4 +66,13 @@ interface InterventionRepositoryInterface
* @return \Illuminate\Database\Eloquent\Collection * @return \Illuminate\Database\Eloquent\Collection
*/ */
public function getByMonth(int $year, int $month): \Illuminate\Database\Eloquent\Collection; public function getByMonth(int $year, int $month): \Illuminate\Database\Eloquent\Collection;
/**
* Add practitioners to an intervention
*
* @param Intervention $intervention
* @param array $practitionerData
* @return array Array with 'principal' and 'assistant' results
*/
public function addPractitioners(Intervention $intervention, array $practitionerData): array;
} }

View File

@ -131,7 +131,9 @@ Route::middleware('auth:sanctum')->group(function () {
Route::put('/{intervention}', [InterventionController::class, 'update']); Route::put('/{intervention}', [InterventionController::class, 'update']);
Route::delete('/{intervention}', [InterventionController::class, 'destroy']); Route::delete('/{intervention}', [InterventionController::class, 'destroy']);
Route::patch('/{intervention}/status', [InterventionController::class, 'changeStatus']); Route::patch('/{intervention}/status', [InterventionController::class, 'changeStatus']);
Route::patch('/{intervention}/assign', [InterventionController::class, 'assignPractitioner']); Route::patch('/{intervention}/assign', [InterventionController::class, 'createAssignment']);
Route::patch('/{intervention}/{practitionerId}/unassignPractitioner', [InterventionController::class, 'unassignPractitioner']);
Route::get('/{intervention}/debug', [InterventionController::class, 'debugPractitioners']);
}); });
}); });

View File

@ -19,15 +19,13 @@
</template> </template>
<template #intervention-detail-sidebar> <template #intervention-detail-sidebar>
<div class="card"> <div class="card">
<div class="card-body"> <InterventionDetailSidebar
<InterventionDetailSidebar :intervention="mappedIntervention"
:intervention="mappedIntervention" :active-tab="activeTab"
:active-tab="activeTab" :practitioners="practitioners"
:practitioners="practitioners" @change-tab="activeTab = $event"
@change-tab="activeTab = $event" @assign-practitioner="handleAssignPractitioner"
@assign-practitioner="handleAssignPractitioner" />
/>
</div>
</div> </div>
</template> </template>
<template #intervention-detail-content> <template #intervention-detail-content>

View File

@ -27,183 +27,207 @@
<template v-else-if="intervention"> <template v-else-if="intervention">
<!-- Overview Tab --> <!-- Overview Tab -->
<div v-if="activeTab === 'overview'" class="tab-pane fade show active"> <div v-if="activeTab === 'overview'" class="tab-pane fade show active">
<div class="mb-4"> <div class="card">
<h6 class="mb-3">Informations Générales</h6> <div class="card-header pb-0">
<div class="row"> <div class="d-flex align-items-center">
<div class="col-md-6"> <h6 class="mb-0">Détails de l'intervention</h6>
<div class="info-horizontal"> <button
<div class="icon-sm text-center"> type="button"
<i class="fas fa-user text-primary"></i> class="btn btn-sm btn-primary ms-auto"
</div> @click="toggleEditMode"
<div class="description"> :disabled="loading"
<p class="text-xs mb-0"> >
<b>Nom du défunt:</b> {{ intervention.defuntName }} <i class="fas fa-edit me-1"></i
</p> >{{ editMode ? "Sauvegarder" : "Modifier" }}
</div> </button>
</div>
</div> </div>
<div class="col-md-6"> </div>
<div class="info-horizontal"> <div class="card-body">
<div class="icon-sm text-center"> <div class="row">
<i class="fas fa-calendar text-primary"></i> <!-- Basic Information Card -->
</div> <div class="col-md-6 mb-3">
<div class="description"> <InfoCard
<p class="text-xs mb-0"> title="Informations Générales"
<b>Date:</b> {{ intervention.date }} icon="fas fa-user text-primary"
</p> >
</div> <ul class="list-group list-group-flush">
<li class="list-group-item border-0 ps-0 pt-0 text-sm">
<strong class="text-dark">Nom du défunt:</strong>
<span v-if="!editMode" class="ms-2">{{
intervention.defuntName || "-"
}}</span>
<SoftInput
v-else
v-model="localIntervention.defuntName"
class="mt-2"
/>
</li>
<li class="list-group-item border-0 ps-0 text-sm">
<strong class="text-dark">Date:</strong>
<span v-if="!editMode" class="ms-2">{{
intervention.date || "-"
}}</span>
<SoftInput
v-else
type="datetime-local"
v-model="localIntervention.date"
class="mt-2"
/>
</li>
<li class="list-group-item border-0 ps-0 text-sm">
<strong class="text-dark">Lieu:</strong>
<span v-if="!editMode" class="ms-2">{{
intervention.lieux || "-"
}}</span>
<SoftInput
v-else
v-model="localIntervention.lieux"
class="mt-2"
/>
</li>
<li class="list-group-item border-0 ps-0 text-sm">
<strong class="text-dark">Durée:</strong>
<span v-if="!editMode" class="ms-2">{{
intervention.duree || "-"
}}</span>
<SoftInput
v-else
v-model="localIntervention.duree"
class="mt-2"
/>
</li>
</ul>
</InfoCard>
</div> </div>
</div>
<div class="col-md-6"> <!-- Contact Information Card -->
<div class="info-horizontal"> <div class="col-md-6 mb-3">
<div class="icon-sm text-center"> <InfoCard
<i class="fas fa-map-marker text-primary"></i> title="Contact et Communication"
</div> icon="fas fa-phone text-primary"
<div class="description"> >
<p class="text-xs mb-0"> <ul class="list-group list-group-flush">
<b>Lieu:</b> {{ intervention.lieux }} <li class="list-group-item border-0 ps-0 pt-0 text-sm">
</p> <strong class="text-dark">Contact familial:</strong>
</div> <span v-if="!editMode" class="ms-2">{{
intervention.contactFamilial || "-"
}}</span>
<SoftInput
v-else
v-model="localIntervention.contactFamilial"
class="mt-2"
/>
</li>
<li class="list-group-item border-0 ps-0 text-sm">
<strong class="text-dark">Coordonnées:</strong>
<span v-if="!editMode" class="ms-2">{{
intervention.coordonneesContact || "-"
}}</span>
<SoftInput
v-else
v-model="localIntervention.coordonneesContact"
class="mt-2"
/>
</li>
<li class="list-group-item border-0 ps-0 text-sm">
<strong class="text-dark">Type de cérémonie:</strong>
<span v-if="!editMode" class="ms-2">{{
intervention.title || "-"
}}</span>
<SoftInput
v-else
v-model="localIntervention.title"
class="mt-2"
/>
</li>
</ul>
</InfoCard>
</div> </div>
</div>
<div class="col-md-6"> <!-- Additional Information Card (Full Width) -->
<div class="info-horizontal"> <div class="col-12 mb-3">
<div class="icon-sm text-center"> <InfoCard
<i class="fas fa-clock text-primary"></i> title="Informations Supplémentaires"
</div> icon="fas fa-info-circle text-info"
<div class="description"> >
<p class="text-xs mb-0"> <div class="row">
<b>Durée:</b> {{ intervention.duree }} <div class="col-md-6">
</p> <ul class="list-group list-group-flush">
</div> <li class="list-group-item border-0 ps-0 pt-0 text-sm">
<strong class="text-dark"
>Nombre de personnes attendues:</strong
>
<span v-if="!editMode" class="ms-2">{{
intervention.nombrePersonnes || "-"
}}</span>
<SoftInput
v-else
type="number"
v-model="localIntervention.nombrePersonnes"
class="mt-2"
/>
</li>
</ul>
</div>
<div class="col-md-6">
<template v-if="!editMode">
<p class="text-sm">
<strong class="text-dark"
>Prestations supplémentaires:</strong
>
<span class="ms-2">{{
intervention.prestationsSupplementaires || "-"
}}</span>
</p>
</template>
<template v-else>
<p class="text-sm">
<strong class="text-dark"
>Prestations supplémentaires:</strong
>
</p>
<SoftInput
type="textarea"
rows="3"
v-model="localIntervention.prestationsSupplementaires"
class="mt-2"
/>
</template>
</div>
</div>
</InfoCard>
</div>
<!-- Description Card (Full Width) -->
<div class="col-12 mb-3">
<InfoCard
title="Description"
icon="fas fa-file-alt text-warning"
>
<template v-if="!editMode">
<p class="text-sm mb-0">
{{
intervention.description ||
"Aucune description disponible"
}}
</p>
</template>
<template v-else>
<SoftInput
type="textarea"
rows="3"
v-model="localIntervention.description"
class="mt-2"
/>
</template>
</InfoCard>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="mb-4"> <!-- Action Buttons for Overview -->
<h6 class="mb-3">Contact et Communication</h6> <div class="d-flex justify-content-end mt-3" v-if="editMode">
<div class="row">
<div class="col-md-6">
<div class="info-horizontal">
<div class="icon-sm text-center">
<i class="fas fa-phone text-primary"></i>
</div>
<div class="description">
<p class="text-xs mb-0">
<b>Contact:</b> {{ intervention.contactFamilial }}
</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="info-horizontal">
<div class="icon-sm text-center">
<i class="fas fa-envelope text-primary"></i>
</div>
<div class="description">
<p class="text-xs mb-0">
<b>Coordonnées:</b> {{ intervention.coordonneesContact }}
</p>
</div>
</div>
</div>
</div>
</div>
<div class="mb-4">
<h6 class="mb-3">Description</h6>
<p class="text-sm">{{ intervention.description }}</p>
</div>
</div>
<!-- Details Tab -->
<div v-if="activeTab === 'details'" class="tab-pane fade show active">
<div class="mb-4">
<div class="d-flex align-items-center justify-content-between mb-3">
<h6 class="mb-0">Détails Complets</h6>
<button
type="button"
class="btn btn-sm bg-gradient-secondary"
@click="toggleEditMode"
:disabled="loading"
>
{{ editMode ? "Sauvegarder" : "Modifier" }}
</button>
</div>
<div class="row">
<div class="col-md-6">
<SoftInput
label="Nom du défunt"
v-model="localIntervention.defuntName"
:disabled="!editMode"
class="mb-3"
/>
<SoftInput
label="Date de l'intervention"
type="datetime-local"
v-model="localIntervention.date"
:disabled="!editMode"
class="mb-3"
/>
<SoftInput
label="Lieu"
v-model="localIntervention.lieux"
:disabled="!editMode"
class="mb-3"
/>
</div>
<div class="col-md-6">
<SoftInput
label="Durée prévue"
v-model="localIntervention.duree"
:disabled="!editMode"
class="mb-3"
/>
<SoftInput
label="Type de cérémonie"
v-model="localIntervention.title"
:disabled="!editMode"
class="mb-3"
/>
<SoftInput
label="Contact familial"
v-model="localIntervention.contactFamilial"
:disabled="!editMode"
class="mb-3"
/>
</div>
</div>
</div>
<div class="mb-4">
<h6 class="mb-3">Informations Suplementaires</h6>
<div class="row">
<div class="col-md-6">
<SoftInput
label="Nombre de personnes attendues"
type="number"
v-model="localIntervention.nombrePersonnes"
:disabled="!editMode"
class="mb-3"
/>
</div>
<div class="col-md-6">
<SoftInput
label="Prestations supplémentaires"
type="textarea"
rows="3"
v-model="localIntervention.prestationsSupplementaires"
:disabled="!editMode"
class="mb-3"
/>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="d-flex justify-content-end mt-4">
<button <button
type="button" type="button"
class="btn btn-sm bg-gradient-secondary me-2" class="btn btn-sm bg-gradient-secondary me-2"
@ -225,104 +249,157 @@
<!-- Team Tab --> <!-- Team Tab -->
<div v-if="activeTab === 'team'" class="tab-pane fade show active"> <div v-if="activeTab === 'team'" class="tab-pane fade show active">
<div class="mb-4"> <div class="card">
<div class="d-flex align-items-center justify-content-between mb-3"> <div class="card-header pb-0">
<h6 class="mb-0">Équipe Assignée</h6> <div class="d-flex align-items-center">
<button <h6 class="mb-0">Équipe Assignée</h6>
type="button" <button
class="btn btn-sm bg-gradient-info" type="button"
@click="$emit('assign-practitioner')" class="btn btn-sm btn-outline-info ms-auto"
:disabled="loading" @click="$emit('assign-practitioner')"
> :disabled="loading"
Gérer l'équipe
</button>
</div>
<div
v-if="
intervention.practitioners &&
intervention.practitioners.length > 0
"
class="list-group list-group-flush"
>
<div
v-for="(practitioner, index) in intervention.practitioners"
:key="index"
class="list-group-item d-flex justify-content-between align-items-center border-0 px-0"
>
<div>
<h6 class="text-sm mb-0">
{{
practitioner.employee?.full_name ||
(practitioner.employee?.first_name &&
practitioner.employee?.last_name
? practitioner.employee.first_name +
" " +
practitioner.employee.last_name
: "Praticien " + (index + 1))
}}
</h6>
<p class="text-xs text-muted mb-0">Praticien</p>
</div>
</div>
</div>
<div v-else class="text-center py-4">
<div class="avatar avatar-xl mb-3">
<div
class="avatar-title bg-gradient-secondary text-white h5 mb-0"
> >
<i class="fas fa-user-plus"></i> Ajouter un thanatopracteur
</div> </button>
</div> </div>
<h6 class="text-sm text-muted">Aucun praticien assigné</h6> </div>
<p class="text-xs text-muted"> <div class="card-body">
Cliquez sur "Gérer l'équipe" pour assigner des praticiens <InfoCard
</p> title="Praticiens Assignés"
icon="fas fa-user-md text-success"
>
<template
v-if="
intervention.practitioners &&
intervention.practitioners.length > 0
"
>
<div class="table-responsive">
<table class="table table-sm table-hover align-middle">
<thead></thead>
<tbody>
<tr
v-for="(
practitioner, index
) in intervention.practitioners"
:key="index"
>
<td class="ps-3">
<div class="d-flex align-items-center">
<div class="avatar avatar-sm me-3">
<div
class="avatar-placeholder bg-gradient-secondary text-white d-flex align-items-center justify-content-center rounded-circle"
>
{{ getInitials(practitioner.employee_name) }}
</div>
</div>
<div class="flex-grow-1">
<h6 class="mb-0 text-sm">
{{ practitioner.employee_name }}
</h6>
</div>
</div>
</td>
<td class="text-end pe-3">
<button
class="btn btn-sm btn-outline-danger"
@click="
$emit('unassign-practitioner', {
practitionerId: practitioner.id,
practitionerName: practitioner.employee_name,
})
"
title="Désassigner le praticien"
:disabled="loading"
>
<i class="fas fa-times"></i>
<span class="d-none d-sm-inline ms-1"
>Désassigner</span
>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<div v-else class="text-center py-4">
<div class="avatar avatar-xl mb-3">
<div
class="avatar-title bg-gradient-secondary text-white h5 mb-0"
>
<i class="fas fa-user-plus"></i>
</div>
</div>
<h6 class="text-sm text-muted">Aucun praticien assigné</h6>
<p class="text-xs text-muted mb-3">
Cliquez sur "Gérer l'équipe" pour assigner des praticiens
</p>
</div>
</InfoCard>
</div> </div>
</div> </div>
</div> </div>
<!-- Documents Tab --> <!-- Documents Tab -->
<div v-if="activeTab === 'documents'" class="tab-pane fade show active"> <div v-if="activeTab === 'documents'" class="tab-pane fade show active">
<div class="text-center py-5"> <div class="card">
<div class="avatar avatar-xl mb-3"> <div class="card-header pb-0">
<div class="avatar-title bg-gradient-info text-white h5 mb-0"> <div class="d-flex align-items-center">
<i class="fas fa-file-alt"></i> <h6 class="mb-0">Documents</h6>
</div>
</div>
<div class="card-body">
<div class="text-center py-5">
<div class="avatar avatar-xl mb-3">
<div class="avatar-title bg-gradient-info text-white h5 mb-0">
<i class="fas fa-file-alt"></i>
</div>
</div>
<h6 class="text-sm text-muted">Documents</h6>
<p class="text-xs text-muted">
Interface de gestion des documents à implémenter...
</p>
</div> </div>
</div> </div>
<h6 class="text-sm text-muted">Documents</h6>
<p class="text-xs text-muted">
Interface de gestion des documents à implémenter...
</p>
</div> </div>
</div> </div>
<!-- History Tab --> <!-- History Tab -->
<div v-if="activeTab === 'history'" class="tab-pane fade show active"> <div v-if="activeTab === 'history'" class="tab-pane fade show active">
<div class="text-center py-5"> <div class="card">
<div class="avatar avatar-xl mb-3"> <div class="card-header pb-0">
<div class="avatar-title bg-gradient-warning text-white h5 mb-0"> <div class="d-flex align-items-center">
<i class="fas fa-history"></i> <h6 class="mb-0">Historique</h6>
</div>
</div>
<div class="card-body">
<div class="text-center py-5">
<div class="avatar avatar-xl mb-3">
<div
class="avatar-title bg-gradient-warning text-white h5 mb-0"
>
<i class="fas fa-history"></i>
</div>
</div>
<h6 class="text-sm text-muted">Historique</h6>
<p class="text-xs text-muted">
Interface d'historique des modifications à implémenter...
</p>
</div> </div>
</div> </div>
<h6 class="text-sm text-muted">Historique</h6>
<p class="text-xs text-muted">
Interface d'historique des modifications à implémenter...
</p>
</div> </div>
</div> </div>
<!-- Navigation Actions --> <!-- Navigation Actions -->
<hr class="horizontal dark" /> <div class="d-flex justify-content-between mt-3">
<div class="d-flex justify-content-between">
<button <button
type="button" type="button"
class="btn btn-sm bg-gradient-danger" class="btn btn-sm bg-gradient-danger"
@click="$emit('cancel')" @click="$emit('cancel')"
:disabled="loading" :disabled="loading"
> >
Annuler <i class="fas fa-arrow-left me-2"></i>Retour
</button> </button>
<div> <div>
@ -338,11 +415,11 @@
<button <button
type="button" type="button"
class="btn btn-sm" class="btn btn-sm"
:class="`bg-gradient-${intervention.action.color}`" :class="`bg-gradient-${intervention.action?.color || 'primary'}`"
@click="saveChanges" @click="saveChanges"
:disabled="!hasChanges || loading" :disabled="!hasChanges || loading"
> >
{{ intervention.action.label }} {{ intervention.action?.label || "Sauvegarder" }}
</button> </button>
</div> </div>
</div> </div>
@ -358,6 +435,7 @@
<script setup> <script setup>
import { ref, computed, watch } from "vue"; import { ref, computed, watch } from "vue";
import SoftInput from "@/components/SoftInput.vue"; import SoftInput from "@/components/SoftInput.vue";
import InfoCard from "@/components/atoms/client/InfoCard.vue";
import { defineProps, defineEmits } from "vue"; import { defineProps, defineEmits } from "vue";
const props = defineProps({ const props = defineProps({
@ -384,6 +462,7 @@ const emit = defineEmits([
"update-intervention", "update-intervention",
"cancel", "cancel",
"assign-practitioner", "assign-practitioner",
"unassign-practitioner",
]); ]);
// État local pour l'édition // État local pour l'édition
@ -431,4 +510,35 @@ watch(
}, },
{ deep: true, immediate: true } { deep: true, immediate: true }
); );
const getInitials = (name) => {
if (!name) return "?";
return name
.split(" ")
.map((word) => word[0])
.join("")
.toUpperCase()
.substring(0, 2);
};
const unassignPractitioner = async (practitionerId) => {
if (!props.intervention?.id) return;
try {
// Use the intervention store to unassign the practitioner
const { useInterventionStore } = await import("@/stores/interventionStore");
const interventionStore = useInterventionStore();
await interventionStore.unassignPractitioner(
props.intervention.id,
practitionerId
);
// Refresh the intervention data by emitting an event to parent
emit("refresh-intervention");
} catch (error) {
console.error("Error unassigning practitioner:", error);
// You might want to show a toast notification here
}
};
</script> </script>

View File

@ -288,6 +288,21 @@ export const InterventionService = {
return response; return response;
}, },
/**
* Unassign a practitioner from intervention
*/
async unassignPractitioner(
interventionId: number,
practitionerId: number
): Promise<Intervention> {
const response = await request<Intervention>({
url: `/api/interventions/${interventionId}/${practitionerId}/unassignPractitioner`,
method: "patch",
});
return response;
},
/** /**
* Get interventions by month * Get interventions by month
*/ */

View File

@ -559,6 +559,53 @@ export const useInterventionStore = defineStore("intervention", () => {
} }
}; };
/**
* Unassign a practitioner from intervention
*/
const unassignPractitioner = async (
interventionId: number,
practitionerId: number
) => {
setLoading(true);
setError(null);
setSuccess(false);
try {
const intervention = await InterventionService.unassignPractitioner(
interventionId,
practitionerId
);
// 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 désassignation du praticien";
setError(errorMessage);
throw err;
} finally {
setLoading(false);
}
};
/** /**
* Get interventions by month * Get interventions by month
*/ */
@ -651,6 +698,7 @@ export const useInterventionStore = defineStore("intervention", () => {
assignPractitioner, assignPractitioner,
assignPractitioners, assignPractitioners,
updatePractitioners, updatePractitioners,
unassignPractitioner,
fetchInterventionsByMonth, fetchInterventionsByMonth,
resetState, resetState,
}; };

View File

@ -47,6 +47,7 @@ const fetchIntervention = async () => {
); );
intervention.value = result; // Store method returns the intervention directly intervention.value = result; // Store method returns the intervention directly
} }
console.log(intervention.value);
} catch (error) { } catch (error) {
console.error("Error loading intervention:", error); console.error("Error loading intervention:", error);
notificationStore.error( notificationStore.error(