add back:
This commit is contained in:
parent
0ea8f1866b
commit
e55cc5253e
88
th
88
th
@ -1,88 +0,0 @@
|
||||
<template>
|
||||
<div :class="['stock-badge', stockClass]">
|
||||
<div class="stock-info">
|
||||
<span class="stock-label">{{ label }}</span>
|
||||
<span class="stock-value">{{ value }} {{ unit }}</span>
|
||||
</div>
|
||||
<div v-if="showThreshold && threshold" class="stock-threshold">
|
||||
<small>Min: {{ threshold }} {{ unit }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
unit: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
threshold: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
showThreshold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const stockClass = computed(() => {
|
||||
if (props.threshold && props.value <= props.threshold) {
|
||||
return 'stock-low';
|
||||
}
|
||||
return 'stock-normal';
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stock-badge {
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e5e7eb;
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
.stock-low {
|
||||
background-color: #fef2f2;
|
||||
border-color: #fecaca;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.stock-normal {
|
||||
background-color: #f0fdf4;
|
||||
border-color: #bbf7d0;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.stock-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.stock-label {
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.stock-value {
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.stock-threshold {
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
611
thanas
611
thanas
@ -1,259 +1,370 @@
|
||||
import { defineStore } from "pinia";
|
||||
import ProductService from "@/services/product";
|
||||
import { ref, computed } from "vue";
|
||||
import EmployeeService from "@/services/employee";
|
||||
|
||||
export const useProductStore = defineStore("product", {
|
||||
state: () => ({
|
||||
products: [],
|
||||
currentProduct: null,
|
||||
loading: false,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
meta: {
|
||||
import type {
|
||||
Employee,
|
||||
CreateEmployeePayload,
|
||||
UpdateEmployeePayload,
|
||||
EmployeeListResponse,
|
||||
} from "@/services/employee";
|
||||
|
||||
export const useEmployeeStore = defineStore("employee", () => {
|
||||
// State
|
||||
const employees = ref<Employee[]>([]);
|
||||
const currentEmployee = ref<Employee | null>(null);
|
||||
const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
const searchResults = ref<Employee[]>([]);
|
||||
|
||||
// Pagination state
|
||||
const pagination = ref({
|
||||
current_page: 1,
|
||||
last_page: 1,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
from: 0,
|
||||
to: 0,
|
||||
});
|
||||
|
||||
// Getters
|
||||
const allEmployees = computed(() => employees.value);
|
||||
const activeEmployees = computed(() =>
|
||||
employees.value.filter((employee) => employee.is_active)
|
||||
);
|
||||
const inactiveEmployees = computed(() =>
|
||||
employees.value.filter((employee) => !employee.is_active)
|
||||
);
|
||||
const isLoading = computed(() => loading.value);
|
||||
const hasError = computed(() => error.value !== null);
|
||||
const getError = computed(() => error.value);
|
||||
const getEmployeeById = computed(
|
||||
() => (id: number) => employees.value.find((employee) => employee.id === id)
|
||||
);
|
||||
const getPagination = computed(() => pagination.value);
|
||||
|
||||
// Actions
|
||||
const setLoading = (isLoading: boolean) => {
|
||||
loading.value = isLoading;
|
||||
};
|
||||
|
||||
const setError = (err: string | null) => {
|
||||
error.value = err;
|
||||
};
|
||||
|
||||
const clearError = () => {
|
||||
error.value = null;
|
||||
};
|
||||
|
||||
const setEmployees = (newEmployees: Employee[]) => {
|
||||
employees.value = newEmployees;
|
||||
};
|
||||
|
||||
const setCurrentEmployee = (employee: Employee | null) => {
|
||||
currentEmployee.value = employee;
|
||||
};
|
||||
|
||||
const setSearchEmployee = (searchEmployee: Employee[]) => {
|
||||
searchResults.value = searchEmployee;
|
||||
};
|
||||
|
||||
const setPagination = (meta: any) => {
|
||||
if (meta) {
|
||||
pagination.value = {
|
||||
current_page: meta.current_page || 1,
|
||||
last_page: meta.last_page || 1,
|
||||
per_page: meta.per_page || 10,
|
||||
total: meta.total || 0,
|
||||
from: meta.from || 0,
|
||||
to: meta.to || 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch all employees with optional pagination and filters
|
||||
*/
|
||||
const fetchEmployees = async (params?: {
|
||||
page?: number;
|
||||
per_page?: number;
|
||||
search?: string;
|
||||
active?: boolean;
|
||||
sort_by?: string;
|
||||
sort_direction?: string;
|
||||
}) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await EmployeeService.getAllEmployees(params);
|
||||
setEmployees(response.data);
|
||||
setPagination(response.pagination);
|
||||
return response;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to fetch employees";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch a single employee by ID
|
||||
*/
|
||||
const fetchEmployee = async (id: number) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await EmployeeService.getEmployee(id);
|
||||
setCurrentEmployee(response.data);
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to fetch employee";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new employee
|
||||
*/
|
||||
const createEmployee = async (payload: CreateEmployeePayload) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await EmployeeService.createEmployee(payload);
|
||||
// Add the new employee to the list
|
||||
employees.value.push(response.data);
|
||||
setCurrentEmployee(response.data);
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to create employee";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update an existing employee
|
||||
*/
|
||||
const updateEmployee = async (payload: UpdateEmployeePayload) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
console.log(payload);
|
||||
const response = await EmployeeService.updateEmployee(payload);
|
||||
const updatedEmployee = response.data;
|
||||
|
||||
// Update in the employees list
|
||||
const index = employees.value.findIndex(
|
||||
(employee) => employee.id === updatedEmployee.id
|
||||
);
|
||||
if (index !== -1) {
|
||||
employees.value[index] = updatedEmployee;
|
||||
}
|
||||
|
||||
// Update current employee if it's the one being edited
|
||||
if (
|
||||
currentEmployee.value &&
|
||||
currentEmployee.value.id === updatedEmployee.id
|
||||
) {
|
||||
setCurrentEmployee(updatedEmployee);
|
||||
}
|
||||
|
||||
return updatedEmployee;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to update employee";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete an employee
|
||||
*/
|
||||
const deleteEmployee = async (id: number) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await EmployeeService.deleteEmployee(id);
|
||||
|
||||
// Remove from the employees list
|
||||
employees.value = employees.value.filter(
|
||||
(employee) => employee.id !== id
|
||||
);
|
||||
|
||||
// Clear current employee if it's the one being deleted
|
||||
if (currentEmployee.value && currentEmployee.value.id === id) {
|
||||
setCurrentEmployee(null);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to delete employee";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Search employees
|
||||
*/
|
||||
const searchEmployees = async (query: string) => {
|
||||
setLoading(true);
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const results = await EmployeeService.searchEmployees(query);
|
||||
setSearchEmployee(results);
|
||||
return results;
|
||||
} catch (err) {
|
||||
error.value = "Erreur lors de la recherche des employés";
|
||||
console.error("Error searching employees:", err);
|
||||
setSearchEmployee([]);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle employee active status
|
||||
*/
|
||||
const toggleEmployeeStatus = async (id: number, isActive: boolean) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await EmployeeService.toggleEmployeeStatus(id, isActive);
|
||||
const updatedEmployee = response.data;
|
||||
|
||||
// Update in the employees list
|
||||
const index = employees.value.findIndex((employee) => employee.id === id);
|
||||
if (index !== -1) {
|
||||
employees.value[index] = updatedEmployee;
|
||||
}
|
||||
|
||||
// Update current employee if it's the one being toggled
|
||||
if (currentEmployee.value && currentEmployee.value.id === id) {
|
||||
setCurrentEmployee(updatedEmployee);
|
||||
}
|
||||
|
||||
return updatedEmployee;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to toggle employee status";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get employee statistics
|
||||
*/
|
||||
const fetchStatistics = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await EmployeeService.getStatistics();
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to fetch statistics";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear current employee
|
||||
*/
|
||||
const clearCurrentEmployee = () => {
|
||||
setCurrentEmployee(null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear all state
|
||||
*/
|
||||
const clearStore = () => {
|
||||
employees.value = [];
|
||||
currentEmployee.value = null;
|
||||
error.value = null;
|
||||
pagination.value = {
|
||||
current_page: 1,
|
||||
last_page: 1,
|
||||
per_page: 15,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
from: 1,
|
||||
from: 0,
|
||||
to: 0,
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
getters: {
|
||||
lowStockProducts: (state) =>
|
||||
state.products.filter((product) => product.is_low_stock),
|
||||
expiringProducts: (state) =>
|
||||
state.products.filter((product) =>
|
||||
product.date_expiration &&
|
||||
new Date(product.date_expiration) <= new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
|
||||
),
|
||||
categories: (state) => {
|
||||
const categorySet = new Set(state.products.map(product => product.categorie).filter(Boolean));
|
||||
return Array.from(categorySet).sort();
|
||||
},
|
||||
totalProducts: (state) => state.meta.total,
|
||||
totalValue: (state) =>
|
||||
state.products.reduce((total, product) =>
|
||||
total + (product.stock_actuel * product.prix_unitaire), 0
|
||||
),
|
||||
},
|
||||
return {
|
||||
// State
|
||||
employees,
|
||||
currentEmployee,
|
||||
loading,
|
||||
error,
|
||||
searchResults,
|
||||
|
||||
actions: {
|
||||
async fetchProducts(params = {}) {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await ProductService.getAllProducts(params);
|
||||
|
||||
this.products = response.data;
|
||||
this.meta = {
|
||||
current_page: response.pagination.current_page,
|
||||
last_page: response.pagination.last_page,
|
||||
per_page: response.pagination.per_page,
|
||||
total: response.pagination.total,
|
||||
from: response.pagination.from,
|
||||
to: response.pagination.to,
|
||||
};
|
||||
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
this.error = error?.message || "Erreur lors du chargement des produits";
|
||||
throw error;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
// Getters
|
||||
allEmployees,
|
||||
activeEmployees,
|
||||
inactiveEmployees,
|
||||
isLoading,
|
||||
hasError,
|
||||
getError,
|
||||
getEmployeeById,
|
||||
getPagination,
|
||||
|
||||
async createProduct(productData: any) {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await ProductService.createProduct(productData);
|
||||
const product = response.data;
|
||||
|
||||
// Add the new product to the beginning of the list
|
||||
this.products.unshift(product);
|
||||
this.meta.total += 1;
|
||||
|
||||
return product;
|
||||
} catch (error: any) {
|
||||
this.error = error?.message || "Erreur lors de la création du produit";
|
||||
throw error;
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async updateProduct(id: number, productData: any) {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await ProductService.updateProduct(id, productData);
|
||||
const updatedProduct = response.data;
|
||||
|
||||
// Update the product in the list
|
||||
const index = this.products.findIndex((p) => p.id === id);
|
||||
if (index !== -1) {
|
||||
this.products[index] = updatedProduct;
|
||||
}
|
||||
|
||||
// Update current product if it matches
|
||||
if (this.currentProduct?.id === id) {
|
||||
this.currentProduct = updatedProduct;
|
||||
}
|
||||
|
||||
return updatedProduct;
|
||||
} catch (error: any) {
|
||||
this.error = error?.message || "Erreur lors de la mise à jour du produit";
|
||||
throw error;
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async deleteProduct(id: number) {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
await ProductService.deleteProduct(id);
|
||||
|
||||
// Remove the product from the list
|
||||
this.products = this.products.filter((p) => p.id !== id);
|
||||
this.meta.total -= 1;
|
||||
|
||||
// Clear current product if it was deleted
|
||||
if (this.currentProduct?.id === id) {
|
||||
this.currentProduct = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
this.error = error?.message || "Erreur lors de la suppression du produit";
|
||||
throw error;
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async fetchProduct(id: number) {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await ProductService.getProduct(id);
|
||||
this.currentProduct = response.data;
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
this.error = error?.message || "Erreur lors du chargement du produit";
|
||||
throw error;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async searchProducts(searchTerm: string, exact = false) {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await ProductService.searchProducts(searchTerm, exact);
|
||||
|
||||
// Update current products list with search results
|
||||
this.products = response.data;
|
||||
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
this.error = error?.message || "Erreur lors de la recherche";
|
||||
throw error;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async fetchLowStockProducts() {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await ProductService.getLowStockProducts();
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
this.error = error?.message || "Erreur lors du chargement des produits à stock faible";
|
||||
throw error;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async fetchProductsByCategory(category: string) {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await ProductService.getProductsByCategory(category);
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
this.error = error?.message || "Erreur lors du chargement des produits par catégorie";
|
||||
throw error;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async getProductStatistics() {
|
||||
try {
|
||||
const response = await ProductService.getProductStatistics();
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
this.error = error?.message || "Erreur lors du chargement des statistiques";
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async updateStock(productId: number, newStock: number) {
|
||||
try {
|
||||
const response = await ProductService.updateStock(productId, newStock);
|
||||
|
||||
// Update the product in the list
|
||||
const index = this.products.findIndex((p) => p.id === productId);
|
||||
if (index !== -1) {
|
||||
this.products[index] = response.data;
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
this.error = error?.message || "Erreur lors de la mise à jour du stock";
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
resetState() {
|
||||
this.products = [];
|
||||
this.currentProduct = null;
|
||||
this.error = null;
|
||||
this.loading = false;
|
||||
this.isLoading = false;
|
||||
},
|
||||
|
||||
// Local filtering functions
|
||||
filterByCategory(category: string) {
|
||||
if (!category) return this.products;
|
||||
return this.products.filter((product: any) => product.categorie === category);
|
||||
},
|
||||
|
||||
filterByLowStock() {
|
||||
return this.products.filter((product: any) => product.is_low_stock);
|
||||
},
|
||||
|
||||
filterByExpiration(days = 30) {
|
||||
const cutoffDate = new Date(Date.now() + days * 24 * 60 * 60 * 1000);
|
||||
return this.products.filter((product: any) =>
|
||||
product.date_expiration &&
|
||||
new Date(product.date_expiration) <= cutoffDate
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
// Actions
|
||||
fetchEmployees,
|
||||
fetchEmployee,
|
||||
createEmployee,
|
||||
updateEmployee,
|
||||
deleteEmployee,
|
||||
searchEmployees,
|
||||
toggleEmployeeStatus,
|
||||
fetchStatistics,
|
||||
clearCurrentEmployee,
|
||||
clearStore,
|
||||
clearError,
|
||||
};
|
||||
});
|
||||
|
||||
179
thanasoft
179
thanasoft
@ -1,64 +1,141 @@
|
||||
<template>
|
||||
<div class="price-display">
|
||||
<span class="price-amount">{{ formattedPrice }}</span>
|
||||
<span v-if="currency" class="price-currency">{{ currency }}</span>
|
||||
<span v-if="unit" class="price-unit">/{{ unit }}</span>
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header pb-0">
|
||||
<div class="d-flex align-items-center">
|
||||
<p class="mb-0 font-weight-bold text-lg">Gestion des Employés</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body px-0 pt-0 pb-2">
|
||||
<div class="table-responsive p-0">
|
||||
<!-- Filter and Action Bar -->
|
||||
<div
|
||||
class="d-flex justify-content-between align-items-center mb-4 px-4"
|
||||
>
|
||||
<div class="d-flex align-items-center">
|
||||
<slot name="select-filter">
|
||||
<filter-table />
|
||||
</slot>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<slot name="employee-other-action">
|
||||
<table-action />
|
||||
</slot>
|
||||
<slot name="employee-new-action">
|
||||
<add-button text="Ajouter" @click="goToEmployee" />
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<div class="employee-content">
|
||||
<slot name="employee-table">
|
||||
<!-- Default table slot - will be overridden by specific implementations -->
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, computed } from 'vue';
|
||||
import FilterTable from "@/components/molecules/Tables/FilterTable.vue";
|
||||
import TableAction from "@/components/molecules/Tables/TableAction.vue";
|
||||
import addButton from "@/components/molecules/new-button/addButton.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const props = defineProps({
|
||||
price: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
currency: {
|
||||
type: String,
|
||||
default: '€'
|
||||
},
|
||||
unit: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
showDecimals: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
const router = useRouter();
|
||||
|
||||
const formattedPrice = computed(() => {
|
||||
const decimals = props.showDecimals ? 2 : 0;
|
||||
return props.price.toLocaleString('fr-FR', {
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals
|
||||
});
|
||||
});
|
||||
const goToEmployee = () => {
|
||||
router.push({
|
||||
name: "Creation employé",
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.price-display {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.container-fluid {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.price-amount {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #059669;
|
||||
}
|
||||
.card {
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.price-currency {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: #059669;
|
||||
}
|
||||
.card-header {
|
||||
background-color: transparent;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.price-unit {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
font-weight: 400;
|
||||
}
|
||||
</style>
|
||||
.card-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.employee-content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.d-flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.align-items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.justify-content-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.px-4 {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.font-weight-bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: #5e72e4 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container-fluid {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.d-flex {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.d-flex.gap-2 {
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
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.
|
||||
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,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -31,7 +31,7 @@ class ProductController extends Controller
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$filters = [
|
||||
'search' => $request->get('search'),
|
||||
'categorie' => $request->get('categorie'),
|
||||
'categorie' => $request->get('categorie_id'),
|
||||
'fournisseur_id' => $request->get('fournisseur_id'),
|
||||
'low_stock' => $request->get('low_stock'),
|
||||
'expiring_soon' => $request->get('expiring_soon'),
|
||||
@ -196,16 +196,16 @@ class ProductController extends Controller
|
||||
public function byCategory(Request $request): ProductCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$category = $request->get('category');
|
||||
$categoryId = $request->get('category_id');
|
||||
$perPage = $request->get('per_page', 15);
|
||||
|
||||
if (empty($category)) {
|
||||
if (empty($categoryId)) {
|
||||
return response()->json([
|
||||
'message' => 'Le paramètre "category" est requis.',
|
||||
'message' => 'Le paramètre "category_id" est requis.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$products = $this->productRepository->getByCategory($category, $perPage);
|
||||
$products = $this->productRepository->getByCategory($categoryId, $perPage);
|
||||
|
||||
return new ProductCollection($products);
|
||||
|
||||
@ -213,7 +213,7 @@ class ProductController extends Controller
|
||||
Log::error('Error fetching products by category: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'category' => $category,
|
||||
'category_id' => $categoryId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
|
||||
@ -0,0 +1,322 @@
|
||||
<?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->find($id);
|
||||
|
||||
if (!$thanatopractitioner) {
|
||||
return response()->json([
|
||||
'message' => 'Thanatopractitioner non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new ThanatopractitionerResource($thanatopractitioner);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching thanatopractitioner: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'thanatopractitioner_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du thanatopractitioner.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a thanatopractitioner by employee ID.
|
||||
*/
|
||||
public function findByEmployee(string $employeeId): ThanatopractitionerResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$thanatopractitioner = $this->thanatopractitionerRepository->findByEmployeeId((int) $employeeId);
|
||||
|
||||
if (!$thanatopractitioner) {
|
||||
return response()->json([
|
||||
'message' => 'Thanatopractitioner non trouvé pour cet employé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new ThanatopractitionerResource($thanatopractitioner);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching thanatopractitioner by employee: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'employee_id' => $employeeId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du thanatopractitioner.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified thanatopractitioner.
|
||||
*/
|
||||
public function update(UpdateThanatopractitionerRequest $request, string $id): ThanatopractitionerResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->thanatopractitionerRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Thanatopractitioner non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$thanatopractitioner = $this->thanatopractitionerRepository->find($id);
|
||||
return new ThanatopractitionerResource($thanatopractitioner);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating thanatopractitioner: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'thanatopractitioner_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du thanatopractitioner.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified thanatopractitioner.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->thanatopractitionerRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Thanatopractitioner non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Thanatopractitioner supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting thanatopractitioner: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'thanatopractitioner_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du thanatopractitioner.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
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.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -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.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -24,7 +24,7 @@ class StoreProductRequest extends FormRequest
|
||||
return [
|
||||
'nom' => 'required|string|max:255',
|
||||
'reference' => 'required|string|max:100|unique:products,reference',
|
||||
'categorie' => 'required|string|max:191',
|
||||
'categorie_id' => 'required|exists:product_categories,id',
|
||||
'fabricant' => 'nullable|string|max:191',
|
||||
'stock_actuel' => 'required|numeric|min:0',
|
||||
'stock_minimum' => 'required|numeric|min:0',
|
||||
@ -52,9 +52,8 @@ class StoreProductRequest extends FormRequest
|
||||
'reference.string' => 'La référence du produit doit être une chaîne de caractères.',
|
||||
'reference.max' => 'La référence du produit ne peut pas dépasser 100 caractères.',
|
||||
'reference.unique' => 'Cette référence de produit existe déjà.',
|
||||
'categorie.required' => 'La catégorie est obligatoire.',
|
||||
'categorie.string' => 'La catégorie doit être une chaîne de caractères.',
|
||||
'categorie.max' => 'La catégorie ne peut pas dépasser 191 caractères.',
|
||||
'categorie_id.required' => 'La catégorie est obligatoire.',
|
||||
'categorie_id.exists' => 'La catégorie sélectionnée n\'existe pas.',
|
||||
'fabricant.string' => 'Le fabricant doit être une chaîne de caractères.',
|
||||
'fabricant.max' => 'Le fabricant ne peut pas dépasser 191 caractères.',
|
||||
'stock_actuel.required' => 'Le stock actuel est obligatoire.',
|
||||
|
||||
@ -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.',
|
||||
];
|
||||
}
|
||||
}
|
||||
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.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -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.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -26,7 +26,7 @@ class UpdateProductRequest extends FormRequest
|
||||
return [
|
||||
'nom' => 'required|string|max:255',
|
||||
'reference' => "nullable",
|
||||
'categorie' => 'required|string|max:191',
|
||||
'categorie_id' => 'required|exists:product_categories,id',
|
||||
'fabricant' => 'nullable|string|max:191',
|
||||
'stock_actuel' => 'required|numeric|min:0',
|
||||
'stock_minimum' => 'required|numeric|min:0',
|
||||
@ -54,9 +54,8 @@ class UpdateProductRequest extends FormRequest
|
||||
'reference.string' => 'La référence du produit doit être une chaîne de caractères.',
|
||||
'reference.max' => 'La référence du produit ne peut pas dépasser 100 caractères.',
|
||||
'reference.unique' => 'Cette référence de produit existe déjà.',
|
||||
'categorie.required' => 'La catégorie est obligatoire.',
|
||||
'categorie.string' => 'La catégorie doit être une chaîne de caractères.',
|
||||
'categorie.max' => 'La catégorie ne peut pas dépasser 191 caractères.',
|
||||
'categorie_id.required' => 'La catégorie est obligatoire.',
|
||||
'categorie_id.exists' => 'La catégorie sélectionnée n\'existe pas.',
|
||||
'fabricant.string' => 'Le fabricant doit être une chaîne de caractères.',
|
||||
'fabricant.max' => 'Le fabricant ne peut pas dépasser 191 caractères.',
|
||||
'stock_actuel.required' => 'Le stock actuel est obligatoire.',
|
||||
|
||||
@ -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' => [
|
||||
'required',
|
||||
'exists:employees,id',
|
||||
Rule::unique('thanatopractitioners', 'employee_id')->ignore($this->route('thanatopractitioner'))
|
||||
],
|
||||
'diploma_number' => 'nullable|string|max:191',
|
||||
'diploma_date' => 'nullable|date',
|
||||
'authorization_number' => 'nullable|string|max:191',
|
||||
'authorization_issue_date' => 'nullable|date',
|
||||
'authorization_expiry_date' => 'nullable|date|after_or_equal:authorization_issue_date',
|
||||
'notes' => 'nullable|string',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<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,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,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Employee;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
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,
|
||||
'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)
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -18,7 +18,7 @@ class ProductResource extends JsonResource
|
||||
'id' => $this->id,
|
||||
'nom' => $this->nom,
|
||||
'reference' => $this->reference,
|
||||
'categorie' => $this->categorie,
|
||||
'categorie_id' => $this->categorie_id,
|
||||
'fabricant' => $this->fabricant,
|
||||
'stock_actuel' => $this->stock_actuel,
|
||||
'stock_minimum' => $this->stock_minimum,
|
||||
@ -47,6 +47,13 @@ class ProductResource extends JsonResource
|
||||
'email' => $this->fournisseur->email,
|
||||
] : null;
|
||||
}),
|
||||
'category' => $this->whenLoaded('category', function() {
|
||||
return $this->category ? [
|
||||
'id' => $this->category->id,
|
||||
'name' => $this->category->name,
|
||||
'code' => $this->category->code,
|
||||
] : null;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
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 . '%');
|
||||
});
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
@ -11,7 +11,7 @@ class Product extends Model
|
||||
protected $fillable = [
|
||||
'nom',
|
||||
'reference',
|
||||
'categorie',
|
||||
'categorie_id',
|
||||
'fabricant',
|
||||
'stock_actuel',
|
||||
'stock_minimum',
|
||||
@ -48,7 +48,7 @@ class Product extends Model
|
||||
*/
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ProductCategory::class, 'categorie', 'name');
|
||||
return $this->belongsTo(ProductCategory::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -41,7 +41,7 @@ class ProductCategory extends Model
|
||||
*/
|
||||
public function products(): HasMany
|
||||
{
|
||||
return $this->hasMany(Product::class, 'categorie', 'name');
|
||||
return $this->hasMany(Product::class, 'categorie_id');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
85
thanasoft-back/app/Models/Thanatopractitioner.php
Normal file
85
thanasoft-back/app/Models/Thanatopractitioner.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\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Thanatopractitioner extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'employee_id',
|
||||
'diploma_number',
|
||||
'diploma_date',
|
||||
'authorization_number',
|
||||
'authorization_issue_date',
|
||||
'authorization_expiry_date',
|
||||
'notes',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'diploma_date' => 'date',
|
||||
'authorization_issue_date' => 'date',
|
||||
'authorization_expiry_date' => 'date',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the employee that owns the thanatopractitioner.
|
||||
*/
|
||||
public function employee(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Employee::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all documents associated with the thanatopractitioner.
|
||||
*/
|
||||
public function documents(): HasMany
|
||||
{
|
||||
return $this->hasMany(PractitionerDocument::class, 'practitioner_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include practitioners with valid authorization.
|
||||
*/
|
||||
public function scopeWithValidAuthorization($query)
|
||||
{
|
||||
return $query->where('authorization_expiry_date', '>=', now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include practitioners with expired authorization.
|
||||
*/
|
||||
public function scopeWithExpiredAuthorization($query)
|
||||
{
|
||||
return $query->where('authorization_expiry_date', '<', now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the authorization is still valid.
|
||||
*/
|
||||
public function getIsAuthorizationValidAttribute(): bool
|
||||
{
|
||||
if (!$this->authorization_expiry_date) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->authorization_expiry_date >= now();
|
||||
}
|
||||
}
|
||||
@ -43,6 +43,19 @@ class AppServiceProvider extends ServiceProvider
|
||||
$this->app->bind(\App\Repositories\ProductCategoryRepositoryInterface::class, function ($app) {
|
||||
return new \App\Repositories\ProductCategoryRepository($app->make(\App\Models\ProductCategory::class));
|
||||
});
|
||||
|
||||
// Employee management repository bindings
|
||||
$this->app->bind(\App\Repositories\EmployeeRepositoryInterface::class, function ($app) {
|
||||
return new \App\Repositories\EmployeeRepository($app->make(\App\Models\Employee::class));
|
||||
});
|
||||
|
||||
$this->app->bind(\App\Repositories\ThanatopractitionerRepositoryInterface::class, function ($app) {
|
||||
return new \App\Repositories\ThanatopractitionerRepository($app->make(\App\Models\Thanatopractitioner::class));
|
||||
});
|
||||
|
||||
$this->app->bind(\App\Repositories\PractitionerDocumentRepositoryInterface::class, function ($app) {
|
||||
return new \App\Repositories\PractitionerDocumentRepository($app->make(\App\Models\PractitionerDocument::class));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
127
thanasoft-back/app/Repositories/EmployeeRepository.php
Normal file
127
thanasoft-back/app/Repositories/EmployeeRepository.php
Normal file
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Employee;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
|
||||
class EmployeeRepository extends BaseRepository implements EmployeeRepositoryInterface
|
||||
{
|
||||
public function __construct(Employee $model)
|
||||
{
|
||||
parent::__construct($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all employees with optional filtering.
|
||||
*/
|
||||
public function getAll(array $filters = []): Collection
|
||||
{
|
||||
$query = $this->model->newQuery();
|
||||
|
||||
// Apply filters
|
||||
if (!empty($filters['search'])) {
|
||||
$query->search($filters['search']);
|
||||
}
|
||||
|
||||
if (isset($filters['active'])) {
|
||||
if ($filters['active']) {
|
||||
$query->active();
|
||||
} else {
|
||||
$query->inactive();
|
||||
}
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
$sortField = $filters['sort_by'] ?? 'last_name';
|
||||
$sortDirection = $filters['sort_direction'] ?? 'asc';
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an employee by ID.
|
||||
*/
|
||||
public function findById(int $id): ?Employee
|
||||
{
|
||||
return $this->model->newQuery()->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an employee by email.
|
||||
*/
|
||||
public function findByEmail(string $email): ?Employee
|
||||
{
|
||||
return $this->model->newQuery()->where('email', $email)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active employees only.
|
||||
*/
|
||||
public function getActive(): Collection
|
||||
{
|
||||
return $this->model->newQuery()->active()->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get inactive employees only.
|
||||
*/
|
||||
public function getInactive(): Collection
|
||||
{
|
||||
return $this->model->newQuery()->inactive()->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search employees by term.
|
||||
*/
|
||||
public function search(string $term): Collection
|
||||
{
|
||||
return $this->model->newQuery()->search($term)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get employees with pagination.
|
||||
*/
|
||||
public function getPaginated(int $perPage = 10): array
|
||||
{
|
||||
$paginator = $this->model->newQuery()->paginate($perPage);
|
||||
|
||||
return [
|
||||
'employees' => $paginator->getCollection(),
|
||||
'pagination' => [
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'total' => $paginator->total(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get employees with their thanatopractitioner data.
|
||||
*/
|
||||
public function getWithThanatopractitioner(): Collection
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->with('thanatopractitioner')
|
||||
->orderBy('last_name')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get employee statistics.
|
||||
*/
|
||||
public function getStatistics(): array
|
||||
{
|
||||
return [
|
||||
'total' => $this->model->newQuery()->count(),
|
||||
'active' => $this->model->newQuery()->active()->count(),
|
||||
'inactive' => $this->model->newQuery()->inactive()->count(),
|
||||
'with_thanatopractitioner' => $this->model->newQuery()->has('thanatopractitioner')->count(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Employee;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Contract for Employee repository operations.
|
||||
*/
|
||||
interface EmployeeRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* Get all employees with optional filtering.
|
||||
*
|
||||
* @param array<string, mixed> $filters
|
||||
* @return Collection<int, Employee>
|
||||
*/
|
||||
public function getAll(array $filters = []): Collection;
|
||||
|
||||
/**
|
||||
* Find an employee by ID.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Employee|null
|
||||
*/
|
||||
public function findById(int $id): ?Employee;
|
||||
|
||||
/**
|
||||
* Find an employee by email.
|
||||
*
|
||||
* @param string $email
|
||||
* @return Employee|null
|
||||
*/
|
||||
public function findByEmail(string $email): ?Employee;
|
||||
|
||||
/**
|
||||
* Get active employees only.
|
||||
*
|
||||
* @return Collection<int, Employee>
|
||||
*/
|
||||
public function getActive(): Collection;
|
||||
|
||||
/**
|
||||
* Get inactive employees only.
|
||||
*
|
||||
* @return Collection<int, Employee>
|
||||
*/
|
||||
public function getInactive(): Collection;
|
||||
|
||||
/**
|
||||
* Search employees by term.
|
||||
*
|
||||
* @param string $term
|
||||
* @return Collection<int, Employee>
|
||||
*/
|
||||
public function search(string $term): Collection;
|
||||
|
||||
/**
|
||||
* Get employees with pagination.
|
||||
*
|
||||
* @param int $perPage
|
||||
* @return array{employees: Collection<int, Employee>, pagination: array}
|
||||
*/
|
||||
public function getPaginated(int $perPage = 10): array;
|
||||
|
||||
/**
|
||||
* Get employees with their thanatopractitioner data.
|
||||
*
|
||||
* @return Collection<int, Employee>
|
||||
*/
|
||||
public function getWithThanatopractitioner(): Collection;
|
||||
|
||||
/**
|
||||
* Get employee statistics.
|
||||
*
|
||||
* @return array<string, int>
|
||||
*/
|
||||
public function getStatistics(): array;
|
||||
}
|
||||
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\PractitionerDocument;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class PractitionerDocumentRepository extends BaseRepository implements PractitionerDocumentRepositoryInterface
|
||||
{
|
||||
public function __construct(PractitionerDocument $model)
|
||||
{
|
||||
parent::__construct($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all practitioner documents with optional filtering.
|
||||
*/
|
||||
public function getAll(array $filters = []): Collection
|
||||
{
|
||||
$query = $this->model->newQuery()->with(['thanatopractitioner.employee']);
|
||||
|
||||
// Apply filters
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where(function ($q) use ($filters) {
|
||||
$q->where('doc_type', 'like', '%' . $filters['search'] . '%')
|
||||
->orWhere('status', 'like', '%' . $filters['search'] . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (!empty($filters['practitioner_id'])) {
|
||||
$query->where('practitioner_id', $filters['practitioner_id']);
|
||||
}
|
||||
|
||||
if (!empty($filters['doc_type'])) {
|
||||
$query->ofType($filters['doc_type']);
|
||||
}
|
||||
|
||||
if (isset($filters['valid_only'])) {
|
||||
if ($filters['valid_only']) {
|
||||
$query->valid();
|
||||
} else {
|
||||
$query->expired();
|
||||
}
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
$sortField = $filters['sort_by'] ?? 'created_at';
|
||||
$sortDirection = $filters['sort_direction'] ?? 'desc';
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a practitioner document by ID.
|
||||
*/
|
||||
public function findById(int $id): ?PractitionerDocument
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->with(['thanatopractitioner.employee'])
|
||||
->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get documents by practitioner ID.
|
||||
*/
|
||||
public function getByPractitionerId(int $practitionerId): Collection
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->where('practitioner_id', $practitionerId)
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get documents by type.
|
||||
*/
|
||||
public function getByDocumentType(string $docType): Collection
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->with(['thanatopractitioner.employee'])
|
||||
->ofType($docType)
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get valid documents (not expired).
|
||||
*/
|
||||
public function getValid(): Collection
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->with(['thanatopractitioner.employee'])
|
||||
->valid()
|
||||
->orderBy('expiry_date')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get expired documents.
|
||||
*/
|
||||
public function getExpired(): Collection
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->with(['thanatopractitioner.employee'])
|
||||
->expired()
|
||||
->orderBy('expiry_date', 'desc')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get documents with pagination.
|
||||
*/
|
||||
public function getPaginated(int $perPage = 10): array
|
||||
{
|
||||
$paginator = $this->model->newQuery()
|
||||
->with(['thanatopractitioner.employee'])
|
||||
->paginate($perPage);
|
||||
|
||||
return [
|
||||
'documents' => $paginator->getCollection(),
|
||||
'pagination' => [
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'total' => $paginator->total(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get document statistics.
|
||||
*/
|
||||
public function getStatistics(): array
|
||||
{
|
||||
return [
|
||||
'total' => $this->model->newQuery()->count(),
|
||||
'valid' => $this->model->newQuery()->valid()->count(),
|
||||
'expired' => $this->model->newQuery()->expired()->count(),
|
||||
'by_type' => $this->model->newQuery()
|
||||
->selectRaw('doc_type, count(*) as count')
|
||||
->groupBy('doc_type')
|
||||
->pluck('count', 'doc_type')
|
||||
->toArray(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\PractitionerDocument;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Contract for PractitionerDocument repository operations.
|
||||
*/
|
||||
interface PractitionerDocumentRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* Get all practitioner documents with optional filtering.
|
||||
*
|
||||
* @param array<string, mixed> $filters
|
||||
* @return Collection<int, PractitionerDocument>
|
||||
*/
|
||||
public function getAll(array $filters = []): Collection;
|
||||
|
||||
/**
|
||||
* Find a practitioner document by ID.
|
||||
*
|
||||
* @param int $id
|
||||
* @return PractitionerDocument|null
|
||||
*/
|
||||
public function findById(int $id): ?PractitionerDocument;
|
||||
|
||||
/**
|
||||
* Get documents by practitioner ID.
|
||||
*
|
||||
* @param int $practitionerId
|
||||
* @return Collection<int, PractitionerDocument>
|
||||
*/
|
||||
public function getByPractitionerId(int $practitionerId): Collection;
|
||||
|
||||
/**
|
||||
* Get documents by type.
|
||||
*
|
||||
* @param string $docType
|
||||
* @return Collection<int, PractitionerDocument>
|
||||
*/
|
||||
public function getByDocumentType(string $docType): Collection;
|
||||
|
||||
/**
|
||||
* Get valid documents (not expired).
|
||||
*
|
||||
* @return Collection<int, PractitionerDocument>
|
||||
*/
|
||||
public function getValid(): Collection;
|
||||
|
||||
/**
|
||||
* Get expired documents.
|
||||
*
|
||||
* @return Collection<int, PractitionerDocument>
|
||||
*/
|
||||
public function getExpired(): Collection;
|
||||
|
||||
/**
|
||||
* Get documents with pagination.
|
||||
*
|
||||
* @param int $perPage
|
||||
* @return array{documents: Collection<int, PractitionerDocument>, pagination: array}
|
||||
*/
|
||||
public function getPaginated(int $perPage = 10): array;
|
||||
|
||||
/**
|
||||
* Get document statistics.
|
||||
*
|
||||
* @return array<string, int>
|
||||
*/
|
||||
public function getStatistics(): array;
|
||||
}
|
||||
@ -19,20 +19,19 @@ class ProductRepository extends BaseRepository implements ProductRepositoryInter
|
||||
*/
|
||||
public function paginate(int $perPage = 15, array $filters = []): LengthAwarePaginator
|
||||
{
|
||||
$query = $this->model->newQuery()->with('fournisseur');
|
||||
$query = $this->model->newQuery()->with(['fournisseur', 'category']);
|
||||
|
||||
// Apply filters
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where(function ($q) use ($filters) {
|
||||
$q->where('nom', 'like', '%' . $filters['search'] . '%')
|
||||
->orWhere('reference', 'like', '%' . $filters['search'] . '%')
|
||||
->orWhere('categorie', 'like', '%' . $filters['search'] . '%')
|
||||
->orWhere('fabricant', 'like', '%' . $filters['search'] . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (!empty($filters['categorie'])) {
|
||||
$query->where('categorie', $filters['categorie']);
|
||||
$query->where('categorie_id', $filters['categorie']);
|
||||
}
|
||||
|
||||
if (!empty($filters['fournisseur_id'])) {
|
||||
@ -62,7 +61,7 @@ class ProductRepository extends BaseRepository implements ProductRepositoryInter
|
||||
public function getLowStockProducts(int $perPage = 15): LengthAwarePaginator
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->with('fournisseur')
|
||||
->with(['fournisseur', 'category'])
|
||||
->whereRaw('stock_actuel <= stock_minimum')
|
||||
->orderBy('stock_actuel', 'asc')
|
||||
->paginate($perPage);
|
||||
@ -73,7 +72,7 @@ class ProductRepository extends BaseRepository implements ProductRepositoryInter
|
||||
*/
|
||||
public function searchByName(string $name, int $perPage = 15, bool $exactMatch = false)
|
||||
{
|
||||
$query = $this->model->newQuery()->with('fournisseur');
|
||||
$query = $this->model->newQuery()->with(['fournisseur', 'category']);
|
||||
|
||||
if ($exactMatch) {
|
||||
$query->where('nom', $name);
|
||||
@ -87,11 +86,11 @@ class ProductRepository extends BaseRepository implements ProductRepositoryInter
|
||||
/**
|
||||
* Get products by category
|
||||
*/
|
||||
public function getByCategory(string $category, int $perPage = 15): LengthAwarePaginator
|
||||
public function getByCategory(int $categoryId, int $perPage = 15): LengthAwarePaginator
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->with('fournisseur')
|
||||
->where('categorie', $category)
|
||||
->with(['fournisseur', 'category'])
|
||||
->where('categorie_id', $categoryId)
|
||||
->orderBy('nom')
|
||||
->paginate($perPage);
|
||||
}
|
||||
|
||||
@ -1,138 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Product;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
|
||||
class ProductRepository extends BaseRepository implements ProductRepositoryInterface
|
||||
{
|
||||
public function __construct(Product $model)
|
||||
{
|
||||
parent::__construct($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get paginated products with filters
|
||||
*/
|
||||
public function paginate(int $perPage = 15, array $filters = []): LengthAwarePaginator
|
||||
{
|
||||
$query = $this->model->newQuery()->with('fournisseur');
|
||||
|
||||
// Apply filters
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where(function ($q) use ($filters) {
|
||||
$q->where('nom', 'like', '%' . $filters['search'] . '%')
|
||||
->orWhere('reference', 'like', '%' . $filters['search'] . '%')
|
||||
->orWhere('categorie', 'like', '%' . $filters['search'] . '%')
|
||||
->orWhere('fabricant', 'like', '%' . $filters['search'] . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (!empty($filters['categorie'])) {
|
||||
$query->where('categorie', $filters['categorie']);
|
||||
}
|
||||
|
||||
if (!empty($filters['fournisseur_id'])) {
|
||||
$query->where('fournisseur_id', $filters['fournisseur_id']);
|
||||
}
|
||||
|
||||
if (isset($filters['low_stock'])) {
|
||||
$query->whereRaw('stock_actuel <= stock_minimum');
|
||||
}
|
||||
|
||||
if (isset($filters['expiring_soon'])) {
|
||||
$query->where('date_expiration', '<=', now()->addDays(30)->toDateString())
|
||||
->where('date_expiration', '>=', now()->toDateString());
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
$sortField = $filters['sort_by'] ?? 'created_at';
|
||||
$sortDirection = $filters['sort_direction'] ?? 'desc';
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
return $query->paginate($perPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get products with low stock
|
||||
*/
|
||||
public function getLowStockProducts(int $perPage = 15): LengthAwarePaginator
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->with('fournisseur')
|
||||
->whereRaw('stock_actuel <= stock_minimum')
|
||||
->orderBy('stock_actuel', 'asc')
|
||||
->paginate($perPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search products by name
|
||||
*/
|
||||
public function searchByName(string $name, int $perPage = 15, bool $exactMatch = false)
|
||||
{
|
||||
$query = $this->model->newQuery()->with('fournisseur');
|
||||
|
||||
if ($exactMatch) {
|
||||
$query->where('nom', $name);
|
||||
} else {
|
||||
$query->where('nom', 'like', '%' . $name . '%');
|
||||
}
|
||||
|
||||
return $query->paginate($perPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get products by category
|
||||
*/
|
||||
public function getByCategory(string $category, int $perPage = 15): LengthAwarePaginator
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->with('fournisseur')
|
||||
->where('categorie', $category)
|
||||
->orderBy('nom')
|
||||
->paginate($perPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get products by fournisseur
|
||||
*/
|
||||
public function getProductsByFournisseur(int $fournisseurId): LengthAwarePaginator
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->where('fournisseur_id', $fournisseurId)
|
||||
->orderBy('nom')
|
||||
->paginate(15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update stock quantity
|
||||
*/
|
||||
public function updateStock(int $productId, float $newQuantity): bool
|
||||
{
|
||||
return $this->model->where('id', $productId)
|
||||
->update(['stock_actuel' => $newQuantity]) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product statistics
|
||||
*/
|
||||
public function getStatistics(): array
|
||||
{
|
||||
$totalProducts = $this->model->count();
|
||||
$lowStockProducts = $this->model->whereRaw('stock_actuel <= stock_minimum')->count();
|
||||
$expiringProducts = $this->model->where('date_expiration', '<=', now()->addDays(30)->toDateString())
|
||||
->where('date_expiration', '>=', now()->toDateString())
|
||||
->count();
|
||||
$totalValue = $this->model->sum(\DB::raw('stock_actuel * prix_unitaire'));
|
||||
|
||||
return [
|
||||
'total_products' => $totalProducts,
|
||||
'low_stock_products' => $lowStockProducts,
|
||||
'expiring_products' => $expiringProducts,
|
||||
'total_value' => $totalValue,
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Thanatopractitioner;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class ThanatopractitionerRepository extends BaseRepository implements ThanatopractitionerRepositoryInterface
|
||||
{
|
||||
public function __construct(Thanatopractitioner $model)
|
||||
{
|
||||
parent::__construct($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all thanatopractitioners with optional filtering.
|
||||
*/
|
||||
public function getAll(array $filters = []): Collection
|
||||
{
|
||||
$query = $this->model->newQuery()->with(['employee']);
|
||||
|
||||
// Apply filters
|
||||
if (!empty($filters['search'])) {
|
||||
$query->whereHas('employee', function ($q) use ($filters) {
|
||||
$q->search($filters['search']);
|
||||
});
|
||||
}
|
||||
|
||||
if (isset($filters['valid_authorization'])) {
|
||||
if ($filters['valid_authorization']) {
|
||||
$query->withValidAuthorization();
|
||||
} else {
|
||||
$query->withExpiredAuthorization();
|
||||
}
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
$sortField = $filters['sort_by'] ?? 'created_at';
|
||||
$sortDirection = $filters['sort_direction'] ?? 'desc';
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a thanatopractitioner by ID.
|
||||
*/
|
||||
public function findById(int $id): ?Thanatopractitioner
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->with(['employee', 'documents'])
|
||||
->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a thanatopractitioner by employee ID.
|
||||
*/
|
||||
public function findByEmployeeId(int $employeeId): ?Thanatopractitioner
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->with(['employee', 'documents'])
|
||||
->where('employee_id', $employeeId)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get thanatopractitioners with valid authorization.
|
||||
*/
|
||||
public function getWithValidAuthorization(): Collection
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->with(['employee'])
|
||||
->withValidAuthorization()
|
||||
->orderBy('authorization_expiry_date')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get thanatopractitioners with expired authorization.
|
||||
*/
|
||||
public function getWithExpiredAuthorization(): Collection
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->with(['employee'])
|
||||
->withExpiredAuthorization()
|
||||
->orderBy('authorization_expiry_date', 'desc')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get thanatopractitioners with their complete data.
|
||||
*/
|
||||
public function getWithRelations(): Collection
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->with(['employee', 'documents'])
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get thanatopractitioners with pagination.
|
||||
*/
|
||||
public function getPaginated(int $perPage = 10): array
|
||||
{
|
||||
$paginator = $this->model->newQuery()
|
||||
->with(['employee'])
|
||||
->paginate($perPage);
|
||||
|
||||
return [
|
||||
'thanatopractitioners' => $paginator->getCollection(),
|
||||
'pagination' => [
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'total' => $paginator->total(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get thanatopractitioner statistics.
|
||||
*/
|
||||
public function getStatistics(): array
|
||||
{
|
||||
return [
|
||||
'total' => $this->model->newQuery()->count(),
|
||||
'with_valid_authorization' => $this->model->newQuery()->withValidAuthorization()->count(),
|
||||
'with_expired_authorization' => $this->model->newQuery()->withExpiredAuthorization()->count(),
|
||||
'with_documents' => $this->model->newQuery()->has('documents')->count(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Thanatopractitioner;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Contract for Thanatopractitioner repository operations.
|
||||
*/
|
||||
interface ThanatopractitionerRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* Get all thanatopractitioners with optional filtering.
|
||||
*
|
||||
* @param array<string, mixed> $filters
|
||||
* @return Collection<int, Thanatopractitioner>
|
||||
*/
|
||||
public function getAll(array $filters = []): Collection;
|
||||
|
||||
/**
|
||||
* Find a thanatopractitioner by ID.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Thanatopractitioner|null
|
||||
*/
|
||||
public function findById(int $id): ?Thanatopractitioner;
|
||||
|
||||
/**
|
||||
* Find a thanatopractitioner by employee ID.
|
||||
*
|
||||
* @param int $employeeId
|
||||
* @return Thanatopractitioner|null
|
||||
*/
|
||||
public function findByEmployeeId(int $employeeId): ?Thanatopractitioner;
|
||||
|
||||
/**
|
||||
* Get thanatopractitioners with valid authorization.
|
||||
*
|
||||
* @return Collection<int, Thanatopractitioner>
|
||||
*/
|
||||
public function getWithValidAuthorization(): Collection;
|
||||
|
||||
/**
|
||||
* Get thanatopractitioners with expired authorization.
|
||||
*
|
||||
* @return Collection<int, Thanatopractitioner>
|
||||
*/
|
||||
public function getWithExpiredAuthorization(): Collection;
|
||||
|
||||
/**
|
||||
* Get thanatopractitioners with their complete data.
|
||||
*
|
||||
* @return Collection<int, Thanatopractitioner>
|
||||
*/
|
||||
public function getWithRelations(): Collection;
|
||||
|
||||
/**
|
||||
* Get thanatopractitioners with pagination.
|
||||
*
|
||||
* @param int $perPage
|
||||
* @return array{thanatopractitioners: Collection<int, Thanatopractitioner>, pagination: array}
|
||||
*/
|
||||
public function getPaginated(int $perPage = 10): array;
|
||||
|
||||
/**
|
||||
* Get thanatopractitioner statistics.
|
||||
*
|
||||
* @return array<string, int>
|
||||
*/
|
||||
public function getStatistics(): array;
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use App\Models\Product;
|
||||
use App\Models\ProductCategory;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
// First, we need to create a new column for categorie_id
|
||||
$table->foreignId('categorie_id')->nullable()->after('reference')
|
||||
->constrained('product_categories')->onDelete('set null');
|
||||
});
|
||||
|
||||
// Migrate existing data: map categorie string values to ProductCategory IDs
|
||||
$categories = ProductCategory::all()->keyBy('name');
|
||||
|
||||
Product::chunk(100, function ($products) use ($categories) {
|
||||
foreach ($products as $product) {
|
||||
if ($product->categorie && isset($categories[$product->categorie])) {
|
||||
$product->categorie_id = $categories[$product->categorie]->id;
|
||||
$product->save();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// First, we need to restore the categorie data from categorie_id
|
||||
$categories = ProductCategory::all()->keyBy('id');
|
||||
|
||||
Product::chunk(100, function ($products) use ($categories) {
|
||||
foreach ($products as $product) {
|
||||
if ($product->categorie_id && isset($categories[$product->categorie_id])) {
|
||||
$product->categorie = $categories[$product->categorie_id]->name;
|
||||
$product->save();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
// Drop the foreign key constraint first
|
||||
$table->dropForeign(['categorie_id']);
|
||||
|
||||
// Drop the categorie_id column
|
||||
$table->dropColumn('categorie_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('employees', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('first_name', 191)->comment('Prénom de l\'employé');
|
||||
$table->string('last_name', 191)->comment('Nom de famille de l\'employé');
|
||||
$table->string('email', 191)->nullable()->comment('Adresse email de l\'employé');
|
||||
$table->string('phone', 50)->nullable()->comment('Numéro de téléphone de l\'employé');
|
||||
$table->string('job_title', 191)->nullable()->comment('Intitulé du poste');
|
||||
$table->date('hire_date')->nullable()->comment('Date d\'embauche');
|
||||
$table->boolean('active')->default(true)->comment('Statut actif de l\'employé');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('employees');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('thanatopractitioners', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('employee_id')->unique()->comment('ID de l\'employé associé');
|
||||
$table->string('diploma_number', 191)->nullable()->comment('Numéro de diplôme');
|
||||
$table->date('diploma_date')->nullable()->comment('Date d\'obtention du diplôme');
|
||||
$table->string('authorization_number', 191)->nullable()->comment('Numéro d\'autorisation');
|
||||
$table->date('authorization_issue_date')->nullable()->comment('Date de délivrance de l\'autorisation');
|
||||
$table->date('authorization_expiry_date')->nullable()->comment('Date d\'expiration de l\'autorisation');
|
||||
$table->text('notes')->nullable()->comment('Notes supplémentaires');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('employee_id')
|
||||
->references('id')
|
||||
->on('employees')
|
||||
->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('thanatopractitioners');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('practitioner_documents', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('practitioner_id')->comment('ID du thanatopractitioner');
|
||||
$table->string('doc_type', 191)->comment('Type de document');
|
||||
$table->unsignedBigInteger('file_id')->nullable()->comment('ID du fichier associé');
|
||||
$table->date('issue_date')->nullable()->comment('Date de délivrance');
|
||||
$table->date('expiry_date')->nullable()->comment('Date d\'expiration');
|
||||
$table->string('status', 64)->nullable()->comment('Statut du document');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('practitioner_id')
|
||||
->references('id')
|
||||
->on('thanatopractitioners')
|
||||
->onDelete('cascade');
|
||||
|
||||
// Note: The files table might not exist yet, so we won't add this foreign key constraint
|
||||
// $table->foreign('file_id')
|
||||
// ->references('id')
|
||||
// ->on('files')
|
||||
// ->onDelete('set null');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('practitioner_documents');
|
||||
}
|
||||
};
|
||||
@ -21,5 +21,7 @@ class DatabaseSeeder extends Seeder
|
||||
]);
|
||||
|
||||
$this->call(ProductCategorySeeder::class);
|
||||
$this->call(EmployeeSeeder::class);
|
||||
$this->call(ThanatopractitionerSeeder::class);
|
||||
}
|
||||
}
|
||||
|
||||
134
thanasoft-back/database/seeders/EmployeeSeeder.php
Normal file
134
thanasoft-back/database/seeders/EmployeeSeeder.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Employee;
|
||||
|
||||
class EmployeeSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// Create employees with different roles (matching actual table columns)
|
||||
$employees = [
|
||||
[
|
||||
'first_name' => 'Jean',
|
||||
'last_name' => 'Dupont',
|
||||
'email' => 'jean.dupont@thanasoft.com',
|
||||
'phone' => '+261341234567',
|
||||
'job_title' => 'Développeur Full-Stack',
|
||||
'hire_date' => '2022-01-15',
|
||||
'active' => true,
|
||||
],
|
||||
[
|
||||
'first_name' => 'Marie',
|
||||
'last_name' => 'Rasoa',
|
||||
'email' => 'marie.rasoa@thanasoft.com',
|
||||
'phone' => '+261341234569',
|
||||
'job_title' => 'Chef de Projet',
|
||||
'hire_date' => '2021-08-01',
|
||||
'active' => true,
|
||||
],
|
||||
[
|
||||
'first_name' => 'Paul',
|
||||
'last_name' => 'Ramanana',
|
||||
'email' => 'paul.ramanana@thanasoft.com',
|
||||
'phone' => '+261341234571',
|
||||
'job_title' => 'Designer UX/UI',
|
||||
'hire_date' => '2022-03-10',
|
||||
'active' => true,
|
||||
],
|
||||
[
|
||||
'first_name' => 'Sophie',
|
||||
'last_name' => 'Andriamatoa',
|
||||
'email' => 'sophie.andriamatoa@thanasoft.com',
|
||||
'phone' => '+261341234573',
|
||||
'job_title' => 'Responsable RH',
|
||||
'hire_date' => '2020-09-15',
|
||||
'active' => true,
|
||||
],
|
||||
[
|
||||
'first_name' => 'David',
|
||||
'last_name' => 'Randria',
|
||||
'email' => 'david.randria@thanasoft.com',
|
||||
'phone' => '+261341234575',
|
||||
'job_title' => 'Développeur Backend',
|
||||
'hire_date' => '2023-01-20',
|
||||
'active' => true,
|
||||
],
|
||||
[
|
||||
'first_name' => 'Lina',
|
||||
'last_name' => 'Ramaniraka',
|
||||
'email' => 'lina.ramaniraka@thanasoft.com',
|
||||
'phone' => '+261341234577',
|
||||
'job_title' => 'Comptable',
|
||||
'hire_date' => '2021-06-01',
|
||||
'active' => true,
|
||||
],
|
||||
[
|
||||
'first_name' => 'Marc',
|
||||
'last_name' => 'Andriantsoa',
|
||||
'email' => 'marc.andriantsoa@thanasoft.com',
|
||||
'phone' => '+261341234579',
|
||||
'job_title' => 'DevOps Engineer',
|
||||
'hire_date' => '2022-11-05',
|
||||
'active' => true,
|
||||
],
|
||||
[
|
||||
'first_name' => 'Julie',
|
||||
'last_name' => 'Rakotomalala',
|
||||
'email' => 'julie.rakotomalala@thanasoft.com',
|
||||
'phone' => '+261341234581',
|
||||
'job_title' => 'Community Manager',
|
||||
'hire_date' => '2023-03-15',
|
||||
'active' => true,
|
||||
],
|
||||
[
|
||||
'first_name' => 'Philippe',
|
||||
'last_name' => 'Rakoto',
|
||||
'email' => 'philippe.rakoto@thanasoft.com',
|
||||
'phone' => '+261341234583',
|
||||
'job_title' => 'Développeur Mobile',
|
||||
'hire_date' => '2023-06-10',
|
||||
'active' => true,
|
||||
],
|
||||
[
|
||||
'first_name' => 'Anne',
|
||||
'last_name' => 'Andriamanjato',
|
||||
'email' => 'anne.andriamanjato@thanasoft.com',
|
||||
'phone' => '+261341234585',
|
||||
'job_title' => 'Stagiaire',
|
||||
'hire_date' => '2024-01-15',
|
||||
'active' => true,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($employees as $employeeData) {
|
||||
Employee::create($employeeData);
|
||||
}
|
||||
|
||||
// Create some inactive employees for testing
|
||||
Employee::create([
|
||||
'first_name' => 'Ancien',
|
||||
'last_name' => 'Employe',
|
||||
'email' => 'ancien.employe@thanasoft.com',
|
||||
'phone' => '+261341234587',
|
||||
'job_title' => 'Testeur',
|
||||
'hire_date' => '2020-01-01',
|
||||
'active' => false,
|
||||
]);
|
||||
|
||||
Employee::create([
|
||||
'first_name' => 'Employe',
|
||||
'last_name' => 'Suspendu',
|
||||
'email' => 'employe.suspendu@thanasoft.com',
|
||||
'phone' => '+261341234589',
|
||||
'job_title' => 'Assistant',
|
||||
'hire_date' => '2021-05-01',
|
||||
'active' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
277
thanasoft-back/database/seeders/ThanatopractitionerSeeder.php
Normal file
277
thanasoft-back/database/seeders/ThanatopractitionerSeeder.php
Normal file
@ -0,0 +1,277 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Thanatopractitioner;
|
||||
use App\Models\PractitionerDocument;
|
||||
|
||||
class ThanatopractitionerSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// First, we need to create employees that will become thanatopractitioners
|
||||
$employees = [
|
||||
[
|
||||
'first_name' => 'Jean-Baptiste',
|
||||
'last_name' => 'Ramanana',
|
||||
'email' => 'jb.ramanana@thanasoft.com',
|
||||
'phone' => '+261341235001',
|
||||
'job_title' => 'Thanatopracteur Senior',
|
||||
'hire_date' => '2020-08-01',
|
||||
'active' => true,
|
||||
],
|
||||
[
|
||||
'first_name' => 'Marie',
|
||||
'last_name' => 'Andriamanantsoa',
|
||||
'email' => 'marie.andriamanantsoa@thanasoft.com',
|
||||
'phone' => '+261341235003',
|
||||
'job_title' => 'Thanatopracteur Spécialisé',
|
||||
'hire_date' => '2019-07-01',
|
||||
'active' => true,
|
||||
],
|
||||
[
|
||||
'first_name' => 'Paul',
|
||||
'last_name' => 'Rakotomanga',
|
||||
'email' => 'paul.rakotomanga@thanasoft.com',
|
||||
'phone' => '+261341235005',
|
||||
'job_title' => 'Thanatopracteur Junior',
|
||||
'hire_date' => '2021-11-01',
|
||||
'active' => true,
|
||||
],
|
||||
[
|
||||
'first_name' => 'Sophie',
|
||||
'last_name' => 'Andriatiana',
|
||||
'email' => 'sophie.andriatiana@thanasoft.com',
|
||||
'phone' => '+261341235007',
|
||||
'job_title' => 'Thanatopracteur Expert',
|
||||
'hire_date' => '2018-06-01',
|
||||
'active' => true,
|
||||
],
|
||||
[
|
||||
'first_name' => 'David',
|
||||
'last_name' => 'Randriamahandry',
|
||||
'email' => 'david.randriamahandry@thanasoft.com',
|
||||
'phone' => '+261341235009',
|
||||
'job_title' => 'Thanatopracteur',
|
||||
'hire_date' => '2022-03-01',
|
||||
'active' => true,
|
||||
],
|
||||
[
|
||||
'first_name' => 'Lina',
|
||||
'last_name' => 'Ramaniraka',
|
||||
'email' => 'lina.ramaniraka@thanasoft.com',
|
||||
'phone' => '+261341235011',
|
||||
'job_title' => 'Thanatopracteur',
|
||||
'hire_date' => '2023-10-01',
|
||||
'active' => true,
|
||||
],
|
||||
[
|
||||
'first_name' => 'Marc',
|
||||
'last_name' => 'Andriamatsiroa',
|
||||
'email' => 'marc.andriamatsiroa@thanasoft.com',
|
||||
'phone' => '+261341235013',
|
||||
'job_title' => 'Thanatopracteur Chief',
|
||||
'hire_date' => '2018-01-01',
|
||||
'active' => true,
|
||||
],
|
||||
[
|
||||
'first_name' => 'Julie',
|
||||
'last_name' => 'Rakotomalala',
|
||||
'email' => 'julie.rakotomalala@thanasoft.com',
|
||||
'phone' => '+261341235015',
|
||||
'job_title' => 'Thanatopracteur Assistant',
|
||||
'hire_date' => '2024-05-01',
|
||||
'active' => true,
|
||||
],
|
||||
];
|
||||
|
||||
// Create employees and get their IDs
|
||||
$employeeIds = [];
|
||||
foreach ($employees as $employeeData) {
|
||||
$employee = \App\Models\Employee::create($employeeData);
|
||||
$employeeIds[] = $employee->id;
|
||||
}
|
||||
|
||||
// Create thanatopractitioners linked to the employees
|
||||
$thanatopractitioners = [
|
||||
[
|
||||
'employee_id' => $employeeIds[0],
|
||||
'diploma_number' => 'TP-DIPL-001-2020',
|
||||
'diploma_date' => '2020-06-15',
|
||||
'authorization_number' => 'TP-AUTH-001-2020',
|
||||
'authorization_issue_date' => '2020-07-01',
|
||||
'authorization_expiry_date' => '2025-07-01',
|
||||
'notes' => 'Thanatopracteur senior spécialisé en thanatopraxie générale et reconstructive.',
|
||||
],
|
||||
[
|
||||
'employee_id' => $employeeIds[1],
|
||||
'diploma_number' => 'TP-DIPL-002-2019',
|
||||
'diploma_date' => '2019-05-10',
|
||||
'authorization_number' => 'TP-AUTH-002-2019',
|
||||
'authorization_issue_date' => '2019-06-01',
|
||||
'authorization_expiry_date' => '2024-06-01',
|
||||
'notes' => 'Spécialiste en thanatopraxie pédiatrique avec 5 ans d\'expérience.',
|
||||
],
|
||||
[
|
||||
'employee_id' => $employeeIds[2],
|
||||
'diploma_number' => 'TP-DIPL-003-2021',
|
||||
'diploma_date' => '2021-09-22',
|
||||
'authorization_number' => 'TP-AUTH-003-2021',
|
||||
'authorization_issue_date' => '2021-10-01',
|
||||
'authorization_expiry_date' => '2026-10-01',
|
||||
'notes' => 'Thanatopracteur junior spécialisé en thanatopraxie esthétique.',
|
||||
],
|
||||
[
|
||||
'employee_id' => $employeeIds[3],
|
||||
'diploma_number' => 'TP-DIPL-004-2018',
|
||||
'diploma_date' => '2018-04-05',
|
||||
'authorization_number' => 'TP-AUTH-004-2018',
|
||||
'authorization_issue_date' => '2018-05-01',
|
||||
'authorization_expiry_date' => '2023-05-01',
|
||||
'notes' => 'Expert en thanatopraxie reconstructive et histopathologie.',
|
||||
],
|
||||
[
|
||||
'employee_id' => $employeeIds[4],
|
||||
'diploma_number' => 'TP-DIPL-005-2022',
|
||||
'diploma_date' => '2022-01-18',
|
||||
'authorization_number' => 'TP-AUTH-005-2022',
|
||||
'authorization_issue_date' => '2022-02-01',
|
||||
'authorization_expiry_date' => '2027-02-01',
|
||||
'notes' => 'Spécialiste en thanatopraxie traditionnelle et culturelle.',
|
||||
],
|
||||
[
|
||||
'employee_id' => $employeeIds[5],
|
||||
'diploma_number' => 'TP-DIPL-006-2023',
|
||||
'diploma_date' => '2023-08-12',
|
||||
'authorization_number' => 'TP-AUTH-006-2023',
|
||||
'authorization_issue_date' => '2023-09-01',
|
||||
'authorization_expiry_date' => '2028-09-01',
|
||||
'notes' => 'Thanatopracteur nouvellement certifiée, spécialisée en techniques modernes.',
|
||||
],
|
||||
[
|
||||
'employee_id' => $employeeIds[6],
|
||||
'diploma_number' => 'TP-DIPL-007-2017',
|
||||
'diploma_date' => '2017-11-30',
|
||||
'authorization_number' => 'TP-AUTH-007-2017',
|
||||
'authorization_issue_date' => '2018-01-01',
|
||||
'authorization_expiry_date' => '2023-01-01',
|
||||
'notes' => 'Responsable principal des thanatopracteurs, expert en histopathologie.',
|
||||
],
|
||||
[
|
||||
'employee_id' => $employeeIds[7],
|
||||
'diploma_number' => 'TP-DIPL-008-2024',
|
||||
'diploma_date' => '2024-03-25',
|
||||
'authorization_number' => 'TP-AUTH-008-2024',
|
||||
'authorization_issue_date' => '2024-04-01',
|
||||
'authorization_expiry_date' => '2029-04-01',
|
||||
'notes' => 'Thanatopracteur assistante, en formation continue en thanatopraxie assistée.',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($thanatopractitioners as $thanatopractitionerData) {
|
||||
$thanatopractitioner = Thanatopractitioner::create($thanatopractitionerData);
|
||||
|
||||
// Create some practitioner documents for each thanatopractitioner
|
||||
$this->createPractitionerDocuments($thanatopractitioner);
|
||||
}
|
||||
|
||||
// Create inactive thanatopractitioners for testing
|
||||
$inactiveEmployee1 = \App\Models\Employee::create([
|
||||
'first_name' => 'Ancien',
|
||||
'last_name' => 'Thanatopracteur',
|
||||
'email' => 'ancien.thanato@thanasoft.com',
|
||||
'phone' => '+261341235017',
|
||||
'job_title' => 'Ancien Thanatopracteur',
|
||||
'hire_date' => '2015-03-01',
|
||||
'active' => false,
|
||||
]);
|
||||
|
||||
$inactive1 = Thanatopractitioner::create([
|
||||
'employee_id' => $inactiveEmployee1->id,
|
||||
'diploma_number' => 'TP-DIPL-TEST-001',
|
||||
'diploma_date' => '2015-01-01',
|
||||
'authorization_number' => 'TP-AUTH-TEST-001',
|
||||
'authorization_issue_date' => '2015-02-01',
|
||||
'authorization_expiry_date' => '2020-02-01',
|
||||
'notes' => 'Thanatopracteur inactif, autorisation expirée.',
|
||||
]);
|
||||
|
||||
$this->createPractitionerDocuments($inactive1);
|
||||
|
||||
$inactiveEmployee2 = \App\Models\Employee::create([
|
||||
'first_name' => 'Thanatopracteur',
|
||||
'last_name' => 'Suspendu',
|
||||
'email' => 'thanato.suspendu@thanasoft.com',
|
||||
'phone' => '+261341235019',
|
||||
'job_title' => 'Thanatopracteur Suspendu',
|
||||
'hire_date' => '2018-08-01',
|
||||
'active' => false,
|
||||
]);
|
||||
|
||||
$inactive2 = Thanatopractitioner::create([
|
||||
'employee_id' => $inactiveEmployee2->id,
|
||||
'diploma_number' => 'TP-DIPL-TEST-002',
|
||||
'diploma_date' => '2018-06-01',
|
||||
'authorization_number' => 'TP-AUTH-TEST-002',
|
||||
'authorization_issue_date' => '2018-07-01',
|
||||
'authorization_expiry_date' => '2023-07-01',
|
||||
'notes' => 'Thanatopracteur temporairement suspendu.',
|
||||
]);
|
||||
|
||||
$this->createPractitionerDocuments($inactive2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create practitioner documents for a thanatopractitioner.
|
||||
*/
|
||||
private function createPractitionerDocuments(Thanatopractitioner $thanatopractitioner): void
|
||||
{
|
||||
$documents = [
|
||||
[
|
||||
'doc_type' => 'diploma',
|
||||
'issue_date' => $thanatopractitioner->diploma_date,
|
||||
'expiry_date' => date('Y-m-d', strtotime('+5 years', strtotime($thanatopractitioner->diploma_date))),
|
||||
'status' => 'active',
|
||||
],
|
||||
[
|
||||
'doc_type' => 'certification',
|
||||
'issue_date' => date('Y-01-01'),
|
||||
'expiry_date' => date('Y-12-31'),
|
||||
'status' => 'active',
|
||||
],
|
||||
];
|
||||
|
||||
// Add specialized documents based on notes content
|
||||
if (strpos($thanatopractitioner->notes, 'reconstructive') !== false ||
|
||||
strpos($thanatopractitioner->notes, 'histopathologie') !== false) {
|
||||
$documents[] = [
|
||||
'doc_type' => 'specialization',
|
||||
'issue_date' => $thanatopractitioner->diploma_date,
|
||||
'expiry_date' => date('Y-m-d', strtotime('+3 years', strtotime($thanatopractitioner->diploma_date))),
|
||||
'status' => 'active',
|
||||
];
|
||||
}
|
||||
|
||||
// Add expired documents for inactive thanatopractitioners
|
||||
$employee = $thanatopractitioner->employee;
|
||||
if ($employee->active === false) {
|
||||
$documents[] = [
|
||||
'doc_type' => 'expired_certificate',
|
||||
'issue_date' => '2020-01-01',
|
||||
'expiry_date' => '2023-01-01',
|
||||
'status' => 'expired',
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($documents as $documentData) {
|
||||
PractitionerDocument::create(array_merge($documentData, [
|
||||
'practitioner_id' => $thanatopractitioner->id,
|
||||
'file_id' => null, // Will be set when files table is implemented
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,9 @@ use App\Http\Controllers\Api\ClientCategoryController;
|
||||
use App\Http\Controllers\Api\FournisseurController;
|
||||
use App\Http\Controllers\Api\ProductController;
|
||||
use App\Http\Controllers\Api\ProductCategoryController;
|
||||
use App\Http\Controllers\Api\EmployeeController;
|
||||
use App\Http\Controllers\Api\ThanatopractitionerController;
|
||||
use App\Http\Controllers\Api\PractitionerDocumentController;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@ -73,4 +76,20 @@ Route::middleware('auth:sanctum')->group(function () {
|
||||
Route::get('/product-categories/statistics', [ProductCategoryController::class, 'statistics']);
|
||||
Route::apiResource('product-categories', ProductCategoryController::class);
|
||||
Route::patch('/product-categories/{id}/toggle-active', [ProductCategoryController::class, 'toggleActive']);
|
||||
|
||||
// Employee management
|
||||
Route::get('/employees/searchBy', [EmployeeController::class, 'searchBy']);
|
||||
Route::get('/employees/thanatopractitioners', [EmployeeController::class, 'getThanatopractitioners']);
|
||||
Route::apiResource('employees', EmployeeController::class);
|
||||
|
||||
// Thanatopractitioner management
|
||||
Route::apiResource('thanatopractitioners', ThanatopractitionerController::class);
|
||||
Route::get('employees/{employeeId}/thanatopractitioners', [ThanatopractitionerController::class, 'getByEmployee']);
|
||||
Route::get('/thanatopractitioners/{id}/documents', [PractitionerDocumentController::class, 'getByThanatopractitioner']);
|
||||
|
||||
// Practitioner Document management
|
||||
Route::get('/practitioner-documents/searchBy', [PractitionerDocumentController::class, 'searchBy']);
|
||||
Route::get('/practitioner-documents/expiring', [PractitionerDocumentController::class, 'getExpiringDocuments']);
|
||||
Route::apiResource('practitioner-documents', PractitionerDocumentController::class);
|
||||
Route::patch('/practitioner-documents/{id}/verify', [PractitionerDocumentController::class, 'verifyDocument']);
|
||||
});
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/{any}', function () {
|
||||
return file_get_contents(public_path('index.html'));
|
||||
})->where('any', '.*');
|
||||
Loading…
x
Reference in New Issue
Block a user