Compare commits
33 Commits
main
...
Feature/CR
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9b6e5e0f6 | ||
|
|
23bce2abcf | ||
|
|
496b427e13 | ||
|
|
a51e05559a | ||
|
|
4b7e075918 | ||
|
|
98d1743def | ||
|
|
69fbe1a7a1 | ||
|
|
7570f46658 | ||
|
|
0c4ff92fd5 | ||
|
|
51ff282d5b | ||
|
|
bbf60fb380 | ||
|
|
8d1d65e27b | ||
|
|
e55cc5253e | ||
|
|
0ea8f1866b | ||
|
|
aa306f5d19 | ||
|
|
638af78e1f | ||
|
|
18f9d83e5a | ||
|
|
cfdbc11b1a | ||
|
|
edb9c87c1e | ||
|
|
4b056038d6 | ||
|
|
ca09f6da2f | ||
|
|
e924c4f819 | ||
|
|
425d2d510c | ||
|
|
ea2b687533 | ||
|
|
2a1de6f384 | ||
|
|
99d88ca30b | ||
|
|
b62cb3d717 | ||
|
|
78700a3c5a | ||
|
|
e2cb4499bb | ||
|
|
98420a29b5 | ||
|
|
c5a4fcc546 | ||
|
|
175446adbe | ||
|
|
215f4c4071 |
70
thanas
Normal file
70
thanas
Normal file
@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<thanatopractitioner-template>
|
||||
<template #thanatopractitioner-new-action>
|
||||
<add-button text="Ajouter" @click="goToAdd" />
|
||||
</template>
|
||||
<template #select-filter>
|
||||
<filter-table />
|
||||
</template>
|
||||
<template #thanatopractitioner-other-action>
|
||||
<table-action />
|
||||
</template>
|
||||
<template #thanatopractitioner-table>
|
||||
<thanatopractitioner-table
|
||||
:data="thanatopractitionerData"
|
||||
:loading="loadingData"
|
||||
:pagination="pagination"
|
||||
@view="goToDetails"
|
||||
@delete="deleteThanatopractitioner"
|
||||
@change-page="$emit('change-page', $event)"
|
||||
/>
|
||||
</template>
|
||||
</thanatopractitioner-template>
|
||||
</template>
|
||||
<script setup>
|
||||
import ThanatopractitionerTemplate from "@/components/templates/CRM/ThanatopractitionerTemplate.vue";
|
||||
import ThanatopractitionerTable from "@/components/molecules/Thanatopractitioners/ThanatopractitionerTable.vue";
|
||||
import addButton from "@/components/molecules/new-button/addButton.vue";
|
||||
import FilterTable from "@/components/molecules/Tables/FilterTable.vue";
|
||||
import TableAction from "@/components/molecules/Tables/TableAction.vue";
|
||||
import { defineProps, defineEmits } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const emit = defineEmits(["pushDetails", "deleteThanatopractitioner", "changePage"]);
|
||||
|
||||
defineProps({
|
||||
thanatopractitionerData: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
loadingData: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
pagination: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
current_page: 1,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
last_page: 1,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const goToAdd = () => {
|
||||
router.push({
|
||||
name: "Creation thanatopractitioner",
|
||||
});
|
||||
};
|
||||
|
||||
const goToDetails = (thanatopractitioner) => {
|
||||
emit("pushDetails", thanatopractitioner);
|
||||
};
|
||||
|
||||
const deleteThanatopractitioner = (thanatopractitioner) => {
|
||||
emit("deleteThanatopractitioner", thanatopractitioner);
|
||||
};
|
||||
</script>
|
||||
60
thanasoft
Normal file
60
thanasoft
Normal file
@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<ul class="nav nav-pills flex-column">
|
||||
<TabNavigationItem
|
||||
icon="fas fa-eye"
|
||||
label="Aperçu"
|
||||
:is-active="activeTab === 'overview'"
|
||||
spacing=""
|
||||
@click="$emit('change-tab', 'overview')"
|
||||
/>
|
||||
<TabNavigationItem
|
||||
icon="fas fa-info-circle"
|
||||
label="Informations"
|
||||
:is-active="activeTab === 'info'"
|
||||
@click="$emit('change-tab', 'info')"
|
||||
/>
|
||||
<TabNavigationItem
|
||||
icon="fas fa-calendar"
|
||||
label="Agenda"
|
||||
:is-active="activeTab === 'agenda'"
|
||||
@click="$emit('change-tab', 'agenda')"
|
||||
/>
|
||||
<TabNavigationItem
|
||||
icon="fas fa-info-circle"
|
||||
label="Activités récentes"
|
||||
:is-active="activeTab === 'activity'"
|
||||
@click="$emit('change-tab', 'activity')"
|
||||
/>
|
||||
<TabNavigationItem
|
||||
icon="fas fa-folder"
|
||||
label="Documents"
|
||||
:is-active="activeTab === 'documents'"
|
||||
:badge="documentsCount > 0 ? documentsCount : null"
|
||||
@click="$emit('change-tab', 'documents')"
|
||||
/>
|
||||
<TabNavigationItem
|
||||
icon="fas fa-sticky-note"
|
||||
label="Notes"
|
||||
:is-active="activeTab === 'notes'"
|
||||
@click="$emit('change-tab', 'notes')"
|
||||
/>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import TabNavigationItem from "@/components/atoms/client/TabNavigationItem.vue";
|
||||
|
||||
import { defineProps, defineEmits } from "vue";
|
||||
defineProps({
|
||||
activeTab: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
documentsCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
defineEmits(["change-tab"]);
|
||||
</script>
|
||||
656
thanasoft-back/API_DOCUMENTATION.md
Normal file
656
thanasoft-back/API_DOCUMENTATION.md
Normal file
@ -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.
|
||||
411
thanasoft-back/FILE_API_DOCUMENTATION.md
Normal file
411
thanasoft-back/FILE_API_DOCUMENTATION.md
Normal file
@ -0,0 +1,411 @@
|
||||
# File Management API Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The File Management API provides a complete CRUD system for handling file uploads with organized storage, metadata management, and file organization by categories and clients.
|
||||
|
||||
## Base URL
|
||||
|
||||
```
|
||||
/api/files
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
All file routes require authentication using Sanctum tokens.
|
||||
|
||||
## File Organization Structure
|
||||
|
||||
Files are automatically organized in storage following this structure:
|
||||
|
||||
```
|
||||
storage/app/public/
|
||||
├── client/{client_id}/{category}/{subcategory}/filename.pdf
|
||||
├── client/{client_id}/devis/DEVIS_2024_12_01_10_30_45.pdf
|
||||
├── client/{client_id}/facture/FACT_2024_12_01_10_30_45.pdf
|
||||
└── general/{category}/{subcategory}/filename.pdf
|
||||
```
|
||||
|
||||
### Supported Categories
|
||||
|
||||
- `devis` - Quotes/Devis
|
||||
- `facture` - Invoices/Factures
|
||||
- `contrat` - Contracts
|
||||
- `document` - General Documents
|
||||
- `image` - Images
|
||||
- `autre` - Other files
|
||||
|
||||
## Endpoints Overview
|
||||
|
||||
### 1. List All Files
|
||||
|
||||
```http
|
||||
GET /api/files
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `per_page` (optional): Number of files per page (default: 15)
|
||||
- `search` (optional): Search by filename
|
||||
- `mime_type` (optional): Filter by MIME type
|
||||
- `uploaded_by` (optional): Filter by uploader user ID
|
||||
- `category` (optional): Filter by category
|
||||
- `client_id` (optional): Filter by client ID
|
||||
- `date_from` (optional): Filter files uploaded after date (YYYY-MM-DD)
|
||||
- `date_to` (optional): Filter files uploaded before date (YYYY-MM-DD)
|
||||
- `sort_by` (optional): Sort field (default: uploaded_at)
|
||||
- `sort_direction` (optional): Sort direction (default: desc)
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"file_name": "document.pdf",
|
||||
"mime_type": "application/pdf",
|
||||
"size_bytes": 1024000,
|
||||
"size_formatted": "1000.00 KB",
|
||||
"extension": "pdf",
|
||||
"storage_uri": "client/123/devis/DEVIS_2024_12_01_10_30_45.pdf",
|
||||
"organized_path": "client/123/devis/DEVIS_2024_12_01_10_30_45.pdf",
|
||||
"sha256": "abc123...",
|
||||
"uploaded_by": 1,
|
||||
"uploader_name": "John Doe",
|
||||
"uploaded_at": "2024-12-01 10:30:45",
|
||||
"is_image": false,
|
||||
"is_pdf": true,
|
||||
"category": "devis",
|
||||
"subcategory": "general"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"current_page": 1,
|
||||
"from": 1,
|
||||
"last_page": 5,
|
||||
"per_page": 15,
|
||||
"to": 15,
|
||||
"total": 75,
|
||||
"has_more_pages": true
|
||||
},
|
||||
"summary": {
|
||||
"total_files": 15,
|
||||
"total_size": 15360000,
|
||||
"total_size_formatted": "14.65 MB",
|
||||
"categories": {
|
||||
"devis": {
|
||||
"count": 8,
|
||||
"total_size_formatted": "8.24 MB"
|
||||
},
|
||||
"facture": {
|
||||
"count": 7,
|
||||
"total_size_formatted": "6.41 MB"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Upload File
|
||||
|
||||
```http
|
||||
POST /api/files
|
||||
```
|
||||
|
||||
**Content-Type:** `multipart/form-data`
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `file` (required): The file to upload (max 10MB)
|
||||
- `file_name` (optional): Custom filename (uses original name if not provided)
|
||||
- `category` (required): File category (devis|facture|contrat|document|image|autre)
|
||||
- `client_id` (optional): Associated client ID
|
||||
- `subcategory` (optional): Subcategory for better organization
|
||||
- `description` (optional): File description (max 500 chars)
|
||||
- `tags` (optional): Array of tags (max 10 tags, 50 chars each)
|
||||
- `is_public` (optional): Whether file is publicly accessible
|
||||
|
||||
**Example Request:**
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
http://localhost/api/files \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-F "file=@/path/to/document.pdf" \
|
||||
-F "category=devis" \
|
||||
-F "client_id=123" \
|
||||
-F "subcategory=annual" \
|
||||
-F "description=Annual quote for client" \
|
||||
-F 'tags[]=quote' \
|
||||
-F 'tags[]=annual'
|
||||
```
|
||||
|
||||
**Success Response (201):**
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 1,
|
||||
"file_name": "document.pdf",
|
||||
"mime_type": "application/pdf",
|
||||
"size_bytes": 1024000,
|
||||
"storage_uri": "client/123/devis/annual/document_2024_12_01_10_30_45.pdf",
|
||||
"category": "devis",
|
||||
"subcategory": "annual",
|
||||
"uploaded_at": "2024-12-01 10:30:45"
|
||||
},
|
||||
"message": "Fichier téléchargé avec succès."
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Get File Details
|
||||
|
||||
```http
|
||||
GET /api/files/{id}
|
||||
```
|
||||
|
||||
**Response:** Same as file object in list endpoint
|
||||
|
||||
### 4. Update File Metadata
|
||||
|
||||
```http
|
||||
PUT /api/files/{id}
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `file_name` (optional): New filename
|
||||
- `description` (optional): Updated description
|
||||
- `tags` (optional): Updated tags array
|
||||
- `is_public` (optional): Updated public status
|
||||
- `category` (optional): Move to new category
|
||||
- `client_id` (optional): Change associated client
|
||||
- `subcategory` (optional): Update subcategory
|
||||
|
||||
**Note:** If category, client_id, or subcategory are changed, the file will be automatically moved to the new location.
|
||||
|
||||
### 5. Delete File
|
||||
|
||||
```http
|
||||
DELETE /api/files/{id}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Fichier supprimé avec succès."
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Get Files by Category
|
||||
|
||||
```http
|
||||
GET /api/files/by-category/{category}
|
||||
```
|
||||
|
||||
**Parameters:** Same as list endpoint with additional `per_page`
|
||||
|
||||
### 7. Get Files by Client
|
||||
|
||||
```http
|
||||
GET /api/files/by-client/{clientId}
|
||||
```
|
||||
|
||||
**Parameters:** Same as list endpoint with additional `per_page`
|
||||
|
||||
### 8. Get Organized File Structure
|
||||
|
||||
```http
|
||||
GET /api/files/organized
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"devis/general": {
|
||||
"category": "devis",
|
||||
"subcategory": "general",
|
||||
"files": [...],
|
||||
"count": 25
|
||||
},
|
||||
"facture/annual": {
|
||||
"category": "facture",
|
||||
"subcategory": "annual",
|
||||
"files": [...],
|
||||
"count": 15
|
||||
}
|
||||
},
|
||||
"message": "Structure de fichiers récupérée avec succès."
|
||||
}
|
||||
```
|
||||
|
||||
### 9. Get Storage Statistics
|
||||
|
||||
```http
|
||||
GET /api/files/statistics
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"total_files": 150,
|
||||
"total_size_bytes": 1073741824,
|
||||
"total_size_formatted": "1.00 GB",
|
||||
"by_type": {
|
||||
"application/pdf": {
|
||||
"count": 85,
|
||||
"total_size": 734003200
|
||||
},
|
||||
"image/jpeg": {
|
||||
"count": 45,
|
||||
"total_size": 209715200
|
||||
}
|
||||
},
|
||||
"by_category": {
|
||||
"devis": {
|
||||
"count": 60,
|
||||
"total_size": 429496729
|
||||
},
|
||||
"facture": {
|
||||
"count": 50,
|
||||
"total_size": 322122547
|
||||
}
|
||||
}
|
||||
},
|
||||
"message": "Statistiques de stockage récupérées avec succès."
|
||||
}
|
||||
```
|
||||
|
||||
### 10. Generate Download URL
|
||||
|
||||
```http
|
||||
GET /api/files/{id}/download
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"download_url": "/storage/client/123/devis/document_2024_12_01_10_30_45.pdf",
|
||||
"file_name": "document.pdf",
|
||||
"mime_type": "application/pdf"
|
||||
},
|
||||
"message": "URL de téléchargement générée avec succès."
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
### Validation Error (422)
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Les données fournies ne sont pas valides.",
|
||||
"errors": {
|
||||
"file": ["Le fichier est obligatoire."],
|
||||
"category": ["La catégorie est obligatoire."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Not Found (404)
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Fichier non trouvé."
|
||||
}
|
||||
```
|
||||
|
||||
### Server Error (500)
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Une erreur est survenue lors du traitement de la requête.",
|
||||
"error": "Detailed error message (only in debug mode)"
|
||||
}
|
||||
```
|
||||
|
||||
## File Features
|
||||
|
||||
### Automatic Organization
|
||||
|
||||
- Files are automatically organized by category and client
|
||||
- Timestamps are added to prevent filename conflicts
|
||||
- Safe slug generation for subcategories
|
||||
|
||||
### Security
|
||||
|
||||
- SHA256 hash calculation for file integrity
|
||||
- User-based access control
|
||||
- File size validation (10MB limit)
|
||||
|
||||
### Metadata Support
|
||||
|
||||
- MIME type detection
|
||||
- File size tracking
|
||||
- Upload timestamp
|
||||
- User attribution
|
||||
- Custom tags and descriptions
|
||||
|
||||
### Storage Management
|
||||
|
||||
- Public storage disk usage
|
||||
- Efficient path organization
|
||||
- Duplicate prevention through hashing
|
||||
- Automatic file moving on metadata updates
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Upload a Quote for Client
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
http://localhost/api/files \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-F "file=@quote_2024.pdf" \
|
||||
-F "category=devis" \
|
||||
-F "client_id=123" \
|
||||
-F "subcategory=annual_2024" \
|
||||
-F 'tags[]=quote' \
|
||||
-F 'tags[]=annual'
|
||||
```
|
||||
|
||||
### Get All Client Files
|
||||
|
||||
```bash
|
||||
curl -X GET \
|
||||
"http://localhost/api/files/by-client/123?per_page=20&sort_by=uploaded_at&sort_direction=desc" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
### Get File Statistics
|
||||
|
||||
```bash
|
||||
curl -X GET \
|
||||
"http://localhost/api/files/statistics" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
### Search Files
|
||||
|
||||
```bash
|
||||
curl -X GET \
|
||||
"http://localhost/api/files?search=annual&category=devis" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- All file operations are logged for audit purposes
|
||||
- Files are stored in `storage/app/public/` directory
|
||||
- The system automatically handles file moving when categories change
|
||||
- Download URLs are generated on-demand for security
|
||||
- Pagination is applied to all list endpoints
|
||||
- French language is used for all API messages and validations
|
||||
219
thanasoft-back/IMPLEMENTATION_SUMMARY.md
Normal file
219
thanasoft-back/IMPLEMENTATION_SUMMARY.md
Normal file
@ -0,0 +1,219 @@
|
||||
# Intervention with All Data - Implementation Summary
|
||||
|
||||
## Overview
|
||||
|
||||
Created a comprehensive `createInterventionalldata` method in the InterventionController that handles creating an intervention along with all related entities (deceased, client, contact, location, documents) in a single atomic transaction.
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
### 1. Form Request
|
||||
|
||||
**File**: `app/Http/Requests/StoreInterventionWithAllDataRequest.php`
|
||||
|
||||
- Comprehensive validation for all entities
|
||||
- Step-level error grouping (deceased, client, contact, location, documents, intervention)
|
||||
- French error messages
|
||||
- File validation for document uploads
|
||||
|
||||
### 2. Controller Method
|
||||
|
||||
**File**: `app/Http/Controllers/Api/InterventionController.php`
|
||||
|
||||
- Added `createInterventionalldata()` method
|
||||
- Database transaction wrapping all operations
|
||||
- Step-by-step creation flow:
|
||||
1. Create deceased
|
||||
2. Create client
|
||||
3. Create contact (if provided)
|
||||
4. Prepare location data
|
||||
5. Create intervention
|
||||
6. Handle document uploads
|
||||
- Automatic rollback on any error
|
||||
- Comprehensive logging
|
||||
|
||||
### 3. API Route
|
||||
|
||||
**File**: `routes/api.php`
|
||||
|
||||
- Added endpoint: `POST /api/interventions/with-all-data`
|
||||
- Protected by authentication middleware
|
||||
|
||||
### 4. Repository Improvements
|
||||
|
||||
**Files**:
|
||||
|
||||
- `app/Repositories/DeceasedRepositoryInterface.php` (created)
|
||||
- `app/Repositories/DeceasedRepository.php` (updated)
|
||||
|
||||
**Changes**:
|
||||
|
||||
- DeceasedRepository now extends BaseRepository
|
||||
- Implements BaseRepositoryInterface
|
||||
- Inherits transaction support from BaseRepository
|
||||
- Consistent with other repository implementations
|
||||
|
||||
## API Endpoint
|
||||
|
||||
### POST /api/interventions/with-all-data
|
||||
|
||||
#### Request Structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"deceased": {
|
||||
"last_name": "Required",
|
||||
"first_name": "Optional",
|
||||
"birth_date": "Optional",
|
||||
"death_date": "Optional",
|
||||
"place_of_death": "Optional",
|
||||
"notes": "Optional"
|
||||
},
|
||||
"client": {
|
||||
"name": "Required",
|
||||
"vat_number": "Optional",
|
||||
"siret": "Optional",
|
||||
"email": "Optional",
|
||||
"phone": "Optional",
|
||||
"billing_address_line1": "Optional",
|
||||
"billing_postal_code": "Optional",
|
||||
"billing_city": "Optional",
|
||||
"billing_country_code": "Optional",
|
||||
"notes": "Optional",
|
||||
"is_active": "Optional"
|
||||
},
|
||||
"contact": {
|
||||
"first_name": "Optional",
|
||||
"last_name": "Optional",
|
||||
"email": "Optional",
|
||||
"phone": "Optional",
|
||||
"role": "Optional"
|
||||
},
|
||||
"location": {
|
||||
"name": "Optional",
|
||||
"address": "Optional",
|
||||
"city": "Optional",
|
||||
"postal_code": "Optional",
|
||||
"country_code": "Optional",
|
||||
"access_instructions": "Optional",
|
||||
"notes": "Optional"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"file": "File upload",
|
||||
"name": "Required",
|
||||
"description": "Optional"
|
||||
}
|
||||
],
|
||||
"intervention": {
|
||||
"type": "Required",
|
||||
"scheduled_at": "Optional",
|
||||
"duration_min": "Optional",
|
||||
"status": "Optional",
|
||||
"assigned_practitioner_id": "Optional",
|
||||
"order_giver": "Optional",
|
||||
"notes": "Optional",
|
||||
"created_by": "Optional"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Success Response (201):
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Intervention créée avec succès",
|
||||
"data": {
|
||||
"intervention": { ... },
|
||||
"deceased": { ... },
|
||||
"client": { ... },
|
||||
"contact_id": 123,
|
||||
"documents_count": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Error Response (422 - Validation):
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Données invalides",
|
||||
"errors": {
|
||||
"deceased": ["Le nom de famille est obligatoire."],
|
||||
"client": ["Le nom du client est obligatoire."],
|
||||
"intervention": ["Le type d'intervention est obligatoire."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Transaction Flow
|
||||
|
||||
```
|
||||
DB::beginTransaction()
|
||||
1. Create Deceased
|
||||
2. Create Client
|
||||
3. Create Contact (if provided)
|
||||
4. Prepare Location Notes
|
||||
5. Create Intervention
|
||||
6. Handle Document Uploads (pending implementation)
|
||||
DB::commit()
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Validation Errors
|
||||
|
||||
- Caught separately with proper HTTP status (422)
|
||||
- Grouped by step for better UX
|
||||
- French error messages
|
||||
|
||||
### General Errors
|
||||
|
||||
- Caught with proper HTTP status (500)
|
||||
- Full exception logged with trace
|
||||
- Input data logged (excluding files)
|
||||
- Transaction automatically rolled back
|
||||
|
||||
## Data Integrity
|
||||
|
||||
1. **BaseRepository Transaction Support**: All repository create operations use transactions
|
||||
2. **Controller-level Transaction**: Main transaction wraps all operations
|
||||
3. **Automatic Rollback**: Any exception triggers automatic rollback
|
||||
4. **Validation Before Transaction**: All data validated before any DB operations
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Atomicity**: All-or-nothing operation - prevents partial data creation
|
||||
2. **Data Integrity**: No orphaned records if any step fails
|
||||
3. **Performance**: Single HTTP request for complex operation
|
||||
4. **User Experience**: One form instead of multiple steps
|
||||
5. **Validation**: Comprehensive client and server-side validation
|
||||
6. **Logging**: Full audit trail for debugging
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Document Upload Implementation**: Complete file upload and storage logic
|
||||
2. **Location Model**: Consider creating a proper Location model instead of notes
|
||||
3. **Client Location Association**: Link interventions to actual locations
|
||||
4. **File Storage**: Implement proper file storage with intervention folders
|
||||
5. **Email Notifications**: Add notifications to relevant parties
|
||||
6. **Audit Trail**: Add more detailed logging for compliance
|
||||
|
||||
## Testing
|
||||
|
||||
To test the endpoint:
|
||||
|
||||
```bash
|
||||
# Create intervention with all data
|
||||
curl -X POST http://localhost/api/interventions/with-all-data \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @intervention-data.json
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- All date fields should use ISO format (Y-m-d H:i:s)
|
||||
- Document files should be sent as multipart/form-data
|
||||
- Location data is currently appended to intervention notes (can be enhanced)
|
||||
- Contact creation is optional - only created if data provided
|
||||
- Default status is "demande" if not specified
|
||||
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ClientCategoryRequest;
|
||||
use App\Http\Resources\Client\ClientCategoryResource;
|
||||
use App\Http\Resources\ClientResource;
|
||||
use App\Models\ClientCategory;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
|
||||
class ClientCategoryController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(): AnonymousResourceCollection
|
||||
{
|
||||
$query = ClientCategory::query();
|
||||
$categories = $query->get();
|
||||
|
||||
return ClientCategoryResource::collection($categories);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(ClientCategoryRequest $request): ClientCategoryResource
|
||||
{
|
||||
$category = ClientCategory::create($request->validated());
|
||||
|
||||
return new ClientCategoryResource($category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(ClientCategory $clientCategory): ClientCategoryResource
|
||||
{
|
||||
return new ClientCategoryResource($clientCategory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource by slug.
|
||||
*/
|
||||
public function showBySlug(string $slug): ClientCategoryResource
|
||||
{
|
||||
$category = ClientCategory::where('slug', $slug)->firstOrFail();
|
||||
|
||||
return new ClientCategoryResource($category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(ClientCategoryRequest $request, ClientCategory $clientCategory): ClientCategoryResource
|
||||
{
|
||||
$clientCategory->update($request->validated());
|
||||
|
||||
return new ClientCategoryResource($clientCategory->fresh());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(ClientCategory $clientCategory): JsonResponse
|
||||
{
|
||||
// Check if category has clients
|
||||
if ($clientCategory->clients()->exists()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Cannot delete category that has clients assigned. Please reassign clients first.'
|
||||
], 422);
|
||||
}
|
||||
|
||||
$clientCategory->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Client category deleted successfully.'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle active status of the category.
|
||||
*/
|
||||
public function toggleStatus(ClientCategory $clientCategory, Request $request): ClientCategoryResource
|
||||
{
|
||||
$request->validate([
|
||||
'is_active' => 'required|boolean',
|
||||
]);
|
||||
|
||||
$clientCategory->update([
|
||||
'is_active' => $request->boolean('is_active'),
|
||||
]);
|
||||
|
||||
return new ClientCategoryResource($clientCategory->fresh());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get clients for a specific category.
|
||||
*/
|
||||
public function clients(ClientCategory $clientCategory, Request $request): AnonymousResourceCollection
|
||||
{
|
||||
$query = $clientCategory->clients();
|
||||
|
||||
// Active status filter
|
||||
if ($request->has('is_active')) {
|
||||
$query->where('is_active', $request->boolean('is_active'));
|
||||
}
|
||||
|
||||
// Pagination
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$clients = $query->paginate($perPage);
|
||||
|
||||
return ClientResource::collection($clients);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder categories.
|
||||
*/
|
||||
public function reorder(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'order' => 'required|array',
|
||||
'order.*' => 'integer|exists:client_categories,id',
|
||||
]);
|
||||
|
||||
foreach ($request->order as $index => $categoryId) {
|
||||
ClientCategory::where('id', $categoryId)->update(['sort_order' => $index]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Categories reordered successfully.'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active categories for dropdowns.
|
||||
*/
|
||||
public function active(): AnonymousResourceCollection
|
||||
{
|
||||
$categories = ClientCategory::where('is_active', true)
|
||||
->orderBy('sort_order', 'asc')
|
||||
->orderBy('name', 'asc')
|
||||
->get();
|
||||
|
||||
return ClientCategoryResource::collection($categories);
|
||||
}
|
||||
}
|
||||
210
thanasoft-back/app/Http/Controllers/Api/ClientController.php
Normal file
210
thanasoft-back/app/Http/Controllers/Api/ClientController.php
Normal file
@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreClientRequest;
|
||||
use App\Http\Requests\UpdateClientRequest;
|
||||
use App\Http\Resources\Client\ClientResource;
|
||||
use App\Http\Resources\Client\ClientCollection;
|
||||
use App\Repositories\ClientRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ClientController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ClientRepositoryInterface $clientRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of clients.
|
||||
*/
|
||||
public function index(Request $request): ClientCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$filters = [
|
||||
'search' => $request->get('search'),
|
||||
'is_active' => $request->get('is_active'),
|
||||
'group_id' => $request->get('group_id'),
|
||||
'client_category_id' => $request->get('client_category_id'),
|
||||
'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 !== '';
|
||||
});
|
||||
|
||||
$clients = $this->clientRepository->paginate($perPage, $filters);
|
||||
|
||||
return new ClientCollection($clients);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching clients: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des clients.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created client.
|
||||
*/
|
||||
public function store(StoreClientRequest $request): ClientResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$client = $this->clientRepository->create($request->validated());
|
||||
return new ClientResource($client);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating client: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified client.
|
||||
*/
|
||||
public function show(string $id): ClientResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$client = $this->clientRepository->find($id);
|
||||
|
||||
if (!$client) {
|
||||
return response()->json([
|
||||
'message' => 'Client non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new ClientResource($client);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching client: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function searchBy(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$name = $request->get('name', '');
|
||||
|
||||
if (empty($name)) {
|
||||
return response()->json([
|
||||
'message' => 'Le paramètre "name" est requis.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$clients = $this->clientRepository->searchByName($name);
|
||||
|
||||
return response()->json([
|
||||
'data' => $clients,
|
||||
'count' => $clients->count(),
|
||||
'message' => $clients->count() > 0
|
||||
? 'Clients trouvés avec succès.'
|
||||
: 'Aucun client trouvé.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error searching clients by name: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'search_term' => $name,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recherche des clients.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified client.
|
||||
*/
|
||||
public function update(UpdateClientRequest $request, string $id): ClientResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->clientRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Client non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$client = $this->clientRepository->find($id);
|
||||
return new ClientResource($client);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating client: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified client.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->clientRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Client non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Client supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting client: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreClientGroupRequest;
|
||||
use App\Http\Requests\UpdateClientGroupRequest;
|
||||
use App\Http\Resources\Client\ClientGroupResource;
|
||||
use App\Repositories\ClientGroupRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ClientGroupController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ClientGroupRepositoryInterface $clientGroupRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of client groups.
|
||||
*/
|
||||
public function index(): AnonymousResourceCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$clientGroups = $this->clientGroupRepository->all();
|
||||
return ClientGroupResource::collection($clientGroups);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching client groups: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des groupes de clients.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created client group.
|
||||
*/
|
||||
public function store(StoreClientGroupRequest $request): ClientGroupResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$clientGroup = $this->clientGroupRepository->create($request->validated());
|
||||
return new ClientGroupResource($clientGroup);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating client group: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du groupe de clients.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified client group.
|
||||
*/
|
||||
public function show(string $id): ClientGroupResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$clientGroup = $this->clientGroupRepository->find($id);
|
||||
|
||||
if (!$clientGroup) {
|
||||
return response()->json([
|
||||
'message' => 'Groupe de clients non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new ClientGroupResource($clientGroup);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching client group: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_group_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du groupe de clients.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified client group.
|
||||
*/
|
||||
public function update(UpdateClientGroupRequest $request, string $id): ClientGroupResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->clientGroupRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Groupe de clients non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$clientGroup = $this->clientGroupRepository->find($id);
|
||||
return new ClientGroupResource($clientGroup);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating client group: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_group_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du groupe de clients.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified client group.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->clientGroupRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Groupe de clients non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Groupe de clients supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting client group: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_group_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du groupe de clients.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreClientLocationRequest;
|
||||
use App\Http\Requests\UpdateClientLocationRequest;
|
||||
use App\Http\Resources\Client\ClientLocationResource;
|
||||
use App\Http\Resources\Client\ClientLocationCollection;
|
||||
use App\Repositories\ClientLocationRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ClientLocationController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ClientLocationRepositoryInterface $clientLocationRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of client locations.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
try {
|
||||
$filters = $request->only(['client_id', 'is_default', 'search']);
|
||||
$perPage = $request->get('per_page', 10);
|
||||
|
||||
$clientLocations = $this->clientLocationRepository->getPaginated($filters, $perPage);
|
||||
return new ClientLocationCollection($clientLocations);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching client locations: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des lieux clients.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created client location.
|
||||
*/
|
||||
public function store(StoreClientLocationRequest $request): ClientLocationResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$clientLocation = $this->clientLocationRepository->create($request->validated());
|
||||
return new ClientLocationResource($clientLocation);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating client location: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du lieu client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified client id.
|
||||
*/
|
||||
public function getLocationsByClient(string $id)
|
||||
{
|
||||
try {
|
||||
$clientLocations = $this->clientLocationRepository->getByClientId((int)$id);
|
||||
return ClientLocationResource::collection($clientLocations);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching client location: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_location_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du lieu client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified client location.
|
||||
*/
|
||||
public function show(string $id): ClientLocationResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$clientLocation = $this->clientLocationRepository->find($id);
|
||||
|
||||
if (!$clientLocation) {
|
||||
return response()->json([
|
||||
'message' => 'Lieu client non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new ClientLocationResource($clientLocation);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching client location: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_location_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du lieu client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified client location.
|
||||
*/
|
||||
public function update(UpdateClientLocationRequest $request, string $id): ClientLocationResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->clientLocationRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Lieu client non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$clientLocation = $this->clientLocationRepository->find($id);
|
||||
return new ClientLocationResource($clientLocation);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating client location: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_location_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du lieu client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified client location.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->clientLocationRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Lieu client non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Lieu client supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting client location: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_location_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du lieu client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
214
thanasoft-back/app/Http/Controllers/Api/ContactController.php
Normal file
214
thanasoft-back/app/Http/Controllers/Api/ContactController.php
Normal file
@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreContactRequest;
|
||||
use App\Http\Requests\UpdateContactRequest;
|
||||
use App\Http\Resources\Contact\ContactResource;
|
||||
use App\Http\Resources\Contact\ContactCollection;
|
||||
use App\Repositories\ContactRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ContactController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ContactRepositoryInterface $contactRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of contacts.
|
||||
*/
|
||||
public function index(): ContactCollection
|
||||
{
|
||||
try {
|
||||
$perPage = request('per_page', 15);
|
||||
$filters = [
|
||||
'search' => request('search'),
|
||||
'is_primary' => request('is_primary'),
|
||||
'client_id' => request('client_id'),
|
||||
'sort_by' => request('sort_by', 'created_at'),
|
||||
'sort_direction' => request('sort_direction', 'desc'),
|
||||
];
|
||||
|
||||
$contacts = $this->contactRepository->paginate($perPage, $filters);
|
||||
|
||||
return new ContactCollection($contacts);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching contacts: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des contacts.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created contact.
|
||||
*/
|
||||
public function store(StoreContactRequest $request): ContactResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$contact = $this->contactRepository->create($request->validated());
|
||||
return new ContactResource($contact);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating contact: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du contact.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified contact.
|
||||
*/
|
||||
public function show(string $id): ContactResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$contact = $this->contactRepository->find($id);
|
||||
|
||||
if (!$contact) {
|
||||
return response()->json([
|
||||
'message' => 'Contact non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new ContactResource($contact);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching contact: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'contact_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du contact.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified contact.
|
||||
*/
|
||||
public function update(UpdateContactRequest $request, string $id): ContactResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->contactRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Contact non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$contact = $this->contactRepository->find($id);
|
||||
return new ContactResource($contact);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating contact: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'contact_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du contact.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified contact.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->contactRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Contact non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Contact supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting contact: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'contact_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du contact.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getContactsByClient(string $clientId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$intId = (int) $clientId;
|
||||
$contacts = $this->contactRepository->getByClientId($intId);
|
||||
return response()->json([
|
||||
'data' => ContactResource::collection($contacts),
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching contacts by client: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_id' => $clientId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des contacts du client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getContactsByFournisseur(string $fournisseurId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$intId = (int) $fournisseurId;
|
||||
$contacts = $this->contactRepository->getByFournisseurId($intId);
|
||||
return response()->json([
|
||||
'data' => ContactResource::collection($contacts),
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching contacts by fournisseur: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'fournisseur_id' => $fournisseurId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des contacts du fournisseur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
183
thanasoft-back/app/Http/Controllers/Api/DeceasedController.php
Normal file
183
thanasoft-back/app/Http/Controllers/Api/DeceasedController.php
Normal file
@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreDeceasedRequest;
|
||||
use App\Http\Requests\UpdateDeceasedRequest;
|
||||
use App\Http\Resources\Deceased\DeceasedResource;
|
||||
use App\Http\Resources\Deceased\DeceasedCollection;
|
||||
use App\Models\Deceased;
|
||||
use App\Repositories\DeceasedRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class DeceasedController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var DeceasedRepositoryInterface
|
||||
*/
|
||||
protected $deceasedRepository;
|
||||
|
||||
/**
|
||||
* DeceasedController constructor.
|
||||
*
|
||||
* @param DeceasedRepositoryInterface $deceasedRepository
|
||||
*/
|
||||
public function __construct(DeceasedRepositoryInterface $deceasedRepository)
|
||||
{
|
||||
$this->deceasedRepository = $deceasedRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$filters = $request->only([
|
||||
'search',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'sort_by',
|
||||
'sort_order'
|
||||
]);
|
||||
|
||||
$perPage = $request->input('per_page', 15);
|
||||
|
||||
$deceased = $this->deceasedRepository->getAllPaginated($filters, $perPage);
|
||||
|
||||
return response()->json(new DeceasedCollection($deceased));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching deceased list: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des défunts.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(StoreDeceasedRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$validated = $request->validated();
|
||||
|
||||
$deceased = $this->deceasedRepository->create($validated);
|
||||
|
||||
return response()->json(new DeceasedResource($deceased), Response::HTTP_CREATED);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating deceased: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du défunt.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deceased = $this->deceasedRepository->findById($id);
|
||||
|
||||
return response()->json(new DeceasedResource($deceased));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching deceased details: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Défunt non trouvé ou une erreur est survenue.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(UpdateDeceasedRequest $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deceased = $this->deceasedRepository->findById($id);
|
||||
|
||||
$validated = $request->validated();
|
||||
|
||||
$updatedDeceased = $this->deceasedRepository->update($deceased, $validated);
|
||||
|
||||
return response()->json(new DeceasedResource($updatedDeceased));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating deceased: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du défunt.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deceased = $this->deceasedRepository->findById($id);
|
||||
|
||||
$this->deceasedRepository->delete($deceased);
|
||||
|
||||
return response()->json(null, Response::HTTP_NO_CONTENT);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting deceased: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du défunt.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Search deceased by name or other criteria.
|
||||
*/
|
||||
public function searchBy(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$search = $request->get('search', '');
|
||||
|
||||
if (empty($search)) {
|
||||
return response()->json([
|
||||
'message' => 'Le paramètre "search" est requis.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$deceased = $this->deceasedRepository->searchByName($search);
|
||||
|
||||
return response()->json([
|
||||
'data' => $deceased,
|
||||
'count' => $deceased->count(),
|
||||
'message' => $deceased->count() > 0
|
||||
? 'Défunts trouvés avec succès.'
|
||||
: 'Aucun défunt trouvé.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error searching deceased by name: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'search_term' => $search ?? '',
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recherche des défunts.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreDeceasedDocumentRequest;
|
||||
use App\Http\Requests\UpdateDeceasedDocumentRequest;
|
||||
use App\Http\Resources\Deceased\DeceasedDocumentResource;
|
||||
use App\Http\Resources\Deceased\DeceasedDocumentCollection;
|
||||
use App\Repositories\DeceasedDocumentRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class DeceasedDocumentController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DeceasedDocumentRepositoryInterface $deceasedDocumentRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of deceased documents.
|
||||
*/
|
||||
public function index(Request $request): DeceasedDocumentCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$filters = [
|
||||
'search' => $request->get('search'),
|
||||
'deceased_id' => $request->get('deceased_id'),
|
||||
'doc_type' => $request->get('doc_type'),
|
||||
'file_id' => $request->get('file_id'),
|
||||
'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->deceasedDocumentRepository->paginate($perPage, $filters);
|
||||
|
||||
return new DeceasedDocumentCollection($documents);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la récupération des documents du défunt: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des documents du défunt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get documents by deceased ID.
|
||||
*/
|
||||
public function byDeceased(string $deceasedId): DeceasedDocumentCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$documents = $this->deceasedDocumentRepository->getByDeceasedId((int) $deceasedId);
|
||||
return new DeceasedDocumentCollection($documents);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la récupération des documents par défunt: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'deceased_id' => $deceasedId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des documents du défunt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get documents by document type.
|
||||
*/
|
||||
public function byDocType(Request $request): DeceasedDocumentCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$docType = $request->get('doc_type');
|
||||
|
||||
if (!$docType) {
|
||||
return response()->json([
|
||||
'message' => 'Le paramètre doc_type est requis.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$documents = $this->deceasedDocumentRepository->getByDocType($docType);
|
||||
return new DeceasedDocumentCollection($documents);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la récupération des documents par type: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'doc_type' => $request->get('doc_type'),
|
||||
]);
|
||||
|
||||
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 documents by file ID.
|
||||
*/
|
||||
public function byFile(string $fileId): DeceasedDocumentCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$documents = $this->deceasedDocumentRepository->getByFileId((int) $fileId);
|
||||
return new DeceasedDocumentCollection($documents);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la récupération des documents par fichier: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'file_id' => $fileId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des documents par fichier.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search documents by various criteria.
|
||||
*/
|
||||
public function search(Request $request): DeceasedDocumentCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$criteria = [
|
||||
'deceased_id' => $request->get('deceased_id'),
|
||||
'doc_type' => $request->get('doc_type'),
|
||||
'file_id' => $request->get('file_id'),
|
||||
'generated_from' => $request->get('generated_from'),
|
||||
'generated_to' => $request->get('generated_to'),
|
||||
];
|
||||
|
||||
// Remove null criteria
|
||||
$criteria = array_filter($criteria, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
|
||||
$documents = $this->deceasedDocumentRepository->search($criteria);
|
||||
return new DeceasedDocumentCollection($documents);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la recherche de documents: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'criteria' => $request->all(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recherche de documents.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created deceased document.
|
||||
*/
|
||||
public function store(StoreDeceasedDocumentRequest $request): DeceasedDocumentResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$document = $this->deceasedDocumentRepository->create($request->validated());
|
||||
return new DeceasedDocumentResource($document);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la création du document du défunt: ' . $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 défunt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified deceased document.
|
||||
*/
|
||||
public function show(string $id): DeceasedDocumentResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$document = $this->deceasedDocumentRepository->find($id);
|
||||
|
||||
if (!$document) {
|
||||
return response()->json([
|
||||
'message' => 'Document du défunt non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new DeceasedDocumentResource($document);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la récupération du document du défunt: ' . $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 défunt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified deceased document.
|
||||
*/
|
||||
public function update(UpdateDeceasedDocumentRequest $request, string $id): DeceasedDocumentResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->deceasedDocumentRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Document du défunt non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$document = $this->deceasedDocumentRepository->find($id);
|
||||
return new DeceasedDocumentResource($document);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la mise à jour du document du défunt: ' . $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 défunt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified deceased document.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->deceasedDocumentRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Document du défunt non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Document du défunt supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la suppression du document du défunt: ' . $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 défunt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
272
thanasoft-back/app/Http/Controllers/Api/EmployeeController.php
Normal file
272
thanasoft-back/app/Http/Controllers/Api/EmployeeController.php
Normal file
@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreEmployeeRequest;
|
||||
use App\Http\Requests\UpdateEmployeeRequest;
|
||||
use App\Http\Resources\Employee\EmployeeResource;
|
||||
use App\Http\Resources\Employee\EmployeeCollection;
|
||||
use App\Repositories\EmployeeRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class EmployeeController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EmployeeRepositoryInterface $employeeRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of employees (paginated).
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = (int) $request->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,438 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\FileAttachment\FileAttachmentResource;
|
||||
use App\Models\File;
|
||||
use App\Models\FileAttachment;
|
||||
use App\Models\Intervention;
|
||||
use App\Models\Client;
|
||||
use App\Models\Deceased;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class FileAttachmentController extends Controller
|
||||
{
|
||||
/**
|
||||
* Attach a file to a model (Intervention, Client, Deceased, etc.)
|
||||
*/
|
||||
public function attach(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'file_id' => 'required|exists:files,id',
|
||||
'attachable_type' => 'required|string|in:App\Models\Intervention,App\Models\Client,App\Models\Deceased',
|
||||
'attachable_id' => 'required|integer',
|
||||
'label' => 'nullable|string|max:255',
|
||||
'sort_order' => 'nullable|integer|min:0',
|
||||
]);
|
||||
|
||||
// Verify the attachable model exists
|
||||
$attachableModel = $this->getAttachableModel($request->attachable_type, $request->attachable_id);
|
||||
if (!$attachableModel) {
|
||||
return response()->json([
|
||||
'message' => 'Le modèle cible n\'existe pas.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
// Check if file is already attached to this model
|
||||
$existingAttachment = FileAttachment::where('file_id', $request->file_id)
|
||||
->where('attachable_type', $request->attachable_type)
|
||||
->where('attachable_id', $request->attachable_id)
|
||||
->first();
|
||||
|
||||
if ($existingAttachment) {
|
||||
return response()->json([
|
||||
'message' => 'Ce fichier est déjà attaché à cet élément.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
// Create the attachment
|
||||
$attachment = FileAttachment::create([
|
||||
'file_id' => $request->file_id,
|
||||
'attachable_type' => $request->attachable_type,
|
||||
'attachable_id' => $request->attachable_id,
|
||||
'label' => $request->label,
|
||||
'sort_order' => $request->sort_order ?? 0,
|
||||
]);
|
||||
|
||||
// Load relationships for response
|
||||
$attachment->load('file');
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'data' => new FileAttachmentResource($attachment),
|
||||
'message' => 'Fichier attaché avec succès.',
|
||||
], 201);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error attaching file: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'request_data' => $request->all(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de l\'attachement du fichier.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach a file from a model
|
||||
*/
|
||||
public function detach(Request $request, string $attachmentId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$attachment = FileAttachment::find($attachmentId);
|
||||
|
||||
if (!$attachment) {
|
||||
return response()->json([
|
||||
'message' => 'Attachement de fichier non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
$attachment->delete();
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Fichier détaché avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error detaching file: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'attachment_id' => $attachmentId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors du détachement du fichier.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update file attachment metadata
|
||||
*/
|
||||
public function update(Request $request, string $attachmentId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'label' => 'nullable|string|max:255',
|
||||
'sort_order' => 'nullable|integer|min:0',
|
||||
]);
|
||||
|
||||
$attachment = FileAttachment::find($attachmentId);
|
||||
|
||||
if (!$attachment) {
|
||||
return response()->json([
|
||||
'message' => 'Attachement de fichier non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
$attachment->update($request->only(['label', 'sort_order']));
|
||||
$attachment->load('file');
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'data' => new FileAttachmentResource($attachment),
|
||||
'message' => 'Attachement de fichier mis à jour avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating file attachment: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'attachment_id' => $attachmentId,
|
||||
'request_data' => $request->all(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour de l\'attachement.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get files attached to a specific model
|
||||
*/
|
||||
public function getAttachedFiles(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'attachable_type' => 'required|string|in:App\Models\Intervention,App\Models\Client,App\Models\Deceased',
|
||||
'attachable_id' => 'required|integer',
|
||||
]);
|
||||
|
||||
$attachments = FileAttachment::where('attachable_type', $request->attachable_type)
|
||||
->where('attachable_id', $request->attachable_id)
|
||||
->with('file')
|
||||
->orderBy('sort_order')
|
||||
->orderBy('created_at')
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'data' => FileAttachmentResource::collection($attachments),
|
||||
'count' => $attachments->count(),
|
||||
'message' => 'Fichiers attachés récupérés avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error getting attached files: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'request_data' => $request->all(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des fichiers attachés.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get files attached to an intervention
|
||||
*/
|
||||
public function getInterventionFiles(Request $request, int $interventionId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$intervention = Intervention::find($interventionId);
|
||||
|
||||
if (!$intervention) {
|
||||
return response()->json([
|
||||
'message' => 'Intervention non trouvée.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$attachments = $intervention->fileAttachments()->with('file')->get();
|
||||
|
||||
return response()->json([
|
||||
'data' => FileAttachmentResource::collection($attachments),
|
||||
'count' => $attachments->count(),
|
||||
'message' => 'Fichiers de l\'intervention récupérés avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error getting intervention files: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'intervention_id' => $interventionId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des fichiers de l\'intervention.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get files attached to a client
|
||||
*/
|
||||
public function getClientFiles(Request $request, int $clientId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$client = Client::find($clientId);
|
||||
|
||||
if (!$client) {
|
||||
return response()->json([
|
||||
'message' => 'Client non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$attachments = $client->fileAttachments()->with('file')->get();
|
||||
|
||||
return response()->json([
|
||||
'data' => FileAttachmentResource::collection($attachments),
|
||||
'count' => $attachments->count(),
|
||||
'message' => 'Fichiers du client récupérés avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error getting client files: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_id' => $clientId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des fichiers du client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get files attached to a deceased
|
||||
*/
|
||||
public function getDeceasedFiles(Request $request, int $deceasedId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deceased = Deceased::find($deceasedId);
|
||||
|
||||
if (!$deceased) {
|
||||
return response()->json([
|
||||
'message' => 'Défunt non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$attachments = $deceased->fileAttachments()->with('file')->get();
|
||||
|
||||
return response()->json([
|
||||
'data' => FileAttachmentResource::collection($attachments),
|
||||
'count' => $attachments->count(),
|
||||
'message' => 'Fichiers du défunt récupérés avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error getting deceased files: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'deceased_id' => $deceasedId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des fichiers du défunt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach multiple files at once
|
||||
*/
|
||||
public function detachMultiple(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'attachment_ids' => 'required|array|min:1',
|
||||
'attachment_ids.*' => 'exists:file_attachments,id',
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
$deletedCount = FileAttachment::whereIn('id', $request->attachment_ids)->delete();
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'deleted_count' => $deletedCount,
|
||||
'message' => $deletedCount . ' fichier(s) détaché(s) avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error detaching multiple files: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'attachment_ids' => $request->attachment_ids ?? [],
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors du détachement des fichiers.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder file attachments
|
||||
*/
|
||||
public function reorder(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'attachments' => 'required|array|min:1',
|
||||
'attachments.*.id' => 'required|exists:file_attachments,id',
|
||||
'attachments.*.sort_order' => 'required|integer|min:0',
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
foreach ($request->attachments as $attachmentData) {
|
||||
FileAttachment::where('id', $attachmentData['id'])
|
||||
->update(['sort_order' => $attachmentData['sort_order']]);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Ordre des fichiers mis à jour avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error reordering file attachments: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'attachments' => $request->attachments ?? [],
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la réorganisation des fichiers.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachable model instance
|
||||
*/
|
||||
private function getAttachableModel(string $type, int $id): ?Model
|
||||
{
|
||||
return match ($type) {
|
||||
Intervention::class => Intervention::find($id),
|
||||
Client::class => Client::find($id),
|
||||
Deceased::class => Deceased::find($id),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
444
thanasoft-back/app/Http/Controllers/Api/FileController.php
Normal file
444
thanasoft-back/app/Http/Controllers/Api/FileController.php
Normal file
@ -0,0 +1,444 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreFileRequest;
|
||||
use App\Http\Requests\UpdateFileRequest;
|
||||
use App\Http\Resources\File\FileResource;
|
||||
use App\Http\Resources\File\FileCollection;
|
||||
use App\Repositories\FileRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class FileController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly FileRepositoryInterface $fileRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of files.
|
||||
*/
|
||||
public function index(Request $request): FileCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$filters = [
|
||||
'search' => $request->get('search'),
|
||||
'mime_type' => $request->get('mime_type'),
|
||||
'uploaded_by' => $request->get('uploaded_by'),
|
||||
'category' => $request->get('category'),
|
||||
'client_id' => $request->get('client_id'),
|
||||
'date_from' => $request->get('date_from'),
|
||||
'date_to' => $request->get('date_to'),
|
||||
'sort_by' => $request->get('sort_by', 'uploaded_at'),
|
||||
'sort_direction' => $request->get('sort_direction', 'desc'),
|
||||
];
|
||||
|
||||
// Remove null filters
|
||||
$filters = array_filter($filters, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
|
||||
$files = $this->fileRepository->paginate($perPage, $filters);
|
||||
|
||||
return new FileCollection($files);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching files: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des fichiers.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly uploaded file.
|
||||
*/
|
||||
public function store(StoreFileRequest $request): FileResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$validatedData = $request->validated();
|
||||
$file = $request->file('file');
|
||||
|
||||
// Generate organized storage path
|
||||
$storagePath = $this->generateOrganizedPath(
|
||||
$validatedData['category'],
|
||||
$validatedData['client_id'] ?? null,
|
||||
$validatedData['subcategory'] ?? null,
|
||||
$validatedData['file_name']
|
||||
);
|
||||
|
||||
// Store the file
|
||||
$storedFilePath = $file->store($storagePath, 'public');
|
||||
|
||||
// Calculate SHA256 hash
|
||||
$hash = hash_file('sha256', $file->path());
|
||||
|
||||
// Prepare data for database
|
||||
$fileData = array_merge($validatedData, [
|
||||
'storage_uri' => $storedFilePath,
|
||||
'sha256' => $hash,
|
||||
'uploaded_by' => $request->user()->id,
|
||||
'uploaded_at' => now(),
|
||||
]);
|
||||
|
||||
$createdFile = $this->fileRepository->create($fileData);
|
||||
|
||||
return new FileResource($createdFile);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error uploading file: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'user_id' => $request->user()->id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de l\'upload du fichier.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified file.
|
||||
*/
|
||||
public function show(string $id): FileResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$file = $this->fileRepository->find($id);
|
||||
|
||||
if (!$file) {
|
||||
return response()->json([
|
||||
'message' => 'Fichier non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new FileResource($file);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching file: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'file_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du fichier.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified file metadata.
|
||||
*/
|
||||
public function update(UpdateFileRequest $request, string $id): FileResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$file = $this->fileRepository->find($id);
|
||||
|
||||
if (!$file) {
|
||||
return response()->json([
|
||||
'message' => 'Fichier non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$validatedData = $request->validated();
|
||||
|
||||
// If category or client changed, move the file
|
||||
if (isset($validatedData['category']) || isset($validatedData['client_id']) || isset($validatedData['subcategory'])) {
|
||||
$newStoragePath = $this->generateOrganizedPath(
|
||||
$validatedData['category'] ?? $this->extractCategoryFromPath($file->storage_uri),
|
||||
$validatedData['client_id'] ?? $this->extractClientFromPath($file->storage_uri),
|
||||
$validatedData['subcategory'] ?? $this->extractSubcategoryFromPath($file->storage_uri),
|
||||
$file->file_name
|
||||
);
|
||||
|
||||
if ($newStoragePath !== $file->storage_uri) {
|
||||
// Move file to new location
|
||||
Storage::disk('public')->move($file->storage_uri, $newStoragePath);
|
||||
$validatedData['storage_uri'] = $newStoragePath;
|
||||
}
|
||||
}
|
||||
|
||||
$updated = $this->fileRepository->update($id, $validatedData);
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Fichier non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$updatedFile = $this->fileRepository->find($id);
|
||||
return new FileResource($updatedFile);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating file: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'file_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du fichier.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified file.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$file = $this->fileRepository->find($id);
|
||||
|
||||
if (!$file) {
|
||||
return response()->json([
|
||||
'message' => 'Fichier non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
// Delete file from storage
|
||||
Storage::disk('public')->delete($file->storage_uri);
|
||||
|
||||
// Delete from database
|
||||
$deleted = $this->fileRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Fichier non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Fichier supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting file: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'file_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du fichier.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get files by category.
|
||||
*/
|
||||
public function byCategory(Request $request, string $category): FileCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$files = $this->fileRepository->getByCategory($category, $perPage);
|
||||
|
||||
return new FileCollection($files);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching files by category: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'category' => $category,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des fichiers par catégorie.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get files by client.
|
||||
*/
|
||||
public function byClient(Request $request, int $clientId): FileCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$files = $this->fileRepository->getByClient($clientId, $perPage);
|
||||
|
||||
return new FileCollection($files);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching files by client: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'client_id' => $clientId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des fichiers du client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get organized file structure.
|
||||
*/
|
||||
public function organized(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$organizedFiles = $this->fileRepository->getOrganizedFiles();
|
||||
|
||||
return response()->json([
|
||||
'data' => $organizedFiles,
|
||||
'message' => 'Structure de fichiers récupérée avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching organized files: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération de la structure de fichiers.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get storage statistics.
|
||||
*/
|
||||
public function stats(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$stats = $this->fileRepository->getStorageStats();
|
||||
|
||||
return response()->json([
|
||||
'data' => $stats,
|
||||
'message' => 'Statistiques de stockage récupérées avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching storage stats: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des statistiques.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file.
|
||||
*/
|
||||
public function download(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$file = $this->fileRepository->find($id);
|
||||
|
||||
if (!$file) {
|
||||
return response()->json([
|
||||
'message' => 'Fichier non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
if (!Storage::disk('public')->exists($file->storage_uri)) {
|
||||
return response()->json([
|
||||
'message' => 'Fichier physique non trouvé sur le stockage.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$downloadUrl = Storage::disk('public')->url($file->storage_uri);
|
||||
|
||||
return response()->json([
|
||||
'data' => [
|
||||
'download_url' => $downloadUrl,
|
||||
'file_name' => $file->file_name,
|
||||
'mime_type' => $file->mime_type,
|
||||
],
|
||||
'message' => 'URL de téléchargement générée avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error generating download URL: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'file_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la génération de l\'URL de téléchargement.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate organized storage path.
|
||||
*/
|
||||
private function generateOrganizedPath(string $category, ?int $clientId, ?string $subcategory, string $fileName): string
|
||||
{
|
||||
$pathParts = [];
|
||||
|
||||
if ($clientId) {
|
||||
$pathParts[] = 'client';
|
||||
$pathParts[] = $clientId;
|
||||
} else {
|
||||
$pathParts[] = 'general';
|
||||
}
|
||||
|
||||
$pathParts[] = $category;
|
||||
|
||||
if ($subcategory) {
|
||||
$pathParts[] = Str::slug($subcategory);
|
||||
} else {
|
||||
$pathParts[] = 'files';
|
||||
}
|
||||
|
||||
// Add timestamp to avoid conflicts
|
||||
$timestamp = now()->format('Y-m-d_H-i-s');
|
||||
$extension = pathinfo($fileName, PATHINFO_EXTENSION);
|
||||
$basename = pathinfo($fileName, PATHINFO_FILENAME);
|
||||
$safeFilename = Str::slug($basename) . '_' . $timestamp . '.' . $extension;
|
||||
|
||||
$pathParts[] = $safeFilename;
|
||||
|
||||
return implode('/', $pathParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract category from storage path.
|
||||
*/
|
||||
private function extractCategoryFromPath(string $storageUri): string
|
||||
{
|
||||
$pathParts = explode('/', $storageUri);
|
||||
return $pathParts[count($pathParts) - 3] ?? 'general';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract client ID from storage path.
|
||||
*/
|
||||
private function extractClientFromPath(string $storageUri): ?int
|
||||
{
|
||||
$pathParts = explode('/', $storageUri);
|
||||
if (count($pathParts) >= 4 && $pathParts[0] === 'client') {
|
||||
return (int) $pathParts[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract subcategory from storage path.
|
||||
*/
|
||||
private function extractSubcategoryFromPath(string $storageUri): ?string
|
||||
{
|
||||
$pathParts = explode('/', $storageUri);
|
||||
return $pathParts[count($pathParts) - 2] ?? null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreFournisseurRequest;
|
||||
use App\Http\Requests\UpdateFournisseurRequest;
|
||||
use App\Http\Resources\Fournisseur\FournisseurResource;
|
||||
use App\Http\Resources\Fournisseur\FournisseurCollection;
|
||||
use App\Repositories\FournisseurRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class FournisseurController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly FournisseurRepositoryInterface $fournisseurRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of fournisseurs.
|
||||
*/
|
||||
public function index(Request $request): FournisseurCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$filters = [
|
||||
'search' => $request->get('search'),
|
||||
'is_active' => $request->get('is_active'),
|
||||
'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 !== '';
|
||||
});
|
||||
|
||||
$fournisseurs = $this->fournisseurRepository->paginate($perPage, $filters);
|
||||
|
||||
return new FournisseurCollection($fournisseurs);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching fournisseurs: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des fournisseurs.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created fournisseur.
|
||||
*/
|
||||
public function store(StoreFournisseurRequest $request): FournisseurResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$fournisseur = $this->fournisseurRepository->create($request->validated());
|
||||
return new FournisseurResource($fournisseur);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating fournisseur: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du fournisseur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified fournisseur.
|
||||
*/
|
||||
public function show(string $id): FournisseurResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$fournisseur = $this->fournisseurRepository->find($id);
|
||||
|
||||
if (!$fournisseur) {
|
||||
return response()->json([
|
||||
'message' => 'Fournisseur non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new FournisseurResource($fournisseur);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching fournisseur: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'fournisseur_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du fournisseur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function searchBy(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$name = $request->get('name', '');
|
||||
|
||||
if (empty($name)) {
|
||||
return response()->json([
|
||||
'message' => 'Le paramètre "name" est requis.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$fournisseurs = $this->fournisseurRepository->searchByName($name);
|
||||
|
||||
return response()->json([
|
||||
'data' => $fournisseurs,
|
||||
'count' => $fournisseurs->count(),
|
||||
'message' => $fournisseurs->count() > 0
|
||||
? 'Fournisseurs trouvés avec succès.'
|
||||
: 'Aucun fournisseur trouvé.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error searching fournisseurs by name: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'search_term' => $name,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recherche des fournisseurs.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified fournisseur.
|
||||
*/
|
||||
public function update(UpdateFournisseurRequest $request, string $id): FournisseurResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->fournisseurRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Fournisseur non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$fournisseur = $this->fournisseurRepository->find($id);
|
||||
return new FournisseurResource($fournisseur);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating fournisseur: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'fournisseur_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du fournisseur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified fournisseur.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->fournisseurRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Fournisseur non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Fournisseur supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting fournisseur: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'fournisseur_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du fournisseur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,557 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreInterventionRequest;
|
||||
use App\HttpRequests\StoreInterventionWithAllDataRequest;
|
||||
use App\Http\Requests\UpdateInterventionRequest;
|
||||
use App\Http\Resources\Intervention\InterventionResource;
|
||||
use App\Http\Resources\Intervention\InterventionCollection;
|
||||
use App\Repositories\InterventionRepositoryInterface;
|
||||
use App\Repositories\InterventionPractitionerRepositoryInterface;
|
||||
use App\Repositories\ClientRepositoryInterface;
|
||||
use App\Repositories\ContactRepositoryInterface;
|
||||
use App\Repositories\DeceasedRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class InterventionController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var InterventionRepositoryInterface
|
||||
*/
|
||||
protected $interventionRepository;
|
||||
|
||||
/**
|
||||
* @var InterventionPractitionerRepositoryInterface
|
||||
*/
|
||||
protected $interventionPractitionerRepository;
|
||||
|
||||
/**
|
||||
* @var ClientRepositoryInterface
|
||||
*/
|
||||
protected $clientRepository;
|
||||
|
||||
/**
|
||||
* @var ContactRepositoryInterface
|
||||
*/
|
||||
protected $contactRepository;
|
||||
|
||||
/**
|
||||
* @var DeceasedRepositoryInterface
|
||||
*/
|
||||
protected $deceasedRepository;
|
||||
|
||||
/**
|
||||
* InterventionController constructor.
|
||||
*
|
||||
* @param InterventionRepositoryInterface $interventionRepository
|
||||
* @param InterventionPractitionerRepositoryInterface $interventionPractitionerRepository
|
||||
* @param ClientRepositoryInterface $clientRepository
|
||||
* @param ContactRepositoryInterface $contactRepository
|
||||
* @param DeceasedRepositoryInterface $deceasedRepository
|
||||
*/
|
||||
public function __construct(
|
||||
InterventionRepositoryInterface $interventionRepository,
|
||||
InterventionPractitionerRepositoryInterface $interventionPractitionerRepository,
|
||||
ClientRepositoryInterface $clientRepository,
|
||||
ContactRepositoryInterface $contactRepository,
|
||||
DeceasedRepositoryInterface $deceasedRepository
|
||||
) {
|
||||
$this->interventionRepository = $interventionRepository;
|
||||
$this->interventionPractitionerRepository = $interventionPractitionerRepository;
|
||||
$this->clientRepository = $clientRepository;
|
||||
$this->contactRepository = $contactRepository;
|
||||
$this->deceasedRepository = $deceasedRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$filters = $request->only([
|
||||
'client_id',
|
||||
'deceased_id',
|
||||
'status',
|
||||
'type',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'sort_by',
|
||||
'sort_order'
|
||||
]);
|
||||
|
||||
$perPage = $request->input('per_page', 15);
|
||||
|
||||
$interventions = $this->interventionRepository->getAllPaginated($filters, $perPage);
|
||||
|
||||
return response()->json(new InterventionCollection($interventions));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching interventions list: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des interventions.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(StoreInterventionRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$validated = $request->validated();
|
||||
|
||||
$intervention = $this->interventionRepository->create($validated);
|
||||
|
||||
return response()->json(new InterventionResource($intervention), Response::HTTP_CREATED);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating intervention: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création de l\'intervention.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an intervention with all related data (deceased, client, contact, location, documents).
|
||||
*/
|
||||
public function createInterventionalldata(StoreInterventionWithAllDataRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$validated = $request->validated();
|
||||
|
||||
// Wrap everything in a database transaction
|
||||
$result = DB::transaction(function () use ($validated) {
|
||||
// Step 1: Create the deceased
|
||||
$deceasedData = $validated['deceased'];
|
||||
$deceased = $this->deceasedRepository->create($deceasedData);
|
||||
|
||||
// Step 2: Create the client
|
||||
$clientData = $validated['client'];
|
||||
$client = $this->clientRepository->create($clientData);
|
||||
|
||||
// Step 3: Create the contact (if provided)
|
||||
$contactId = null;
|
||||
if (!empty($validated['contact'])) {
|
||||
$contactData = array_merge($validated['contact'], [
|
||||
'client_id' => $client->id
|
||||
]);
|
||||
$contact = $this->contactRepository->create($contactData);
|
||||
$contactId = $contact->id;
|
||||
}
|
||||
|
||||
// Step 4: Prepare location data (for now, we'll include it in intervention notes)
|
||||
// In the future, you might want to create a ClientLocation entry
|
||||
$locationData = $validated['location'] ?? [];
|
||||
$locationNotes = '';
|
||||
if (!empty($locationData)) {
|
||||
$locationParts = [];
|
||||
if (!empty($locationData['name'])) {
|
||||
$locationParts[] = 'Lieu: ' . $locationData['name'];
|
||||
}
|
||||
if (!empty($locationData['address'])) {
|
||||
$locationParts[] = 'Adresse: ' . $locationData['address'];
|
||||
}
|
||||
if (!empty($locationData['city'])) {
|
||||
$locationParts[] = 'Ville: ' . $locationData['city'];
|
||||
}
|
||||
if (!empty($locationData['access_instructions'])) {
|
||||
$locationParts[] = 'Instructions: ' . $locationData['access_instructions'];
|
||||
}
|
||||
if (!empty($locationData['notes'])) {
|
||||
$locationParts[] = 'Notes: ' . $locationData['notes'];
|
||||
}
|
||||
$locationNotes = !empty($locationParts) ? "\n\n" . implode("\n", $locationParts) : '';
|
||||
}
|
||||
|
||||
// Step 5: Create the intervention
|
||||
$interventionData = array_merge($validated['intervention'], [
|
||||
'deceased_id' => $deceased->id,
|
||||
'client_id' => $client->id,
|
||||
'notes' => ($validated['intervention']['notes'] ?? '') . $locationNotes
|
||||
]);
|
||||
$intervention = $this->interventionRepository->create($interventionData);
|
||||
|
||||
// Step 6: Handle document uploads (if any)
|
||||
$documents = $validated['documents'] ?? [];
|
||||
if (!empty($documents)) {
|
||||
foreach ($documents as $documentData) {
|
||||
if (isset($documentData['file']) && $documentData['file']->isValid()) {
|
||||
// Store the file and create intervention attachment
|
||||
// This is a placeholder - implement actual file upload logic
|
||||
// $path = $documentData['file']->store('intervention_documents');
|
||||
// Create intervention attachment record
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return all created data
|
||||
return [
|
||||
'intervention' => $intervention,
|
||||
'deceased' => $deceased,
|
||||
'client' => $client,
|
||||
'contact_id' => $contactId,
|
||||
'documents_count' => count($documents)
|
||||
];
|
||||
});
|
||||
|
||||
Log::info('Intervention with all data created successfully', [
|
||||
'intervention_id' => $result['intervention']->id,
|
||||
'deceased_id' => $result['deceased']->id,
|
||||
'client_id' => $result['client']->id,
|
||||
'documents_count' => $result['documents_count']
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Intervention créée avec succès',
|
||||
'data' => [
|
||||
'intervention' => new InterventionResource($result['intervention']),
|
||||
'deceased' => $result['deceased'],
|
||||
'client' => $result['client'],
|
||||
'contact_id' => $result['contact_id'],
|
||||
'documents_count' => $result['documents_count']
|
||||
]
|
||||
], Response::HTTP_CREATED);
|
||||
|
||||
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||
// Validation errors are handled by the FormRequest
|
||||
return response()->json([
|
||||
'message' => 'Données invalides',
|
||||
'errors' => $e->errors()
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating intervention with all data: ' . $e->getMessage(), [
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'input' => $request->except(['documents']) // Don't log file data
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création de l\'intervention.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$intervention = $this->interventionRepository->findById($id);
|
||||
|
||||
return response()->json(new InterventionResource($intervention));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching intervention details: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Intervention non trouvée ou une erreur est survenue.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(UpdateInterventionRequest $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$intervention = $this->interventionRepository->findById($id);
|
||||
|
||||
$validated = $request->validated();
|
||||
|
||||
$updatedIntervention = $this->interventionRepository->update($intervention, $validated);
|
||||
|
||||
return response()->json(new InterventionResource($updatedIntervention));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating intervention: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour de l\'intervention.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$intervention = $this->interventionRepository->findById($id);
|
||||
|
||||
$this->interventionRepository->delete($intervention);
|
||||
|
||||
return response()->json(null, Response::HTTP_NO_CONTENT);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting intervention: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression de l\'intervention.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the status of an intervention.
|
||||
*/
|
||||
public function changeStatus(Request $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'status' => 'required|in:demande,planifie,en_cours,termine,annule'
|
||||
]);
|
||||
|
||||
$intervention = $this->interventionRepository->findById($id);
|
||||
|
||||
$updatedIntervention = $this->interventionRepository->changeStatus(
|
||||
$intervention,
|
||||
$validated['status']
|
||||
);
|
||||
|
||||
return response()->json(new InterventionResource($updatedIntervention));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error changing intervention status: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la modification du statut de l\'intervention.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get interventions for a specific month.
|
||||
*/
|
||||
public function byMonth(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'year' => 'required|integer|min:2000|max:2100',
|
||||
'month' => 'required|integer|min:1|max:12'
|
||||
]);
|
||||
|
||||
$interventions = $this->interventionRepository->getByMonth(
|
||||
$validated['year'],
|
||||
$validated['month']
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'data' => $interventions->map(function ($intervention) {
|
||||
return new InterventionResource($intervention);
|
||||
}),
|
||||
'meta' => [
|
||||
'total' => $interventions->count(),
|
||||
'year' => $validated['year'],
|
||||
'month' => $validated['month'],
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching interventions by month: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des interventions du mois.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create assignment of practitioners to an intervention
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function createAssignment(Request $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'principal_practitioner_id' => 'nullable|integer|exists:thanatopractitioners,id',
|
||||
'assistant_practitioner_ids' => 'nullable|array',
|
||||
'assistant_practitioner_ids.*' => 'integer|exists:thanatopractitioners,id',
|
||||
]);
|
||||
|
||||
|
||||
$intervention = $this->interventionRepository->findById($id);
|
||||
|
||||
if (!$intervention) {
|
||||
return response()->json([
|
||||
'message' => 'Intervention non trouvée.'
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Remove existing principal practitioner first
|
||||
if (isset($validated['principal_practitioner_id'])) {
|
||||
$principalId = $validated['principal_practitioner_id'];
|
||||
$this->interventionPractitionerRepository->createAssignment($id, $principalId, 'principal');
|
||||
}
|
||||
|
||||
// Handle assistant practitioners
|
||||
if (isset($validated['assistant_practitioner_ids']) && is_array($validated['assistant_practitioner_ids'])) {
|
||||
foreach ($validated['assistant_practitioner_ids'] as $assistantId) {
|
||||
$this->interventionPractitionerRepository->createAssignment($id, $assistantId, 'assistant');
|
||||
}
|
||||
}
|
||||
|
||||
// Load the intervention with practitioners to return updated data
|
||||
$intervention->load('practitioners');
|
||||
$practitioners = $intervention->practitioners;
|
||||
|
||||
return response()->json([
|
||||
'data' => new InterventionResource($intervention),
|
||||
'message' => 'Assignment(s) créé(s) avec succès.',
|
||||
'practitioners_count' => $practitioners->count(),
|
||||
'practitioners' => $practitioners->map(function($p) {
|
||||
return [
|
||||
'id' => $p->id,
|
||||
'full_name' => $p->full_name ?? ($p->first_name . ' ' . $p->last_name),
|
||||
'role' => $p->pivot->role ?? 'unknown'
|
||||
];
|
||||
})->toArray()
|
||||
], Response::HTTP_OK);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création de l\'assignment.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unassign a practitioner from an intervention
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $interventionId
|
||||
* @param int $practitionerId
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function unassignPractitioner(Request $request, int $interventionId, int $practitionerId): JsonResponse
|
||||
{
|
||||
try {
|
||||
Log::info('Unassigning practitioner from intervention', [
|
||||
'intervention_id' => $interventionId,
|
||||
'practitioner_id' => $practitionerId
|
||||
]);
|
||||
|
||||
// Validate that the intervention exists
|
||||
$intervention = $this->interventionRepository->findById($interventionId);
|
||||
|
||||
if (!$intervention) {
|
||||
return response()->json([
|
||||
'message' => 'Intervention non trouvée.'
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check if the practitioner is actually assigned to this intervention
|
||||
$isAssigned = $this->interventionPractitionerRepository->isPractitionerAssigned($interventionId, $practitionerId);
|
||||
|
||||
if (!$isAssigned) {
|
||||
return response()->json([
|
||||
'message' => 'Le praticien n\'est pas assigné à cette intervention.'
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Remove the practitioner assignment
|
||||
$deleted = $this->interventionPractitionerRepository->removeAssignment($interventionId, $practitionerId);
|
||||
|
||||
if ($deleted > 0) {
|
||||
Log::info('Practitioner unassigned successfully', [
|
||||
'intervention_id' => $interventionId,
|
||||
'practitioner_id' => $practitionerId,
|
||||
'deleted_records' => $deleted
|
||||
]);
|
||||
|
||||
// Reload intervention with remaining practitioners
|
||||
$intervention->load('practitioners');
|
||||
$remainingPractitioners = $intervention->practitioners;
|
||||
|
||||
return response()->json([
|
||||
'data' => new InterventionResource($intervention),
|
||||
'message' => 'Praticien désassigné avec succès.',
|
||||
'remaining_practitioners_count' => $remainingPractitioners->count(),
|
||||
'remaining_practitioners' => $remainingPractitioners->map(function($p) {
|
||||
return [
|
||||
'id' => $p->id,
|
||||
'employee_name' => $p->employee->full_name ?? ($p->employee->first_name . ' ' . $p->employee->last_name),
|
||||
'role' => $p->pivot->role ?? 'unknown'
|
||||
];
|
||||
})->toArray()
|
||||
], Response::HTTP_OK);
|
||||
} else {
|
||||
Log::warning('No practitioner assignment found to delete', [
|
||||
'intervention_id' => $interventionId,
|
||||
'practitioner_id' => $practitionerId
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Aucun assignment de praticien trouvé à supprimer.'
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error unassigning practitioner from intervention: ' . $e->getMessage(), [
|
||||
'intervention_id' => $interventionId,
|
||||
'practitioner_id' => $practitionerId,
|
||||
'request_data' => $request->all(),
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la désassignation du praticien.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug endpoint to check practitioners in database
|
||||
*/
|
||||
public function debugPractitioners(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$intervention = $this->interventionRepository->findById($id);
|
||||
|
||||
// Direct database query
|
||||
$dbPractitioners = DB::table('intervention_practitioner')
|
||||
->where('intervention_id', $id)
|
||||
->get();
|
||||
|
||||
// Eager loaded practitioners
|
||||
$eagerPractitioners = $intervention->practitioners()->get();
|
||||
|
||||
return response()->json([
|
||||
'intervention_id' => $id,
|
||||
'database_records' => $dbPractitioners,
|
||||
'eager_loaded_count' => $eagerPractitioners->count(),
|
||||
'eager_loaded_data' => $eagerPractitioners->map(function($p) {
|
||||
return [
|
||||
'id' => $p->id,
|
||||
'full_name' => $p->full_name ?? ($p->first_name . ' ' . $p->last_name),
|
||||
'role' => $p->pivot->role ?? 'unknown'
|
||||
];
|
||||
})->toArray()
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error in debug practitioners: ' . $e->getMessage());
|
||||
return response()->json(['error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,320 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StorePractitionerDocumentRequest;
|
||||
use App\Http\Requests\UpdatePractitionerDocumentRequest;
|
||||
use App\Http\Resources\Employee\PractitionerDocumentResource;
|
||||
use App\Http\Resources\Employee\PractitionerDocumentCollection;
|
||||
use App\Repositories\PractitionerDocumentRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PractitionerDocumentController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PractitionerDocumentRepositoryInterface $practitionerDocumentRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of practitioner documents.
|
||||
*/
|
||||
public function index(Request $request): PractitionerDocumentCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$filters = [
|
||||
'search' => $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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,349 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreProductCategoryRequest;
|
||||
use App\Http\Requests\UpdateProductCategoryRequest;
|
||||
use App\Http\Resources\ProductCategory\ProductCategoryResource;
|
||||
use App\Http\Resources\ProductCategory\ProductCategoryCollection;
|
||||
use App\Repositories\ProductCategoryRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProductCategoryController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProductCategoryRepositoryInterface $productCategoryRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of product categories.
|
||||
*/
|
||||
public function index(Request $request): ProductCategoryCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$filters = [
|
||||
'search' => $request->get('search'),
|
||||
'active' => $request->get('active'),
|
||||
'parent_id' => $request->get('parent_id'),
|
||||
'sort_by' => $request->get('sort_by', 'name'),
|
||||
'sort_direction' => $request->get('sort_direction', 'asc'),
|
||||
];
|
||||
|
||||
// Remove null filters
|
||||
$filters = array_filter($filters, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
|
||||
$categories = $this->productCategoryRepository->paginate($perPage, $filters);
|
||||
|
||||
return new ProductCategoryCollection($categories);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching product categories: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des catégories de produits.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created product category.
|
||||
*/
|
||||
public function store(StoreProductCategoryRequest $request): ProductCategoryResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$category = $this->productCategoryRepository->create($request->validated());
|
||||
return new ProductCategoryResource($category);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating product category: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création de la catégorie.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified product category.
|
||||
*/
|
||||
public function show(string $id): ProductCategoryResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$category = $this->productCategoryRepository->find($id);
|
||||
|
||||
if (!$category) {
|
||||
return response()->json([
|
||||
'message' => 'Catégorie non trouvée.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new ProductCategoryResource($category);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching product category: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'category_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération de la catégorie.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified product category.
|
||||
*/
|
||||
public function update(UpdateProductCategoryRequest $request, string $id): ProductCategoryResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->productCategoryRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Catégorie non trouvée ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$category = $this->productCategoryRepository->find($id);
|
||||
return new ProductCategoryResource($category);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating product category: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'category_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour de la catégorie.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified product category.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
// Check if category can be deleted
|
||||
if (!$this->productCategoryRepository->canDelete($id)) {
|
||||
return response()->json([
|
||||
'message' => 'Impossible de supprimer cette catégorie. Elle peut avoir des sous-catégories ou des produits associés.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$deleted = $this->productCategoryRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Catégorie non trouvée ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Catégorie supprimée avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting product category: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'category_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression de la catégorie.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active categories only
|
||||
*/
|
||||
public function active(): ProductCategoryCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$categories = $this->productCategoryRepository->getActive();
|
||||
return new ProductCategoryCollection(collect(['data' => $categories]));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching active product categories: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des catégories actives.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get root categories (no parent)
|
||||
*/
|
||||
public function roots(): ProductCategoryCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$categories = $this->productCategoryRepository->getRoots();
|
||||
return new ProductCategoryCollection(collect(['data' => $categories]));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching root product categories: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des catégories racine.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get categories with their children (hierarchical structure)
|
||||
*/
|
||||
public function hierarchical(): ProductCategoryCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$categories = $this->productCategoryRepository->getWithChildren();
|
||||
return new ProductCategoryCollection(collect(['data' => $categories]));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching hierarchical product categories: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération de la structure hiérarchique.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search categories by name, code or description
|
||||
*/
|
||||
public function search(Request $request): ProductCategoryCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$term = $request->get('term', '');
|
||||
$perPage = $request->get('per_page', 15);
|
||||
|
||||
if (empty($term)) {
|
||||
return response()->json([
|
||||
'message' => 'Le paramètre "term" est requis pour la recherche.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$categories = $this->productCategoryRepository->search($term, $perPage);
|
||||
|
||||
return new ProductCategoryCollection($categories);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error searching product categories: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'search_term' => $term,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recherche des catégories.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get category statistics
|
||||
*/
|
||||
public function statistics(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$stats = $this->productCategoryRepository->getStatistics();
|
||||
|
||||
return response()->json([
|
||||
'data' => $stats,
|
||||
'message' => 'Statistiques des catégories récupérées avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching product category 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle category active status
|
||||
*/
|
||||
public function toggleActive(Request $request, string $id): ProductCategoryResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'active' => 'required|boolean',
|
||||
]);
|
||||
|
||||
$category = $this->productCategoryRepository->find($id);
|
||||
|
||||
if (!$category) {
|
||||
return response()->json([
|
||||
'message' => 'Catégorie non trouvée.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$updated = $this->productCategoryRepository->update($id, [
|
||||
'active' => $request->boolean('active')
|
||||
]);
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Échec de la mise à jour du statut.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$category = $this->productCategoryRepository->find($id);
|
||||
return new ProductCategoryResource($category);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error toggling product category status: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'category_id' => $id,
|
||||
'data' => $request->all(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du statut.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
375
thanasoft-back/app/Http/Controllers/Api/ProductController.php
Normal file
375
thanasoft-back/app/Http/Controllers/Api/ProductController.php
Normal file
@ -0,0 +1,375 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreProductRequest;
|
||||
use App\Http\Requests\UpdateProductRequest;
|
||||
use App\Http\Resources\Product\ProductResource;
|
||||
use App\Http\Resources\Product\ProductCollection;
|
||||
use App\Repositories\ProductRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProductController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProductRepositoryInterface $productRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of products.
|
||||
*/
|
||||
public function index(Request $request): ProductCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$filters = [
|
||||
'search' => $request->get('search'),
|
||||
'categorie' => $request->get('categorie_id'),
|
||||
'fournisseur_id' => $request->get('fournisseur_id'),
|
||||
'low_stock' => $request->get('low_stock'),
|
||||
'expiring_soon' => $request->get('expiring_soon'),
|
||||
'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 !== '';
|
||||
});
|
||||
|
||||
$products = $this->productRepository->paginate($perPage, $filters);
|
||||
|
||||
return new ProductCollection($products);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching products: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des produits.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created product.
|
||||
*/
|
||||
public function store(StoreProductRequest $request): ProductResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$validatedData = $request->validated();
|
||||
|
||||
// Handle image upload
|
||||
if ($request->hasFile('image')) {
|
||||
// Create product without image first
|
||||
$product = $this->productRepository->create($validatedData);
|
||||
|
||||
// Upload and attach image
|
||||
$imagePath = $product->uploadImage($request->file('image'));
|
||||
|
||||
// Refresh product to get updated data
|
||||
$product = $this->productRepository->find($product->id);
|
||||
} else {
|
||||
$product = $this->productRepository->create($validatedData);
|
||||
}
|
||||
|
||||
return new ProductResource($product);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating product: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du produit.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified product.
|
||||
*/
|
||||
public function show(string $id): ProductResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$product = $this->productRepository->find($id);
|
||||
|
||||
if (!$product) {
|
||||
return response()->json([
|
||||
'message' => 'Produit non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new ProductResource($product);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching product: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'product_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du produit.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search products by name.
|
||||
*/
|
||||
public function searchBy(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$name = $request->get('name', '');
|
||||
$exact = $request->boolean('exact', false);
|
||||
|
||||
if (empty($name)) {
|
||||
return response()->json([
|
||||
'message' => 'Le paramètre "name" est requis.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$products = $this->productRepository->searchByName($name, 15, $exact);
|
||||
|
||||
return response()->json([
|
||||
'data' => $products,
|
||||
'count' => $products->count(),
|
||||
'message' => $products->count() > 0
|
||||
? 'Produits trouvés avec succès.'
|
||||
: 'Aucun produit trouvé.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error searching products by name: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'search_term' => $name,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recherche des produits.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get products with low stock.
|
||||
*/
|
||||
public function lowStock(Request $request): ProductCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$products = $this->productRepository->getLowStockProducts($perPage);
|
||||
|
||||
return new ProductCollection($products);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching low stock products: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des produits à stock faible.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get products by category.
|
||||
*/
|
||||
public function byCategory(Request $request): ProductCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$categoryId = $request->get('category_id');
|
||||
$perPage = $request->get('per_page', 15);
|
||||
|
||||
if (empty($categoryId)) {
|
||||
return response()->json([
|
||||
'message' => 'Le paramètre "category_id" est requis.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$products = $this->productRepository->getByCategory($categoryId, $perPage);
|
||||
|
||||
return new ProductCollection($products);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching products by category: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'category_id' => $categoryId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des produits par catégorie.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get products statistics.
|
||||
*/
|
||||
public function statistics(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$stats = $this->productRepository->getStatistics();
|
||||
|
||||
return response()->json([
|
||||
'data' => $stats,
|
||||
'message' => 'Statistiques des produits récupérées avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching product 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified product.
|
||||
*/
|
||||
public function update(UpdateProductRequest $request, string $id): ProductResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$validatedData = $request->validated();
|
||||
$product = $this->productRepository->find($id);
|
||||
|
||||
if (!$product) {
|
||||
return response()->json([
|
||||
'message' => 'Produit non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
// Handle image upload/removal
|
||||
if ($request->boolean('remove_image')) {
|
||||
// Remove existing image
|
||||
$product->deleteImage();
|
||||
} elseif ($request->hasFile('image')) {
|
||||
// Upload new image
|
||||
$product->uploadImage($request->file('image'));
|
||||
}
|
||||
|
||||
// Remove image-related fields from validated data before updating other fields
|
||||
unset($validatedData['image'], $validatedData['remove_image']);
|
||||
|
||||
// Update other product fields
|
||||
$updated = $this->productRepository->update($id, $validatedData);
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Produit non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$product = $this->productRepository->find($id);
|
||||
return new ProductResource($product);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating product: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'product_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du produit.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified product.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->productRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Produit non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Produit supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting product: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'product_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du produit.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update stock quantity for a product.
|
||||
*/
|
||||
public function updateStock(Request $request, string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'stock_actuel' => 'required|numeric|min:0',
|
||||
]);
|
||||
|
||||
$updated = $this->productRepository->updateStock((int) $id, $request->stock_actuel);
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Produit non trouvé ou échec de la mise à jour du stock.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$product = $this->productRepository->find($id);
|
||||
|
||||
return response()->json([
|
||||
'data' => new ProductResource($product),
|
||||
'message' => 'Stock mis à jour avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating product stock: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'product_id' => $id,
|
||||
'stock_data' => $request->all(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du stock.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,357 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreThanatopractitionerRequest;
|
||||
use App\Http\Requests\UpdateThanatopractitionerRequest;
|
||||
use App\Http\Resources\Employee\ThanatopractitionerResource;
|
||||
use App\Http\Resources\Employee\ThanatopractitionerCollection;
|
||||
use App\Repositories\ThanatopractitionerRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ThanatopractitionerController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ThanatopractitionerRepositoryInterface $thanatopractitionerRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of thanatopractitioners (paginated).
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = (int) $request->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->findById((int) $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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search thanatopractitioners by employee name.
|
||||
*/
|
||||
public function searchByEmployeeName(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$query = $request->get('query', '');
|
||||
|
||||
if (strlen($query) < 2) {
|
||||
return response()->json([
|
||||
'data' => [],
|
||||
'message' => 'Veuillez entrer au moins 2 caractères pour la recherche.',
|
||||
], 200);
|
||||
}
|
||||
|
||||
$thanatopractitioners = $this->thanatopractitionerRepository->searchByEmployeeName($query);
|
||||
|
||||
return response()->json([
|
||||
'data' => new ThanatopractitionerCollection($thanatopractitioners),
|
||||
'message' => 'Recherche effectuée avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error searching thanatopractitioners by employee name: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'query' => $request->get('query'),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recherche.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified thanatopractitioner.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
70
thanasoft-back/app/Http/Requests/ClientCategoryRequest.php
Normal file
70
thanasoft-back/app/Http/Requests/ClientCategoryRequest.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ClientCategoryRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$categoryId = $this->route('client_category')?->id;
|
||||
|
||||
return [
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
Rule::unique('client_categories')->ignore($categoryId)
|
||||
],
|
||||
'slug' => [
|
||||
'nullable',
|
||||
'string',
|
||||
'max:255',
|
||||
'alpha_dash',
|
||||
Rule::unique('client_categories')->ignore($categoryId)
|
||||
],
|
||||
'description' => 'nullable|string|max:1000',
|
||||
'is_active' => 'boolean',
|
||||
'sort_order' => 'nullable|integer|min:0',
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'category name',
|
||||
'slug' => 'URL slug',
|
||||
];
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
// Generate slug from name if not provided and name exists
|
||||
if (!$this->slug && $this->name) {
|
||||
$this->merge([
|
||||
'slug' => \Str::slug($this->name),
|
||||
]);
|
||||
}
|
||||
|
||||
// Ensure boolean values are properly cast
|
||||
if ($this->has('is_active')) {
|
||||
$this->merge([
|
||||
'is_active' => filter_var($this->is_active, FILTER_VALIDATE_BOOLEAN),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
40
thanasoft-back/app/Http/Requests/StoreClientGroupRequest.php
Normal file
40
thanasoft-back/app/Http/Requests/StoreClientGroupRequest.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreClientGroupRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:191|unique:client_groups,name',
|
||||
'description' => 'nullable|string',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => 'Le nom du groupe est obligatoire.',
|
||||
'name.string' => 'Le nom du groupe doit être une chaîne de caractères.',
|
||||
'name.max' => 'Le nom du groupe ne peut pas dépasser 191 caractères.',
|
||||
'name.unique' => 'Un groupe avec ce nom existe déjà.',
|
||||
'description.string' => 'La description doit être une chaîne de caractères.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreClientLocationRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_id' => 'required|exists:clients,id',
|
||||
'name' => 'nullable|string|max:191',
|
||||
'address_line1' => 'nullable|string|max:255',
|
||||
'address_line2' => 'nullable|string|max:255',
|
||||
'postal_code' => 'nullable|string|max:20',
|
||||
'city' => 'nullable|string|max:191',
|
||||
'country_code' => 'nullable|string|size:2',
|
||||
'gps_lat' => 'nullable|numeric|between:-90,90',
|
||||
'gps_lng' => 'nullable|numeric|between:-180,180',
|
||||
'is_default' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_id.required' => 'Le client est obligatoire.',
|
||||
'client_id.exists' => 'Le client sélectionné n\'existe pas.',
|
||||
'name.max' => 'Le nom ne peut pas dépasser 191 caractères.',
|
||||
'address_line1.max' => 'L\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'address_line2.max' => 'Le complément d\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'postal_code.max' => 'Le code postal ne peut pas dépasser 20 caractères.',
|
||||
'city.max' => 'La ville ne peut pas dépasser 191 caractères.',
|
||||
'country_code.size' => 'Le code pays doit contenir 2 caractères.',
|
||||
'gps_lat.numeric' => 'La latitude doit être un nombre.',
|
||||
'gps_lat.between' => 'La latitude doit être comprise entre -90 et 90.',
|
||||
'gps_lng.numeric' => 'La longitude doit être un nombre.',
|
||||
'gps_lng.between' => 'La longitude doit être comprise entre -180 et 180.',
|
||||
'is_default.boolean' => 'Le statut par défaut doit être vrai ou faux.',
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
if (empty($this->address_line1) && empty($this->postal_code) && empty($this->city)) {
|
||||
$validator->errors()->add(
|
||||
'general',
|
||||
'Au moins un champ d\'adresse (adresse, code postal ou ville) doit être renseigné.'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
69
thanasoft-back/app/Http/Requests/StoreClientRequest.php
Normal file
69
thanasoft-back/app/Http/Requests/StoreClientRequest.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreClientRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_category_id' => 'nullable',
|
||||
'name' => 'required|string|max:255',
|
||||
'vat_number' => 'nullable|string|max:32',
|
||||
'siret' => 'nullable|string|max:20',
|
||||
'email' => 'nullable|email|max:191',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'billing_address_line1' => 'nullable|string|max:255',
|
||||
'billing_address_line2' => 'nullable|string|max:255',
|
||||
'billing_postal_code' => 'nullable|string|max:20',
|
||||
'billing_city' => 'nullable|string|max:191',
|
||||
'billing_country_code' => 'nullable|string|size:2',
|
||||
'group_id' => 'nullable|exists:client_groups,id',
|
||||
'notes' => 'nullable|string',
|
||||
'is_active' => 'boolean',
|
||||
'default_tva_rate_id' => 'nullable|exists:tva_rates,id',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'company_id.required' => 'La société est obligatoire.',
|
||||
'company_id.exists' => 'La société sélectionnée n\'existe pas.',
|
||||
'type.required' => 'Le type de client est obligatoire.',
|
||||
'type.in' => 'Le type de client sélectionné est invalide.',
|
||||
'name.required' => 'Le nom du client est obligatoire.',
|
||||
'name.string' => 'Le nom du client doit être une chaîne de caractères.',
|
||||
'name.max' => 'Le nom du client ne peut pas dépasser 255 caractères.',
|
||||
'vat_number.max' => 'Le numéro de TVA ne peut pas dépasser 32 caractères.',
|
||||
'siret.max' => 'Le SIRET ne peut pas dépasser 20 caractères.',
|
||||
'email.email' => 'L\'adresse email doit être valide.',
|
||||
'email.max' => 'L\'adresse email ne peut pas dépasser 191 caractères.',
|
||||
'phone.max' => 'Le téléphone ne peut pas dépasser 50 caractères.',
|
||||
'billing_address_line1.max' => 'L\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'billing_address_line2.max' => 'Le complément d\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'billing_postal_code.max' => 'Le code postal ne peut pas dépasser 20 caractères.',
|
||||
'billing_city.max' => 'La ville ne peut pas dépasser 191 caractères.',
|
||||
'billing_country_code.size' => 'Le code pays doit contenir 2 caractères.',
|
||||
'group_id.exists' => 'Le groupe de clients sélectionné n\'existe pas.',
|
||||
'is_active.boolean' => 'Le statut actif doit être vrai ou faux.',
|
||||
'default_tva_rate_id.exists' => 'Le taux de TVA sélectionné n\'existe pas.',
|
||||
];
|
||||
}
|
||||
}
|
||||
70
thanasoft-back/app/Http/Requests/StoreContactRequest.php
Normal file
70
thanasoft-back/app/Http/Requests/StoreContactRequest.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreContactRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_id' => 'nullable|exists:clients,id',
|
||||
'fournisseur_id' => 'nullable|exists:fournisseurs,id',
|
||||
'first_name' => 'nullable|string|max:191',
|
||||
'last_name' => 'nullable|string|max:191',
|
||||
'email' => 'nullable|email|max:191',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'role' => 'nullable|string|max:191',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_id.exists' => 'Le client sélectionné n\'existe pas.',
|
||||
'fournisseur_id.exists' => 'Le fournisseur sélectionné n\'existe pas.',
|
||||
'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 191 caractères.',
|
||||
'last_name.string' => 'Le nom doit être une chaîne de caractères.',
|
||||
'last_name.max' => 'Le nom ne peut pas dépasser 191 caractères.',
|
||||
'email.email' => 'L\'adresse email doit être valide.',
|
||||
'email.max' => 'L\'adresse email ne peut pas dépasser 191 caractères.',
|
||||
'phone.max' => 'Le téléphone ne peut pas dépasser 50 caractères.',
|
||||
'role.max' => 'Le rôle ne peut pas dépasser 191 caractères.',
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
// At least one of client_id or fournisseur_id must be provided
|
||||
if (empty($this->client_id) && empty($this->fournisseur_id)) {
|
||||
$validator->errors()->add(
|
||||
'general',
|
||||
'Le contact doit être associé à un client ou un fournisseur.'
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($this->first_name) && empty($this->last_name) && empty($this->email) && empty($this->phone)) {
|
||||
$validator->errors()->add(
|
||||
'general',
|
||||
'Au moins un champ (prénom, nom, email ou téléphone) doit être renseigné.'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreDeceasedDocumentRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Add your authorization logic here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'deceased_id' => 'required|exists:deceased,id',
|
||||
'doc_type' => 'required|string|max:191',
|
||||
'file_id' => 'nullable|exists:files,id',
|
||||
'generated_at' => 'nullable|date',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'deceased_id.required' => 'Le défunt est obligatoire.',
|
||||
'deceased_id.exists' => 'Le défunt 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.',
|
||||
'generated_at.date' => 'La date de génération doit être une date valide.',
|
||||
];
|
||||
}
|
||||
}
|
||||
49
thanasoft-back/app/Http/Requests/StoreDeceasedRequest.php
Normal file
49
thanasoft-back/app/Http/Requests/StoreDeceasedRequest.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreDeceasedRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
// Add authorization logic if needed
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'last_name' => ['required', 'string', 'max:191'],
|
||||
'first_name' => ['nullable', 'string', 'max:191'],
|
||||
'birth_date' => ['nullable', 'date'],
|
||||
'death_date' => ['nullable', 'date', 'after_or_equal:birth_date'],
|
||||
'place_of_death' => ['nullable', 'string', 'max:255'],
|
||||
'notes' => ['nullable', 'string']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom error messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'last_name.required' => 'Le nom de famille est obligatoire.',
|
||||
'last_name.max' => 'Le nom de famille ne peut pas dépasser 191 caractères.',
|
||||
'first_name.max' => 'Le prénom ne peut pas dépasser 191 caractères.',
|
||||
'death_date.after_or_equal' => 'La date de décès doit être postérieure ou égale à la date de naissance.',
|
||||
'place_of_death.max' => 'Le lieu de décès ne peut pas dépasser 255 caractères.'
|
||||
];
|
||||
}
|
||||
}
|
||||
59
thanasoft-back/app/Http/Requests/StoreEmployeeRequest.php
Normal file
59
thanasoft-back/app/Http/Requests/StoreEmployeeRequest.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreEmployeeRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Add your authorization logic here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
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<string, string>
|
||||
*/
|
||||
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.',
|
||||
];
|
||||
}
|
||||
}
|
||||
109
thanasoft-back/app/Http/Requests/StoreFileRequest.php
Normal file
109
thanasoft-back/app/Http/Requests/StoreFileRequest.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class StoreFileRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return Auth::check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'file' => 'required|file|max:10240', // Max 10MB
|
||||
'file_name' => 'nullable|string|max:255',
|
||||
'category' => 'required|string|in:devis,facture,contrat,document,image,autre',
|
||||
'client_id' => 'nullable|integer|exists:clients,id',
|
||||
'subcategory' => 'nullable|string|max:100',
|
||||
'description' => 'nullable|string|max:500',
|
||||
'tags' => 'nullable|array|max:10',
|
||||
'tags.*' => 'string|max:50',
|
||||
'is_public' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'file' => 'fichier',
|
||||
'file_name' => 'nom du fichier',
|
||||
'category' => 'catégorie',
|
||||
'client_id' => 'client',
|
||||
'subcategory' => 'sous-catégorie',
|
||||
'description' => 'description',
|
||||
'tags' => 'étiquettes',
|
||||
'is_public' => 'visibilité publique',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'file.required' => 'Le fichier est obligatoire.',
|
||||
'file.file' => 'Le fichier doit être un fichier valide.',
|
||||
'file.max' => 'Le fichier ne peut pas dépasser 10 MB.',
|
||||
'file_name.string' => 'Le nom du fichier doit être une chaîne de caractères.',
|
||||
'file_name.max' => 'Le nom du fichier ne peut pas dépasser 255 caractères.',
|
||||
'category.required' => 'La catégorie est obligatoire.',
|
||||
'category.in' => 'La catégorie sélectionnée n\'est pas valide.',
|
||||
'client_id.exists' => 'Le client sélectionné n\'existe pas.',
|
||||
'subcategory.string' => 'La sous-catégorie doit être une chaîne de caractères.',
|
||||
'subcategory.max' => 'La sous-catégorie ne peut pas dépasser 100 caractères.',
|
||||
'description.string' => 'La description doit être une chaîne de caractères.',
|
||||
'description.max' => 'La description ne peut pas dépasser 500 caractères.',
|
||||
'tags.array' => 'Les étiquettes doivent être un tableau.',
|
||||
'tags.max' => 'Vous ne pouvez pas ajouter plus de 10 étiquettes.',
|
||||
'tags.*.string' => 'Chaque étiquette doit être une chaîne de caractères.',
|
||||
'tags.*.max' => 'Chaque étiquette ne peut pas dépasser 50 caractères.',
|
||||
'is_public.boolean' => 'La visibilité publique doit être vrai ou faux.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
// Set default values
|
||||
$this->merge([
|
||||
'uploaded_by' => $this->user()->id,
|
||||
'is_public' => $this->boolean('is_public', false),
|
||||
'category' => $this->input('category', 'autre'), // Default category to 'autre' if not provided
|
||||
]);
|
||||
|
||||
// If no file_name provided, use the original file name
|
||||
if (!$this->has('file_name') && $this->hasFile('file')) {
|
||||
$this->merge([
|
||||
'file_name' => $this->file->getClientOriginalName(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Extract file information
|
||||
if ($this->hasFile('file')) {
|
||||
$file = $this->file;
|
||||
$this->merge([
|
||||
'mime_type' => $file->getMimeType(),
|
||||
'size_bytes' => $file->getSize(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
59
thanasoft-back/app/Http/Requests/StoreFournisseurRequest.php
Normal file
59
thanasoft-back/app/Http/Requests/StoreFournisseurRequest.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreFournisseurRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:255',
|
||||
'vat_number' => 'nullable|string|max:32',
|
||||
'siret' => 'nullable|string|max:20',
|
||||
'email' => 'nullable|email|max:191',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'billing_address_line1' => 'nullable|string|max:255',
|
||||
'billing_address_line2' => 'nullable|string|max:255',
|
||||
'billing_postal_code' => 'nullable|string|max:20',
|
||||
'billing_city' => 'nullable|string|max:191',
|
||||
'billing_country_code' => 'nullable|string|size:2',
|
||||
'notes' => 'nullable|string',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => 'Le nom du fournisseur est obligatoire.',
|
||||
'name.string' => 'Le nom du fournisseur doit être une chaîne de caractères.',
|
||||
'name.max' => 'Le nom du fournisseur ne peut pas dépasser 255 caractères.',
|
||||
'vat_number.max' => 'Le numéro de TVA ne peut pas dépasser 32 caractères.',
|
||||
'siret.max' => 'Le SIRET ne peut pas dépasser 20 caractères.',
|
||||
'email.email' => 'L\'adresse email doit être valide.',
|
||||
'email.max' => 'L\'adresse email ne peut pas dépasser 191 caractères.',
|
||||
'phone.max' => 'Le téléphone ne peut pas dépasser 50 caractères.',
|
||||
'billing_address_line1.max' => 'L\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'billing_address_line2.max' => 'Le complément d\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'billing_postal_code.max' => 'Le code postal ne peut pas dépasser 20 caractères.',
|
||||
'billing_city.max' => 'La ville ne peut pas dépasser 191 caractères.',
|
||||
'billing_country_code.size' => 'Le code pays doit contenir 2 caractères.',
|
||||
'is_active.boolean' => 'Le statut actif doit être vrai ou faux.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreInterventionRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
// Add authorization logic if needed
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_id' => ['required', 'exists:clients,id'],
|
||||
'deceased_id' => ['nullable', 'exists:deceased,id'],
|
||||
'order_giver' => ['nullable', 'string', 'max:255'],
|
||||
'location_id' => ['nullable', 'exists:client_locations,id'],
|
||||
'type' => ['required', Rule::in([
|
||||
'thanatopraxie',
|
||||
'toilette_mortuaire',
|
||||
'exhumation',
|
||||
'retrait_pacemaker',
|
||||
'retrait_bijoux',
|
||||
'autre'
|
||||
])],
|
||||
'scheduled_at' => ['nullable', 'date_format:Y-m-d H:i:s'],
|
||||
'duration_min' => ['nullable', 'integer', 'min:0'],
|
||||
'status' => ['sometimes', Rule::in([
|
||||
'demande',
|
||||
'planifie',
|
||||
'en_cours',
|
||||
'termine',
|
||||
'annule'
|
||||
])],
|
||||
'practitioners' => ['nullable', 'array'],
|
||||
'practitioners.*' => ['exists:thanatopractitioners,id'],
|
||||
'principal_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'],
|
||||
'assistant_practitioner_ids' => ['nullable', 'array'],
|
||||
'assistant_practitioner_ids.*' => ['exists:thanatopractitioners,id'],
|
||||
'notes' => ['nullable', 'string'],
|
||||
'created_by' => ['nullable', 'exists:users,id']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom error messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_id.required' => 'Le client est obligatoire.',
|
||||
'client_id.exists' => 'Le client sélectionné est invalide.',
|
||||
'deceased_id.exists' => 'Le défunt sélectionné est invalide.',
|
||||
'order_giver.max' => 'Le donneur d\'ordre ne peut pas dépasser 255 caractères.',
|
||||
'location_id.exists' => 'Le lieu sélectionné est invalide.',
|
||||
'type.required' => 'Le type d\'intervention est obligatoire.',
|
||||
'type.in' => 'Le type d\'intervention est invalide.',
|
||||
'scheduled_at.date_format' => 'Le format de la date programmée est invalide.',
|
||||
'duration_min.integer' => 'La durée doit être un nombre entier.',
|
||||
'duration_min.min' => 'La durée ne peut pas être négative.',
|
||||
'status.in' => 'Le statut de l\'intervention est invalide.',
|
||||
'practitioners.array' => 'Les praticiens doivent être un tableau.',
|
||||
'practitioners.*.exists' => 'Un des praticiens sélectionnés est invalide.',
|
||||
'principal_practitioner_id.exists' => 'Le praticien principal sélectionné est invalide.',
|
||||
'assistant_practitioner_ids.array' => 'Les praticiens assistants doivent être un tableau.',
|
||||
'assistant_practitioner_ids.*.exists' => 'Un des praticiens assistants est invalide.',
|
||||
'created_by.exists' => 'L\'utilisateur créateur est invalide.'
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreInterventionWithAllDataRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'deceased' => 'required|array',
|
||||
'deceased.last_name' => ['required', 'string', 'max:191'],
|
||||
'deceased.first_name' => ['nullable', 'string', 'max:191'],
|
||||
'deceased.birth_date' => ['nullable', 'date'],
|
||||
'deceased.death_date' => ['nullable', 'date', 'after_or_equal:deceased.birth_date'],
|
||||
'deceased.place_of_death' => ['nullable', 'string', 'max:255'],
|
||||
'deceased.notes' => ['nullable', 'string'],
|
||||
|
||||
'client' => 'required|array',
|
||||
'client.name' => ['required', 'string', 'max:255'],
|
||||
'client.vat_number' => ['nullable', 'string', 'max:32'],
|
||||
'client.siret' => ['nullable', 'string', 'max:20'],
|
||||
'client.email' => ['nullable', 'email', 'max:191'],
|
||||
'client.phone' => ['nullable', 'string', 'max:50'],
|
||||
'client.billing_address_line1' => ['nullable', 'string', 'max:255'],
|
||||
'client.billing_address_line2' => ['nullable', 'string', 'max:255'],
|
||||
'client.billing_postal_code' => ['nullable', 'string', 'max:20'],
|
||||
'client.billing_city' => ['nullable', 'string', 'max:191'],
|
||||
'client.billing_country_code' => ['nullable', 'string', 'size:2'],
|
||||
'client.notes' => ['nullable', 'string'],
|
||||
|
||||
'contact' => 'nullable|array',
|
||||
'contact.first_name' => ['nullable', 'string', 'max:191'],
|
||||
'contact.last_name' => ['nullable', 'string', 'max:191'],
|
||||
'contact.email' => ['nullable', 'email', 'max:191'],
|
||||
'contact.phone' => ['nullable', 'string', 'max:50'],
|
||||
'contact.role' => ['nullable', 'string', 'max:191'],
|
||||
|
||||
'location' => 'nullable|array',
|
||||
'location.name' => ['nullable', 'string', 'max:255'],
|
||||
'location.address' => ['nullable', 'string', 'max:255'],
|
||||
'location.city' => ['nullable', 'string', 'max:191'],
|
||||
'location.postal_code' => ['nullable', 'string', 'max:20'],
|
||||
'location.country_code' => ['nullable', 'string', 'size:2'],
|
||||
'location.access_instructions' => ['nullable', 'string'],
|
||||
'location.notes' => ['nullable', 'string'],
|
||||
|
||||
'documents' => 'nullable|array',
|
||||
'documents.*.file' => ['required', 'file'],
|
||||
'documents.*.name' => ['required', 'string', 'max:255'],
|
||||
'documents.*.description' => ['nullable', 'string'],
|
||||
|
||||
'intervention' => 'required|array',
|
||||
'intervention.type' => ['required', Rule::in([
|
||||
'thanatopraxie',
|
||||
'toilette_mortuaire',
|
||||
'exhumation',
|
||||
'retrait_pacemaker',
|
||||
'retrait_bijoux',
|
||||
'autre'
|
||||
])],
|
||||
'intervention.scheduled_at' => ['nullable', 'date_format:Y-m-d H:i:s'],
|
||||
'intervention.duration_min' => ['nullable', 'integer', 'min:0'],
|
||||
'intervention.status' => ['sometimes', Rule::in([
|
||||
'demande',
|
||||
'planifie',
|
||||
'en_cours',
|
||||
'termine',
|
||||
'annule'
|
||||
])],
|
||||
'intervention.practitioners' => ['nullable', 'array'],
|
||||
'intervention.practitioners.*' => ['exists:thanatopractitioners,id'],
|
||||
'intervention.principal_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'],
|
||||
'intervention.assistant_practitioner_ids' => ['nullable', 'array'],
|
||||
'intervention.assistant_practitioner_ids.*' => ['exists:thanatopractitioners,id'],
|
||||
'intervention.order_giver' => ['nullable', 'string', 'max:255'],
|
||||
'intervention.notes' => ['nullable', 'string'],
|
||||
'intervention.created_by' => ['nullable', 'exists:users,id']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom error messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
$messages = [
|
||||
'deceased.required' => 'Les informations du défunt sont obligatoires.',
|
||||
'deceased.last_name.required' => 'Le nom de famille du défunt est obligatoire.',
|
||||
'deceased.last_name.max' => 'Le nom de famille ne peut pas dépasser 191 caractères.',
|
||||
'deceased.first_name.max' => 'Le prénom ne peut pas dépasser 191 caractères.',
|
||||
'deceased.death_date.after_or_equal' => 'La date de décès doit être postérieure ou égale à la date de naissance.',
|
||||
'deceased.place_of_death.max' => 'Le lieu de décès ne peut pas dépasser 255 caractères.',
|
||||
|
||||
'client.required' => 'Les informations du client sont obligatoires.',
|
||||
'client.name.required' => 'Le nom du client est obligatoire.',
|
||||
'client.name.max' => 'Le nom du client ne peut pas dépasser 255 caractères.',
|
||||
'client.vat_number.max' => 'Le numéro de TVA ne peut pas dépasser 32 caractères.',
|
||||
'client.siret.max' => 'Le SIRET ne peut pas dépasser 20 caractères.',
|
||||
'client.email.email' => 'L\'adresse email doit être valide.',
|
||||
'client.email.max' => 'L\'adresse email ne peut pas dépasser 191 caractères.',
|
||||
'client.phone.max' => 'Le téléphone ne peut pas dépasser 50 caractères.',
|
||||
'client.billing_address_line1.max' => 'L\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'client.billing_postal_code.max' => 'Le code postal ne peut pas dépasser 20 caractères.',
|
||||
'client.billing_city.max' => 'La ville ne peut pas dépasser 191 caractères.',
|
||||
'client.billing_country_code.size' => 'Le code pays doit contenir 2 caractères.',
|
||||
'client.is_active.boolean' => 'Le statut actif doit être vrai ou faux.',
|
||||
|
||||
'contact.first_name.max' => 'Le prénom ne peut pas dépasser 191 caractères.',
|
||||
'contact.last_name.max' => 'Le nom ne peut pas dépasser 191 caractères.',
|
||||
'contact.email.email' => 'L\'adresse email doit être valide.',
|
||||
'contact.email.max' => 'L\'adresse email ne peut pas dépasser 191 caractères.',
|
||||
'contact.phone.max' => 'Le téléphone ne peut pas dépasser 50 caractères.',
|
||||
'contact.role.max' => 'Le rôle ne peut pas dépasser 191 caractères.',
|
||||
|
||||
'intervention.required' => 'Les informations de l\'intervention sont obligatoires.',
|
||||
'intervention.type.required' => 'Le type d\'intervention est obligatoire.',
|
||||
'intervention.type.in' => 'Le type d\'intervention est invalide.',
|
||||
'intervention.scheduled_at.date_format' => 'Le format de la date programmée est invalide.',
|
||||
'intervention.duration_min.integer' => 'La durée doit être un nombre entier.',
|
||||
'intervention.duration_min.min' => 'La durée ne peut pas être négative.',
|
||||
'intervention.status.in' => 'Le statut de l\'intervention est invalide.',
|
||||
'intervention.practitioners.array' => 'Les praticiens doivent être un tableau.',
|
||||
'intervention.practitioners.*.exists' => 'Un des praticiens sélectionnés est invalide.',
|
||||
'intervention.principal_practitioner_id.exists' => 'Le praticien principal sélectionné est invalide.',
|
||||
'intervention.assistant_practitioner_ids.array' => 'Les praticiens assistants doivent être un tableau.',
|
||||
'intervention.assistant_practitioner_ids.*.exists' => 'Un des praticiens assistants est invalide.',
|
||||
'intervention.order_giver.max' => 'Le donneur d\'ordre ne peut pas dépasser 255 caractères.',
|
||||
'intervention.created_by.exists' => 'L\'utilisateur créateur est invalide.'
|
||||
];
|
||||
|
||||
// Add document-specific messages
|
||||
$messages = array_merge($messages, [
|
||||
'documents.array' => 'Les documents doivent être un tableau.',
|
||||
'documents.*.file.required' => 'Chaque document doit avoir un fichier.',
|
||||
'documents.*.name.required' => 'Le nom du document est obligatoire.',
|
||||
'documents.*.name.max' => 'Le nom du document ne peut pas dépasser 255 caractères.',
|
||||
]);
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a failed validation attempt.
|
||||
*/
|
||||
protected function failedValidation(\Illuminate\Contracts\Validation\Validator $validator)
|
||||
{
|
||||
$errors = $validator->errors();
|
||||
|
||||
// Group errors by step for better UX
|
||||
$groupedErrors = [
|
||||
'deceased' => [],
|
||||
'client' => [],
|
||||
'contact' => [],
|
||||
'location' => [],
|
||||
'documents' => [],
|
||||
'intervention' => [],
|
||||
'global' => []
|
||||
];
|
||||
|
||||
foreach ($errors->messages() as $field => $messages) {
|
||||
if (str_starts_with($field, 'deceased.')) {
|
||||
$groupedErrors['deceased'] = array_merge($groupedErrors['deceased'], $messages);
|
||||
} elseif (str_starts_with($field, 'client.')) {
|
||||
$groupedErrors['client'] = array_merge($groupedErrors['client'], $messages);
|
||||
} elseif (str_starts_with($field, 'contact.')) {
|
||||
$groupedErrors['contact'] = array_merge($groupedErrors['contact'], $messages);
|
||||
} elseif (str_starts_with($field, 'location.')) {
|
||||
$groupedErrors['location'] = array_merge($groupedErrors['location'], $messages);
|
||||
} elseif (str_starts_with($field, 'documents.')) {
|
||||
$groupedErrors['documents'] = array_merge($groupedErrors['documents'], $messages);
|
||||
} elseif (str_starts_with($field, 'intervention.')) {
|
||||
$groupedErrors['intervention'] = array_merge($groupedErrors['intervention'], $messages);
|
||||
} else {
|
||||
$groupedErrors['global'] = array_merge($groupedErrors['global'], $messages);
|
||||
}
|
||||
}
|
||||
|
||||
parent::failedValidation($validator);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StorePractitionerDocumentRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Add your authorization logic here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
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<string, string>
|
||||
*/
|
||||
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.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreProductCategoryRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'parent_id' => 'nullable|exists:product_categories,id',
|
||||
'code' => 'required|string|max:64|unique:product_categories,code',
|
||||
'name' => 'required|string|max:191',
|
||||
'description' => 'nullable|string',
|
||||
'active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'parent_id.exists' => 'La catégorie parente sélectionnée n\'existe pas.',
|
||||
'code.required' => 'Le code de la catégorie est obligatoire.',
|
||||
'code.string' => 'Le code de la catégorie doit être une chaîne de caractères.',
|
||||
'code.max' => 'Le code de la catégorie ne peut pas dépasser 64 caractères.',
|
||||
'code.unique' => 'Ce code de catégorie existe déjà.',
|
||||
'name.required' => 'Le nom de la catégorie est obligatoire.',
|
||||
'name.string' => 'Le nom de la catégorie doit être une chaîne de caractères.',
|
||||
'name.max' => 'Le nom de la catégorie ne peut pas dépasser 191 caractères.',
|
||||
'description.string' => 'La description doit être une chaîne de caractères.',
|
||||
'active.boolean' => 'Le statut actif doit être un booléen.',
|
||||
];
|
||||
}
|
||||
}
|
||||
89
thanasoft-back/app/Http/Requests/StoreProductRequest.php
Normal file
89
thanasoft-back/app/Http/Requests/StoreProductRequest.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreProductRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'nom' => 'required|string|max:255',
|
||||
'reference' => 'required|string|max:100|unique:products,reference',
|
||||
'categorie_id' => 'required|exists:product_categories,id',
|
||||
'fabricant' => 'nullable|string|max:191',
|
||||
'stock_actuel' => 'required|numeric|min:0',
|
||||
'stock_minimum' => 'required|numeric|min:0',
|
||||
'unite' => 'required|string|max:50',
|
||||
'prix_unitaire' => 'required|numeric|min:0',
|
||||
'date_expiration' => 'nullable|date|after:today',
|
||||
'numero_lot' => 'nullable|string|max:100',
|
||||
'conditionnement_nom' => 'nullable|string|max:191',
|
||||
'conditionnement_quantite' => 'nullable|numeric|min:0',
|
||||
'conditionnement_unite' => 'nullable|string|max:50',
|
||||
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
|
||||
'remove_image' => 'nullable|boolean',
|
||||
'fiche_technique_url' => 'nullable|url|max:500',
|
||||
'fournisseur_id' => 'nullable|exists:fournisseurs,id',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'nom.required' => 'Le nom du produit est obligatoire.',
|
||||
'nom.string' => 'Le nom du produit doit être une chaîne de caractères.',
|
||||
'nom.max' => 'Le nom du produit ne peut pas dépasser 255 caractères.',
|
||||
'reference.required' => 'La référence du produit est obligatoire.',
|
||||
'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_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.',
|
||||
'stock_actuel.numeric' => 'Le stock actuel doit être un nombre.',
|
||||
'stock_actuel.min' => 'Le stock actuel doit être supérieur ou égal à 0.',
|
||||
'stock_minimum.required' => 'Le stock minimum est obligatoire.',
|
||||
'stock_minimum.numeric' => 'Le stock minimum doit être un nombre.',
|
||||
'stock_minimum.min' => 'Le stock minimum doit être supérieur ou égal à 0.',
|
||||
'unite.required' => 'L\'unité est obligatoire.',
|
||||
'unite.string' => 'L\'unité doit être une chaîne de caractères.',
|
||||
'unite.max' => 'L\'unité ne peut pas dépasser 50 caractères.',
|
||||
'prix_unitaire.required' => 'Le prix unitaire est obligatoire.',
|
||||
'prix_unitaire.numeric' => 'Le prix unitaire doit être un nombre.',
|
||||
'prix_unitaire.min' => 'Le prix unitaire doit être supérieur ou égal à 0.',
|
||||
'date_expiration.date' => 'La date d\'expiration doit être une date valide.',
|
||||
'date_expiration.after' => 'La date d\'expiration doit être postérieure à aujourd\'hui.',
|
||||
'numero_lot.string' => 'Le numéro de lot doit être une chaîne de caractères.',
|
||||
'numero_lot.max' => 'Le numéro de lot ne peut pas dépasser 100 caractères.',
|
||||
'conditionnement_nom.string' => 'Le nom du conditionnement doit être une chaîne de caractères.',
|
||||
'conditionnement_nom.max' => 'Le nom du conditionnement ne peut pas dépasser 191 caractères.',
|
||||
'conditionnement_quantite.numeric' => 'La quantité du conditionnement doit être un nombre.',
|
||||
'conditionnement_quantite.min' => 'La quantité du conditionnement doit être supérieure ou égal à 0.',
|
||||
'conditionnement_unite.string' => 'L\'unité du conditionnement doit être une chaîne de caractères.',
|
||||
'conditionnement_unite.max' => 'L\'unité du conditionnement ne peut pas dépasser 50 caractères.',
|
||||
'image.image' => 'Le fichier doit être une image valide.',
|
||||
'image.mimes' => 'L\'image doit être de type: jpeg, png, jpg, gif ou svg.',
|
||||
'image.max' => 'L\'image ne peut pas dépasser 2MB.',
|
||||
'fiche_technique_url.url' => 'L\'URL de la fiche technique doit être une URL valide.',
|
||||
'fiche_technique_url.max' => 'L\'URL de la fiche technique ne peut pas dépasser 500 caractères.',
|
||||
'fournisseur_id.exists' => 'Le fournisseur sélectionné n\'existe pas.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreThanatopractitionerRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Add your authorization logic here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
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<string, string>
|
||||
*/
|
||||
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.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateClientGroupRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$clientGroupId = $this->route('client_group') ? $this->route('client_group')->id : $this->route('id');
|
||||
|
||||
return [
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:191',
|
||||
Rule::unique('client_groups', 'name')->ignore($clientGroupId)
|
||||
],
|
||||
'description' => 'nullable|string',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => 'Le nom du groupe est obligatoire.',
|
||||
'name.string' => 'Le nom du groupe doit être une chaîne de caractères.',
|
||||
'name.max' => 'Le nom du groupe ne peut pas dépasser 191 caractères.',
|
||||
'name.unique' => 'Un groupe avec ce nom existe déjà.',
|
||||
'description.string' => 'La description doit être une chaîne de caractères.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateClientLocationRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_id' => 'required|exists:clients,id',
|
||||
'name' => 'nullable|string|max:191',
|
||||
'address_line1' => 'nullable|string|max:255',
|
||||
'address_line2' => 'nullable|string|max:255',
|
||||
'postal_code' => 'nullable|string|max:20',
|
||||
'city' => 'nullable|string|max:191',
|
||||
'country_code' => 'nullable|string|size:2',
|
||||
'gps_lat' => 'nullable|numeric|between:-90,90',
|
||||
'gps_lng' => 'nullable|numeric|between:-180,180',
|
||||
'is_default' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_id.required' => 'Le client est obligatoire.',
|
||||
'client_id.exists' => 'Le client sélectionné n\'existe pas.',
|
||||
'name.max' => 'Le nom ne peut pas dépasser 191 caractères.',
|
||||
'address_line1.max' => 'L\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'address_line2.max' => 'Le complément d\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'postal_code.max' => 'Le code postal ne peut pas dépasser 20 caractères.',
|
||||
'city.max' => 'La ville ne peut pas dépasser 191 caractères.',
|
||||
'country_code.size' => 'Le code pays doit contenir 2 caractères.',
|
||||
'gps_lat.numeric' => 'La latitude doit être un nombre.',
|
||||
'gps_lat.between' => 'La latitude doit être comprise entre -90 et 90.',
|
||||
'gps_lng.numeric' => 'La longitude doit être un nombre.',
|
||||
'gps_lng.between' => 'La longitude doit être comprise entre -180 et 180.',
|
||||
'is_default.boolean' => 'Le statut par défaut doit être vrai ou faux.',
|
||||
];
|
||||
}
|
||||
}
|
||||
68
thanasoft-back/app/Http/Requests/UpdateClientRequest.php
Normal file
68
thanasoft-back/app/Http/Requests/UpdateClientRequest.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateClientRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
||||
'name' => 'required|string|max:255',
|
||||
'vat_number' => 'nullable|string|max:32',
|
||||
'siret' => 'nullable|string|max:20',
|
||||
'email' => 'nullable|email|max:191',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'billing_address_line1' => 'nullable|string|max:255',
|
||||
'billing_address_line2' => 'nullable|string|max:255',
|
||||
'billing_postal_code' => 'nullable|string|max:20',
|
||||
'billing_city' => 'nullable|string|max:191',
|
||||
'billing_country_code' => 'nullable|string|size:2',
|
||||
'group_id' => 'nullable|exists:client_groups,id',
|
||||
'notes' => 'nullable|string',
|
||||
'is_active' => 'boolean',
|
||||
'default_tva_rate_id' => 'nullable|exists:tva_rates,id',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'company_id.required' => 'La société est obligatoire.',
|
||||
'company_id.exists' => 'La société sélectionnée n\'existe pas.',
|
||||
'type.required' => 'Le type de client est obligatoire.',
|
||||
'type.in' => 'Le type de client sélectionné est invalide.',
|
||||
'name.required' => 'Le nom du client est obligatoire.',
|
||||
'name.string' => 'Le nom du client doit être une chaîne de caractères.',
|
||||
'name.max' => 'Le nom du client ne peut pas dépasser 255 caractères.',
|
||||
'vat_number.max' => 'Le numéro de TVA ne peut pas dépasser 32 caractères.',
|
||||
'siret.max' => 'Le SIRET ne peut pas dépasser 20 caractères.',
|
||||
'email.email' => 'L\'adresse email doit être valide.',
|
||||
'email.max' => 'L\'adresse email ne peut pas dépasser 191 caractères.',
|
||||
'phone.max' => 'Le téléphone ne peut pas dépasser 50 caractères.',
|
||||
'billing_address_line1.max' => 'L\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'billing_address_line2.max' => 'Le complément d\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'billing_postal_code.max' => 'Le code postal ne peut pas dépasser 20 caractères.',
|
||||
'billing_city.max' => 'La ville ne peut pas dépasser 191 caractères.',
|
||||
'billing_country_code.size' => 'Le code pays doit contenir 2 caractères.',
|
||||
'group_id.exists' => 'Le groupe de clients sélectionné n\'existe pas.',
|
||||
'is_active.boolean' => 'Le statut actif doit être vrai ou faux.',
|
||||
'default_tva_rate_id.exists' => 'Le taux de TVA sélectionné n\'existe pas.',
|
||||
];
|
||||
}
|
||||
}
|
||||
71
thanasoft-back/app/Http/Requests/UpdateContactRequest.php
Normal file
71
thanasoft-back/app/Http/Requests/UpdateContactRequest.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateContactRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_id' => 'nullable|exists:clients,id',
|
||||
'fournisseur_id' => 'nullable|exists:fournisseurs,id',
|
||||
'first_name' => 'nullable|string|max:191',
|
||||
'last_name' => 'nullable|string|max:191',
|
||||
'email' => 'nullable|email|max:191',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'role' => 'nullable|string|max:191',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_id.exists' => 'Le client sélectionné n\'existe pas.',
|
||||
'fournisseur_id.exists' => 'Le fournisseur sélectionné n\'existe pas.',
|
||||
'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 191 caractères.',
|
||||
'last_name.string' => 'Le nom doit être une chaîne de caractères.',
|
||||
'last_name.max' => 'Le nom ne peut pas dépasser 191 caractères.',
|
||||
'email.email' => 'L\'adresse email doit être valide.',
|
||||
'email.max' => 'L\'adresse email ne peut pas dépasser 191 caractères.',
|
||||
'phone.max' => 'Le téléphone ne peut pas dépasser 50 caractères.',
|
||||
'role.max' => 'Le rôle ne peut pas dépasser 191 caractères.',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
// At least one of client_id or fournisseur_id must be provided
|
||||
if (empty($this->client_id) && empty($this->fournisseur_id)) {
|
||||
$validator->errors()->add(
|
||||
'general',
|
||||
'Le contact doit être associé à un client ou un fournisseur.'
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($this->first_name) && empty($this->last_name) && empty($this->email) && empty($this->phone)) {
|
||||
$validator->errors()->add(
|
||||
'general',
|
||||
'Au moins un champ (prénom, nom, email ou téléphone) doit être renseigné.'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateDeceasedDocumentRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Add your authorization logic here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'deceased_id' => 'sometimes|required|exists:deceased,id',
|
||||
'doc_type' => 'sometimes|required|string|max:191',
|
||||
'file_id' => 'nullable|exists:files,id',
|
||||
'generated_at' => 'nullable|date',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'deceased_id.required' => 'Le défunt est obligatoire.',
|
||||
'deceased_id.exists' => 'Le défunt 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.',
|
||||
'generated_at.date' => 'La date de génération doit être une date valide.',
|
||||
];
|
||||
}
|
||||
}
|
||||
49
thanasoft-back/app/Http/Requests/UpdateDeceasedRequest.php
Normal file
49
thanasoft-back/app/Http/Requests/UpdateDeceasedRequest.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateDeceasedRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
// Add authorization logic if needed
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'last_name' => ['sometimes', 'required', 'string', 'max:191'],
|
||||
'first_name' => ['nullable', 'string', 'max:191'],
|
||||
'birth_date' => ['nullable', 'date'],
|
||||
'death_date' => ['nullable', 'date', 'after_or_equal:birth_date'],
|
||||
'place_of_death' => ['nullable', 'string', 'max:255'],
|
||||
'notes' => ['nullable', 'string']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom error messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'last_name.required' => 'Le nom de famille est obligatoire.',
|
||||
'last_name.max' => 'Le nom de famille ne peut pas dépasser 191 caractères.',
|
||||
'first_name.max' => 'Le prénom ne peut pas dépasser 191 caractères.',
|
||||
'death_date.after_or_equal' => 'La date de décès doit être postérieure ou égale à la date de naissance.',
|
||||
'place_of_death.max' => 'Le lieu de décès ne peut pas dépasser 255 caractères.'
|
||||
];
|
||||
}
|
||||
}
|
||||
65
thanasoft-back/app/Http/Requests/UpdateEmployeeRequest.php
Normal file
65
thanasoft-back/app/Http/Requests/UpdateEmployeeRequest.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateEmployeeRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Add your authorization logic here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
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<string, string>
|
||||
*/
|
||||
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.',
|
||||
];
|
||||
}
|
||||
}
|
||||
96
thanasoft-back/app/Http/Requests/UpdateFileRequest.php
Normal file
96
thanasoft-back/app/Http/Requests/UpdateFileRequest.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class UpdateFileRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
$file = $this->route('file');
|
||||
|
||||
// Allow if user owns the file or is admin
|
||||
return Auth::check() && (
|
||||
$file->uploaded_by === Auth::id() ||
|
||||
Auth::user()->hasRole('admin')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'file_name' => 'sometimes|string|max:255',
|
||||
'description' => 'nullable|string|max:500',
|
||||
'tags' => 'nullable|array|max:10',
|
||||
'tags.*' => 'string|max:50',
|
||||
'is_public' => 'boolean',
|
||||
'category' => 'sometimes|string|in:devis,facture,contrat,document,image,autre',
|
||||
'client_id' => 'nullable|integer|exists:clients,id',
|
||||
'subcategory' => 'nullable|string|max:100',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'file_name' => 'nom du fichier',
|
||||
'description' => 'description',
|
||||
'tags' => 'étiquettes',
|
||||
'is_public' => 'visibilité publique',
|
||||
'category' => 'catégorie',
|
||||
'client_id' => 'client',
|
||||
'subcategory' => 'sous-catégorie',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'file_name.string' => 'Le nom du fichier doit être une chaîne de caractères.',
|
||||
'file_name.max' => 'Le nom du fichier ne peut pas dépasser 255 caractères.',
|
||||
'description.string' => 'La description doit être une chaîne de caractères.',
|
||||
'description.max' => 'La description ne peut pas dépasser 500 caractères.',
|
||||
'tags.array' => 'Les étiquettes doivent être un tableau.',
|
||||
'tags.max' => 'Vous ne pouvez pas ajouter plus de 10 étiquettes.',
|
||||
'tags.*.string' => 'Chaque étiquette doit être une chaîne de caractères.',
|
||||
'tags.*.max' => 'Chaque étiquette ne peut pas dépasser 50 caractères.',
|
||||
'is_public.boolean' => 'La visibilité publique doit être vrai ou faux.',
|
||||
'category.string' => 'La catégorie doit être une chaîne de caractères.',
|
||||
'category.in' => 'La catégorie sélectionnée n\'est pas valide.',
|
||||
'client_id.exists' => 'Le client sélectionné n\'existe pas.',
|
||||
'subcategory.string' => 'La sous-catégorie doit être une chaîne de caractères.',
|
||||
'subcategory.max' => 'La sous-catégorie ne peut pas dépasser 100 caractères.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
// Only merge fields that are present in the request
|
||||
$data = [];
|
||||
|
||||
if ($this->has('is_public')) {
|
||||
$data['is_public'] = $this->boolean('is_public');
|
||||
}
|
||||
|
||||
$this->merge($data);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateFournisseurRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'sometimes|required|string|max:255',
|
||||
'vat_number' => 'nullable|string|max:32',
|
||||
'siret' => 'nullable|string|max:20',
|
||||
'email' => 'nullable|email|max:191',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'billing_address_line1' => 'nullable|string|max:255',
|
||||
'billing_address_line2' => 'nullable|string|max:255',
|
||||
'billing_postal_code' => 'nullable|string|max:20',
|
||||
'billing_city' => 'nullable|string|max:191',
|
||||
'billing_country_code' => 'nullable|string|size:2',
|
||||
'notes' => 'nullable|string',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => 'Le nom du fournisseur est obligatoire.',
|
||||
'name.string' => 'Le nom du fournisseur doit être une chaîne de caractères.',
|
||||
'name.max' => 'Le nom du fournisseur ne peut pas dépasser 255 caractères.',
|
||||
'vat_number.max' => 'Le numéro de TVA ne peut pas dépasser 32 caractères.',
|
||||
'siret.max' => 'Le SIRET ne peut pas dépasser 20 caractères.',
|
||||
'email.email' => 'L\'adresse email doit être valide.',
|
||||
'email.max' => 'L\'adresse email ne peut pas dépasser 191 caractères.',
|
||||
'phone.max' => 'Le téléphone ne peut pas dépasser 50 caractères.',
|
||||
'billing_address_line1.max' => 'L\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'billing_address_line2.max' => 'Le complément d\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'billing_postal_code.max' => 'Le code postal ne peut pas dépasser 20 caractères.',
|
||||
'billing_city.max' => 'La ville ne peut pas dépasser 191 caractères.',
|
||||
'billing_country_code.size' => 'Le code pays doit contenir 2 caractères.',
|
||||
'is_active.boolean' => 'Le statut actif doit être vrai ou faux.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateInterventionRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
// Add authorization logic if needed
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_id' => ['sometimes', 'required', 'exists:clients,id'],
|
||||
'deceased_id' => ['nullable', 'exists:deceased,id'],
|
||||
'order_giver' => ['nullable', 'string', 'max:255'],
|
||||
'location_id' => ['nullable', 'exists:client_locations,id'],
|
||||
'type' => ['sometimes', 'required', Rule::in([
|
||||
'thanatopraxie',
|
||||
'toilette_mortuaire',
|
||||
'exhumation',
|
||||
'retrait_pacemaker',
|
||||
'retrait_bijoux',
|
||||
'autre'
|
||||
])],
|
||||
'scheduled_at' => ['nullable', 'date_format:Y-m-d H:i:s'],
|
||||
'duration_min' => ['nullable', 'integer', 'min:0'],
|
||||
'status' => ['sometimes', Rule::in([
|
||||
'demande',
|
||||
'planifie',
|
||||
'en_cours',
|
||||
'termine',
|
||||
'annule'
|
||||
])],
|
||||
'practitioners' => ['nullable', 'array'],
|
||||
'practitioners.*' => ['exists:thanatopractitioners,id'],
|
||||
'principal_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'],
|
||||
'assistant_practitioner_ids' => ['nullable', 'array'],
|
||||
'assistant_practitioner_ids.*' => ['exists:thanatopractitioners,id'],
|
||||
'notes' => ['nullable', 'string'],
|
||||
'created_by' => ['nullable', 'exists:users,id']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom error messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_id.required' => 'Le client est obligatoire.',
|
||||
'client_id.exists' => 'Le client sélectionné est invalide.',
|
||||
'deceased_id.exists' => 'Le défunt sélectionné est invalide.',
|
||||
'order_giver.max' => 'Le donneur d\'ordre ne peut pas dépasser 255 caractères.',
|
||||
'location_id.exists' => 'Le lieu sélectionné est invalide.',
|
||||
'type.required' => 'Le type d\'intervention est obligatoire.',
|
||||
'type.in' => 'Le type d\'intervention est invalide.',
|
||||
'scheduled_at.date_format' => 'Le format de la date programmée est invalide.',
|
||||
'duration_min.integer' => 'La durée doit être un nombre entier.',
|
||||
'duration_min.min' => 'La durée ne peut pas être négative.',
|
||||
'status.in' => 'Le statut de l\'intervention est invalide.',
|
||||
'practitioners.array' => 'Les praticiens doivent être un tableau.',
|
||||
'practitioners.*.exists' => 'Un des praticiens sélectionnés est invalide.',
|
||||
'principal_practitioner_id.exists' => 'Le praticien principal sélectionné est invalide.',
|
||||
'assistant_practitioner_ids.array' => 'Les praticiens assistants doivent être un tableau.',
|
||||
'assistant_practitioner_ids.*.exists' => 'Un des praticiens assistants est invalide.',
|
||||
'created_by.exists' => 'L\'utilisateur créateur est invalide.'
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdatePractitionerDocumentRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Add your authorization logic here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
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<string, string>
|
||||
*/
|
||||
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.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateProductCategoryRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$categoryId = $this->route('id');
|
||||
|
||||
return [
|
||||
'parent_id' => 'nullable|exists:product_categories,id',
|
||||
'code' => "nullable|string|max:64|unique:product_categories,code,{$categoryId}",
|
||||
'name' => 'nullable|string|max:191',
|
||||
'description' => 'nullable|string',
|
||||
'active' => 'nullable|boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'parent_id.exists' => 'La catégorie parente sélectionnée n\'existe pas.',
|
||||
'code.string' => 'Le code de la catégorie doit être une chaîne de caractères.',
|
||||
'code.max' => 'Le code de la catégorie ne peut pas dépasser 64 caractères.',
|
||||
'code.unique' => 'Ce code de catégorie existe déjà.',
|
||||
'name.string' => 'Le nom de la catégorie doit être une chaîne de caractères.',
|
||||
'name.max' => 'Le nom de la catégorie ne peut pas dépasser 191 caractères.',
|
||||
'description.string' => 'La description doit être une chaîne de caractères.',
|
||||
'active.boolean' => 'Le statut actif doit être un booléen.',
|
||||
];
|
||||
}
|
||||
}
|
||||
91
thanasoft-back/app/Http/Requests/UpdateProductRequest.php
Normal file
91
thanasoft-back/app/Http/Requests/UpdateProductRequest.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateProductRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$productId = $this->route('id');
|
||||
|
||||
return [
|
||||
'nom' => 'required|string|max:255',
|
||||
'reference' => "nullable",
|
||||
'categorie_id' => 'required|exists:product_categories,id',
|
||||
'fabricant' => 'nullable|string|max:191',
|
||||
'stock_actuel' => 'required|numeric|min:0',
|
||||
'stock_minimum' => 'required|numeric|min:0',
|
||||
'unite' => 'required|string|max:50',
|
||||
'prix_unitaire' => 'required|numeric|min:0',
|
||||
'date_expiration' => 'nullable|date|after:today',
|
||||
'numero_lot' => 'nullable|string|max:100',
|
||||
'conditionnement_nom' => 'nullable|string|max:191',
|
||||
'conditionnement_quantite' => 'nullable|numeric|min:0',
|
||||
'conditionnement_unite' => 'nullable|string|max:50',
|
||||
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
|
||||
'remove_image' => 'nullable|boolean',
|
||||
'fiche_technique_url' => 'nullable|url|max:500',
|
||||
'fournisseur_id' => 'nullable|exists:fournisseurs,id',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'nom.required' => 'Le nom du produit est obligatoire.',
|
||||
'nom.string' => 'Le nom du produit doit être une chaîne de caractères.',
|
||||
'nom.max' => 'Le nom du produit ne peut pas dépasser 255 caractères.',
|
||||
'reference.required' => 'La référence du produit est obligatoire.',
|
||||
'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_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.',
|
||||
'stock_actuel.numeric' => 'Le stock actuel doit être un nombre.',
|
||||
'stock_actuel.min' => 'Le stock actuel doit être supérieur ou égal à 0.',
|
||||
'stock_minimum.required' => 'Le stock minimum est obligatoire.',
|
||||
'stock_minimum.numeric' => 'Le stock minimum doit être un nombre.',
|
||||
'stock_minimum.min' => 'Le stock minimum doit être supérieur ou égal à 0.',
|
||||
'unite.required' => 'L\'unité est obligatoire.',
|
||||
'unite.string' => 'L\'unité doit être une chaîne de caractères.',
|
||||
'unite.max' => 'L\'unité ne peut pas dépasser 50 caractères.',
|
||||
'prix_unitaire.required' => 'Le prix unitaire est obligatoire.',
|
||||
'prix_unitaire.numeric' => 'Le prix unitaire doit être un nombre.',
|
||||
'prix_unitaire.min' => 'Le prix unitaire doit être supérieur ou égal à 0.',
|
||||
'date_expiration.date' => 'La date d\'expiration doit être une date valide.',
|
||||
'date_expiration.after' => 'La date d\'expiration doit être postérieure à aujourd\'hui.',
|
||||
'numero_lot.string' => 'Le numéro de lot doit être une chaîne de caractères.',
|
||||
'numero_lot.max' => 'Le numéro de lot ne peut pas dépasser 100 caractères.',
|
||||
'conditionnement_nom.string' => 'Le nom du conditionnement doit être une chaîne de caractères.',
|
||||
'conditionnement_nom.max' => 'Le nom du conditionnement ne peut pas dépasser 191 caractères.',
|
||||
'conditionnement_quantite.numeric' => 'La quantité du conditionnement doit être un nombre.',
|
||||
'conditionnement_quantite.min' => 'La quantité du conditionnement doit être supérieure ou égal à 0.',
|
||||
'conditionnement_unite.string' => 'L\'unité du conditionnement doit être une chaîne de caractères.',
|
||||
'conditionnement_unite.max' => 'L\'unité du conditionnement ne peut pas dépasser 50 caractères.',
|
||||
'image.image' => 'Le fichier doit être une image valide.',
|
||||
'image.mimes' => 'L\'image doit être de type: jpeg, png, jpg, gif ou svg.',
|
||||
'image.max' => 'L\'image ne peut pas dépasser 2MB.',
|
||||
'fiche_technique_url.url' => 'L\'URL de la fiche technique doit être une URL valide.',
|
||||
'fiche_technique_url.max' => 'L\'URL de la fiche technique ne peut pas dépasser 500 caractères.',
|
||||
'fournisseur_id.exists' => 'Le fournisseur sélectionné n\'existe pas.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateThanatopractitionerRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Add your authorization logic here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'employee_id' => [
|
||||
'nullable',
|
||||
'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<string, string>
|
||||
*/
|
||||
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.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Client;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ClientCategoryResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'slug' => $this->slug,
|
||||
'description' => $this->description,
|
||||
'is_active' => $this->is_active,
|
||||
'sort_order' => $this->sort_order,
|
||||
'created_at' => $this->created_at?->toISOString(),
|
||||
'updated_at' => $this->updated_at?->toISOString(),
|
||||
|
||||
// Relationships (loaded when needed)
|
||||
// 'clients_count' => $this->whenCounted('clients'),
|
||||
// 'clients' => ClientResource::collection($this->whenLoaded('clients')),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Client;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class ClientCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->collection,
|
||||
'meta' => [
|
||||
'total' => $this->total(),
|
||||
'per_page' => $this->perPage(),
|
||||
'current_page' => $this->currentPage(),
|
||||
'last_page' => $this->lastPage(),
|
||||
'from' => $this->firstItem(),
|
||||
'to' => $this->lastItem(),
|
||||
'stats' => [
|
||||
'active' => $this->collection->where('is_active', true)->count(),
|
||||
'inactive' => $this->collection->where('is_active', false)->count(),
|
||||
'by_type' => $this->collection->groupBy('client_category_id')->map->count(),
|
||||
],
|
||||
],
|
||||
'links' => [
|
||||
'first' => $this->url(1),
|
||||
'last' => $this->url($this->lastPage()),
|
||||
'prev' => $this->previousPageUrl(),
|
||||
'next' => $this->nextPageUrl(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function with(Request $request): array
|
||||
{
|
||||
return [
|
||||
'status' => 'success',
|
||||
'message' => 'Clients récupérés avec succès',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Client;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class ClientGroupCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->collection,
|
||||
'meta' => [
|
||||
'total' => $this->total(),
|
||||
'per_page' => $this->perPage(),
|
||||
'current_page' => $this->currentPage(),
|
||||
'last_page' => $this->lastPage(),
|
||||
'from' => $this->firstItem(),
|
||||
'to' => $this->lastItem(),
|
||||
],
|
||||
'links' => [
|
||||
'first' => $this->url(1),
|
||||
'last' => $this->url($this->lastPage()),
|
||||
'prev' => $this->previousPageUrl(),
|
||||
'next' => $this->nextPageUrl(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function with(Request $request): array
|
||||
{
|
||||
return [
|
||||
'status' => 'success',
|
||||
'message' => 'Groupes de clients récupérés avec succès',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Client;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ClientGroupResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'description' => $this->description ?? null,
|
||||
'created_at' => $this->created_at?->format('Y-m-d H:i:s'),
|
||||
'updated_at' => $this->updated_at?->format('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Client;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class ClientLocationCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->collection,
|
||||
'meta' => [
|
||||
'total' => $this->total(),
|
||||
'per_page' => $this->perPage(),
|
||||
'current_page' => $this->currentPage(),
|
||||
'last_page' => $this->lastPage(),
|
||||
'from' => $this->firstItem(),
|
||||
'to' => $this->lastItem(),
|
||||
'stats' => [
|
||||
'default_locations' => $this->collection->where('is_default', true)->count(),
|
||||
'with_gps' => $this->collection->filter(fn($location) => $location->gps_lat && $location->gps_lng)->count(),
|
||||
],
|
||||
],
|
||||
'links' => [
|
||||
'first' => $this->url(1),
|
||||
'last' => $this->url($this->lastPage()),
|
||||
'prev' => $this->previousPageUrl(),
|
||||
'next' => $this->nextPageUrl(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function with(Request $request): array
|
||||
{
|
||||
return [
|
||||
'status' => 'success',
|
||||
'message' => 'Lieux clients récupérés avec succès',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Client;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ClientLocationResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'client_id' => $this->client_id,
|
||||
'name' => $this->name,
|
||||
'address' => [
|
||||
'line1' => $this->address_line1,
|
||||
'line2' => $this->address_line2,
|
||||
'postal_code' => $this->postal_code,
|
||||
'city' => $this->city,
|
||||
'country_code' => $this->country_code,
|
||||
'full_address' => $this->full_address,
|
||||
],
|
||||
'gps_coordinates' => $this->gps_coordinates,
|
||||
'gps_lat' => $this->gps_lat,
|
||||
'gps_lng' => $this->gps_lng,
|
||||
'is_default' => $this->is_default,
|
||||
'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
|
||||
'client' => new ClientResource($this->whenLoaded('client')),
|
||||
//'interventions_as_origin' => InterventionResource::collection($this->whenLoaded('interventionsAsOrigin')),
|
||||
//'transports_as_origin' => TransportResource::collection($this->whenLoaded('transportsAsOrigin')),
|
||||
//'transports_as_destination' => TransportResource::collection($this->whenLoaded('transportsAsDestination')),
|
||||
];
|
||||
}
|
||||
|
||||
public function with(Request $request): array
|
||||
{
|
||||
return [
|
||||
'status' => 'success',
|
||||
'message' => 'Lieu client récupéré avec succès',
|
||||
];
|
||||
}
|
||||
}
|
||||
62
thanasoft-back/app/Http/Resources/Client/ClientResource.php
Normal file
62
thanasoft-back/app/Http/Resources/Client/ClientResource.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Client;
|
||||
|
||||
use App\Http\Resources\Contact\ContactResource;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ClientResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
//'company_id' => $this->company_id,
|
||||
'commercial' => $this->commercial(),
|
||||
'type_label' => $this->getTypeLabel(),
|
||||
'name' => $this->name,
|
||||
'vat_number' => $this->vat_number,
|
||||
'siret' => $this->siret,
|
||||
'email' => $this->email,
|
||||
'phone' => $this->phone,
|
||||
'billing_address' => [
|
||||
'line1' => $this->billing_address_line1,
|
||||
'line2' => $this->billing_address_line2,
|
||||
'postal_code' => $this->billing_postal_code,
|
||||
'city' => $this->billing_city,
|
||||
'country_code' => $this->billing_country_code,
|
||||
'full_address' => $this->billing_address,
|
||||
],
|
||||
'group_id' => $this->group_id,
|
||||
'notes' => $this->notes,
|
||||
'is_active' => $this->is_active,
|
||||
// 'default_tva_rate_id' => $this->default_tva_rate_id,
|
||||
'created_at' => $this->created_at?->format('Y-m-d H:i:s'),
|
||||
'updated_at' => $this->updated_at?->format('Y-m-d H:i:s'),
|
||||
|
||||
// Counts
|
||||
'contacts_count' => $this->whenCounted('contacts'),
|
||||
'locations_count' => $this->whenCounted('locations'),
|
||||
// 'interventions_count' => $this->whenCounted('interventions'),
|
||||
// 'quotes_count' => $this->whenCounted('quotes'),
|
||||
// 'invoices_count' => $this->whenCounted('invoices'),
|
||||
|
||||
// Relations
|
||||
// 'company' => new CompanyResource($this->whenLoaded('company')),
|
||||
'group' => new ClientGroupResource($this->whenLoaded('group')),
|
||||
// 'default_tva_rate' => new TvaRateResource($this->whenLoaded('defaultTvaRate')),
|
||||
'contacts' => ContactResource::collection($this->whenLoaded('contacts')),
|
||||
'locations' => ClientLocationResource::collection($this->whenLoaded('locations')),
|
||||
// 'price_lists' => PriceListResource::collection($this->whenLoaded('priceLists')),
|
||||
// 'interventions' => InterventionResource::collection($this->whenLoaded('interventions')),
|
||||
// 'quotes' => QuoteResource::collection($this->whenLoaded('quotes')),
|
||||
// 'invoices' => InvoiceResource::collection($this->whenLoaded('invoices')),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Contact;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class ContactCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'data' => ContactResource::collection($this->collection),
|
||||
'meta' => [
|
||||
'total' => $this->total(),
|
||||
'per_page' => $this->perPage(),
|
||||
'current_page' => $this->currentPage(),
|
||||
'last_page' => $this->lastPage(),
|
||||
'from' => $this->firstItem(),
|
||||
'to' => $this->lastItem(),
|
||||
],
|
||||
'links' => [
|
||||
'first' => $this->url(1),
|
||||
'last' => $this->url($this->lastPage()),
|
||||
'prev' => $this->previousPageUrl(),
|
||||
'next' => $this->nextPageUrl(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function with(Request $request): array
|
||||
{
|
||||
return [
|
||||
'status' => 'success',
|
||||
'message' => 'Contacts récupérés avec succès',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Contact;
|
||||
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ContactResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'client_id' => $this->client_id,
|
||||
'fournisseur_id' => $this->fournisseur_id,
|
||||
'first_name' => $this->first_name,
|
||||
'last_name' => $this->last_name,
|
||||
'full_name' => $this->full_name,
|
||||
'email' => $this->email,
|
||||
'phone' => $this->phone,
|
||||
'role' => $this->role,
|
||||
'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
|
||||
'client' => $this->whenLoaded('client', function() {
|
||||
return $this->client ? [
|
||||
'id' => $this->client->id,
|
||||
'name' => $this->client->name,
|
||||
] : null;
|
||||
}),
|
||||
'fournisseur' => $this->whenLoaded('fournisseur', function() {
|
||||
return $this->fournisseur ? [
|
||||
'id' => $this->fournisseur->id,
|
||||
'name' => $this->fournisseur->name,
|
||||
] : null;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public function with(Request $request): array
|
||||
{
|
||||
return [
|
||||
'status' => 'success',
|
||||
'message' => 'Contact récupéré avec succès',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Deceased;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class DeceasedCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->collection,
|
||||
'meta' => [
|
||||
'total' => $this->total(),
|
||||
'per_page' => $this->perPage(),
|
||||
'current_page' => $this->currentPage(),
|
||||
'last_page' => $this->lastPage(),
|
||||
'from' => $this->firstItem(),
|
||||
'to' => $this->lastItem()
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Deceased;
|
||||
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class DeceasedDocumentCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->collection->map(function ($document) {
|
||||
return [
|
||||
'id' => $document->id,
|
||||
'deceased_id' => $document->deceased_id,
|
||||
'doc_type' => $document->doc_type,
|
||||
'file_id' => $document->file_id,
|
||||
'generated_at' => $document->generated_at?->format('Y-m-d H:i:s'),
|
||||
'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
|
||||
'deceased' => $document->deceased ? [
|
||||
'id' => $document->deceased->id,
|
||||
'first_name' => $document->deceased->first_name,
|
||||
'last_name' => $document->deceased->last_name,
|
||||
'full_name' => $document->deceased->first_name . ' ' . $document->deceased->last_name,
|
||||
'date_of_birth' => $document->deceased->date_of_birth?->format('Y-m-d'),
|
||||
'date_of_death' => $document->deceased->date_of_death?->format('Y-m-d'),
|
||||
] : null,
|
||||
'file' => $document->file ? [
|
||||
'id' => $document->file->id,
|
||||
'filename' => $document->file->filename ?? null,
|
||||
'path' => $document->file->path ?? null,
|
||||
'mime_type' => $document->file->mime_type ?? null,
|
||||
'size' => $document->file->size ?? null,
|
||||
] : null,
|
||||
];
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Deceased;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class DeceasedDocumentResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'deceased_id' => $this->deceased_id,
|
||||
'doc_type' => $this->doc_type,
|
||||
'file_id' => $this->file_id,
|
||||
'generated_at' => $this->generated_at?->format('Y-m-d H:i:s'),
|
||||
'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
|
||||
'deceased' => $this->when(
|
||||
$this->relationLoaded('deceased'),
|
||||
function () {
|
||||
return [
|
||||
'id' => $this->deceased->id,
|
||||
'first_name' => $this->deceased->first_name,
|
||||
'last_name' => $this->deceased->last_name,
|
||||
'full_name' => $this->deceased->first_name . ' ' . $this->deceased->last_name,
|
||||
'date_of_birth' => $this->deceased->date_of_birth?->format('Y-m-d'),
|
||||
'date_of_death' => $this->deceased->date_of_death?->format('Y-m-d'),
|
||||
];
|
||||
}
|
||||
),
|
||||
'file' => $this->when(
|
||||
$this->relationLoaded('file'),
|
||||
function () {
|
||||
return $this->file ? [
|
||||
'id' => $this->file->id,
|
||||
'filename' => $this->file->filename ?? null,
|
||||
'path' => $this->file->path ?? null,
|
||||
'mime_type' => $this->file->mime_type ?? null,
|
||||
'size' => $this->file->size ?? null,
|
||||
] : null;
|
||||
}
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Deceased;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class DeceasedResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'last_name' => $this->last_name,
|
||||
'first_name' => $this->first_name,
|
||||
'full_name' => trim($this->first_name . ' ' . $this->last_name),
|
||||
'birth_date' => $this->birth_date ? $this->birth_date->format('Y-m-d') : null,
|
||||
'death_date' => $this->death_date ? $this->death_date->format('Y-m-d') : null,
|
||||
'place_of_death' => $this->place_of_death,
|
||||
'notes' => $this->notes,
|
||||
'documents_count' => $this->documents_count ?? $this->documents()->count(),
|
||||
'interventions_count' => $this->interventions_count ?? $this->interventions()->count(),
|
||||
'created_at' => $this->created_at->format('Y-m-d H:i:s'),
|
||||
'updated_at' => $this->updated_at->format('Y-m-d H:i:s')
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Employee;
|
||||
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class EmployeeCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
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,
|
||||
];
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Employee;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class EmployeeResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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)
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Employee;
|
||||
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class PractitionerDocumentCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
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,
|
||||
];
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Employee;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class PractitionerDocumentResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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)
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Employee;
|
||||
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class ThanatopractitionerCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
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,
|
||||
];
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Employee;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use App\Http\Resources\Employee\EmployeeResource;
|
||||
use App\Http\Resources\Employee\PractitionerDocumentResource;
|
||||
|
||||
class ThanatopractitionerResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'employee_id' => $this->employee_id,
|
||||
'employee_name' => $this->when(
|
||||
$this->relationLoaded('employee'),
|
||||
$this->employee->full_name ?? ($this->employee->first_name . ' ' . $this->employee->last_name)
|
||||
),
|
||||
'diploma_number' => $this->diploma_number,
|
||||
'diploma_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)
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
86
thanasoft-back/app/Http/Resources/File/FileCollection.php
Normal file
86
thanasoft-back/app/Http/Resources/File/FileCollection.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\File;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class FileCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'data' => FileResource::collection($this->collection),
|
||||
'pagination' => [
|
||||
'current_page' => $this->currentPage(),
|
||||
'from' => $this->firstItem(),
|
||||
'last_page' => $this->lastPage(),
|
||||
'per_page' => $this->perPage(),
|
||||
'to' => $this->lastItem(),
|
||||
'total' => $this->total(),
|
||||
'has_more_pages' => $this->hasMorePages(),
|
||||
],
|
||||
'summary' => [
|
||||
'total_files' => $this->collection->count(),
|
||||
'total_size' => $this->collection->sum('size_bytes'),
|
||||
'total_size_formatted' => $this->formatBytes($this->collection->sum('size_bytes')),
|
||||
'categories' => $this->getCategoryStats(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate category statistics from the collection
|
||||
*/
|
||||
private function getCategoryStats(): array
|
||||
{
|
||||
$categories = [];
|
||||
|
||||
foreach ($this->collection as $file) {
|
||||
$pathParts = explode('/', $file->storage_uri);
|
||||
$category = $pathParts[count($pathParts) - 3] ?? 'general';
|
||||
|
||||
if (!isset($categories[$category])) {
|
||||
$categories[$category] = [
|
||||
'count' => 0,
|
||||
'total_size' => 0,
|
||||
'files' => []
|
||||
];
|
||||
}
|
||||
|
||||
$categories[$category]['count']++;
|
||||
$categories[$category]['total_size'] += $file->size_bytes ?? 0;
|
||||
$categories[$category]['files'][] = $file->file_name;
|
||||
}
|
||||
|
||||
// Format sizes
|
||||
foreach ($categories as $category => &$stats) {
|
||||
$stats['total_size_formatted'] = $this->formatBytes($stats['total_size']);
|
||||
// Remove file list to avoid too much data in collection
|
||||
unset($stats['files']);
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format bytes to human readable format
|
||||
*/
|
||||
private function formatBytes(int $bytes, int $precision = 2): string
|
||||
{
|
||||
if ($bytes === 0) {
|
||||
return '0 B';
|
||||
}
|
||||
|
||||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
$base = 1024;
|
||||
$factor = floor((strlen($bytes) - 1) / 3);
|
||||
|
||||
return sprintf("%.{$precision}f", $bytes / pow($base, $factor)) . ' ' . $units[$factor];
|
||||
}
|
||||
}
|
||||
68
thanasoft-back/app/Http/Resources/File/FileResource.php
Normal file
68
thanasoft-back/app/Http/Resources/File/FileResource.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\File;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class FileResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'file_name' => $this->file_name,
|
||||
'mime_type' => $this->mime_type,
|
||||
'size_bytes' => $this->size_bytes,
|
||||
'size_formatted' => $this->formatted_size,
|
||||
'extension' => $this->extension,
|
||||
'storage_uri' => $this->storage_uri,
|
||||
'organized_path' => $this->organized_path,
|
||||
'sha256' => $this->sha256,
|
||||
'uploaded_by' => $this->uploaded_by,
|
||||
'uploader_name' => $this->uploader_name,
|
||||
'uploaded_at' => $this->uploaded_at?->format('Y-m-d H:i:s'),
|
||||
'created_at' => $this->created_at?->format('Y-m-d H:i:s'),
|
||||
'updated_at' => $this->updated_at?->format('Y-m-d H:i:s'),
|
||||
|
||||
// File type helpers
|
||||
'is_image' => $this->is_image,
|
||||
'is_pdf' => $this->is_pdf,
|
||||
|
||||
// URL for accessing the file (if public)
|
||||
'url' => $this->when(
|
||||
$this->is_public ?? false,
|
||||
asset('storage/' . $this->storage_uri)
|
||||
),
|
||||
|
||||
// Relations
|
||||
'user' => [
|
||||
'id' => $this->user?->id,
|
||||
'name' => $this->user?->name,
|
||||
'email' => $this->user?->email,
|
||||
],
|
||||
|
||||
// Additional metadata from the file's path structure
|
||||
'category' => $this->when(
|
||||
$this->storage_uri,
|
||||
function () {
|
||||
$pathParts = explode('/', $this->storage_uri);
|
||||
return $pathParts[count($pathParts) - 3] ?? 'general';
|
||||
}
|
||||
),
|
||||
|
||||
'subcategory' => $this->when(
|
||||
$this->storage_uri,
|
||||
function () {
|
||||
$pathParts = explode('/', $this->storage_uri);
|
||||
return $pathParts[count($pathParts) - 2] ?? 'general';
|
||||
}
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\FileAttachment;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class FileAttachmentResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'file_id' => $this->file_id,
|
||||
'label' => $this->label,
|
||||
'sort_order' => $this->sort_order,
|
||||
'attachable_type' => $this->attachable_type,
|
||||
'attachable_id' => $this->attachable_id,
|
||||
'created_at' => $this->created_at->format('Y-m-d H:i:s'),
|
||||
'updated_at' => $this->updated_at->format('Y-m-d H:i:s'),
|
||||
|
||||
// File information
|
||||
'file' => $this->whenLoaded('file', function () {
|
||||
return [
|
||||
'id' => $this->file->id,
|
||||
'name' => $this->file->name,
|
||||
'original_name' => $this->file->original_name ?? $this->file->name,
|
||||
'path' => $this->file->path,
|
||||
'mime_type' => $this->file->mime_type,
|
||||
'size' => $this->file->size,
|
||||
'size_formatted' => $this->formatFileSize($this->file->size ?? 0),
|
||||
'extension' => pathinfo($this->file->name, PATHINFO_EXTENSION),
|
||||
'download_url' => url('/api/files/' . $this->file->id . '/download'),
|
||||
];
|
||||
}),
|
||||
|
||||
// Attachable model information
|
||||
'attachable' => $this->whenLoaded('attachable', function () {
|
||||
return [
|
||||
'id' => $this->attachable->id,
|
||||
'type' => class_basename($this->attachable),
|
||||
'name' => $this->getAttachableName(),
|
||||
];
|
||||
}),
|
||||
|
||||
// Helper methods
|
||||
'is_for_intervention' => $this->isForIntervention(),
|
||||
'is_for_client' => $this->isForClient(),
|
||||
'is_for_deceased' => $this->isForDeceased(),
|
||||
'download_url' => $this->downloadUrl,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format file size in human readable format
|
||||
*/
|
||||
private function formatFileSize(int $bytes): string
|
||||
{
|
||||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
$bytes = max($bytes, 0);
|
||||
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
|
||||
$pow = min($pow, count($units) - 1);
|
||||
|
||||
$bytes /= (1 << (10 * $pow));
|
||||
|
||||
return round($bytes, 2) . ' ' . $units[$pow];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the display name of the attached model
|
||||
*/
|
||||
private function getAttachableName(): string
|
||||
{
|
||||
if (!$this->attachable) {
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
return match (get_class($this->attachable)) {
|
||||
\App\Models\Intervention::class => $this->attachable->title ?? "Intervention #{$this->attachable->id}",
|
||||
\App\Models\Client::class => $this->attachable->name ?? "Client #{$this->attachable->id}",
|
||||
\App\Models\Deceased::class => $this->attachable->name ?? "Deceased #{$this->attachable->id}",
|
||||
default => 'Unknown Model'
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Fournisseur;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class FournisseurCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->collection,
|
||||
'meta' => [
|
||||
'total' => $this->total(),
|
||||
'per_page' => $this->perPage(),
|
||||
'current_page' => $this->currentPage(),
|
||||
'last_page' => $this->lastPage(),
|
||||
'from' => $this->firstItem(),
|
||||
'to' => $this->lastItem(),
|
||||
'stats' => [
|
||||
'active' => $this->collection->where('is_active', true)->count(),
|
||||
'inactive' => $this->collection->where('is_active', false)->count(),
|
||||
],
|
||||
],
|
||||
'links' => [
|
||||
'first' => $this->url(1),
|
||||
'last' => $this->url($this->lastPage()),
|
||||
'prev' => $this->previousPageUrl(),
|
||||
'next' => $this->nextPageUrl(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function with(Request $request): array
|
||||
{
|
||||
return [
|
||||
'status' => 'success',
|
||||
'message' => 'Fournisseurs récupérés avec succès',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Fournisseur;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class FournisseurResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'commercial' => $this->commercial(),
|
||||
'name' => $this->name,
|
||||
'vat_number' => $this->vat_number,
|
||||
'siret' => $this->siret,
|
||||
'email' => $this->email,
|
||||
'phone' => $this->phone,
|
||||
'billing_address' => [
|
||||
'line1' => $this->billing_address_line1,
|
||||
'line2' => $this->billing_address_line2,
|
||||
'postal_code' => $this->billing_postal_code,
|
||||
'city' => $this->billing_city,
|
||||
'country_code' => $this->billing_country_code,
|
||||
'full_address' => $this->billing_address,
|
||||
],
|
||||
'notes' => $this->notes,
|
||||
'is_active' => $this->is_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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Intervention;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class InterventionAttachmentResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'intervention_id' => $this->intervention_id,
|
||||
'file' => $this->whenLoaded('file', function () {
|
||||
return [
|
||||
'id' => $this->file->id,
|
||||
'name' => $this->file->name,
|
||||
'path' => $this->file->path,
|
||||
'mime_type' => $this->file->mime_type,
|
||||
'size' => $this->file->size
|
||||
];
|
||||
}),
|
||||
'label' => $this->label,
|
||||
'created_at' => $this->created_at->format('Y-m-d H:i:s')
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Intervention;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class InterventionCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->collection,
|
||||
'meta' => [
|
||||
'total' => $this->total(),
|
||||
'per_page' => $this->perPage(),
|
||||
'current_page' => $this->currentPage(),
|
||||
'last_page' => $this->lastPage(),
|
||||
'from' => $this->firstItem(),
|
||||
'to' => $this->lastItem(),
|
||||
'status_summary' => $this->calculateStatusSummary()
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate summary of intervention statuses.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function calculateStatusSummary(): array
|
||||
{
|
||||
$statusCounts = $this->collection->groupBy('status')
|
||||
->map(function ($group) {
|
||||
return $group->count();
|
||||
})
|
||||
->toArray();
|
||||
|
||||
return $statusCounts;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Intervention;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class InterventionNotificationResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'intervention_id' => $this->intervention_id,
|
||||
'channel' => $this->channel,
|
||||
'destination' => $this->destination,
|
||||
'payload' => $this->payload,
|
||||
'status' => $this->status,
|
||||
'sent_at' => $this->sent_at ? $this->sent_at->format('Y-m-d H:i:s') : null,
|
||||
'created_at' => $this->created_at->format('Y-m-d H:i:s')
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Intervention;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use App\Http\Resources\Deceased\DeceasedResource;
|
||||
use App\Http\Resources\Client\ClientResource;
|
||||
use App\Http\Resources\Employee\ThanatopractitionerResource;
|
||||
|
||||
class InterventionResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'client' => $this->whenLoaded('client', function () {
|
||||
return new ClientResource($this->client);
|
||||
}),
|
||||
'deceased' => $this->whenLoaded('deceased', function () {
|
||||
return new DeceasedResource($this->deceased);
|
||||
}),
|
||||
'order_giver' => $this->order_giver,
|
||||
'location' => $this->whenLoaded('location', function () {
|
||||
return [
|
||||
'id' => $this->location->id,
|
||||
'name' => $this->location->name
|
||||
];
|
||||
}),
|
||||
'type' => $this->type,
|
||||
'scheduled_at' => $this->scheduled_at ? $this->scheduled_at->format('Y-m-d H:i:s') : null,
|
||||
'duration_min' => $this->duration_min,
|
||||
'status' => $this->status,
|
||||
'practitioners' => $this->whenLoaded('practitioners', function () {
|
||||
return $this->practitioners->map(function ($practitioner) {
|
||||
return [
|
||||
'id' => $practitioner->id,
|
||||
'employee_id' => $practitioner->employee_id,
|
||||
'employee_name' => $practitioner->employee->full_name ?? ($practitioner->employee->first_name . ' ' . $practitioner->employee->last_name),
|
||||
'diploma_number' => $practitioner->diploma_number,
|
||||
'diploma_date' => $practitioner->diploma_date?->format('Y-m-d'),
|
||||
'authorization_number' => $practitioner->authorization_number,
|
||||
'authorization_issue_date' => $practitioner->authorization_issue_date?->format('Y-m-d'),
|
||||
'authorization_expiry_date' => $practitioner->authorization_expiry_date?->format('Y-m-d'),
|
||||
'notes' => $practitioner->notes,
|
||||
'is_authorization_valid' => $practitioner->is_authorization_valid,
|
||||
'created_at' => $practitioner->created_at?->format('Y-m-d H:i:s'),
|
||||
'updated_at' => $practitioner->updated_at?->format('Y-m-d H:i:s'),
|
||||
'role' => $practitioner->pivot->role ?? null,
|
||||
];
|
||||
});
|
||||
}),
|
||||
'principal_practitioner' => $this->whenLoaded('practitioners', function () {
|
||||
$principal = $this->practitioners->where('pivot.role', 'principal')->first();
|
||||
if (!$principal) {
|
||||
return null;
|
||||
}
|
||||
return [
|
||||
'id' => $principal->id,
|
||||
'employee_id' => $principal->employee_id,
|
||||
'employee_name' => $principal->employee->full_name ?? ($principal->employee->first_name . ' ' . $principal->employee->last_name),
|
||||
'diploma_number' => $principal->diploma_number,
|
||||
'diploma_date' => $principal->diploma_date?->format('Y-m-d'),
|
||||
'authorization_number' => $principal->authorization_number,
|
||||
'authorization_issue_date' => $principal->authorization_issue_date?->format('Y-m-d'),
|
||||
'authorization_expiry_date' => $principal->authorization_expiry_date?->format('Y-m-d'),
|
||||
'notes' => $principal->notes,
|
||||
'is_authorization_valid' => $principal->is_authorization_valid,
|
||||
'created_at' => $principal->created_at?->format('Y-m-d H:i:s'),
|
||||
'updated_at' => $principal->updated_at?->format('Y-m-d H:i:s'),
|
||||
'role' => $principal->pivot->role ?? null,
|
||||
];
|
||||
}),
|
||||
'attachments_count' => $this->attachments_count,
|
||||
'notes' => $this->notes,
|
||||
'created_by' => $this->created_by,
|
||||
'created_at' => $this->created_at->format('Y-m-d H:i:s'),
|
||||
'updated_at' => $this->updated_at->format('Y-m-d H:i:s'),
|
||||
'attachments' => $this->whenLoaded('attachments', function () {
|
||||
return InterventionAttachmentResource::collection($this->attachments);
|
||||
}),
|
||||
'notifications' => $this->whenLoaded('notifications', function () {
|
||||
return InterventionNotificationResource::collection($this->notifications);
|
||||
})
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Product;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class ProductCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->collection,
|
||||
'pagination' => [
|
||||
'current_page' => $this->currentPage(),
|
||||
'from' => $this->firstItem(),
|
||||
'last_page' => $this->lastPage(),
|
||||
'per_page' => $this->perPage(),
|
||||
'to' => $this->lastItem(),
|
||||
'total' => $this->total(),
|
||||
],
|
||||
'summary' => [
|
||||
'total_products' => $this->collection->count(),
|
||||
'low_stock_products' => $this->collection->filter(function ($product) {
|
||||
return $product->stock_actuel <= $product->stock_minimum;
|
||||
})->count(),
|
||||
'total_value' => $this->collection->sum(function ($product) {
|
||||
return $product->stock_actuel * $product->prix_unitaire;
|
||||
}),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function with(Request $request): array
|
||||
{
|
||||
return [
|
||||
'status' => 'success',
|
||||
'message' => 'Produits récupérés avec succès',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Product;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ProductResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'nom' => $this->nom,
|
||||
'reference' => $this->reference,
|
||||
'categorie_id' => $this->categorie_id,
|
||||
'fabricant' => $this->fabricant,
|
||||
'stock_actuel' => $this->stock_actuel,
|
||||
'stock_minimum' => $this->stock_minimum,
|
||||
'unite' => $this->unite,
|
||||
'prix_unitaire' => $this->prix_unitaire,
|
||||
'date_expiration' => $this->date_expiration?->format('Y-m-d'),
|
||||
'numero_lot' => $this->numero_lot,
|
||||
'conditionnement' => [
|
||||
'nom' => $this->conditionnement_nom,
|
||||
'quantite' => $this->conditionnement_quantite,
|
||||
'unite' => $this->conditionnement_unite,
|
||||
],
|
||||
'media' => [
|
||||
'photo_url' => $this->photo_url,
|
||||
'fiche_technique_url' => $this->fiche_technique_url,
|
||||
],
|
||||
'is_low_stock' => $this->stock_actuel <= $this->stock_minimum,
|
||||
'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
|
||||
'fournisseur' => $this->whenLoaded('fournisseur', function() {
|
||||
return $this->fournisseur ? [
|
||||
'id' => $this->fournisseur->id,
|
||||
'name' => $this->fournisseur->name,
|
||||
'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;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public function with(Request $request): array
|
||||
{
|
||||
return [
|
||||
'status' => 'success',
|
||||
'message' => 'Produit récupéré avec succès',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\ProductCategory;
|
||||
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class ProductCategoryCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
if ($this->resource instanceof \Illuminate\Pagination\LengthAwarePaginator) {
|
||||
return [
|
||||
'data' => ProductCategoryResource::collection($this->collection),
|
||||
'pagination' => [
|
||||
'current_page' => $this->currentPage(),
|
||||
'from' => $this->firstItem(),
|
||||
'last_page' => $this->lastPage(),
|
||||
'per_page' => $this->perPage(),
|
||||
'to' => $this->lastItem(),
|
||||
'total' => $this->total(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'data' => ProductCategoryResource::collection($this->collection),
|
||||
'count' => $this->collection->count(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\ProductCategory;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ProductCategoryResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'parent_id' => $this->parent_id,
|
||||
'code' => $this->code,
|
||||
'name' => $this->name,
|
||||
'description' => $this->description,
|
||||
'active' => $this->active,
|
||||
'path' => $this->path,
|
||||
'has_children' => $this->hasChildren(),
|
||||
'has_products' => $this->hasProducts(),
|
||||
'children_count' => $this->children()->count(),
|
||||
'products_count' => $this->products()->count(),
|
||||
|
||||
// Parent information
|
||||
'parent' => $this->whenLoaded('parent', function () {
|
||||
return new ProductCategoryResource($this->parent);
|
||||
}),
|
||||
|
||||
// Children information
|
||||
'children' => $this->whenLoaded('children', function () {
|
||||
return ProductCategoryResource::collection($this->children);
|
||||
}),
|
||||
|
||||
// Relationships
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
86
thanasoft-back/app/Models/Client.php
Normal file
86
thanasoft-back/app/Models/Client.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Client extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'vat_number',
|
||||
'siret',
|
||||
'email',
|
||||
'phone',
|
||||
'billing_address_line1',
|
||||
'billing_address_line2',
|
||||
'billing_postal_code',
|
||||
'billing_city',
|
||||
'billing_country_code',
|
||||
'group_id',
|
||||
'notes',
|
||||
'is_active',
|
||||
// 'default_tva_rate_id',
|
||||
'client_category_id',
|
||||
'user_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function commercial(): ?string
|
||||
{
|
||||
return $this->user ? $this->user->name : 'Système';
|
||||
}
|
||||
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ClientCategory::class, 'client_category_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the human-readable label for the client type.
|
||||
*/
|
||||
public function getTypeLabel(): string
|
||||
{
|
||||
return $this->category ? $this->category->name : 'Non catégorisé';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full billing address as a string.
|
||||
*/
|
||||
public function getBillingAddressAttribute(): ?string
|
||||
{
|
||||
$parts = array_filter([
|
||||
$this->billing_address_line1,
|
||||
$this->billing_address_line2,
|
||||
$this->billing_postal_code ? $this->billing_postal_code . ' ' . $this->billing_city : $this->billing_city,
|
||||
$this->billing_country_code,
|
||||
]);
|
||||
|
||||
return !empty($parts) ? implode(', ', $parts) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file attachments for the client (polymorphic).
|
||||
*/
|
||||
public function fileAttachments()
|
||||
{
|
||||
return $this->morphMany(FileAttachment::class, 'attachable')->orderBy('sort_order');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the files attached to this client.
|
||||
*/
|
||||
public function attachedFiles()
|
||||
{
|
||||
return $this->fileAttachments()->with('file');
|
||||
}
|
||||
}
|
||||
29
thanasoft-back/app/Models/ClientCategory.php
Normal file
29
thanasoft-back/app/Models/ClientCategory.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class ClientCategory extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
'is_active',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the clients for the category.
|
||||
*/
|
||||
public function clients(): HasMany
|
||||
{
|
||||
return $this->hasMany(Client::class);
|
||||
}
|
||||
}
|
||||
10
thanasoft-back/app/Models/ClientContact.php
Normal file
10
thanasoft-back/app/Models/ClientContact.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ClientContact extends Model
|
||||
{
|
||||
//
|
||||
}
|
||||
13
thanasoft-back/app/Models/ClientGroup.php
Normal file
13
thanasoft-back/app/Models/ClientGroup.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ClientGroup extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
}
|
||||
27
thanasoft-back/app/Models/ClientLocation.php
Normal file
27
thanasoft-back/app/Models/ClientLocation.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ClientLocation extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'client_id',
|
||||
'name',
|
||||
'address_line1',
|
||||
'address_line2',
|
||||
'postal_code',
|
||||
'city',
|
||||
'country_code',
|
||||
'gps_lat',
|
||||
'gps_lng',
|
||||
'is_default',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_default' => 'boolean',
|
||||
'gps_lat' => 'decimal:8',
|
||||
'gps_lng' => 'decimal:8',
|
||||
];
|
||||
}
|
||||
44
thanasoft-back/app/Models/Contact.php
Normal file
44
thanasoft-back/app/Models/Contact.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Contact extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'phone',
|
||||
'mobile',
|
||||
'position',
|
||||
'notes',
|
||||
'is_primary',
|
||||
'client_id',
|
||||
'fournisseur_id'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_primary' => 'boolean',
|
||||
];
|
||||
|
||||
public function client(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Client::class);
|
||||
}
|
||||
|
||||
public function fournisseur(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Fournisseur::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contact's full name.
|
||||
*/
|
||||
public function getFullNameAttribute(): string
|
||||
{
|
||||
return trim("{$this->first_name} {$this->last_name}");
|
||||
}
|
||||
}
|
||||
75
thanasoft-back/app/Models/Deceased.php
Normal file
75
thanasoft-back/app/Models/Deceased.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Deceased extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'deceased';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'last_name',
|
||||
'first_name',
|
||||
'birth_date',
|
||||
'death_date',
|
||||
'place_of_death',
|
||||
'notes'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'birth_date' => 'date',
|
||||
'death_date' => 'date',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the documents associated with the deceased.
|
||||
*/
|
||||
public function documents(): HasMany
|
||||
{
|
||||
return $this->hasMany(DeceasedDocument::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the interventions associated with the deceased.
|
||||
*/
|
||||
public function interventions(): HasMany
|
||||
{
|
||||
return $this->hasMany(Intervention::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file attachments for the deceased (polymorphic).
|
||||
*/
|
||||
public function fileAttachments()
|
||||
{
|
||||
return $this->morphMany(FileAttachment::class, 'attachable')->orderBy('sort_order');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the files attached to this deceased.
|
||||
*/
|
||||
public function attachedFiles()
|
||||
{
|
||||
return $this->fileAttachments()->with('file');
|
||||
}
|
||||
}
|
||||
49
thanasoft-back/app/Models/DeceasedDocument.php
Normal file
49
thanasoft-back/app/Models/DeceasedDocument.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class DeceasedDocument extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'deceased_id',
|
||||
'doc_type',
|
||||
'file_id',
|
||||
'generated_at'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'generated_at' => 'datetime'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the deceased associated with the document.
|
||||
*/
|
||||
public function deceased(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Deceased::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file associated with the document.
|
||||
*/
|
||||
public function file(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(File::class);
|
||||
}
|
||||
}
|
||||
85
thanasoft-back/app/Models/Employee.php
Normal file
85
thanasoft-back/app/Models/Employee.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Employee extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'phone',
|
||||
'job_title',
|
||||
'hire_date',
|
||||
'active',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
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 . '%');
|
||||
});
|
||||
}
|
||||
}
|
||||
98
thanasoft-back/app/Models/File.php
Normal file
98
thanasoft-back/app/Models/File.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class File extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'file_name',
|
||||
'mime_type',
|
||||
'size_bytes',
|
||||
'storage_uri',
|
||||
'sha256',
|
||||
'uploaded_by',
|
||||
'uploaded_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'size_bytes' => 'integer',
|
||||
'uploaded_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'uploaded_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the uploader name.
|
||||
*/
|
||||
public function getUploaderName(): string
|
||||
{
|
||||
return $this->user ? $this->user->name : 'Utilisateur inconnu';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted file size.
|
||||
*/
|
||||
public function getFormattedSize(): string
|
||||
{
|
||||
if (!$this->size_bytes) {
|
||||
return '0 B';
|
||||
}
|
||||
|
||||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
$bytes = $this->size_bytes;
|
||||
$i = 0;
|
||||
|
||||
while ($bytes >= 1024 && $i < count($units) - 1) {
|
||||
$bytes /= 1024;
|
||||
$i++;
|
||||
}
|
||||
|
||||
return round($bytes, 2) . ' ' . $units[$i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file extension from the file name.
|
||||
*/
|
||||
public function getExtension(): string
|
||||
{
|
||||
return pathinfo($this->file_name, PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file is an image.
|
||||
*/
|
||||
public function isImage(): bool
|
||||
{
|
||||
return str_starts_with($this->mime_type ?? '', 'image/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file is a PDF.
|
||||
*/
|
||||
public function isPdf(): bool
|
||||
{
|
||||
return $this->mime_type === 'application/pdf';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the organized storage path (e.g., client/devis/filename.pdf).
|
||||
*/
|
||||
public function getOrganizedPath(): string
|
||||
{
|
||||
// Extract directory structure from storage_uri
|
||||
$path = $this->storage_uri;
|
||||
|
||||
// Remove storage path prefix if present
|
||||
if (str_contains($path, 'storage/')) {
|
||||
$path = substr($path, strpos($path, 'storage/') + 8);
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
141
thanasoft-back/app/Models/FileAttachment.php
Normal file
141
thanasoft-back/app/Models/FileAttachment.php
Normal file
@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\MorphTo;
|
||||
|
||||
class FileAttachment extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'file_attachments';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'file_id',
|
||||
'label',
|
||||
'attachable_type',
|
||||
'attachable_id',
|
||||
'sort_order',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the parent attachable model (polymorphic).
|
||||
*/
|
||||
public function attachable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file associated with the attachment.
|
||||
*/
|
||||
public function file(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(File::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the intervention associated with the attachment (legacy support).
|
||||
*/
|
||||
public function intervention(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Intervention::class, 'attachable_id')->where('attachable_type', Intervention::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client associated with the attachment.
|
||||
*/
|
||||
public function client(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Client::class, 'attachable_id')->where('attachable_type', Client::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the deceased associated with the attachment.
|
||||
*/
|
||||
public function deceased(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Deceased::class, 'attachable_id')->where('attachable_type', Deceased::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to filter by attachable type.
|
||||
*/
|
||||
public function scopeOfType($query, string $type)
|
||||
{
|
||||
return $query->where('attachable_type', $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to filter by attachable model.
|
||||
*/
|
||||
public function scopeFor($query, Model $model)
|
||||
{
|
||||
return $query->where('attachable_type', get_class($model))
|
||||
->where('attachable_id', $model->getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the display name of the attached model.
|
||||
*/
|
||||
public function getAttachableNameAttribute(): string
|
||||
{
|
||||
return $this->attachable?->name ?? $this->attachable?->file_name ?? 'Unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL for downloading the attached file.
|
||||
*/
|
||||
public function getDownloadUrlAttribute(): string
|
||||
{
|
||||
return url('/api/files/' . $this->file_id . '/download');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this attachment belongs to an intervention.
|
||||
*/
|
||||
public function isForIntervention(): bool
|
||||
{
|
||||
return $this->attachable_type === Intervention::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this attachment belongs to a client.
|
||||
*/
|
||||
public function isForClient(): bool
|
||||
{
|
||||
return $this->attachable_type === Client::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this attachment belongs to a deceased.
|
||||
*/
|
||||
public function isForDeceased(): bool
|
||||
{
|
||||
return $this->attachable_type === Deceased::class;
|
||||
}
|
||||
}
|
||||
60
thanasoft-back/app/Models/Fournisseur.php
Normal file
60
thanasoft-back/app/Models/Fournisseur.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Fournisseur extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'vat_number',
|
||||
'siret',
|
||||
'email',
|
||||
'phone',
|
||||
'billing_address_line1',
|
||||
'billing_address_line2',
|
||||
'billing_postal_code',
|
||||
'billing_city',
|
||||
'billing_country_code',
|
||||
'notes',
|
||||
'is_active',
|
||||
'user_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function products(): HasMany
|
||||
{
|
||||
return $this->hasMany(Product::class);
|
||||
}
|
||||
|
||||
public function commercial(): ?string
|
||||
{
|
||||
return $this->user ? $this->user->name : 'Système';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full billing address as a string.
|
||||
*/
|
||||
public function getBillingAddressAttribute(): ?string
|
||||
{
|
||||
$parts = array_filter([
|
||||
$this->billing_address_line1,
|
||||
$this->billing_address_line2,
|
||||
$this->billing_postal_code ? $this->billing_postal_code . ' ' . $this->billing_city : $this->billing_city,
|
||||
$this->billing_country_code,
|
||||
]);
|
||||
|
||||
return !empty($parts) ? implode(', ', $parts) : null;
|
||||
}
|
||||
}
|
||||
147
thanasoft-back/app/Models/Intervention.php
Normal file
147
thanasoft-back/app/Models/Intervention.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Intervention extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'client_id',
|
||||
'deceased_id',
|
||||
'order_giver',
|
||||
'location_id',
|
||||
'type',
|
||||
'scheduled_at',
|
||||
'duration_min',
|
||||
'status',
|
||||
'attachments_count',
|
||||
'notes',
|
||||
'created_by'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'scheduled_at' => 'datetime',
|
||||
'attachments_count' => 'integer'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the client associated with the intervention.
|
||||
*/
|
||||
public function client(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Client::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the deceased associated with the intervention.
|
||||
*/
|
||||
public function deceased(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Deceased::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location associated with the intervention.
|
||||
*/
|
||||
public function location(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ClientLocation::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the practitioners assigned to the intervention.
|
||||
*/
|
||||
public function practitioners(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Thanatopractitioner::class, 'intervention_practitioner', 'intervention_id', 'practitioner_id')
|
||||
->withPivot('role', 'assigned_at')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for practitioners relationship (for backward compatibility).
|
||||
*/
|
||||
public function assignedPractitioner(): BelongsToMany
|
||||
{
|
||||
return $this->practitioners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the principal practitioner assigned to the intervention.
|
||||
*/
|
||||
public function principalPractitioner(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Thanatopractitioner::class, 'intervention_practitioner', 'intervention_id', 'practitioner_id')
|
||||
->wherePivot('role', 'principal')
|
||||
->withPivot('role', 'assigned_at')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the assistant practitioners assigned to the intervention.
|
||||
*/
|
||||
public function assistantPractitioners(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Thanatopractitioner::class, 'intervention_practitioner', 'intervention_id', 'practitioner_id')
|
||||
->wherePivot('role', 'assistant')
|
||||
->withPivot('role', 'assigned_at')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user who created the intervention.
|
||||
*/
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the intervention (legacy support).
|
||||
*/
|
||||
public function attachments(): HasMany
|
||||
{
|
||||
return $this->hasMany(InterventionAttachment::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file attachments for the intervention (polymorphic).
|
||||
*/
|
||||
public function fileAttachments()
|
||||
{
|
||||
return $this->morphMany(FileAttachment::class, 'attachable')->orderBy('sort_order');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the files attached to this intervention.
|
||||
*/
|
||||
public function attachedFiles()
|
||||
{
|
||||
return $this->fileAttachments()->with('file');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notifications for the intervention.
|
||||
*/
|
||||
public function notifications(): HasMany
|
||||
{
|
||||
return $this->hasMany(InterventionNotification::class);
|
||||
}
|
||||
}
|
||||
39
thanasoft-back/app/Models/InterventionAttachment.php
Normal file
39
thanasoft-back/app/Models/InterventionAttachment.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class InterventionAttachment extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'intervention_id',
|
||||
'file_id',
|
||||
'label'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the intervention associated with the attachment.
|
||||
*/
|
||||
public function intervention(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Intervention::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file associated with the attachment.
|
||||
*/
|
||||
public function file(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(File::class);
|
||||
}
|
||||
}
|
||||
44
thanasoft-back/app/Models/InterventionNotification.php
Normal file
44
thanasoft-back/app/Models/InterventionNotification.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class InterventionNotification extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'intervention_id',
|
||||
'channel',
|
||||
'destination',
|
||||
'payload',
|
||||
'status',
|
||||
'sent_at'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'payload' => 'array',
|
||||
'sent_at' => 'datetime'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the intervention associated with the notification.
|
||||
*/
|
||||
public function intervention(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Intervention::class);
|
||||
}
|
||||
}
|
||||
72
thanasoft-back/app/Models/InterventionPractitioner.php
Normal file
72
thanasoft-back/app/Models/InterventionPractitioner.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class InterventionPractitioner extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'intervention_practitioner';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'intervention_id',
|
||||
'practitioner_id',
|
||||
'role',
|
||||
'assigned_at'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'assigned_at' => 'datetime'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the intervention that owns the practitioner assignment.
|
||||
*/
|
||||
public function intervention(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Intervention::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the practitioner assigned to the intervention.
|
||||
*/
|
||||
public function practitioner(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Thanatopractitioner::class, 'practitioner_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to get principal practitioners.
|
||||
*/
|
||||
public function scopePrincipal($query)
|
||||
{
|
||||
return $query->where('role', 'principal');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to get assistant practitioners.
|
||||
*/
|
||||
public function scopeAssistant($query)
|
||||
{
|
||||
return $query->where('role', 'assistant');
|
||||
}
|
||||
}
|
||||
86
thanasoft-back/app/Models/PractitionerDocument.php
Normal file
86
thanasoft-back/app/Models/PractitionerDocument.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class PractitionerDocument extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'practitioner_id',
|
||||
'doc_type',
|
||||
'file_id',
|
||||
'issue_date',
|
||||
'expiry_date',
|
||||
'status',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user