-
{{ formattedPrice }}
-
{{ currency }}
-
/{{ unit }}
+
\ No newline at end of file
+ .card-body {
+ padding: 1.5rem;
+ }
+
+ .text-lg {
+ font-size: 1.125rem;
+ }
+
+ .employee-content {
+ position: relative;
+ }
+
+ .d-flex {
+ display: flex;
+ }
+
+ .align-items-center {
+ align-items: center;
+ }
+
+ .justify-content-between {
+ justify-content: space-between;
+ }
+
+ .gap-2 {
+ gap: 0.5rem;
+ }
+
+ .mb-4 {
+ margin-bottom: 1.5rem;
+ }
+
+ .px-4 {
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+ }
+
+ .font-weight-bold {
+ font-weight: 600;
+ }
+
+ .text-primary {
+ color: #5e72e4 !important;
+ }
+
+ @media (max-width: 768px) {
+ .container-fluid {
+ padding-left: 1rem;
+ padding-right: 1rem;
+ }
+
+ .d-flex {
+ flex-direction: column;
+ align-items: stretch;
+ gap: 1rem;
+ }
+
+ .d-flex.gap-2 {
+ flex-direction: row;
+ justify-content: center;
+ }
+ }
+
diff --git a/thanasoft-back/API_DOCUMENTATION.md b/thanasoft-back/API_DOCUMENTATION.md
new file mode 100644
index 0000000..e9d846e
--- /dev/null
+++ b/thanasoft-back/API_DOCUMENTATION.md
@@ -0,0 +1,656 @@
+# API Documentation - Employee Management System
+
+## Overview
+
+The ThanaSoft Employee Management System provides comprehensive RESTful API endpoints for managing employees, thanatopractitioners, and their associated documents. This system is built using Laravel and follows RESTful API conventions.
+
+## Base URL
+
+```
+https://your-domain.com/api
+```
+
+## Authentication
+
+All API endpoints require authentication using Laravel Sanctum. Include the token in the Authorization header:
+
+```
+Authorization: Bearer {your_token}
+```
+
+## Employee Management System
+
+### Entities
+
+1. **Employees** - Core employee records with personal information
+2. **Thanatopractitioners** - Specialized practitioners linked to employees
+3. **Practitioner Documents** - Documents associated with thanatopractitioners
+
+---
+
+## Employees API
+
+### Base Endpoint
+
+```
+/employees
+```
+
+### Endpoints Overview
+
+| Method | Endpoint | Description |
+| ------ | --------------------------------- | ----------------------------------------------- |
+| GET | `/employees` | List all employees with pagination |
+| POST | `/employees` | Create a new employee |
+| GET | `/employees/{id}` | Get specific employee details |
+| PUT | `/employees/{id}` | Update employee information |
+| DELETE | `/employees/{id}` | Delete an employee |
+| GET | `/employees/searchBy` | Search employees by criteria |
+| GET | `/employees/thanatopractitioners` | Get all thanatopractitioners with employee info |
+
+### 1. List All Employees
+
+**GET** `/api/employees`
+
+**Query Parameters:**
+
+- `page` (optional): Page number for pagination (default: 1)
+- `per_page` (optional): Items per page (default: 15, max: 100)
+- `search` (optional): Search term for name, email, or employee_number
+- `department` (optional): Filter by department
+- `status` (optional): Filter by employment status (active, inactive, terminated)
+
+**Success Response (200):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "data": [
+ {
+ "id": 1,
+ "employee_number": "EMP001",
+ "first_name": "Jean",
+ "last_name": "Dupont",
+ "email": "jean.dupont@thanasoft.com",
+ "phone": "+261 34 12 345 67",
+ "department": "Direction",
+ "position": "Directeur Général",
+ "hire_date": "2020-01-15",
+ "status": "active",
+ "created_at": "2025-11-05T10:30:00.000000Z",
+ "updated_at": "2025-11-05T10:30:00.000000Z"
+ }
+ ],
+ "current_page": 1,
+ "per_page": 15,
+ "total": 25,
+ "last_page": 2
+ },
+ "message": "Employés récupérés avec succès"
+}
+```
+
+### 2. Create Employee
+
+**POST** `/api/employees`
+
+**Request Body:**
+
+```json
+{
+ "employee_number": "EMP026",
+ "first_name": "Jean",
+ "last_name": "Dupont",
+ "email": "jean.dupont@thanasoft.com",
+ "phone": "+261 34 12 345 67",
+ "address": "123 Rue de la Liberté, Antananarivo",
+ "birth_date": "1985-06-15",
+ "gender": "male",
+ "department": "Direction",
+ "position": "Directeur Général",
+ "hire_date": "2025-11-05",
+ "salary": 2500000,
+ "status": "active"
+}
+```
+
+**Success Response (201):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": 26,
+ "employee_number": "EMP026",
+ "first_name": "Jean",
+ "last_name": "Dupont",
+ "email": "jean.dupont@thanasoft.com",
+ "phone": "+261 34 12 345 67",
+ "address": "123 Rue de la Liberté, Antananarivo",
+ "birth_date": "1985-06-15",
+ "gender": "male",
+ "department": "Direction",
+ "position": "Directeur Général",
+ "hire_date": "2025-11-05",
+ "salary": 2500000,
+ "status": "active",
+ "created_at": "2025-11-05T12:16:05.000000Z",
+ "updated_at": "2025-11-05T12:16:05.000000Z"
+ },
+ "message": "Employé créé avec succès"
+}
+```
+
+**Validation Errors (422):**
+
+```json
+{
+ "success": false,
+ "message": "Les données fournies ne sont pas valides",
+ "errors": {
+ "email": ["L'adresse email doit être unique"],
+ "employee_number": ["Le numéro d'employé est requis"]
+ }
+}
+```
+
+### 3. Get Employee Details
+
+**GET** `/api/employees/{id}`
+
+**Success Response (200):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": 1,
+ "employee_number": "EMP001",
+ "first_name": "Jean",
+ "last_name": "Dupont",
+ "email": "jean.dupont@thanasoft.com",
+ "phone": "+261 34 12 345 67",
+ "address": "123 Rue de la Liberté, Antananarivo",
+ "birth_date": "1985-06-15",
+ "gender": "male",
+ "department": "Direction",
+ "position": "Directeur Général",
+ "hire_date": "2020-01-15",
+ "salary": 2500000,
+ "status": "active",
+ "created_at": "2020-01-15T08:00:00.000000Z",
+ "updated_at": "2025-11-05T10:30:00.000000Z"
+ },
+ "message": "Détails de l'employé récupérés avec succès"
+}
+```
+
+### 4. Update Employee
+
+**PUT** `/api/employees/{id}`
+
+**Request Body:** Same as create, but all fields are optional.
+
+**Success Response (200):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": 1,
+ "employee_number": "EMP001",
+ "first_name": "Jean",
+ "last_name": "Dupont",
+ "email": "jean.dupont@thanasoft.com",
+ "phone": "+261 34 12 345 68",
+ "department": "Direction",
+ "position": "Directeur Général Adjoint",
+ "updated_at": "2025-11-05T12:16:05.000000Z"
+ },
+ "message": "Employé mis à jour avec succès"
+}
+```
+
+### 5. Search Employees
+
+**GET** `/api/employees/searchBy`
+
+**Query Parameters:**
+
+- `query` (required): Search term
+- `field` (optional): Field to search in (first_name, last_name, email, employee_number, department, position)
+
+**Example:** `/api/employees/searchBy?query=jean&field=first_name`
+
+**Success Response (200):**
+
+```json
+{
+ "success": true,
+ "data": [
+ {
+ "id": 1,
+ "employee_number": "EMP001",
+ "first_name": "Jean",
+ "last_name": "Dupont",
+ "email": "jean.dupont@thanasoft.com",
+ "department": "Direction",
+ "position": "Directeur Général"
+ }
+ ],
+ "message": "Résultats de recherche récupérés avec succès"
+}
+```
+
+---
+
+## Thanatopractitioners API
+
+### Base Endpoint
+
+```
+/thanatopractitioners
+```
+
+### Endpoints Overview
+
+| Method | Endpoint | Description |
+| ------ | ---------------------------------------------- | ------------------------------------ |
+| GET | `/thanatopractitioners` | List all thanatopractitioners |
+| POST | `/thanatopractitioners` | Create a new thanatopractitioner |
+| GET | `/thanatopractitioners/{id}` | Get specific thanatopractitioner |
+| PUT | `/thanatopractitioners/{id}` | Update thanatopractitioner |
+| DELETE | `/thanatopractitioners/{id}` | Delete thanatopractitioner |
+| GET | `/employees/{employeeId}/thanatopractitioners` | Get thanatopractitioners by employee |
+
+### 1. List All Thanatopractitioners
+
+**GET** `/api/thanatopractitioners`
+
+**Query Parameters:**
+
+- `page` (optional): Page number
+- `per_page` (optional): Items per page
+- `specialization` (optional): Filter by specialization
+
+**Success Response (200):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "data": [
+ {
+ "id": 1,
+ "employee_id": 1,
+ "specialization": "Thanatopraticien principal",
+ "license_number": "TH001",
+ "authorization_number": "AUTH001",
+ "authorization_date": "2020-01-15",
+ "authorization_expiry": "2025-01-15",
+ "is_authorized": true,
+ "created_at": "2020-01-15T08:00:00.000000Z",
+ "updated_at": "2025-11-05T10:30:00.000000Z",
+ "employee": {
+ "id": 1,
+ "first_name": "Jean",
+ "last_name": "Dupont",
+ "employee_number": "EMP001",
+ "department": "Direction"
+ }
+ }
+ ],
+ "current_page": 1,
+ "per_page": 15,
+ "total": 5,
+ "last_page": 1
+ },
+ "message": "Thanatopraticiens récupérés avec succès"
+}
+```
+
+### 2. Create Thanatopractitioner
+
+**POST** `/api/thanatopractitioners`
+
+**Request Body:**
+
+```json
+{
+ "employee_id": 1,
+ "specialization": "Thanatopraticien principal",
+ "license_number": "TH001",
+ "authorization_number": "AUTH001",
+ "authorization_date": "2025-11-05",
+ "authorization_expiry": "2026-11-05",
+ "is_authorized": true
+}
+```
+
+**Success Response (201):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": 6,
+ "employee_id": 1,
+ "specialization": "Thanatopraticien principal",
+ "license_number": "TH001",
+ "authorization_number": "AUTH001",
+ "authorization_date": "2025-11-05",
+ "authorization_expiry": "2026-11-05",
+ "is_authorized": true,
+ "created_at": "2025-11-05T12:16:05.000000Z",
+ "updated_at": "2025-11-05T12:16:05.000000Z"
+ },
+ "message": "Thanatopraticien créé avec succès"
+}
+```
+
+### 3. Get Thanatopractitioners by Employee
+
+**GET** `/api/employees/{employeeId}/thanatopractitioners`
+
+**Success Response (200):**
+
+```json
+{
+ "success": true,
+ "data": [
+ {
+ "id": 1,
+ "specialization": "Thanatopraticien principal",
+ "license_number": "TH001",
+ "authorization_number": "AUTH001",
+ "authorization_date": "2020-01-15",
+ "authorization_expiry": "2025-01-15",
+ "is_authorized": true
+ }
+ ],
+ "message": "Thanatopraticiens de l'employé récupérés avec succès"
+}
+```
+
+---
+
+## Practitioner Documents API
+
+### Base Endpoint
+
+```
+/practitioner-documents
+```
+
+### Endpoints Overview
+
+| Method | Endpoint | Description |
+| ------ | -------------------------------------- | ------------------------------------ |
+| GET | `/practitioner-documents` | List all documents |
+| POST | `/practitioner-documents` | Upload new document |
+| GET | `/practitioner-documents/{id}` | Get specific document |
+| PUT | `/practitioner-documents/{id}` | Update document info |
+| DELETE | `/practitioner-documents/{id}` | Delete document |
+| GET | `/practitioner-documents/searchBy` | Search documents |
+| GET | `/practitioner-documents/expiring` | Get expiring documents |
+| GET | `/thanatopractitioners/{id}/documents` | Get documents by thanatopractitioner |
+| PATCH | `/practitioner-documents/{id}/verify` | Verify document |
+
+### 1. List All Documents
+
+**GET** `/api/practitioner-documents`
+
+**Query Parameters:**
+
+- `page` (optional): Page number
+- `per_page` (optional): Items per page
+- `type` (optional): Filter by document type
+- `is_verified` (optional): Filter by verification status
+
+**Success Response (200):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "data": [
+ {
+ "id": 1,
+ "thanatopractitioner_id": 1,
+ "document_type": "diplome",
+ "file_name": "diplome_thanatopraticien.pdf",
+ "file_path": "/documents/diplome_thanatopraticien.pdf",
+ "issue_date": "2019-06-30",
+ "expiry_date": "2024-06-30",
+ "issuing_authority": "Ministère de la Santé",
+ "is_verified": true,
+ "verified_at": "2020-01-20T10:00:00.000000Z",
+ "created_at": "2020-01-15T08:00:00.000000Z",
+ "updated_at": "2020-01-20T10:00:00.000000Z"
+ }
+ ],
+ "current_page": 1,
+ "per_page": 15,
+ "total": 12,
+ "last_page": 1
+ },
+ "message": "Documents récupérés avec succès"
+}
+```
+
+### 2. Upload Document
+
+**POST** `/api/practitioner-documents`
+
+**Form Data:**
+
+```
+thanatopractitioner_id: 1
+document_type: diplome
+file: [binary file data]
+issue_date: 2019-06-30
+expiry_date: 2024-06-30
+issuing_authority: Ministère de la Santé
+```
+
+**Success Response (201):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": 13,
+ "thanatopractitioner_id": 1,
+ "document_type": "diplome",
+ "file_name": "diplome_thanatopraticien_2025.pdf",
+ "file_path": "/documents/diplome_thanatopraticien_2025.pdf",
+ "issue_date": "2019-06-30",
+ "expiry_date": "2024-06-30",
+ "issuing_authority": "Ministère de la Santé",
+ "is_verified": false,
+ "created_at": "2025-11-05T12:16:05.000000Z",
+ "updated_at": "2025-11-05T12:16:05.000000Z"
+ },
+ "message": "Document téléchargé avec succès"
+}
+```
+
+### 3. Get Expiring Documents
+
+**GET** `/api/practitioner-documents/expiring`
+
+**Query Parameters:**
+
+- `days` (optional): Number of days to look ahead (default: 30)
+
+**Success Response (200):**
+
+```json
+{
+ "success": true,
+ "data": [
+ {
+ "id": 5,
+ "thanatopractitioner_id": 2,
+ "document_type": "certificat",
+ "file_name": "certificat_renouvellement.pdf",
+ "expiry_date": "2025-11-20",
+ "days_until_expiry": 15,
+ "employee": {
+ "first_name": "Marie",
+ "last_name": "Rasoa",
+ "employee_number": "EMP002"
+ }
+ }
+ ],
+ "message": "Documents expirants récupérés avec succès"
+}
+```
+
+### 4. Verify Document
+
+**PATCH** `/api/practitioner-documents/{id}/verify`
+
+**Success Response (200):**
+
+```json
+{
+ "success": true,
+ "data": {
+ "id": 13,
+ "document_type": "diplome",
+ "is_verified": true,
+ "verified_at": "2025-11-05T12:20:00.000000Z"
+ },
+ "message": "Document vérifié avec succès"
+}
+```
+
+---
+
+## Data Models
+
+### Employee Model
+
+| Field | Type | Required | Description |
+| ----------------- | ------- | -------- | ------------------------------------------------ |
+| `employee_number` | string | Yes | Unique employee identifier |
+| `first_name` | string | Yes | Employee's first name |
+| `last_name` | string | Yes | Employee's last name |
+| `email` | email | Yes | Unique email address |
+| `phone` | string | No | Phone number |
+| `address` | text | No | Physical address |
+| `birth_date` | date | No | Date of birth |
+| `gender` | string | No | Gender (male, female, other) |
+| `department` | string | No | Department name |
+| `position` | string | No | Job position |
+| `hire_date` | date | No | Employment start date |
+| `salary` | decimal | No | Monthly salary |
+| `status` | string | No | Employment status (active, inactive, terminated) |
+
+### Thanatopractitioner Model
+
+| Field | Type | Required | Description |
+| ---------------------- | ------- | -------- | ------------------------------ |
+| `employee_id` | integer | Yes | Foreign key to employees table |
+| `specialization` | string | Yes | Area of specialization |
+| `license_number` | string | Yes | Professional license number |
+| `authorization_number` | string | Yes | Authorization number |
+| `authorization_date` | date | Yes | Authorization issue date |
+| `authorization_expiry` | date | Yes | Authorization expiry date |
+| `is_authorized` | boolean | Yes | Authorization status |
+
+### Practitioner Document Model
+
+| Field | Type | Required | Description |
+| ------------------------ | --------- | -------- | ----------------------------------------- |
+| `thanatopractitioner_id` | integer | Yes | Foreign key to thanatopractitioners table |
+| `document_type` | string | Yes | Type of document |
+| `file` | file | Yes | Document file upload |
+| `issue_date` | date | No | Document issue date |
+| `expiry_date` | date | No | Document expiry date |
+| `issuing_authority` | string | No | Authority that issued the document |
+| `is_verified` | boolean | Yes | Verification status |
+| `verified_at` | timestamp | No | Verification timestamp |
+
+---
+
+## Error Handling
+
+### HTTP Status Codes
+
+- `200` - Success
+- `201` - Created successfully
+- `400` - Bad Request
+- `401` - Unauthorized
+- `403` - Forbidden
+- `404` - Resource not found
+- `422` - Validation error
+- `500` - Internal server error
+
+### Error Response Format
+
+```json
+{
+ "success": false,
+ "message": "Description of the error",
+ "errors": {
+ "field_name": ["Error message for this field"]
+ }
+}
+```
+
+---
+
+## File Upload
+
+For document uploads, use multipart/form-data:
+
+```
+POST /api/practitioner-documents
+Content-Type: multipart/form-data
+
+{
+ "thanatopractitioner_id": 1,
+ "document_type": "diplome",
+ "file": [binary file],
+ "issue_date": "2019-06-30",
+ "expiry_date": "2024-06-30",
+ "issuing_authority": "Ministère de la Santé"
+}
+```
+
+---
+
+## Pagination
+
+All list endpoints support pagination with the following query parameters:
+
+- `page`: Page number (default: 1)
+- `per_page`: Items per page (default: 15, max: 100)
+
+Response includes pagination metadata:
+
+```json
+{
+ "current_page": 1,
+ "per_page": 15,
+ "total": 50,
+ "last_page": 4,
+ "from": 1,
+ "to": 15
+}
+```
+
+---
+
+## Rate Limiting
+
+API requests are rate limited to 1000 requests per hour per authenticated user. Exceeding this limit will result in a `429 Too Many Requests` response.
+
+---
+
+## Support
+
+For API support or questions, please contact the development team or refer to the Laravel documentation at https://laravel.com/docs.
diff --git a/thanasoft-back/app/Http/Controllers/Api/EmployeeController.php b/thanasoft-back/app/Http/Controllers/Api/EmployeeController.php
new file mode 100644
index 0000000..8d675b2
--- /dev/null
+++ b/thanasoft-back/app/Http/Controllers/Api/EmployeeController.php
@@ -0,0 +1,272 @@
+get('per_page', 15);
+
+ $filters = [
+ 'search' => $request->get('search'),
+ 'active' => $request->get('active'),
+ 'sort_by' => $request->get('sort_by', 'last_name'),
+ 'sort_direction' => $request->get('sort_direction', 'asc'),
+ ];
+
+ // Remove null filters
+ $filters = array_filter($filters, function ($value) {
+ return $value !== null && $value !== '';
+ });
+
+ $result = $this->employeeRepository->getPaginated($perPage, $filters);
+
+ return response()->json([
+ 'data' => new EmployeeCollection($result['employees']),
+ 'pagination' => $result['pagination'],
+ 'message' => 'Employés récupérés avec succès.',
+ ], 200);
+
+ } catch (\Exception $e) {
+ Log::error('Error fetching employees: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des employés.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Display paginated employees.
+ */
+ public function paginated(Request $request): JsonResponse
+ {
+ try {
+ $perPage = (int) $request->get('per_page', 15);
+ $result = $this->employeeRepository->getPaginated($perPage, []);
+
+ return response()->json([
+ 'data' => new EmployeeCollection($result['employees']),
+ 'pagination' => $result['pagination'],
+ 'message' => 'Employés récupérés avec succès.',
+ ], 200);
+
+ } catch (\Exception $e) {
+ Log::error('Error fetching paginated employees: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des employés.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Get active employees only.
+ */
+ public function active(): EmployeeCollection|JsonResponse
+ {
+ try {
+ $employees = $this->employeeRepository->getActive();
+ return new EmployeeCollection($employees);
+ } catch (\Exception $e) {
+ Log::error('Error fetching active employees: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des employés actifs.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Get employees with thanatopractitioner data.
+ */
+ public function withThanatopractitioner(): EmployeeCollection|JsonResponse
+ {
+ try {
+ $employees = $this->employeeRepository->getWithThanatopractitioner();
+ return new EmployeeCollection($employees);
+ } catch (\Exception $e) {
+ Log::error('Error fetching employees with thanatopractitioner: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des employés avec données thanatopractitioner.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Get employee statistics.
+ */
+ public function statistics(): JsonResponse
+ {
+ try {
+ $statistics = $this->employeeRepository->getStatistics();
+
+ return response()->json([
+ 'data' => $statistics,
+ 'message' => 'Statistiques des employés récupérées avec succès.',
+ ], 200);
+ } catch (\Exception $e) {
+ Log::error('Error fetching employee statistics: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des statistiques.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Store a newly created employee.
+ */
+ public function store(StoreEmployeeRequest $request): EmployeeResource|JsonResponse
+ {
+ try {
+ $employee = $this->employeeRepository->create($request->validated());
+ return new EmployeeResource($employee);
+ } catch (\Exception $e) {
+ Log::error('Error creating employee: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ 'data' => $request->validated(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la création de l\'employé.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Display the specified employee.
+ */
+ public function show(string $id): EmployeeResource|JsonResponse
+ {
+ try {
+ $employee = $this->employeeRepository->find($id);
+
+ if (!$employee) {
+ return response()->json([
+ 'message' => 'Employé non trouvé.',
+ ], 404);
+ }
+
+ return new EmployeeResource($employee);
+ } catch (\Exception $e) {
+ Log::error('Error fetching employee: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ 'employee_id' => $id,
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération de l\'employé.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Update the specified employee.
+ */
+ public function update(UpdateEmployeeRequest $request, string $id): EmployeeResource|JsonResponse
+ {
+ try {
+ $updated = $this->employeeRepository->update($id, $request->validated());
+
+ if (!$updated) {
+ return response()->json([
+ 'message' => 'Employé non trouvé ou échec de la mise à jour.',
+ ], 404);
+ }
+
+ $employee = $this->employeeRepository->find($id);
+ return new EmployeeResource($employee);
+ } catch (\Exception $e) {
+ Log::error('Error updating employee: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ 'employee_id' => $id,
+ 'data' => $request->validated(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la mise à jour de l\'employé.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Remove the specified employee.
+ */
+ public function destroy(string $id): JsonResponse
+ {
+ try {
+ $deleted = $this->employeeRepository->delete($id);
+
+ if (!$deleted) {
+ return response()->json([
+ 'message' => 'Employé non trouvé ou échec de la suppression.',
+ ], 404);
+ }
+
+ return response()->json([
+ 'message' => 'Employé supprimé avec succès.',
+ ], 200);
+ } catch (\Exception $e) {
+ Log::error('Error deleting employee: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ 'employee_id' => $id,
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la suppression de l\'employé.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+}
diff --git a/thanasoft-back/app/Http/Controllers/Api/PractitionerDocumentController.php b/thanasoft-back/app/Http/Controllers/Api/PractitionerDocumentController.php
new file mode 100644
index 0000000..eb0e1f3
--- /dev/null
+++ b/thanasoft-back/app/Http/Controllers/Api/PractitionerDocumentController.php
@@ -0,0 +1,320 @@
+ $request->get('search'),
+ 'practitioner_id' => $request->get('practitioner_id'),
+ 'doc_type' => $request->get('doc_type'),
+ 'valid_only' => $request->get('valid_only'),
+ 'sort_by' => $request->get('sort_by', 'created_at'),
+ 'sort_direction' => $request->get('sort_direction', 'desc'),
+ ];
+
+ // Remove null filters
+ $filters = array_filter($filters, function ($value) {
+ return $value !== null && $value !== '';
+ });
+
+ $documents = $this->practitionerDocumentRepository->getAll($filters);
+
+ return new PractitionerDocumentCollection($documents);
+
+ } catch (\Exception $e) {
+ Log::error('Error fetching practitioner documents: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des documents des praticiens.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Display paginated practitioner documents.
+ */
+ public function paginated(Request $request): JsonResponse
+ {
+ try {
+ $perPage = $request->get('per_page', 15);
+ $result = $this->practitionerDocumentRepository->getPaginated($perPage);
+
+ return response()->json([
+ 'data' => new PractitionerDocumentCollection($result['documents']),
+ 'pagination' => $result['pagination'],
+ 'message' => 'Documents des praticiens récupérés avec succès.',
+ ], 200);
+
+ } catch (\Exception $e) {
+ Log::error('Error fetching paginated practitioner documents: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des documents des praticiens.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Get documents by practitioner ID.
+ */
+ public function byPractitioner(string $practitionerId): PractitionerDocumentCollection|JsonResponse
+ {
+ try {
+ $documents = $this->practitionerDocumentRepository->getByPractitionerId((int) $practitionerId);
+ return new PractitionerDocumentCollection($documents);
+ } catch (\Exception $e) {
+ Log::error('Error fetching documents by practitioner: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ 'practitioner_id' => $practitionerId,
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des documents du praticien.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Get documents by type.
+ */
+ public function byType(Request $request): PractitionerDocumentCollection|JsonResponse
+ {
+ try {
+ $docType = $request->get('doc_type');
+
+ if (!$docType) {
+ return response()->json([
+ 'message' => 'Le paramètre doc_type est requis.',
+ ], 400);
+ }
+
+ $documents = $this->practitionerDocumentRepository->getByDocumentType($docType);
+ return new PractitionerDocumentCollection($documents);
+ } catch (\Exception $e) {
+ Log::error('Error fetching documents by type: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ 'doc_type' => $docType,
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des documents par type.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Get valid documents (not expired).
+ */
+ public function valid(): PractitionerDocumentCollection|JsonResponse
+ {
+ try {
+ $documents = $this->practitionerDocumentRepository->getValid();
+ return new PractitionerDocumentCollection($documents);
+ } catch (\Exception $e) {
+ Log::error('Error fetching valid documents: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des documents valides.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Get expired documents.
+ */
+ public function expired(): PractitionerDocumentCollection|JsonResponse
+ {
+ try {
+ $documents = $this->practitionerDocumentRepository->getExpired();
+ return new PractitionerDocumentCollection($documents);
+ } catch (\Exception $e) {
+ Log::error('Error fetching expired documents: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des documents expirés.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Get practitioner document statistics.
+ */
+ public function statistics(): JsonResponse
+ {
+ try {
+ $statistics = $this->practitionerDocumentRepository->getStatistics();
+
+ return response()->json([
+ 'data' => $statistics,
+ 'message' => 'Statistiques des documents des praticiens récupérées avec succès.',
+ ], 200);
+ } catch (\Exception $e) {
+ Log::error('Error fetching practitioner document statistics: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des statistiques.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Store a newly created practitioner document.
+ */
+ public function store(StorePractitionerDocumentRequest $request): PractitionerDocumentResource|JsonResponse
+ {
+ try {
+ $document = $this->practitionerDocumentRepository->create($request->validated());
+ return new PractitionerDocumentResource($document);
+ } catch (\Exception $e) {
+ Log::error('Error creating practitioner document: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ 'data' => $request->validated(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la création du document du praticien.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Display the specified practitioner document.
+ */
+ public function show(string $id): PractitionerDocumentResource|JsonResponse
+ {
+ try {
+ $document = $this->practitionerDocumentRepository->find($id);
+
+ if (!$document) {
+ return response()->json([
+ 'message' => 'Document du praticien non trouvé.',
+ ], 404);
+ }
+
+ return new PractitionerDocumentResource($document);
+ } catch (\Exception $e) {
+ Log::error('Error fetching practitioner document: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ 'document_id' => $id,
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération du document du praticien.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Update the specified practitioner document.
+ */
+ public function update(UpdatePractitionerDocumentRequest $request, string $id): PractitionerDocumentResource|JsonResponse
+ {
+ try {
+ $updated = $this->practitionerDocumentRepository->update($id, $request->validated());
+
+ if (!$updated) {
+ return response()->json([
+ 'message' => 'Document du praticien non trouvé ou échec de la mise à jour.',
+ ], 404);
+ }
+
+ $document = $this->practitionerDocumentRepository->find($id);
+ return new PractitionerDocumentResource($document);
+ } catch (\Exception $e) {
+ Log::error('Error updating practitioner document: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ 'document_id' => $id,
+ 'data' => $request->validated(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la mise à jour du document du praticien.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Remove the specified practitioner document.
+ */
+ public function destroy(string $id): JsonResponse
+ {
+ try {
+ $deleted = $this->practitionerDocumentRepository->delete($id);
+
+ if (!$deleted) {
+ return response()->json([
+ 'message' => 'Document du praticien non trouvé ou échec de la suppression.',
+ ], 404);
+ }
+
+ return response()->json([
+ 'message' => 'Document du praticien supprimé avec succès.',
+ ], 200);
+ } catch (\Exception $e) {
+ Log::error('Error deleting practitioner document: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ 'document_id' => $id,
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la suppression du document du praticien.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+}
diff --git a/thanasoft-back/app/Http/Controllers/Api/ProductController.php b/thanasoft-back/app/Http/Controllers/Api/ProductController.php
index f476f89..f16e6da 100644
--- a/thanasoft-back/app/Http/Controllers/Api/ProductController.php
+++ b/thanasoft-back/app/Http/Controllers/Api/ProductController.php
@@ -31,7 +31,7 @@ class ProductController extends Controller
$perPage = $request->get('per_page', 15);
$filters = [
'search' => $request->get('search'),
- 'categorie' => $request->get('categorie'),
+ 'categorie' => $request->get('categorie_id'),
'fournisseur_id' => $request->get('fournisseur_id'),
'low_stock' => $request->get('low_stock'),
'expiring_soon' => $request->get('expiring_soon'),
@@ -196,16 +196,16 @@ class ProductController extends Controller
public function byCategory(Request $request): ProductCollection|JsonResponse
{
try {
- $category = $request->get('category');
+ $categoryId = $request->get('category_id');
$perPage = $request->get('per_page', 15);
- if (empty($category)) {
+ if (empty($categoryId)) {
return response()->json([
- 'message' => 'Le paramètre "category" est requis.',
+ 'message' => 'Le paramètre "category_id" est requis.',
], 400);
}
- $products = $this->productRepository->getByCategory($category, $perPage);
+ $products = $this->productRepository->getByCategory($categoryId, $perPage);
return new ProductCollection($products);
@@ -213,7 +213,7 @@ class ProductController extends Controller
Log::error('Error fetching products by category: ' . $e->getMessage(), [
'exception' => $e,
'trace' => $e->getTraceAsString(),
- 'category' => $category,
+ 'category_id' => $categoryId,
]);
return response()->json([
diff --git a/thanasoft-back/app/Http/Controllers/Api/ThanatopractitionerController.php b/thanasoft-back/app/Http/Controllers/Api/ThanatopractitionerController.php
new file mode 100644
index 0000000..2b6f8ef
--- /dev/null
+++ b/thanasoft-back/app/Http/Controllers/Api/ThanatopractitionerController.php
@@ -0,0 +1,322 @@
+get('per_page', 15);
+
+ $filters = [
+ 'search' => $request->get('search'),
+ 'valid_authorization' => $request->get('valid_authorization'),
+ 'sort_by' => $request->get('sort_by', 'created_at'),
+ 'sort_direction' => $request->get('sort_direction', 'desc'),
+ ];
+
+ // Remove null filters
+ $filters = array_filter($filters, function ($value) {
+ return $value !== null && $value !== '';
+ });
+
+ $result = $this->thanatopractitionerRepository->getPaginated($perPage, $filters);
+
+ return response()->json([
+ 'data' => new ThanatopractitionerCollection($result['thanatopractitioners']),
+ 'pagination' => $result['pagination'],
+ 'message' => 'Thanatopractitioners récupérés avec succès.',
+ ], 200);
+
+ } catch (\Exception $e) {
+ Log::error('Error fetching thanatopractitioners: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des thanatopractitioners.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Display paginated thanatopractitioners.
+ */
+ public function paginated(Request $request): JsonResponse
+ {
+ try {
+ $perPage = (int) $request->get('per_page', 15);
+ $result = $this->thanatopractitionerRepository->getPaginated($perPage, []);
+
+ return response()->json([
+ 'data' => new ThanatopractitionerCollection($result['thanatopractitioners']),
+ 'pagination' => $result['pagination'],
+ 'message' => 'Thanatopractitioners récupérés avec succès.',
+ ], 200);
+
+ } catch (\Exception $e) {
+ Log::error('Error fetching paginated thanatopractitioners: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des thanatopractitioners.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Get thanatopractitioners with valid authorization.
+ */
+ public function withValidAuthorization(): ThanatopractitionerCollection|JsonResponse
+ {
+ try {
+ $thanatopractitioners = $this->thanatopractitionerRepository->getWithValidAuthorization();
+ return new ThanatopractitionerCollection($thanatopractitioners);
+ } catch (\Exception $e) {
+ Log::error('Error fetching thanatopractitioners with valid authorization: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des thanatopractitioners avec autorisation valide.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Get thanatopractitioners with expired authorization.
+ */
+ public function withExpiredAuthorization(): ThanatopractitionerCollection|JsonResponse
+ {
+ try {
+ $thanatopractitioners = $this->thanatopractitionerRepository->getWithExpiredAuthorization();
+ return new ThanatopractitionerCollection($thanatopractitioners);
+ } catch (\Exception $e) {
+ Log::error('Error fetching thanatopractitioners with expired authorization: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des thanatopractitioners avec autorisation expirée.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Get thanatopractitioners with their complete data.
+ */
+ public function withRelations(): ThanatopractitionerCollection|JsonResponse
+ {
+ try {
+ $thanatopractitioners = $this->thanatopractitionerRepository->getWithRelations();
+ return new ThanatopractitionerCollection($thanatopractitioners);
+ } catch (\Exception $e) {
+ Log::error('Error fetching thanatopractitioners with relations: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des thanatopractitioners avec relations.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Get thanatopractitioner statistics.
+ */
+ public function statistics(): JsonResponse
+ {
+ try {
+ $statistics = $this->thanatopractitionerRepository->getStatistics();
+
+ return response()->json([
+ 'data' => $statistics,
+ 'message' => 'Statistiques des thanatopractitioners récupérées avec succès.',
+ ], 200);
+ } catch (\Exception $e) {
+ Log::error('Error fetching thanatopractitioner statistics: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération des statistiques.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Store a newly created thanatopractitioner.
+ */
+ public function store(StoreThanatopractitionerRequest $request): ThanatopractitionerResource|JsonResponse
+ {
+ try {
+ $thanatopractitioner = $this->thanatopractitionerRepository->create($request->validated());
+ return new ThanatopractitionerResource($thanatopractitioner);
+ } catch (\Exception $e) {
+ Log::error('Error creating thanatopractitioner: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ 'data' => $request->validated(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la création du thanatopractitioner.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Display the specified thanatopractitioner.
+ */
+ public function show(string $id): ThanatopractitionerResource|JsonResponse
+ {
+ try {
+ $thanatopractitioner = $this->thanatopractitionerRepository->find($id);
+
+ if (!$thanatopractitioner) {
+ return response()->json([
+ 'message' => 'Thanatopractitioner non trouvé.',
+ ], 404);
+ }
+
+ return new ThanatopractitionerResource($thanatopractitioner);
+ } catch (\Exception $e) {
+ Log::error('Error fetching thanatopractitioner: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ 'thanatopractitioner_id' => $id,
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération du thanatopractitioner.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Find a thanatopractitioner by employee ID.
+ */
+ public function findByEmployee(string $employeeId): ThanatopractitionerResource|JsonResponse
+ {
+ try {
+ $thanatopractitioner = $this->thanatopractitionerRepository->findByEmployeeId((int) $employeeId);
+
+ if (!$thanatopractitioner) {
+ return response()->json([
+ 'message' => 'Thanatopractitioner non trouvé pour cet employé.',
+ ], 404);
+ }
+
+ return new ThanatopractitionerResource($thanatopractitioner);
+ } catch (\Exception $e) {
+ Log::error('Error fetching thanatopractitioner by employee: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ 'employee_id' => $employeeId,
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la récupération du thanatopractitioner.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Update the specified thanatopractitioner.
+ */
+ public function update(UpdateThanatopractitionerRequest $request, string $id): ThanatopractitionerResource|JsonResponse
+ {
+ try {
+ $updated = $this->thanatopractitionerRepository->update($id, $request->validated());
+
+ if (!$updated) {
+ return response()->json([
+ 'message' => 'Thanatopractitioner non trouvé ou échec de la mise à jour.',
+ ], 404);
+ }
+
+ $thanatopractitioner = $this->thanatopractitionerRepository->find($id);
+ return new ThanatopractitionerResource($thanatopractitioner);
+ } catch (\Exception $e) {
+ Log::error('Error updating thanatopractitioner: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ 'thanatopractitioner_id' => $id,
+ 'data' => $request->validated(),
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la mise à jour du thanatopractitioner.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+
+ /**
+ * Remove the specified thanatopractitioner.
+ */
+ public function destroy(string $id): JsonResponse
+ {
+ try {
+ $deleted = $this->thanatopractitionerRepository->delete($id);
+
+ if (!$deleted) {
+ return response()->json([
+ 'message' => 'Thanatopractitioner non trouvé ou échec de la suppression.',
+ ], 404);
+ }
+
+ return response()->json([
+ 'message' => 'Thanatopractitioner supprimé avec succès.',
+ ], 200);
+ } catch (\Exception $e) {
+ Log::error('Error deleting thanatopractitioner: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'trace' => $e->getTraceAsString(),
+ 'thanatopractitioner_id' => $id,
+ ]);
+
+ return response()->json([
+ 'message' => 'Une erreur est survenue lors de la suppression du thanatopractitioner.',
+ 'error' => config('app.debug') ? $e->getMessage() : null,
+ ], 500);
+ }
+ }
+}
diff --git a/thanasoft-back/app/Http/Requests/StoreEmployeeRequest.php b/thanasoft-back/app/Http/Requests/StoreEmployeeRequest.php
new file mode 100644
index 0000000..93fc0b1
--- /dev/null
+++ b/thanasoft-back/app/Http/Requests/StoreEmployeeRequest.php
@@ -0,0 +1,59 @@
+
+ */
+ public function rules(): array
+ {
+ return [
+ 'first_name' => 'required|string|max:191',
+ 'last_name' => 'required|string|max:191',
+ 'email' => 'nullable|email|max:191|unique:employees,email',
+ 'phone' => 'nullable|string|max:50',
+ 'job_title' => 'nullable|string|max:191',
+ 'hire_date' => 'nullable|date',
+ 'active' => 'boolean',
+ ];
+ }
+
+ /**
+ * Get the error messages for the defined validation rules.
+ *
+ * @return array
+ */
+ public function messages(): array
+ {
+ return [
+ 'first_name.required' => 'Le prénom est obligatoire.',
+ 'first_name.string' => 'Le prénom doit être une chaîne de caractères.',
+ 'first_name.max' => 'Le prénom ne peut pas dépasser :max caractères.',
+ 'last_name.required' => 'Le nom de famille est obligatoire.',
+ 'last_name.string' => 'Le nom de famille doit être une chaîne de caractères.',
+ 'last_name.max' => 'Le nom de famille ne peut pas dépasser :max caractères.',
+ 'email.email' => 'L\'adresse email doit être valide.',
+ 'email.unique' => 'Cette adresse email est déjà utilisée.',
+ 'phone.string' => 'Le téléphone doit être une chaîne de caractères.',
+ 'phone.max' => 'Le téléphone ne peut pas dépasser :max caractères.',
+ 'job_title.string' => 'L\'intitulé du poste doit être une chaîne de caractères.',
+ 'job_title.max' => 'L\'intitulé du poste ne peut pas dépasser :max caractères.',
+ 'hire_date.date' => 'La date d\'embauche doit être une date valide.',
+ 'active.boolean' => 'Le statut actif doit être un booléen.',
+ ];
+ }
+}
diff --git a/thanasoft-back/app/Http/Requests/StorePractitionerDocumentRequest.php b/thanasoft-back/app/Http/Requests/StorePractitionerDocumentRequest.php
new file mode 100644
index 0000000..2f18457
--- /dev/null
+++ b/thanasoft-back/app/Http/Requests/StorePractitionerDocumentRequest.php
@@ -0,0 +1,55 @@
+
+ */
+ public function rules(): array
+ {
+ return [
+ 'practitioner_id' => 'required|exists:thanatopractitioners,id',
+ 'doc_type' => 'required|string|max:191',
+ 'file_id' => 'nullable|exists:files,id',
+ 'issue_date' => 'nullable|date',
+ 'expiry_date' => 'nullable|date|after_or_equal:issue_date',
+ 'status' => 'nullable|string|max:64',
+ ];
+ }
+
+ /**
+ * Get the error messages for the defined validation rules.
+ *
+ * @return array
+ */
+ public function messages(): array
+ {
+ return [
+ 'practitioner_id.required' => 'Le thanatopractitioner est obligatoire.',
+ 'practitioner_id.exists' => 'Le thanatopractitioner sélectionné n\'existe pas.',
+ 'doc_type.required' => 'Le type de document est obligatoire.',
+ 'doc_type.string' => 'Le type de document doit être une chaîne de caractères.',
+ 'doc_type.max' => 'Le type de document ne peut pas dépasser :max caractères.',
+ 'file_id.exists' => 'Le fichier sélectionné n\'existe pas.',
+ 'issue_date.date' => 'La date de délivrance doit être une date valide.',
+ 'expiry_date.date' => 'La date d\'expiration doit être une date valide.',
+ 'expiry_date.after_or_equal' => 'La date d\'expiration doit être égale ou postérieure à la date de délivrance.',
+ 'status.string' => 'Le statut doit être une chaîne de caractères.',
+ 'status.max' => 'Le statut ne peut pas dépasser :max caractères.',
+ ];
+ }
+}
diff --git a/thanasoft-back/app/Http/Requests/StoreProductRequest.php b/thanasoft-back/app/Http/Requests/StoreProductRequest.php
index 2936556..1e293cb 100644
--- a/thanasoft-back/app/Http/Requests/StoreProductRequest.php
+++ b/thanasoft-back/app/Http/Requests/StoreProductRequest.php
@@ -24,7 +24,7 @@ class StoreProductRequest extends FormRequest
return [
'nom' => 'required|string|max:255',
'reference' => 'required|string|max:100|unique:products,reference',
- 'categorie' => 'required|string|max:191',
+ 'categorie_id' => 'required|exists:product_categories,id',
'fabricant' => 'nullable|string|max:191',
'stock_actuel' => 'required|numeric|min:0',
'stock_minimum' => 'required|numeric|min:0',
@@ -52,9 +52,8 @@ class StoreProductRequest extends FormRequest
'reference.string' => 'La référence du produit doit être une chaîne de caractères.',
'reference.max' => 'La référence du produit ne peut pas dépasser 100 caractères.',
'reference.unique' => 'Cette référence de produit existe déjà.',
- 'categorie.required' => 'La catégorie est obligatoire.',
- 'categorie.string' => 'La catégorie doit être une chaîne de caractères.',
- 'categorie.max' => 'La catégorie ne peut pas dépasser 191 caractères.',
+ 'categorie_id.required' => 'La catégorie est obligatoire.',
+ 'categorie_id.exists' => 'La catégorie sélectionnée n\'existe pas.',
'fabricant.string' => 'Le fabricant doit être une chaîne de caractères.',
'fabricant.max' => 'Le fabricant ne peut pas dépasser 191 caractères.',
'stock_actuel.required' => 'Le stock actuel est obligatoire.',
diff --git a/thanasoft-back/app/Http/Requests/StoreThanatopractitionerRequest.php b/thanasoft-back/app/Http/Requests/StoreThanatopractitionerRequest.php
new file mode 100644
index 0000000..7ad6870
--- /dev/null
+++ b/thanasoft-back/app/Http/Requests/StoreThanatopractitionerRequest.php
@@ -0,0 +1,57 @@
+
+ */
+ public function rules(): array
+ {
+ return [
+ 'employee_id' => 'required|exists:employees,id|unique:thanatopractitioners,employee_id',
+ 'diploma_number' => 'nullable|string|max:191',
+ 'diploma_date' => 'nullable|date',
+ 'authorization_number' => 'nullable|string|max:191',
+ 'authorization_issue_date' => 'nullable|date',
+ 'authorization_expiry_date' => 'nullable|date|after_or_equal:authorization_issue_date',
+ 'notes' => 'nullable|string',
+ ];
+ }
+
+ /**
+ * Get the error messages for the defined validation rules.
+ *
+ * @return array
+ */
+ public function messages(): array
+ {
+ return [
+ 'employee_id.required' => 'L\'employé est obligatoire.',
+ 'employee_id.exists' => 'L\'employé sélectionné n\'existe pas.',
+ 'employee_id.unique' => 'Cet employé est déjà enregistré comme thanatopractitioner.',
+ 'diploma_number.string' => 'Le numéro de diplôme doit être une chaîne de caractères.',
+ 'diploma_number.max' => 'Le numéro de diplôme ne peut pas dépasser :max caractères.',
+ 'diploma_date.date' => 'La date d\'obtention du diplôme doit être une date valide.',
+ 'authorization_number.string' => 'Le numéro d\'autorisation doit être une chaîne de caractères.',
+ 'authorization_number.max' => 'Le numéro d\'autorisation ne peut pas dépasser :max caractères.',
+ 'authorization_issue_date.date' => 'La date de délivrance de l\'autorisation doit être une date valide.',
+ 'authorization_expiry_date.date' => 'La date d\'expiration de l\'autorisation doit être une date valide.',
+ 'authorization_expiry_date.after_or_equal' => 'La date d\'expiration doit être égale ou postérieure à la date de délivrance.',
+ 'notes.string' => 'Les notes doivent être une chaîne de caractères.',
+ ];
+ }
+}
diff --git a/thanasoft-back/app/Http/Requests/UpdateEmployeeRequest.php b/thanasoft-back/app/Http/Requests/UpdateEmployeeRequest.php
new file mode 100644
index 0000000..9f9784e
--- /dev/null
+++ b/thanasoft-back/app/Http/Requests/UpdateEmployeeRequest.php
@@ -0,0 +1,65 @@
+
+ */
+ public function rules(): array
+ {
+ return [
+ 'first_name' => 'required|string|max:191',
+ 'last_name' => 'required|string|max:191',
+ 'email' => [
+ 'nullable',
+ 'email',
+ 'max:191',
+ Rule::unique('employees', 'email')->ignore($this->route('employee'))
+ ],
+ 'phone' => 'nullable|string|max:50',
+ 'job_title' => 'nullable|string|max:191',
+ 'hire_date' => 'nullable|date',
+ 'active' => 'boolean',
+ ];
+ }
+
+ /**
+ * Get the error messages for the defined validation rules.
+ *
+ * @return array
+ */
+ public function messages(): array
+ {
+ return [
+ 'first_name.required' => 'Le prénom est obligatoire.',
+ 'first_name.string' => 'Le prénom doit être une chaîne de caractères.',
+ 'first_name.max' => 'Le prénom ne peut pas dépasser :max caractères.',
+ 'last_name.required' => 'Le nom de famille est obligatoire.',
+ 'last_name.string' => 'Le nom de famille doit être une chaîne de caractères.',
+ 'last_name.max' => 'Le nom de famille ne peut pas dépasser :max caractères.',
+ 'email.email' => 'L\'adresse email doit être valide.',
+ 'email.unique' => 'Cette adresse email est déjà utilisée.',
+ 'phone.string' => 'Le téléphone doit être une chaîne de caractères.',
+ 'phone.max' => 'Le téléphone ne peut pas dépasser :max caractères.',
+ 'job_title.string' => 'L\'intitulé du poste doit être une chaîne de caractères.',
+ 'job_title.max' => 'L\'intitulé du poste ne peut pas dépasser :max caractères.',
+ 'hire_date.date' => 'La date d\'embauche doit être une date valide.',
+ 'active.boolean' => 'Le statut actif doit être un booléen.',
+ ];
+ }
+}
diff --git a/thanasoft-back/app/Http/Requests/UpdatePractitionerDocumentRequest.php b/thanasoft-back/app/Http/Requests/UpdatePractitionerDocumentRequest.php
new file mode 100644
index 0000000..6ee2815
--- /dev/null
+++ b/thanasoft-back/app/Http/Requests/UpdatePractitionerDocumentRequest.php
@@ -0,0 +1,55 @@
+
+ */
+ public function rules(): array
+ {
+ return [
+ 'practitioner_id' => 'required|exists:thanatopractitioners,id',
+ 'doc_type' => 'required|string|max:191',
+ 'file_id' => 'nullable|exists:files,id',
+ 'issue_date' => 'nullable|date',
+ 'expiry_date' => 'nullable|date|after_or_equal:issue_date',
+ 'status' => 'nullable|string|max:64',
+ ];
+ }
+
+ /**
+ * Get the error messages for the defined validation rules.
+ *
+ * @return array
+ */
+ public function messages(): array
+ {
+ return [
+ 'practitioner_id.required' => 'Le thanatopractitioner est obligatoire.',
+ 'practitioner_id.exists' => 'Le thanatopractitioner sélectionné n\'existe pas.',
+ 'doc_type.required' => 'Le type de document est obligatoire.',
+ 'doc_type.string' => 'Le type de document doit être une chaîne de caractères.',
+ 'doc_type.max' => 'Le type de document ne peut pas dépasser :max caractères.',
+ 'file_id.exists' => 'Le fichier sélectionné n\'existe pas.',
+ 'issue_date.date' => 'La date de délivrance doit être une date valide.',
+ 'expiry_date.date' => 'La date d\'expiration doit être une date valide.',
+ 'expiry_date.after_or_equal' => 'La date d\'expiration doit être égale ou postérieure à la date de délivrance.',
+ 'status.string' => 'Le statut doit être une chaîne de caractères.',
+ 'status.max' => 'Le statut ne peut pas dépasser :max caractères.',
+ ];
+ }
+}
diff --git a/thanasoft-back/app/Http/Requests/UpdateProductRequest.php b/thanasoft-back/app/Http/Requests/UpdateProductRequest.php
index ef92b38..14b27b0 100644
--- a/thanasoft-back/app/Http/Requests/UpdateProductRequest.php
+++ b/thanasoft-back/app/Http/Requests/UpdateProductRequest.php
@@ -26,7 +26,7 @@ class UpdateProductRequest extends FormRequest
return [
'nom' => 'required|string|max:255',
'reference' => "nullable",
- 'categorie' => 'required|string|max:191',
+ 'categorie_id' => 'required|exists:product_categories,id',
'fabricant' => 'nullable|string|max:191',
'stock_actuel' => 'required|numeric|min:0',
'stock_minimum' => 'required|numeric|min:0',
@@ -54,9 +54,8 @@ class UpdateProductRequest extends FormRequest
'reference.string' => 'La référence du produit doit être une chaîne de caractères.',
'reference.max' => 'La référence du produit ne peut pas dépasser 100 caractères.',
'reference.unique' => 'Cette référence de produit existe déjà.',
- 'categorie.required' => 'La catégorie est obligatoire.',
- 'categorie.string' => 'La catégorie doit être une chaîne de caractères.',
- 'categorie.max' => 'La catégorie ne peut pas dépasser 191 caractères.',
+ 'categorie_id.required' => 'La catégorie est obligatoire.',
+ 'categorie_id.exists' => 'La catégorie sélectionnée n\'existe pas.',
'fabricant.string' => 'Le fabricant doit être une chaîne de caractères.',
'fabricant.max' => 'Le fabricant ne peut pas dépasser 191 caractères.',
'stock_actuel.required' => 'Le stock actuel est obligatoire.',
diff --git a/thanasoft-back/app/Http/Requests/UpdateThanatopractitionerRequest.php b/thanasoft-back/app/Http/Requests/UpdateThanatopractitionerRequest.php
new file mode 100644
index 0000000..bb9aff4
--- /dev/null
+++ b/thanasoft-back/app/Http/Requests/UpdateThanatopractitionerRequest.php
@@ -0,0 +1,62 @@
+
+ */
+ public function rules(): array
+ {
+ return [
+ 'employee_id' => [
+ 'required',
+ 'exists:employees,id',
+ Rule::unique('thanatopractitioners', 'employee_id')->ignore($this->route('thanatopractitioner'))
+ ],
+ 'diploma_number' => 'nullable|string|max:191',
+ 'diploma_date' => 'nullable|date',
+ 'authorization_number' => 'nullable|string|max:191',
+ 'authorization_issue_date' => 'nullable|date',
+ 'authorization_expiry_date' => 'nullable|date|after_or_equal:authorization_issue_date',
+ 'notes' => 'nullable|string',
+ ];
+ }
+
+ /**
+ * Get the error messages for the defined validation rules.
+ *
+ * @return array
+ */
+ public function messages(): array
+ {
+ return [
+ 'employee_id.required' => 'L\'employé est obligatoire.',
+ 'employee_id.exists' => 'L\'employé sélectionné n\'existe pas.',
+ 'employee_id.unique' => 'Cet employé est déjà enregistré comme thanatopractitioner.',
+ 'diploma_number.string' => 'Le numéro de diplôme doit être une chaîne de caractères.',
+ 'diploma_number.max' => 'Le numéro de diplôme ne peut pas dépasser :max caractères.',
+ 'diploma_date.date' => 'La date d\'obtention du diplôme doit être une date valide.',
+ 'authorization_number.string' => 'Le numéro d\'autorisation doit être une chaîne de caractères.',
+ 'authorization_number.max' => 'Le numéro d\'autorisation ne peut pas dépasser :max caractères.',
+ 'authorization_issue_date.date' => 'La date de délivrance de l\'autorisation doit être une date valide.',
+ 'authorization_expiry_date.date' => 'La date d\'expiration de l\'autorisation doit être une date valide.',
+ 'authorization_expiry_date.after_or_equal' => 'La date d\'expiration doit être égale ou postérieure à la date de délivrance.',
+ 'notes.string' => 'Les notes doivent être une chaîne de caractères.',
+ ];
+ }
+}
diff --git a/thanasoft-back/app/Http/Resources/Employee/EmployeeCollection.php b/thanasoft-back/app/Http/Resources/Employee/EmployeeCollection.php
new file mode 100644
index 0000000..0c54e46
--- /dev/null
+++ b/thanasoft-back/app/Http/Resources/Employee/EmployeeCollection.php
@@ -0,0 +1,43 @@
+
+ */
+ public function toArray($request): array
+ {
+ return [
+ 'data' => $this->collection->map(function ($employee) {
+ return [
+ 'id' => $employee->id,
+ 'first_name' => $employee->first_name,
+ 'last_name' => $employee->last_name,
+ 'full_name' => $employee->full_name,
+ 'email' => $employee->email,
+ 'phone' => $employee->phone,
+ 'job_title' => $employee->job_title,
+ 'hire_date' => $employee->hire_date?->format('Y-m-d'),
+ 'active' => $employee->active,
+ 'created_at' => $employee->created_at?->format('Y-m-d H:i:s'),
+ 'updated_at' => $employee->updated_at?->format('Y-m-d H:i:s'),
+
+ // Relations
+ 'thanatopractitioner' => $employee->thanatopractitioner ? [
+ 'id' => $employee->thanatopractitioner->id,
+ 'diploma_number' => $employee->thanatopractitioner->diploma_number,
+ 'authorization_number' => $employee->thanatopractitioner->authorization_number,
+ 'is_authorization_valid' => $employee->thanatopractitioner->is_authorization_valid,
+ ] : null,
+ ];
+ }),
+ ];
+ }
+}
diff --git a/thanasoft-back/app/Http/Resources/Employee/EmployeeResource.php b/thanasoft-back/app/Http/Resources/Employee/EmployeeResource.php
new file mode 100644
index 0000000..84dc821
--- /dev/null
+++ b/thanasoft-back/app/Http/Resources/Employee/EmployeeResource.php
@@ -0,0 +1,37 @@
+
+ */
+ public function toArray($request): array
+ {
+ return [
+ 'id' => $this->id,
+ 'first_name' => $this->first_name,
+ 'last_name' => $this->last_name,
+ 'full_name' => $this->full_name,
+ 'email' => $this->email,
+ 'phone' => $this->phone,
+ 'job_title' => $this->job_title,
+ 'hire_date' => $this->hire_date?->format('Y-m-d'),
+ 'active' => $this->active,
+ 'created_at' => $this->created_at?->format('Y-m-d H:i:s'),
+ 'updated_at' => $this->updated_at?->format('Y-m-d H:i:s'),
+
+ // Relations
+ 'thanatopractitioner' => $this->when(
+ $this->relationLoaded('thanatopractitioner'),
+ new ThanatopractitionerResource($this->thanatopractitioner)
+ ),
+ ];
+ }
+}
diff --git a/thanasoft-back/app/Http/Resources/Employee/PractitionerDocumentCollection.php b/thanasoft-back/app/Http/Resources/Employee/PractitionerDocumentCollection.php
new file mode 100644
index 0000000..9cc2780
--- /dev/null
+++ b/thanasoft-back/app/Http/Resources/Employee/PractitionerDocumentCollection.php
@@ -0,0 +1,48 @@
+
+ */
+ public function toArray($request): array
+ {
+ return [
+ 'data' => $this->collection->map(function ($document) {
+ return [
+ 'id' => $document->id,
+ 'practitioner_id' => $document->practitioner_id,
+ 'doc_type' => $document->doc_type,
+ 'file_id' => $document->file_id,
+ 'issue_date' => $document->issue_date?->format('Y-m-d'),
+ 'expiry_date' => $document->expiry_date?->format('Y-m-d'),
+ 'status' => $document->status,
+ 'is_valid' => $document->is_valid,
+ 'created_at' => $document->created_at?->format('Y-m-d H:i:s'),
+ 'updated_at' => $document->updated_at?->format('Y-m-d H:i:s'),
+
+ // Relations
+ 'thanatopractitioner' => $document->thanatopractitioner ? [
+ 'id' => $document->thanatopractitioner->id,
+ 'employee_id' => $document->thanatopractitioner->employee_id,
+ 'diploma_number' => $document->thanatopractitioner->diploma_number,
+ 'authorization_number' => $document->thanatopractitioner->authorization_number,
+ 'employee' => $document->thanatopractitioner->employee ? [
+ 'id' => $document->thanatopractitioner->employee->id,
+ 'first_name' => $document->thanatopractitioner->employee->first_name,
+ 'last_name' => $document->thanatopractitioner->employee->last_name,
+ 'full_name' => $document->thanatopractitioner->employee->full_name,
+ ] : null,
+ ] : null,
+ ];
+ }),
+ ];
+ }
+}
diff --git a/thanasoft-back/app/Http/Resources/Employee/PractitionerDocumentResource.php b/thanasoft-back/app/Http/Resources/Employee/PractitionerDocumentResource.php
new file mode 100644
index 0000000..dd467d2
--- /dev/null
+++ b/thanasoft-back/app/Http/Resources/Employee/PractitionerDocumentResource.php
@@ -0,0 +1,36 @@
+
+ */
+ public function toArray($request): array
+ {
+ return [
+ 'id' => $this->id,
+ 'practitioner_id' => $this->practitioner_id,
+ 'doc_type' => $this->doc_type,
+ 'file_id' => $this->file_id,
+ 'issue_date' => $this->issue_date?->format('Y-m-d'),
+ 'expiry_date' => $this->expiry_date?->format('Y-m-d'),
+ 'status' => $this->status,
+ 'is_valid' => $this->is_valid,
+ 'created_at' => $this->created_at?->format('Y-m-d H:i:s'),
+ 'updated_at' => $this->updated_at?->format('Y-m-d H:i:s'),
+
+ // Relations
+ 'thanatopractitioner' => $this->when(
+ $this->relationLoaded('thanatopractitioner'),
+ new ThanatopractitionerResource($this->thanatopractitioner)
+ ),
+ ];
+ }
+}
diff --git a/thanasoft-back/app/Http/Resources/Employee/ThanatopractitionerCollection.php b/thanasoft-back/app/Http/Resources/Employee/ThanatopractitionerCollection.php
new file mode 100644
index 0000000..d86aad5
--- /dev/null
+++ b/thanasoft-back/app/Http/Resources/Employee/ThanatopractitionerCollection.php
@@ -0,0 +1,45 @@
+
+ */
+ public function toArray($request): array
+ {
+ return [
+ 'data' => $this->collection->map(function ($thanatopractitioner) {
+ return [
+ 'id' => $thanatopractitioner->id,
+ 'employee_id' => $thanatopractitioner->employee_id,
+ 'diploma_number' => $thanatopractitioner->diploma_number,
+ 'diploma_date' => $thanatopractitioner->diploma_date?->format('Y-m-d'),
+ 'authorization_number' => $thanatopractitioner->authorization_number,
+ 'authorization_issue_date' => $thanatopractitioner->authorization_issue_date?->format('Y-m-d'),
+ 'authorization_expiry_date' => $thanatopractitioner->authorization_expiry_date?->format('Y-m-d'),
+ 'notes' => $thanatopractitioner->notes,
+ 'is_authorization_valid' => $thanatopractitioner->is_authorization_valid,
+ 'created_at' => $thanatopractitioner->created_at?->format('Y-m-d H:i:s'),
+ 'updated_at' => $thanatopractitioner->updated_at?->format('Y-m-d H:i:s'),
+
+ // Relations
+ 'employee' => $thanatopractitioner->employee ? [
+ 'id' => $thanatopractitioner->employee->id,
+ 'first_name' => $thanatopractitioner->employee->first_name,
+ 'last_name' => $thanatopractitioner->employee->last_name,
+ 'full_name' => $thanatopractitioner->employee->full_name,
+ 'email' => $thanatopractitioner->employee->email,
+ 'job_title' => $thanatopractitioner->employee->job_title,
+ ] : null,
+ ];
+ }),
+ ];
+ }
+}
diff --git a/thanasoft-back/app/Http/Resources/Employee/ThanatopractitionerResource.php b/thanasoft-back/app/Http/Resources/Employee/ThanatopractitionerResource.php
new file mode 100644
index 0000000..97c787d
--- /dev/null
+++ b/thanasoft-back/app/Http/Resources/Employee/ThanatopractitionerResource.php
@@ -0,0 +1,41 @@
+
+ */
+ public function toArray($request): array
+ {
+ return [
+ 'id' => $this->id,
+ 'employee_id' => $this->employee_id,
+ 'diploma_number' => $this->diploma_number,
+ 'diploma_date' => $this->diploma_date?->format('Y-m-d'),
+ 'authorization_number' => $this->authorization_number,
+ 'authorization_issue_date' => $this->authorization_issue_date?->format('Y-m-d'),
+ 'authorization_expiry_date' => $this->authorization_expiry_date?->format('Y-m-d'),
+ 'notes' => $this->notes,
+ 'is_authorization_valid' => $this->is_authorization_valid,
+ 'created_at' => $this->created_at?->format('Y-m-d H:i:s'),
+ 'updated_at' => $this->updated_at?->format('Y-m-d H:i:s'),
+
+ // Relations
+ 'employee' => $this->when(
+ $this->relationLoaded('employee'),
+ new EmployeeResource($this->employee)
+ ),
+ 'documents' => $this->when(
+ $this->relationLoaded('documents'),
+ PractitionerDocumentResource::collection($this->documents)
+ ),
+ ];
+ }
+}
diff --git a/thanasoft-back/app/Http/Resources/Product/ProductResource.php b/thanasoft-back/app/Http/Resources/Product/ProductResource.php
index 6c7190a..30b0dcd 100644
--- a/thanasoft-back/app/Http/Resources/Product/ProductResource.php
+++ b/thanasoft-back/app/Http/Resources/Product/ProductResource.php
@@ -18,7 +18,7 @@ class ProductResource extends JsonResource
'id' => $this->id,
'nom' => $this->nom,
'reference' => $this->reference,
- 'categorie' => $this->categorie,
+ 'categorie_id' => $this->categorie_id,
'fabricant' => $this->fabricant,
'stock_actuel' => $this->stock_actuel,
'stock_minimum' => $this->stock_minimum,
@@ -47,6 +47,13 @@ class ProductResource extends JsonResource
'email' => $this->fournisseur->email,
] : null;
}),
+ 'category' => $this->whenLoaded('category', function() {
+ return $this->category ? [
+ 'id' => $this->category->id,
+ 'name' => $this->category->name,
+ 'code' => $this->category->code,
+ ] : null;
+ }),
];
}
diff --git a/thanasoft-back/app/Models/Employee.php b/thanasoft-back/app/Models/Employee.php
new file mode 100644
index 0000000..9d0fe56
--- /dev/null
+++ b/thanasoft-back/app/Models/Employee.php
@@ -0,0 +1,85 @@
+
+ */
+ protected $fillable = [
+ 'first_name',
+ 'last_name',
+ 'email',
+ 'phone',
+ 'job_title',
+ 'hire_date',
+ 'active',
+ ];
+
+ /**
+ * The attributes that should be cast.
+ *
+ * @var array
+ */
+ protected $casts = [
+ 'active' => 'boolean',
+ 'hire_date' => 'date',
+ 'created_at' => 'datetime',
+ 'updated_at' => 'datetime',
+ ];
+
+ /**
+ * Get the thanatopractitioner associated with the employee.
+ */
+ public function thanatopractitioner(): HasOne
+ {
+ return $this->hasOne(Thanatopractitioner::class);
+ }
+
+ /**
+ * Get the full name of the employee.
+ */
+ public function getFullNameAttribute(): string
+ {
+ return $this->first_name . ' ' . $this->last_name;
+ }
+
+ /**
+ * Scope a query to only include active employees.
+ */
+ public function scopeActive($query)
+ {
+ return $query->where('active', true);
+ }
+
+ /**
+ * Scope a query to only include inactive employees.
+ */
+ public function scopeInactive($query)
+ {
+ return $query->where('active', false);
+ }
+
+ /**
+ * Scope a query to search employees.
+ */
+ public function scopeSearch($query, string $term)
+ {
+ return $query->where(function ($q) use ($term) {
+ $q->where('first_name', 'like', '%' . $term . '%')
+ ->orWhere('last_name', 'like', '%' . $term . '%')
+ ->orWhere('email', 'like', '%' . $term . '%')
+ ->orWhere('job_title', 'like', '%' . $term . '%');
+ });
+ }
+}
diff --git a/thanasoft-back/app/Models/PractitionerDocument.php b/thanasoft-back/app/Models/PractitionerDocument.php
new file mode 100644
index 0000000..66d1f9d
--- /dev/null
+++ b/thanasoft-back/app/Models/PractitionerDocument.php
@@ -0,0 +1,86 @@
+
+ */
+ protected $fillable = [
+ 'practitioner_id',
+ 'doc_type',
+ 'file_id',
+ 'issue_date',
+ 'expiry_date',
+ 'status',
+ ];
+
+ /**
+ * The attributes that should be cast.
+ *
+ * @var array
+ */
+ protected $casts = [
+ 'issue_date' => 'date',
+ 'expiry_date' => 'date',
+ 'created_at' => 'datetime',
+ 'updated_at' => 'datetime',
+ ];
+
+ /**
+ * Get the thanatopractitioner that owns the document.
+ */
+ public function thanatopractitioner(): BelongsTo
+ {
+ return $this->belongsTo(Thanatopractitioner::class, 'practitioner_id');
+ }
+
+ /**
+ * Scope a query to only include documents with valid expiry date.
+ */
+ public function scopeValid($query)
+ {
+ return $query->where(function ($q) {
+ $q->whereNull('expiry_date')
+ ->orWhere('expiry_date', '>=', now());
+ });
+ }
+
+ /**
+ * Scope a query to only include documents with expired expiry date.
+ */
+ public function scopeExpired($query)
+ {
+ return $query->whereNotNull('expiry_date')
+ ->where('expiry_date', '<', now());
+ }
+
+ /**
+ * Scope a query to filter by document type.
+ */
+ public function scopeOfType($query, string $type)
+ {
+ return $query->where('doc_type', $type);
+ }
+
+ /**
+ * Check if the document is still valid.
+ */
+ public function getIsValidAttribute(): bool
+ {
+ if (!$this->expiry_date) {
+ return true; // No expiry date means it's valid
+ }
+
+ return $this->expiry_date >= now();
+ }
+}
diff --git a/thanasoft-back/app/Models/Product.php b/thanasoft-back/app/Models/Product.php
index c35a147..fde2923 100644
--- a/thanasoft-back/app/Models/Product.php
+++ b/thanasoft-back/app/Models/Product.php
@@ -11,7 +11,7 @@ class Product extends Model
protected $fillable = [
'nom',
'reference',
- 'categorie',
+ 'categorie_id',
'fabricant',
'stock_actuel',
'stock_minimum',
@@ -48,7 +48,7 @@ class Product extends Model
*/
public function category(): BelongsTo
{
- return $this->belongsTo(ProductCategory::class, 'categorie', 'name');
+ return $this->belongsTo(ProductCategory::class);
}
/**
diff --git a/thanasoft-back/app/Models/ProductCategory.php b/thanasoft-back/app/Models/ProductCategory.php
index 9caca89..87f68f0 100644
--- a/thanasoft-back/app/Models/ProductCategory.php
+++ b/thanasoft-back/app/Models/ProductCategory.php
@@ -41,7 +41,7 @@ class ProductCategory extends Model
*/
public function products(): HasMany
{
- return $this->hasMany(Product::class, 'categorie', 'name');
+ return $this->hasMany(Product::class, 'categorie_id');
}
/**
diff --git a/thanasoft-back/app/Models/Thanatopractitioner.php b/thanasoft-back/app/Models/Thanatopractitioner.php
new file mode 100644
index 0000000..df6ca07
--- /dev/null
+++ b/thanasoft-back/app/Models/Thanatopractitioner.php
@@ -0,0 +1,85 @@
+
+ */
+ protected $fillable = [
+ 'employee_id',
+ 'diploma_number',
+ 'diploma_date',
+ 'authorization_number',
+ 'authorization_issue_date',
+ 'authorization_expiry_date',
+ 'notes',
+ ];
+
+ /**
+ * The attributes that should be cast.
+ *
+ * @var array
+ */
+ protected $casts = [
+ 'diploma_date' => 'date',
+ 'authorization_issue_date' => 'date',
+ 'authorization_expiry_date' => 'date',
+ 'created_at' => 'datetime',
+ 'updated_at' => 'datetime',
+ ];
+
+ /**
+ * Get the employee that owns the thanatopractitioner.
+ */
+ public function employee(): BelongsTo
+ {
+ return $this->belongsTo(Employee::class);
+ }
+
+ /**
+ * Get all documents associated with the thanatopractitioner.
+ */
+ public function documents(): HasMany
+ {
+ return $this->hasMany(PractitionerDocument::class, 'practitioner_id');
+ }
+
+ /**
+ * Scope a query to only include practitioners with valid authorization.
+ */
+ public function scopeWithValidAuthorization($query)
+ {
+ return $query->where('authorization_expiry_date', '>=', now());
+ }
+
+ /**
+ * Scope a query to only include practitioners with expired authorization.
+ */
+ public function scopeWithExpiredAuthorization($query)
+ {
+ return $query->where('authorization_expiry_date', '<', now());
+ }
+
+ /**
+ * Check if the authorization is still valid.
+ */
+ public function getIsAuthorizationValidAttribute(): bool
+ {
+ if (!$this->authorization_expiry_date) {
+ return false;
+ }
+
+ return $this->authorization_expiry_date >= now();
+ }
+}
diff --git a/thanasoft-back/app/Providers/AppServiceProvider.php b/thanasoft-back/app/Providers/AppServiceProvider.php
index c1e9f5b..76de503 100644
--- a/thanasoft-back/app/Providers/AppServiceProvider.php
+++ b/thanasoft-back/app/Providers/AppServiceProvider.php
@@ -43,6 +43,19 @@ class AppServiceProvider extends ServiceProvider
$this->app->bind(\App\Repositories\ProductCategoryRepositoryInterface::class, function ($app) {
return new \App\Repositories\ProductCategoryRepository($app->make(\App\Models\ProductCategory::class));
});
+
+ // Employee management repository bindings
+ $this->app->bind(\App\Repositories\EmployeeRepositoryInterface::class, function ($app) {
+ return new \App\Repositories\EmployeeRepository($app->make(\App\Models\Employee::class));
+ });
+
+ $this->app->bind(\App\Repositories\ThanatopractitionerRepositoryInterface::class, function ($app) {
+ return new \App\Repositories\ThanatopractitionerRepository($app->make(\App\Models\Thanatopractitioner::class));
+ });
+
+ $this->app->bind(\App\Repositories\PractitionerDocumentRepositoryInterface::class, function ($app) {
+ return new \App\Repositories\PractitionerDocumentRepository($app->make(\App\Models\PractitionerDocument::class));
+ });
}
/**
diff --git a/thanasoft-back/app/Repositories/EmployeeRepository.php b/thanasoft-back/app/Repositories/EmployeeRepository.php
new file mode 100644
index 0000000..587153d
--- /dev/null
+++ b/thanasoft-back/app/Repositories/EmployeeRepository.php
@@ -0,0 +1,127 @@
+model->newQuery();
+
+ // Apply filters
+ if (!empty($filters['search'])) {
+ $query->search($filters['search']);
+ }
+
+ if (isset($filters['active'])) {
+ if ($filters['active']) {
+ $query->active();
+ } else {
+ $query->inactive();
+ }
+ }
+
+ // Apply sorting
+ $sortField = $filters['sort_by'] ?? 'last_name';
+ $sortDirection = $filters['sort_direction'] ?? 'asc';
+ $query->orderBy($sortField, $sortDirection);
+
+ return $query->get();
+ }
+
+ /**
+ * Find an employee by ID.
+ */
+ public function findById(int $id): ?Employee
+ {
+ return $this->model->newQuery()->find($id);
+ }
+
+ /**
+ * Find an employee by email.
+ */
+ public function findByEmail(string $email): ?Employee
+ {
+ return $this->model->newQuery()->where('email', $email)->first();
+ }
+
+ /**
+ * Get active employees only.
+ */
+ public function getActive(): Collection
+ {
+ return $this->model->newQuery()->active()->get();
+ }
+
+ /**
+ * Get inactive employees only.
+ */
+ public function getInactive(): Collection
+ {
+ return $this->model->newQuery()->inactive()->get();
+ }
+
+ /**
+ * Search employees by term.
+ */
+ public function search(string $term): Collection
+ {
+ return $this->model->newQuery()->search($term)->get();
+ }
+
+ /**
+ * Get employees with pagination.
+ */
+ public function getPaginated(int $perPage = 10): array
+ {
+ $paginator = $this->model->newQuery()->paginate($perPage);
+
+ return [
+ 'employees' => $paginator->getCollection(),
+ 'pagination' => [
+ 'current_page' => $paginator->currentPage(),
+ 'last_page' => $paginator->lastPage(),
+ 'per_page' => $paginator->perPage(),
+ 'total' => $paginator->total(),
+ ],
+ ];
+ }
+
+ /**
+ * Get employees with their thanatopractitioner data.
+ */
+ public function getWithThanatopractitioner(): Collection
+ {
+ return $this->model->newQuery()
+ ->with('thanatopractitioner')
+ ->orderBy('last_name')
+ ->get();
+ }
+
+ /**
+ * Get employee statistics.
+ */
+ public function getStatistics(): array
+ {
+ return [
+ 'total' => $this->model->newQuery()->count(),
+ 'active' => $this->model->newQuery()->active()->count(),
+ 'inactive' => $this->model->newQuery()->inactive()->count(),
+ 'with_thanatopractitioner' => $this->model->newQuery()->has('thanatopractitioner')->count(),
+ ];
+ }
+}
diff --git a/thanasoft-back/app/Repositories/EmployeeRepositoryInterface.php b/thanasoft-back/app/Repositories/EmployeeRepositoryInterface.php
new file mode 100644
index 0000000..bca85d4
--- /dev/null
+++ b/thanasoft-back/app/Repositories/EmployeeRepositoryInterface.php
@@ -0,0 +1,82 @@
+ $filters
+ * @return Collection
+ */
+ public function getAll(array $filters = []): Collection;
+
+ /**
+ * Find an employee by ID.
+ *
+ * @param int $id
+ * @return Employee|null
+ */
+ public function findById(int $id): ?Employee;
+
+ /**
+ * Find an employee by email.
+ *
+ * @param string $email
+ * @return Employee|null
+ */
+ public function findByEmail(string $email): ?Employee;
+
+ /**
+ * Get active employees only.
+ *
+ * @return Collection
+ */
+ public function getActive(): Collection;
+
+ /**
+ * Get inactive employees only.
+ *
+ * @return Collection
+ */
+ public function getInactive(): Collection;
+
+ /**
+ * Search employees by term.
+ *
+ * @param string $term
+ * @return Collection
+ */
+ public function search(string $term): Collection;
+
+ /**
+ * Get employees with pagination.
+ *
+ * @param int $perPage
+ * @return array{employees: Collection, pagination: array}
+ */
+ public function getPaginated(int $perPage = 10): array;
+
+ /**
+ * Get employees with their thanatopractitioner data.
+ *
+ * @return Collection
+ */
+ public function getWithThanatopractitioner(): Collection;
+
+ /**
+ * Get employee statistics.
+ *
+ * @return array
+ */
+ public function getStatistics(): array;
+}
diff --git a/thanasoft-back/app/Repositories/PractitionerDocumentRepository.php b/thanasoft-back/app/Repositories/PractitionerDocumentRepository.php
new file mode 100644
index 0000000..175fe8f
--- /dev/null
+++ b/thanasoft-back/app/Repositories/PractitionerDocumentRepository.php
@@ -0,0 +1,149 @@
+model->newQuery()->with(['thanatopractitioner.employee']);
+
+ // Apply filters
+ if (!empty($filters['search'])) {
+ $query->where(function ($q) use ($filters) {
+ $q->where('doc_type', 'like', '%' . $filters['search'] . '%')
+ ->orWhere('status', 'like', '%' . $filters['search'] . '%');
+ });
+ }
+
+ if (!empty($filters['practitioner_id'])) {
+ $query->where('practitioner_id', $filters['practitioner_id']);
+ }
+
+ if (!empty($filters['doc_type'])) {
+ $query->ofType($filters['doc_type']);
+ }
+
+ if (isset($filters['valid_only'])) {
+ if ($filters['valid_only']) {
+ $query->valid();
+ } else {
+ $query->expired();
+ }
+ }
+
+ // Apply sorting
+ $sortField = $filters['sort_by'] ?? 'created_at';
+ $sortDirection = $filters['sort_direction'] ?? 'desc';
+ $query->orderBy($sortField, $sortDirection);
+
+ return $query->get();
+ }
+
+ /**
+ * Find a practitioner document by ID.
+ */
+ public function findById(int $id): ?PractitionerDocument
+ {
+ return $this->model->newQuery()
+ ->with(['thanatopractitioner.employee'])
+ ->find($id);
+ }
+
+ /**
+ * Get documents by practitioner ID.
+ */
+ public function getByPractitionerId(int $practitionerId): Collection
+ {
+ return $this->model->newQuery()
+ ->where('practitioner_id', $practitionerId)
+ ->orderBy('created_at', 'desc')
+ ->get();
+ }
+
+ /**
+ * Get documents by type.
+ */
+ public function getByDocumentType(string $docType): Collection
+ {
+ return $this->model->newQuery()
+ ->with(['thanatopractitioner.employee'])
+ ->ofType($docType)
+ ->orderBy('created_at', 'desc')
+ ->get();
+ }
+
+ /**
+ * Get valid documents (not expired).
+ */
+ public function getValid(): Collection
+ {
+ return $this->model->newQuery()
+ ->with(['thanatopractitioner.employee'])
+ ->valid()
+ ->orderBy('expiry_date')
+ ->get();
+ }
+
+ /**
+ * Get expired documents.
+ */
+ public function getExpired(): Collection
+ {
+ return $this->model->newQuery()
+ ->with(['thanatopractitioner.employee'])
+ ->expired()
+ ->orderBy('expiry_date', 'desc')
+ ->get();
+ }
+
+ /**
+ * Get documents with pagination.
+ */
+ public function getPaginated(int $perPage = 10): array
+ {
+ $paginator = $this->model->newQuery()
+ ->with(['thanatopractitioner.employee'])
+ ->paginate($perPage);
+
+ return [
+ 'documents' => $paginator->getCollection(),
+ 'pagination' => [
+ 'current_page' => $paginator->currentPage(),
+ 'last_page' => $paginator->lastPage(),
+ 'per_page' => $paginator->perPage(),
+ 'total' => $paginator->total(),
+ ],
+ ];
+ }
+
+ /**
+ * Get document statistics.
+ */
+ public function getStatistics(): array
+ {
+ return [
+ 'total' => $this->model->newQuery()->count(),
+ 'valid' => $this->model->newQuery()->valid()->count(),
+ 'expired' => $this->model->newQuery()->expired()->count(),
+ 'by_type' => $this->model->newQuery()
+ ->selectRaw('doc_type, count(*) as count')
+ ->groupBy('doc_type')
+ ->pluck('count', 'doc_type')
+ ->toArray(),
+ ];
+ }
+}
diff --git a/thanasoft-back/app/Repositories/PractitionerDocumentRepositoryInterface.php b/thanasoft-back/app/Repositories/PractitionerDocumentRepositoryInterface.php
new file mode 100644
index 0000000..77b3116
--- /dev/null
+++ b/thanasoft-back/app/Repositories/PractitionerDocumentRepositoryInterface.php
@@ -0,0 +1,75 @@
+ $filters
+ * @return Collection
+ */
+ public function getAll(array $filters = []): Collection;
+
+ /**
+ * Find a practitioner document by ID.
+ *
+ * @param int $id
+ * @return PractitionerDocument|null
+ */
+ public function findById(int $id): ?PractitionerDocument;
+
+ /**
+ * Get documents by practitioner ID.
+ *
+ * @param int $practitionerId
+ * @return Collection
+ */
+ public function getByPractitionerId(int $practitionerId): Collection;
+
+ /**
+ * Get documents by type.
+ *
+ * @param string $docType
+ * @return Collection
+ */
+ public function getByDocumentType(string $docType): Collection;
+
+ /**
+ * Get valid documents (not expired).
+ *
+ * @return Collection
+ */
+ public function getValid(): Collection;
+
+ /**
+ * Get expired documents.
+ *
+ * @return Collection
+ */
+ public function getExpired(): Collection;
+
+ /**
+ * Get documents with pagination.
+ *
+ * @param int $perPage
+ * @return array{documents: Collection, pagination: array}
+ */
+ public function getPaginated(int $perPage = 10): array;
+
+ /**
+ * Get document statistics.
+ *
+ * @return array
+ */
+ public function getStatistics(): array;
+}
diff --git a/thanasoft-back/app/Repositories/ProductRepository.php b/thanasoft-back/app/Repositories/ProductRepository.php
index 03d536c..2196950 100644
--- a/thanasoft-back/app/Repositories/ProductRepository.php
+++ b/thanasoft-back/app/Repositories/ProductRepository.php
@@ -19,20 +19,19 @@ class ProductRepository extends BaseRepository implements ProductRepositoryInter
*/
public function paginate(int $perPage = 15, array $filters = []): LengthAwarePaginator
{
- $query = $this->model->newQuery()->with('fournisseur');
+ $query = $this->model->newQuery()->with(['fournisseur', 'category']);
// Apply filters
if (!empty($filters['search'])) {
$query->where(function ($q) use ($filters) {
$q->where('nom', 'like', '%' . $filters['search'] . '%')
->orWhere('reference', 'like', '%' . $filters['search'] . '%')
- ->orWhere('categorie', 'like', '%' . $filters['search'] . '%')
->orWhere('fabricant', 'like', '%' . $filters['search'] . '%');
});
}
if (!empty($filters['categorie'])) {
- $query->where('categorie', $filters['categorie']);
+ $query->where('categorie_id', $filters['categorie']);
}
if (!empty($filters['fournisseur_id'])) {
@@ -62,7 +61,7 @@ class ProductRepository extends BaseRepository implements ProductRepositoryInter
public function getLowStockProducts(int $perPage = 15): LengthAwarePaginator
{
return $this->model->newQuery()
- ->with('fournisseur')
+ ->with(['fournisseur', 'category'])
->whereRaw('stock_actuel <= stock_minimum')
->orderBy('stock_actuel', 'asc')
->paginate($perPage);
@@ -73,7 +72,7 @@ class ProductRepository extends BaseRepository implements ProductRepositoryInter
*/
public function searchByName(string $name, int $perPage = 15, bool $exactMatch = false)
{
- $query = $this->model->newQuery()->with('fournisseur');
+ $query = $this->model->newQuery()->with(['fournisseur', 'category']);
if ($exactMatch) {
$query->where('nom', $name);
@@ -87,11 +86,11 @@ class ProductRepository extends BaseRepository implements ProductRepositoryInter
/**
* Get products by category
*/
- public function getByCategory(string $category, int $perPage = 15): LengthAwarePaginator
+ public function getByCategory(int $categoryId, int $perPage = 15): LengthAwarePaginator
{
return $this->model->newQuery()
- ->with('fournisseur')
- ->where('categorie', $category)
+ ->with(['fournisseur', 'category'])
+ ->where('categorie_id', $categoryId)
->orderBy('nom')
->paginate($perPage);
}
diff --git a/thanasoft-back/app/Repositories/ProductRepository.phpmodel->newQuery()->with('fournisseur');
-
- // Apply filters
- if (!empty($filters['search'])) {
- $query->where(function ($q) use ($filters) {
- $q->where('nom', 'like', '%' . $filters['search'] . '%')
- ->orWhere('reference', 'like', '%' . $filters['search'] . '%')
- ->orWhere('categorie', 'like', '%' . $filters['search'] . '%')
- ->orWhere('fabricant', 'like', '%' . $filters['search'] . '%');
- });
- }
-
- if (!empty($filters['categorie'])) {
- $query->where('categorie', $filters['categorie']);
- }
-
- if (!empty($filters['fournisseur_id'])) {
- $query->where('fournisseur_id', $filters['fournisseur_id']);
- }
-
- if (isset($filters['low_stock'])) {
- $query->whereRaw('stock_actuel <= stock_minimum');
- }
-
- if (isset($filters['expiring_soon'])) {
- $query->where('date_expiration', '<=', now()->addDays(30)->toDateString())
- ->where('date_expiration', '>=', now()->toDateString());
- }
-
- // Apply sorting
- $sortField = $filters['sort_by'] ?? 'created_at';
- $sortDirection = $filters['sort_direction'] ?? 'desc';
- $query->orderBy($sortField, $sortDirection);
-
- return $query->paginate($perPage);
- }
-
- /**
- * Get products with low stock
- */
- public function getLowStockProducts(int $perPage = 15): LengthAwarePaginator
- {
- return $this->model->newQuery()
- ->with('fournisseur')
- ->whereRaw('stock_actuel <= stock_minimum')
- ->orderBy('stock_actuel', 'asc')
- ->paginate($perPage);
- }
-
- /**
- * Search products by name
- */
- public function searchByName(string $name, int $perPage = 15, bool $exactMatch = false)
- {
- $query = $this->model->newQuery()->with('fournisseur');
-
- if ($exactMatch) {
- $query->where('nom', $name);
- } else {
- $query->where('nom', 'like', '%' . $name . '%');
- }
-
- return $query->paginate($perPage);
- }
-
- /**
- * Get products by category
- */
- public function getByCategory(string $category, int $perPage = 15): LengthAwarePaginator
- {
- return $this->model->newQuery()
- ->with('fournisseur')
- ->where('categorie', $category)
- ->orderBy('nom')
- ->paginate($perPage);
- }
-
- /**
- * Get products by fournisseur
- */
- public function getProductsByFournisseur(int $fournisseurId): LengthAwarePaginator
- {
- return $this->model->newQuery()
- ->where('fournisseur_id', $fournisseurId)
- ->orderBy('nom')
- ->paginate(15);
- }
-
- /**
- * Update stock quantity
- */
- public function updateStock(int $productId, float $newQuantity): bool
- {
- return $this->model->where('id', $productId)
- ->update(['stock_actuel' => $newQuantity]) > 0;
- }
-
- /**
- * Get product statistics
- */
- public function getStatistics(): array
- {
- $totalProducts = $this->model->count();
- $lowStockProducts = $this->model->whereRaw('stock_actuel <= stock_minimum')->count();
- $expiringProducts = $this->model->where('date_expiration', '<=', now()->addDays(30)->toDateString())
- ->where('date_expiration', '>=', now()->toDateString())
- ->count();
- $totalValue = $this->model->sum(\DB::raw('stock_actuel * prix_unitaire'));
-
- return [
- 'total_products' => $totalProducts,
- 'low_stock_products' => $lowStockProducts,
- 'expiring_products' => $expiringProducts,
- 'total_value' => $totalValue,
- ];
- }
-}
diff --git a/thanasoft-back/app/Repositories/ThanatopractitionerRepository.php b/thanasoft-back/app/Repositories/ThanatopractitionerRepository.php
new file mode 100644
index 0000000..f199753
--- /dev/null
+++ b/thanasoft-back/app/Repositories/ThanatopractitionerRepository.php
@@ -0,0 +1,135 @@
+model->newQuery()->with(['employee']);
+
+ // Apply filters
+ if (!empty($filters['search'])) {
+ $query->whereHas('employee', function ($q) use ($filters) {
+ $q->search($filters['search']);
+ });
+ }
+
+ if (isset($filters['valid_authorization'])) {
+ if ($filters['valid_authorization']) {
+ $query->withValidAuthorization();
+ } else {
+ $query->withExpiredAuthorization();
+ }
+ }
+
+ // Apply sorting
+ $sortField = $filters['sort_by'] ?? 'created_at';
+ $sortDirection = $filters['sort_direction'] ?? 'desc';
+ $query->orderBy($sortField, $sortDirection);
+
+ return $query->get();
+ }
+
+ /**
+ * Find a thanatopractitioner by ID.
+ */
+ public function findById(int $id): ?Thanatopractitioner
+ {
+ return $this->model->newQuery()
+ ->with(['employee', 'documents'])
+ ->find($id);
+ }
+
+ /**
+ * Find a thanatopractitioner by employee ID.
+ */
+ public function findByEmployeeId(int $employeeId): ?Thanatopractitioner
+ {
+ return $this->model->newQuery()
+ ->with(['employee', 'documents'])
+ ->where('employee_id', $employeeId)
+ ->first();
+ }
+
+ /**
+ * Get thanatopractitioners with valid authorization.
+ */
+ public function getWithValidAuthorization(): Collection
+ {
+ return $this->model->newQuery()
+ ->with(['employee'])
+ ->withValidAuthorization()
+ ->orderBy('authorization_expiry_date')
+ ->get();
+ }
+
+ /**
+ * Get thanatopractitioners with expired authorization.
+ */
+ public function getWithExpiredAuthorization(): Collection
+ {
+ return $this->model->newQuery()
+ ->with(['employee'])
+ ->withExpiredAuthorization()
+ ->orderBy('authorization_expiry_date', 'desc')
+ ->get();
+ }
+
+ /**
+ * Get thanatopractitioners with their complete data.
+ */
+ public function getWithRelations(): Collection
+ {
+ return $this->model->newQuery()
+ ->with(['employee', 'documents'])
+ ->orderBy('created_at', 'desc')
+ ->get();
+ }
+
+ /**
+ * Get thanatopractitioners with pagination.
+ */
+ public function getPaginated(int $perPage = 10): array
+ {
+ $paginator = $this->model->newQuery()
+ ->with(['employee'])
+ ->paginate($perPage);
+
+ return [
+ 'thanatopractitioners' => $paginator->getCollection(),
+ 'pagination' => [
+ 'current_page' => $paginator->currentPage(),
+ 'last_page' => $paginator->lastPage(),
+ 'per_page' => $paginator->perPage(),
+ 'total' => $paginator->total(),
+ ],
+ ];
+ }
+
+ /**
+ * Get thanatopractitioner statistics.
+ */
+ public function getStatistics(): array
+ {
+ return [
+ 'total' => $this->model->newQuery()->count(),
+ 'with_valid_authorization' => $this->model->newQuery()->withValidAuthorization()->count(),
+ 'with_expired_authorization' => $this->model->newQuery()->withExpiredAuthorization()->count(),
+ 'with_documents' => $this->model->newQuery()->has('documents')->count(),
+ ];
+ }
+}
diff --git a/thanasoft-back/app/Repositories/ThanatopractitionerRepositoryInterface.php b/thanasoft-back/app/Repositories/ThanatopractitionerRepositoryInterface.php
new file mode 100644
index 0000000..4e12187
--- /dev/null
+++ b/thanasoft-back/app/Repositories/ThanatopractitionerRepositoryInterface.php
@@ -0,0 +1,74 @@
+ $filters
+ * @return Collection
+ */
+ public function getAll(array $filters = []): Collection;
+
+ /**
+ * Find a thanatopractitioner by ID.
+ *
+ * @param int $id
+ * @return Thanatopractitioner|null
+ */
+ public function findById(int $id): ?Thanatopractitioner;
+
+ /**
+ * Find a thanatopractitioner by employee ID.
+ *
+ * @param int $employeeId
+ * @return Thanatopractitioner|null
+ */
+ public function findByEmployeeId(int $employeeId): ?Thanatopractitioner;
+
+ /**
+ * Get thanatopractitioners with valid authorization.
+ *
+ * @return Collection
+ */
+ public function getWithValidAuthorization(): Collection;
+
+ /**
+ * Get thanatopractitioners with expired authorization.
+ *
+ * @return Collection
+ */
+ public function getWithExpiredAuthorization(): Collection;
+
+ /**
+ * Get thanatopractitioners with their complete data.
+ *
+ * @return Collection
+ */
+ public function getWithRelations(): Collection;
+
+ /**
+ * Get thanatopractitioners with pagination.
+ *
+ * @param int $perPage
+ * @return array{thanatopractitioners: Collection, pagination: array}
+ */
+ public function getPaginated(int $perPage = 10): array;
+
+ /**
+ * Get thanatopractitioner statistics.
+ *
+ * @return array
+ */
+ public function getStatistics(): array;
+}
diff --git a/thanasoft-back/database/migrations/2025_11_05_070647_change_categorie_to_categorie_id_in_products_table.php b/thanasoft-back/database/migrations/2025_11_05_070647_change_categorie_to_categorie_id_in_products_table.php
new file mode 100644
index 0000000..944b2cd
--- /dev/null
+++ b/thanasoft-back/database/migrations/2025_11_05_070647_change_categorie_to_categorie_id_in_products_table.php
@@ -0,0 +1,60 @@
+foreignId('categorie_id')->nullable()->after('reference')
+ ->constrained('product_categories')->onDelete('set null');
+ });
+
+ // Migrate existing data: map categorie string values to ProductCategory IDs
+ $categories = ProductCategory::all()->keyBy('name');
+
+ Product::chunk(100, function ($products) use ($categories) {
+ foreach ($products as $product) {
+ if ($product->categorie && isset($categories[$product->categorie])) {
+ $product->categorie_id = $categories[$product->categorie]->id;
+ $product->save();
+ }
+ }
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ // First, we need to restore the categorie data from categorie_id
+ $categories = ProductCategory::all()->keyBy('id');
+
+ Product::chunk(100, function ($products) use ($categories) {
+ foreach ($products as $product) {
+ if ($product->categorie_id && isset($categories[$product->categorie_id])) {
+ $product->categorie = $categories[$product->categorie_id]->name;
+ $product->save();
+ }
+ }
+ });
+
+ Schema::table('products', function (Blueprint $table) {
+ // Drop the foreign key constraint first
+ $table->dropForeign(['categorie_id']);
+
+ // Drop the categorie_id column
+ $table->dropColumn('categorie_id');
+ });
+ }
+};
diff --git a/thanasoft-back/database/migrations/2025_11_05_115756_create_employees_table.php b/thanasoft-back/database/migrations/2025_11_05_115756_create_employees_table.php
new file mode 100644
index 0000000..121de6f
--- /dev/null
+++ b/thanasoft-back/database/migrations/2025_11_05_115756_create_employees_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->string('first_name', 191)->comment('Prénom de l\'employé');
+ $table->string('last_name', 191)->comment('Nom de famille de l\'employé');
+ $table->string('email', 191)->nullable()->comment('Adresse email de l\'employé');
+ $table->string('phone', 50)->nullable()->comment('Numéro de téléphone de l\'employé');
+ $table->string('job_title', 191)->nullable()->comment('Intitulé du poste');
+ $table->date('hire_date')->nullable()->comment('Date d\'embauche');
+ $table->boolean('active')->default(true)->comment('Statut actif de l\'employé');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('employees');
+ }
+};
diff --git a/thanasoft-back/database/migrations/2025_11_05_115807_create_thanatopractitioners_table.php b/thanasoft-back/database/migrations/2025_11_05_115807_create_thanatopractitioners_table.php
new file mode 100644
index 0000000..6b56747
--- /dev/null
+++ b/thanasoft-back/database/migrations/2025_11_05_115807_create_thanatopractitioners_table.php
@@ -0,0 +1,39 @@
+id();
+ $table->unsignedBigInteger('employee_id')->unique()->comment('ID de l\'employé associé');
+ $table->string('diploma_number', 191)->nullable()->comment('Numéro de diplôme');
+ $table->date('diploma_date')->nullable()->comment('Date d\'obtention du diplôme');
+ $table->string('authorization_number', 191)->nullable()->comment('Numéro d\'autorisation');
+ $table->date('authorization_issue_date')->nullable()->comment('Date de délivrance de l\'autorisation');
+ $table->date('authorization_expiry_date')->nullable()->comment('Date d\'expiration de l\'autorisation');
+ $table->text('notes')->nullable()->comment('Notes supplémentaires');
+ $table->timestamps();
+
+ $table->foreign('employee_id')
+ ->references('id')
+ ->on('employees')
+ ->onDelete('cascade');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('thanatopractitioners');
+ }
+};
diff --git a/thanasoft-back/database/migrations/2025_11_05_115820_create_practitioner_documents_table.php b/thanasoft-back/database/migrations/2025_11_05_115820_create_practitioner_documents_table.php
new file mode 100644
index 0000000..85f7ff9
--- /dev/null
+++ b/thanasoft-back/database/migrations/2025_11_05_115820_create_practitioner_documents_table.php
@@ -0,0 +1,44 @@
+id();
+ $table->unsignedBigInteger('practitioner_id')->comment('ID du thanatopractitioner');
+ $table->string('doc_type', 191)->comment('Type de document');
+ $table->unsignedBigInteger('file_id')->nullable()->comment('ID du fichier associé');
+ $table->date('issue_date')->nullable()->comment('Date de délivrance');
+ $table->date('expiry_date')->nullable()->comment('Date d\'expiration');
+ $table->string('status', 64)->nullable()->comment('Statut du document');
+ $table->timestamps();
+
+ $table->foreign('practitioner_id')
+ ->references('id')
+ ->on('thanatopractitioners')
+ ->onDelete('cascade');
+
+ // Note: The files table might not exist yet, so we won't add this foreign key constraint
+ // $table->foreign('file_id')
+ // ->references('id')
+ // ->on('files')
+ // ->onDelete('set null');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('practitioner_documents');
+ }
+};
diff --git a/thanasoft-back/database/seeders/DatabaseSeeder.php b/thanasoft-back/database/seeders/DatabaseSeeder.php
index 50dd255..fe09534 100644
--- a/thanasoft-back/database/seeders/DatabaseSeeder.php
+++ b/thanasoft-back/database/seeders/DatabaseSeeder.php
@@ -21,5 +21,7 @@ class DatabaseSeeder extends Seeder
]);
$this->call(ProductCategorySeeder::class);
+ $this->call(EmployeeSeeder::class);
+ $this->call(ThanatopractitionerSeeder::class);
}
}
diff --git a/thanasoft-back/database/seeders/EmployeeSeeder.php b/thanasoft-back/database/seeders/EmployeeSeeder.php
new file mode 100644
index 0000000..812d3f8
--- /dev/null
+++ b/thanasoft-back/database/seeders/EmployeeSeeder.php
@@ -0,0 +1,134 @@
+ 'Jean',
+ 'last_name' => 'Dupont',
+ 'email' => 'jean.dupont@thanasoft.com',
+ 'phone' => '+261341234567',
+ 'job_title' => 'Développeur Full-Stack',
+ 'hire_date' => '2022-01-15',
+ 'active' => true,
+ ],
+ [
+ 'first_name' => 'Marie',
+ 'last_name' => 'Rasoa',
+ 'email' => 'marie.rasoa@thanasoft.com',
+ 'phone' => '+261341234569',
+ 'job_title' => 'Chef de Projet',
+ 'hire_date' => '2021-08-01',
+ 'active' => true,
+ ],
+ [
+ 'first_name' => 'Paul',
+ 'last_name' => 'Ramanana',
+ 'email' => 'paul.ramanana@thanasoft.com',
+ 'phone' => '+261341234571',
+ 'job_title' => 'Designer UX/UI',
+ 'hire_date' => '2022-03-10',
+ 'active' => true,
+ ],
+ [
+ 'first_name' => 'Sophie',
+ 'last_name' => 'Andriamatoa',
+ 'email' => 'sophie.andriamatoa@thanasoft.com',
+ 'phone' => '+261341234573',
+ 'job_title' => 'Responsable RH',
+ 'hire_date' => '2020-09-15',
+ 'active' => true,
+ ],
+ [
+ 'first_name' => 'David',
+ 'last_name' => 'Randria',
+ 'email' => 'david.randria@thanasoft.com',
+ 'phone' => '+261341234575',
+ 'job_title' => 'Développeur Backend',
+ 'hire_date' => '2023-01-20',
+ 'active' => true,
+ ],
+ [
+ 'first_name' => 'Lina',
+ 'last_name' => 'Ramaniraka',
+ 'email' => 'lina.ramaniraka@thanasoft.com',
+ 'phone' => '+261341234577',
+ 'job_title' => 'Comptable',
+ 'hire_date' => '2021-06-01',
+ 'active' => true,
+ ],
+ [
+ 'first_name' => 'Marc',
+ 'last_name' => 'Andriantsoa',
+ 'email' => 'marc.andriantsoa@thanasoft.com',
+ 'phone' => '+261341234579',
+ 'job_title' => 'DevOps Engineer',
+ 'hire_date' => '2022-11-05',
+ 'active' => true,
+ ],
+ [
+ 'first_name' => 'Julie',
+ 'last_name' => 'Rakotomalala',
+ 'email' => 'julie.rakotomalala@thanasoft.com',
+ 'phone' => '+261341234581',
+ 'job_title' => 'Community Manager',
+ 'hire_date' => '2023-03-15',
+ 'active' => true,
+ ],
+ [
+ 'first_name' => 'Philippe',
+ 'last_name' => 'Rakoto',
+ 'email' => 'philippe.rakoto@thanasoft.com',
+ 'phone' => '+261341234583',
+ 'job_title' => 'Développeur Mobile',
+ 'hire_date' => '2023-06-10',
+ 'active' => true,
+ ],
+ [
+ 'first_name' => 'Anne',
+ 'last_name' => 'Andriamanjato',
+ 'email' => 'anne.andriamanjato@thanasoft.com',
+ 'phone' => '+261341234585',
+ 'job_title' => 'Stagiaire',
+ 'hire_date' => '2024-01-15',
+ 'active' => true,
+ ],
+ ];
+
+ foreach ($employees as $employeeData) {
+ Employee::create($employeeData);
+ }
+
+ // Create some inactive employees for testing
+ Employee::create([
+ 'first_name' => 'Ancien',
+ 'last_name' => 'Employe',
+ 'email' => 'ancien.employe@thanasoft.com',
+ 'phone' => '+261341234587',
+ 'job_title' => 'Testeur',
+ 'hire_date' => '2020-01-01',
+ 'active' => false,
+ ]);
+
+ Employee::create([
+ 'first_name' => 'Employe',
+ 'last_name' => 'Suspendu',
+ 'email' => 'employe.suspendu@thanasoft.com',
+ 'phone' => '+261341234589',
+ 'job_title' => 'Assistant',
+ 'hire_date' => '2021-05-01',
+ 'active' => false,
+ ]);
+ }
+}
diff --git a/thanasoft-back/database/seeders/ThanatopractitionerSeeder.php b/thanasoft-back/database/seeders/ThanatopractitionerSeeder.php
new file mode 100644
index 0000000..2b7b361
--- /dev/null
+++ b/thanasoft-back/database/seeders/ThanatopractitionerSeeder.php
@@ -0,0 +1,277 @@
+ 'Jean-Baptiste',
+ 'last_name' => 'Ramanana',
+ 'email' => 'jb.ramanana@thanasoft.com',
+ 'phone' => '+261341235001',
+ 'job_title' => 'Thanatopracteur Senior',
+ 'hire_date' => '2020-08-01',
+ 'active' => true,
+ ],
+ [
+ 'first_name' => 'Marie',
+ 'last_name' => 'Andriamanantsoa',
+ 'email' => 'marie.andriamanantsoa@thanasoft.com',
+ 'phone' => '+261341235003',
+ 'job_title' => 'Thanatopracteur Spécialisé',
+ 'hire_date' => '2019-07-01',
+ 'active' => true,
+ ],
+ [
+ 'first_name' => 'Paul',
+ 'last_name' => 'Rakotomanga',
+ 'email' => 'paul.rakotomanga@thanasoft.com',
+ 'phone' => '+261341235005',
+ 'job_title' => 'Thanatopracteur Junior',
+ 'hire_date' => '2021-11-01',
+ 'active' => true,
+ ],
+ [
+ 'first_name' => 'Sophie',
+ 'last_name' => 'Andriatiana',
+ 'email' => 'sophie.andriatiana@thanasoft.com',
+ 'phone' => '+261341235007',
+ 'job_title' => 'Thanatopracteur Expert',
+ 'hire_date' => '2018-06-01',
+ 'active' => true,
+ ],
+ [
+ 'first_name' => 'David',
+ 'last_name' => 'Randriamahandry',
+ 'email' => 'david.randriamahandry@thanasoft.com',
+ 'phone' => '+261341235009',
+ 'job_title' => 'Thanatopracteur',
+ 'hire_date' => '2022-03-01',
+ 'active' => true,
+ ],
+ [
+ 'first_name' => 'Lina',
+ 'last_name' => 'Ramaniraka',
+ 'email' => 'lina.ramaniraka@thanasoft.com',
+ 'phone' => '+261341235011',
+ 'job_title' => 'Thanatopracteur',
+ 'hire_date' => '2023-10-01',
+ 'active' => true,
+ ],
+ [
+ 'first_name' => 'Marc',
+ 'last_name' => 'Andriamatsiroa',
+ 'email' => 'marc.andriamatsiroa@thanasoft.com',
+ 'phone' => '+261341235013',
+ 'job_title' => 'Thanatopracteur Chief',
+ 'hire_date' => '2018-01-01',
+ 'active' => true,
+ ],
+ [
+ 'first_name' => 'Julie',
+ 'last_name' => 'Rakotomalala',
+ 'email' => 'julie.rakotomalala@thanasoft.com',
+ 'phone' => '+261341235015',
+ 'job_title' => 'Thanatopracteur Assistant',
+ 'hire_date' => '2024-05-01',
+ 'active' => true,
+ ],
+ ];
+
+ // Create employees and get their IDs
+ $employeeIds = [];
+ foreach ($employees as $employeeData) {
+ $employee = \App\Models\Employee::create($employeeData);
+ $employeeIds[] = $employee->id;
+ }
+
+ // Create thanatopractitioners linked to the employees
+ $thanatopractitioners = [
+ [
+ 'employee_id' => $employeeIds[0],
+ 'diploma_number' => 'TP-DIPL-001-2020',
+ 'diploma_date' => '2020-06-15',
+ 'authorization_number' => 'TP-AUTH-001-2020',
+ 'authorization_issue_date' => '2020-07-01',
+ 'authorization_expiry_date' => '2025-07-01',
+ 'notes' => 'Thanatopracteur senior spécialisé en thanatopraxie générale et reconstructive.',
+ ],
+ [
+ 'employee_id' => $employeeIds[1],
+ 'diploma_number' => 'TP-DIPL-002-2019',
+ 'diploma_date' => '2019-05-10',
+ 'authorization_number' => 'TP-AUTH-002-2019',
+ 'authorization_issue_date' => '2019-06-01',
+ 'authorization_expiry_date' => '2024-06-01',
+ 'notes' => 'Spécialiste en thanatopraxie pédiatrique avec 5 ans d\'expérience.',
+ ],
+ [
+ 'employee_id' => $employeeIds[2],
+ 'diploma_number' => 'TP-DIPL-003-2021',
+ 'diploma_date' => '2021-09-22',
+ 'authorization_number' => 'TP-AUTH-003-2021',
+ 'authorization_issue_date' => '2021-10-01',
+ 'authorization_expiry_date' => '2026-10-01',
+ 'notes' => 'Thanatopracteur junior spécialisé en thanatopraxie esthétique.',
+ ],
+ [
+ 'employee_id' => $employeeIds[3],
+ 'diploma_number' => 'TP-DIPL-004-2018',
+ 'diploma_date' => '2018-04-05',
+ 'authorization_number' => 'TP-AUTH-004-2018',
+ 'authorization_issue_date' => '2018-05-01',
+ 'authorization_expiry_date' => '2023-05-01',
+ 'notes' => 'Expert en thanatopraxie reconstructive et histopathologie.',
+ ],
+ [
+ 'employee_id' => $employeeIds[4],
+ 'diploma_number' => 'TP-DIPL-005-2022',
+ 'diploma_date' => '2022-01-18',
+ 'authorization_number' => 'TP-AUTH-005-2022',
+ 'authorization_issue_date' => '2022-02-01',
+ 'authorization_expiry_date' => '2027-02-01',
+ 'notes' => 'Spécialiste en thanatopraxie traditionnelle et culturelle.',
+ ],
+ [
+ 'employee_id' => $employeeIds[5],
+ 'diploma_number' => 'TP-DIPL-006-2023',
+ 'diploma_date' => '2023-08-12',
+ 'authorization_number' => 'TP-AUTH-006-2023',
+ 'authorization_issue_date' => '2023-09-01',
+ 'authorization_expiry_date' => '2028-09-01',
+ 'notes' => 'Thanatopracteur nouvellement certifiée, spécialisée en techniques modernes.',
+ ],
+ [
+ 'employee_id' => $employeeIds[6],
+ 'diploma_number' => 'TP-DIPL-007-2017',
+ 'diploma_date' => '2017-11-30',
+ 'authorization_number' => 'TP-AUTH-007-2017',
+ 'authorization_issue_date' => '2018-01-01',
+ 'authorization_expiry_date' => '2023-01-01',
+ 'notes' => 'Responsable principal des thanatopracteurs, expert en histopathologie.',
+ ],
+ [
+ 'employee_id' => $employeeIds[7],
+ 'diploma_number' => 'TP-DIPL-008-2024',
+ 'diploma_date' => '2024-03-25',
+ 'authorization_number' => 'TP-AUTH-008-2024',
+ 'authorization_issue_date' => '2024-04-01',
+ 'authorization_expiry_date' => '2029-04-01',
+ 'notes' => 'Thanatopracteur assistante, en formation continue en thanatopraxie assistée.',
+ ],
+ ];
+
+ foreach ($thanatopractitioners as $thanatopractitionerData) {
+ $thanatopractitioner = Thanatopractitioner::create($thanatopractitionerData);
+
+ // Create some practitioner documents for each thanatopractitioner
+ $this->createPractitionerDocuments($thanatopractitioner);
+ }
+
+ // Create inactive thanatopractitioners for testing
+ $inactiveEmployee1 = \App\Models\Employee::create([
+ 'first_name' => 'Ancien',
+ 'last_name' => 'Thanatopracteur',
+ 'email' => 'ancien.thanato@thanasoft.com',
+ 'phone' => '+261341235017',
+ 'job_title' => 'Ancien Thanatopracteur',
+ 'hire_date' => '2015-03-01',
+ 'active' => false,
+ ]);
+
+ $inactive1 = Thanatopractitioner::create([
+ 'employee_id' => $inactiveEmployee1->id,
+ 'diploma_number' => 'TP-DIPL-TEST-001',
+ 'diploma_date' => '2015-01-01',
+ 'authorization_number' => 'TP-AUTH-TEST-001',
+ 'authorization_issue_date' => '2015-02-01',
+ 'authorization_expiry_date' => '2020-02-01',
+ 'notes' => 'Thanatopracteur inactif, autorisation expirée.',
+ ]);
+
+ $this->createPractitionerDocuments($inactive1);
+
+ $inactiveEmployee2 = \App\Models\Employee::create([
+ 'first_name' => 'Thanatopracteur',
+ 'last_name' => 'Suspendu',
+ 'email' => 'thanato.suspendu@thanasoft.com',
+ 'phone' => '+261341235019',
+ 'job_title' => 'Thanatopracteur Suspendu',
+ 'hire_date' => '2018-08-01',
+ 'active' => false,
+ ]);
+
+ $inactive2 = Thanatopractitioner::create([
+ 'employee_id' => $inactiveEmployee2->id,
+ 'diploma_number' => 'TP-DIPL-TEST-002',
+ 'diploma_date' => '2018-06-01',
+ 'authorization_number' => 'TP-AUTH-TEST-002',
+ 'authorization_issue_date' => '2018-07-01',
+ 'authorization_expiry_date' => '2023-07-01',
+ 'notes' => 'Thanatopracteur temporairement suspendu.',
+ ]);
+
+ $this->createPractitionerDocuments($inactive2);
+ }
+
+ /**
+ * Create practitioner documents for a thanatopractitioner.
+ */
+ private function createPractitionerDocuments(Thanatopractitioner $thanatopractitioner): void
+ {
+ $documents = [
+ [
+ 'doc_type' => 'diploma',
+ 'issue_date' => $thanatopractitioner->diploma_date,
+ 'expiry_date' => date('Y-m-d', strtotime('+5 years', strtotime($thanatopractitioner->diploma_date))),
+ 'status' => 'active',
+ ],
+ [
+ 'doc_type' => 'certification',
+ 'issue_date' => date('Y-01-01'),
+ 'expiry_date' => date('Y-12-31'),
+ 'status' => 'active',
+ ],
+ ];
+
+ // Add specialized documents based on notes content
+ if (strpos($thanatopractitioner->notes, 'reconstructive') !== false ||
+ strpos($thanatopractitioner->notes, 'histopathologie') !== false) {
+ $documents[] = [
+ 'doc_type' => 'specialization',
+ 'issue_date' => $thanatopractitioner->diploma_date,
+ 'expiry_date' => date('Y-m-d', strtotime('+3 years', strtotime($thanatopractitioner->diploma_date))),
+ 'status' => 'active',
+ ];
+ }
+
+ // Add expired documents for inactive thanatopractitioners
+ $employee = $thanatopractitioner->employee;
+ if ($employee->active === false) {
+ $documents[] = [
+ 'doc_type' => 'expired_certificate',
+ 'issue_date' => '2020-01-01',
+ 'expiry_date' => '2023-01-01',
+ 'status' => 'expired',
+ ];
+ }
+
+ foreach ($documents as $documentData) {
+ PractitionerDocument::create(array_merge($documentData, [
+ 'practitioner_id' => $thanatopractitioner->id,
+ 'file_id' => null, // Will be set when files table is implemented
+ ]));
+ }
+ }
+}
diff --git a/thanasoft-back/routes/api.php b/thanasoft-back/routes/api.php
index 18aab33..fee0164 100644
--- a/thanasoft-back/routes/api.php
+++ b/thanasoft-back/routes/api.php
@@ -10,6 +10,9 @@ use App\Http\Controllers\Api\ClientCategoryController;
use App\Http\Controllers\Api\FournisseurController;
use App\Http\Controllers\Api\ProductController;
use App\Http\Controllers\Api\ProductCategoryController;
+use App\Http\Controllers\Api\EmployeeController;
+use App\Http\Controllers\Api\ThanatopractitionerController;
+use App\Http\Controllers\Api\PractitionerDocumentController;
/*
|--------------------------------------------------------------------------
@@ -73,4 +76,20 @@ Route::middleware('auth:sanctum')->group(function () {
Route::get('/product-categories/statistics', [ProductCategoryController::class, 'statistics']);
Route::apiResource('product-categories', ProductCategoryController::class);
Route::patch('/product-categories/{id}/toggle-active', [ProductCategoryController::class, 'toggleActive']);
+
+ // Employee management
+ Route::get('/employees/searchBy', [EmployeeController::class, 'searchBy']);
+ Route::get('/employees/thanatopractitioners', [EmployeeController::class, 'getThanatopractitioners']);
+ Route::apiResource('employees', EmployeeController::class);
+
+ // Thanatopractitioner management
+ Route::apiResource('thanatopractitioners', ThanatopractitionerController::class);
+ Route::get('employees/{employeeId}/thanatopractitioners', [ThanatopractitionerController::class, 'getByEmployee']);
+ Route::get('/thanatopractitioners/{id}/documents', [PractitionerDocumentController::class, 'getByThanatopractitioner']);
+
+ // Practitioner Document management
+ Route::get('/practitioner-documents/searchBy', [PractitionerDocumentController::class, 'searchBy']);
+ Route::get('/practitioner-documents/expiring', [PractitionerDocumentController::class, 'getExpiringDocuments']);
+ Route::apiResource('practitioner-documents', PractitionerDocumentController::class);
+ Route::patch('/practitioner-documents/{id}/verify', [PractitionerDocumentController::class, 'verifyDocument']);
});
diff --git a/thanasoft-back/routes/web.php.server b/thanasoft-back/routes/web.php.server
deleted file mode 100644
index 856b450..0000000
--- a/thanasoft-back/routes/web.php.server
+++ /dev/null
@@ -1,7 +0,0 @@
-where('any', '.*');