From 5d93f9d39a98abaf544d650e2085ab9e279ec9ab Mon Sep 17 00:00:00 2001 From: kevin Date: Mon, 5 Jan 2026 17:21:02 +0300 Subject: [PATCH] feat: Implement product category management and a multi-step intervention wizard with product selection, including new database migrations and CORS configuration updates. --- .../Api/InterventionController.php | 2 +- .../Api/ProductCategoryController.php | 4 +- .../Controllers/Api/ProductController.php | 7 +- .../Requests/StoreInterventionRequest.php | 1 + .../Requests/StoreProductCategoryRequest.php | 19 +- .../Requests/UpdateInterventionRequest.php | 1 + .../Requests/UpdateProductCategoryRequest.php | 40 +- .../ProductCategoryResource.php | 1 + .../Resources/ProductCategoryResource.php | 31 + thanasoft-back/app/Models/Intervention.php | 9 + thanasoft-back/app/Models/Product.php | 2 +- thanasoft-back/app/Models/ProductCategory.php | 57 +- .../app/Providers/AppServiceProvider.php | 2 + .../ProductCategoryRepository.php | 171 +- .../ProductCategoryRepositoryInterface.php | 29 +- .../app/Repositories/ProductRepository.php | 6 + .../ProductRepositoryInterface.php | 2 +- thanasoft-back/config/cors.php | 2 +- ...tervention_to_product_categories_table.php | 32 + ...4000_refactor_products_category_column.php | 35 + ..._add_product_id_to_interventions_table.php | 30 + .../Agenda/InterventionMultiStepModal.vue | 1829 +++-------------- .../Agenda/WizardSteps/StepClient.vue | 231 +++ .../Agenda/WizardSteps/StepDeceased.vue | 153 ++ .../Agenda/WizardSteps/StepDocuments.vue | 161 ++ .../Agenda/WizardSteps/StepIntervention.vue | 175 ++ .../Agenda/WizardSteps/StepLocation.vue | 154 ++ .../WizardSteps/StepProductSelection.vue | 121 ++ .../Agenda/WizardSteps/WizardProgress.vue | 41 + .../ProductCategories/ProductCategoryList.vue | 119 ++ .../ProductCategoryModal.vue | 232 +++ .../Organism/Stock/AddProductPresentation.vue | 100 +- .../src/examples/Sidenav/SidenavList.vue | 6 + thanasoft-front/src/router/index.js | 6 + .../src/stores/productCategoryStore.ts | 624 ++---- .../pages/Parametrage/ProductCategories.vue | 146 ++ .../src/views/pages/Stock/AddProduct.vue | 4 +- 37 files changed, 2314 insertions(+), 2271 deletions(-) create mode 100644 thanasoft-back/app/Http/Resources/ProductCategoryResource.php create mode 100644 thanasoft-back/database/migrations/2026_01_05_155500_add_intervention_to_product_categories_table.php create mode 100644 thanasoft-back/database/migrations/2026_01_05_164000_refactor_products_category_column.php create mode 100644 thanasoft-back/database/migrations/2026_01_05_165400_add_product_id_to_interventions_table.php create mode 100644 thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepClient.vue create mode 100644 thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepDeceased.vue create mode 100644 thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepDocuments.vue create mode 100644 thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepIntervention.vue create mode 100644 thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepLocation.vue create mode 100644 thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepProductSelection.vue create mode 100644 thanasoft-front/src/components/Organism/Agenda/WizardSteps/WizardProgress.vue create mode 100644 thanasoft-front/src/components/Organism/Parametrage/ProductCategories/ProductCategoryList.vue create mode 100644 thanasoft-front/src/components/Organism/Parametrage/ProductCategories/ProductCategoryModal.vue create mode 100644 thanasoft-front/src/views/pages/Parametrage/ProductCategories.vue diff --git a/thanasoft-back/app/Http/Controllers/Api/InterventionController.php b/thanasoft-back/app/Http/Controllers/Api/InterventionController.php index ef61317..1eb5fa7 100644 --- a/thanasoft-back/app/Http/Controllers/Api/InterventionController.php +++ b/thanasoft-back/app/Http/Controllers/Api/InterventionController.php @@ -4,7 +4,7 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Http\Requests\StoreInterventionRequest; -use App\HttpRequests\StoreInterventionWithAllDataRequest; +use App\Http\Requests\StoreInterventionWithAllDataRequest; use App\Http\Requests\UpdateInterventionRequest; use App\Http\Resources\Intervention\InterventionResource; use App\Http\Resources\Intervention\InterventionCollection; diff --git a/thanasoft-back/app/Http/Controllers/Api/ProductCategoryController.php b/thanasoft-back/app/Http/Controllers/Api/ProductCategoryController.php index 1f0ec79..4facd09 100644 --- a/thanasoft-back/app/Http/Controllers/Api/ProductCategoryController.php +++ b/thanasoft-back/app/Http/Controllers/Api/ProductCategoryController.php @@ -28,7 +28,7 @@ class ProductCategoryController extends Controller public function index(Request $request): ProductCategoryCollection|JsonResponse { try { - $perPage = $request->get('per_page', 15); + $perPage = (int) $request->get('per_page', 15); $filters = [ 'search' => $request->get('search'), 'active' => $request->get('active'), @@ -249,7 +249,7 @@ class ProductCategoryController extends Controller { try { $term = $request->get('term', ''); - $perPage = $request->get('per_page', 15); + $perPage = (int) $request->get('per_page', 15); if (empty($term)) { return response()->json([ diff --git a/thanasoft-back/app/Http/Controllers/Api/ProductController.php b/thanasoft-back/app/Http/Controllers/Api/ProductController.php index f16e6da..33bec79 100644 --- a/thanasoft-back/app/Http/Controllers/Api/ProductController.php +++ b/thanasoft-back/app/Http/Controllers/Api/ProductController.php @@ -28,13 +28,14 @@ class ProductController extends Controller public function index(Request $request): ProductCollection|JsonResponse { try { - $perPage = $request->get('per_page', 15); + $perPage = (int) $request->get('per_page', 15); $filters = [ 'search' => $request->get('search'), 'categorie' => $request->get('categorie_id'), 'fournisseur_id' => $request->get('fournisseur_id'), 'low_stock' => $request->get('low_stock'), 'expiring_soon' => $request->get('expiring_soon'), + 'is_intervention' => $request->get('is_intervention'), 'sort_by' => $request->get('sort_by', 'created_at'), 'sort_direction' => $request->get('sort_direction', 'desc'), ]; @@ -172,7 +173,7 @@ class ProductController extends Controller public function lowStock(Request $request): ProductCollection|JsonResponse { try { - $perPage = $request->get('per_page', 15); + $perPage = (int) $request->get('per_page', 15); $products = $this->productRepository->getLowStockProducts($perPage); return new ProductCollection($products); @@ -197,7 +198,7 @@ class ProductController extends Controller { try { $categoryId = $request->get('category_id'); - $perPage = $request->get('per_page', 15); + $perPage = (int) $request->get('per_page', 15); if (empty($categoryId)) { return response()->json([ diff --git a/thanasoft-back/app/Http/Requests/StoreInterventionRequest.php b/thanasoft-back/app/Http/Requests/StoreInterventionRequest.php index a63a6a7..4d27545 100644 --- a/thanasoft-back/app/Http/Requests/StoreInterventionRequest.php +++ b/thanasoft-back/app/Http/Requests/StoreInterventionRequest.php @@ -28,6 +28,7 @@ class StoreInterventionRequest extends FormRequest 'deceased_id' => ['nullable', 'exists:deceased,id'], 'order_giver' => ['nullable', 'string', 'max:255'], 'location_id' => ['nullable', 'exists:client_locations,id'], + 'product_id' => ['nullable', 'exists:products,id'], 'type' => ['required', Rule::in([ 'thanatopraxie', 'toilette_mortuaire', diff --git a/thanasoft-back/app/Http/Requests/StoreProductCategoryRequest.php b/thanasoft-back/app/Http/Requests/StoreProductCategoryRequest.php index c056295..61480de 100644 --- a/thanasoft-back/app/Http/Requests/StoreProductCategoryRequest.php +++ b/thanasoft-back/app/Http/Requests/StoreProductCategoryRequest.php @@ -22,27 +22,12 @@ class StoreProductCategoryRequest extends FormRequest public function rules(): array { return [ - 'parent_id' => 'nullable|exists:product_categories,id', 'code' => 'required|string|max:64|unique:product_categories,code', 'name' => 'required|string|max:191', 'description' => 'nullable|string', + 'parent_id' => 'nullable|exists:product_categories,id', + 'intervention' => 'boolean', 'active' => 'boolean', ]; } - - public function messages(): array - { - return [ - 'parent_id.exists' => 'La catégorie parente sélectionnée n\'existe pas.', - 'code.required' => 'Le code de la catégorie est obligatoire.', - 'code.string' => 'Le code de la catégorie doit être une chaîne de caractères.', - 'code.max' => 'Le code de la catégorie ne peut pas dépasser 64 caractères.', - 'code.unique' => 'Ce code de catégorie existe déjà.', - 'name.required' => 'Le nom de la catégorie est obligatoire.', - 'name.string' => 'Le nom de la catégorie doit être une chaîne de caractères.', - 'name.max' => 'Le nom de la catégorie ne peut pas dépasser 191 caractères.', - 'description.string' => 'La description doit être une chaîne de caractères.', - 'active.boolean' => 'Le statut actif doit être un booléen.', - ]; - } } diff --git a/thanasoft-back/app/Http/Requests/UpdateInterventionRequest.php b/thanasoft-back/app/Http/Requests/UpdateInterventionRequest.php index b9e27e1..6a3c1c5 100644 --- a/thanasoft-back/app/Http/Requests/UpdateInterventionRequest.php +++ b/thanasoft-back/app/Http/Requests/UpdateInterventionRequest.php @@ -28,6 +28,7 @@ class UpdateInterventionRequest extends FormRequest 'deceased_id' => ['nullable', 'exists:deceased,id'], 'order_giver' => ['nullable', 'string', 'max:255'], 'location_id' => ['nullable', 'exists:client_locations,id'], + 'product_id' => ['nullable', 'exists:products,id'], 'type' => ['sometimes', 'required', Rule::in([ 'thanatopraxie', 'toilette_mortuaire', diff --git a/thanasoft-back/app/Http/Requests/UpdateProductCategoryRequest.php b/thanasoft-back/app/Http/Requests/UpdateProductCategoryRequest.php index c59f630..11f6cf1 100644 --- a/thanasoft-back/app/Http/Requests/UpdateProductCategoryRequest.php +++ b/thanasoft-back/app/Http/Requests/UpdateProductCategoryRequest.php @@ -3,6 +3,7 @@ namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\Rule; class UpdateProductCategoryRequest extends FormRequest { @@ -21,28 +22,27 @@ class UpdateProductCategoryRequest extends FormRequest */ public function rules(): array { - $categoryId = $this->route('id'); - return [ - 'parent_id' => 'nullable|exists:product_categories,id', - 'code' => "nullable|string|max:64|unique:product_categories,code,{$categoryId}", - 'name' => 'nullable|string|max:191', + 'code' => [ + 'required', + 'string', + 'max:64', + Rule::unique('product_categories')->ignore($this->route('product_category')), + ], + 'name' => 'required|string|max:191', 'description' => 'nullable|string', - 'active' => 'nullable|boolean', - ]; - } - - public function messages(): array - { - return [ - 'parent_id.exists' => 'La catégorie parente sélectionnée n\'existe pas.', - 'code.string' => 'Le code de la catégorie doit être une chaîne de caractères.', - 'code.max' => 'Le code de la catégorie ne peut pas dépasser 64 caractères.', - 'code.unique' => 'Ce code de catégorie existe déjà.', - 'name.string' => 'Le nom de la catégorie doit être une chaîne de caractères.', - 'name.max' => 'Le nom de la catégorie ne peut pas dépasser 191 caractères.', - 'description.string' => 'La description doit être une chaîne de caractères.', - 'active.boolean' => 'Le statut actif doit être un booléen.', + 'parent_id' => [ + 'nullable', + 'exists:product_categories,id', + // Prevent setting parent to itself + function ($attribute, $value, $fail) { + if ($this->route('product_category') && $value == $this->route('product_category')->id) { + $fail('The parent category cannot be the category itself.'); + } + }, + ], + 'intervention' => 'boolean', + 'active' => 'boolean', ]; } } diff --git a/thanasoft-back/app/Http/Resources/ProductCategory/ProductCategoryResource.php b/thanasoft-back/app/Http/Resources/ProductCategory/ProductCategoryResource.php index 7ca9716..ae45d76 100644 --- a/thanasoft-back/app/Http/Resources/ProductCategory/ProductCategoryResource.php +++ b/thanasoft-back/app/Http/Resources/ProductCategory/ProductCategoryResource.php @@ -22,6 +22,7 @@ class ProductCategoryResource extends JsonResource 'description' => $this->description, 'active' => $this->active, 'path' => $this->path, + 'intervention'=> $this->intervention, 'has_children' => $this->hasChildren(), 'has_products' => $this->hasProducts(), 'children_count' => $this->children()->count(), diff --git a/thanasoft-back/app/Http/Resources/ProductCategoryResource.php b/thanasoft-back/app/Http/Resources/ProductCategoryResource.php new file mode 100644 index 0000000..5632ded --- /dev/null +++ b/thanasoft-back/app/Http/Resources/ProductCategoryResource.php @@ -0,0 +1,31 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->id, + 'parent_id' => $this->parent_id, + 'code' => $this->code, + 'name' => $this->name, + 'description' => $this->description, + 'intervention' => $this->intervention, + 'active' => $this->active, + 'created_at' => $this->created_at, + 'updated_at' => $this->updated_at, + 'parent' => new ProductCategoryResource($this->whenLoaded('parent')), + 'children' => ProductCategoryResource::collection($this->whenLoaded('children')), + ]; + } +} diff --git a/thanasoft-back/app/Models/Intervention.php b/thanasoft-back/app/Models/Intervention.php index 2ce4329..1572028 100644 --- a/thanasoft-back/app/Models/Intervention.php +++ b/thanasoft-back/app/Models/Intervention.php @@ -23,6 +23,7 @@ class Intervention extends Model 'order_giver', 'location_id', 'type', + 'product_id', 'scheduled_at', 'duration_min', 'status', @@ -41,6 +42,14 @@ class Intervention extends Model 'attachments_count' => 'integer' ]; + /** + * Get the product associated with the intervention. + */ + public function product(): BelongsTo + { + return $this->belongsTo(Product::class); + } + /** * Get the client associated with the intervention. */ diff --git a/thanasoft-back/app/Models/Product.php b/thanasoft-back/app/Models/Product.php index fde2923..fe332cc 100644 --- a/thanasoft-back/app/Models/Product.php +++ b/thanasoft-back/app/Models/Product.php @@ -48,7 +48,7 @@ class Product extends Model */ public function category(): BelongsTo { - return $this->belongsTo(ProductCategory::class); + return $this->belongsTo(ProductCategory::class, 'categorie_id'); } /** diff --git a/thanasoft-back/app/Models/ProductCategory.php b/thanasoft-back/app/Models/ProductCategory.php index 87f68f0..a0cf4d8 100644 --- a/thanasoft-back/app/Models/ProductCategory.php +++ b/thanasoft-back/app/Models/ProductCategory.php @@ -2,26 +2,31 @@ 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 ProductCategory extends Model { + use HasFactory; + protected $fillable = [ 'parent_id', 'code', 'name', 'description', + 'intervention', 'active', ]; protected $casts = [ 'active' => 'boolean', + 'intervention' => 'boolean', ]; /** - * Get the parent category + * Get the parent category. */ public function parent(): BelongsTo { @@ -29,7 +34,7 @@ class ProductCategory extends Model } /** - * Get the child categories + * Get the child categories. */ public function children(): HasMany { @@ -37,39 +42,7 @@ class ProductCategory extends Model } /** - * Get all products in this category - */ - public function products(): HasMany - { - return $this->hasMany(Product::class, 'categorie_id'); - } - - /** - * Get all descendant categories (recursive) - */ - public function descendants(): HasMany - { - return $this->children()->with('descendants'); - } - - /** - * Get the hierarchical path of the category - */ - public function getPathAttribute(): string - { - $path = $this->name; - $parent = $this->parent; - - while ($parent) { - $path = $parent->name . ' > ' . $path; - $parent = $parent->parent; - } - - return $path; - } - - /** - * Scope to get only active categories + * Scope a query to only include active categories. */ public function scopeActive($query) { @@ -77,7 +50,7 @@ class ProductCategory extends Model } /** - * Scope to get root categories (no parent) + * Scope a query to only include root categories. */ public function scopeRoots($query) { @@ -85,7 +58,7 @@ class ProductCategory extends Model } /** - * Check if category has children + * Check if the category has children. */ public function hasChildren(): bool { @@ -93,8 +66,16 @@ class ProductCategory extends Model } /** - * Check if category has products + * Check if the category has products. + * Note: Assuming a Product model exists with a category_id or similar relationship. + * Since the migration for products might not be linked yet, this is a placeholder or checks a relation if defined. + * For now, I will assume a products relationship exists or will be added. */ + public function products(): HasMany + { + return $this->hasMany(Product::class, 'categorie_id'); + } + public function hasProducts(): bool { return $this->products()->exists(); diff --git a/thanasoft-back/app/Providers/AppServiceProvider.php b/thanasoft-back/app/Providers/AppServiceProvider.php index 4b57918..f79d6fd 100644 --- a/thanasoft-back/app/Providers/AppServiceProvider.php +++ b/thanasoft-back/app/Providers/AppServiceProvider.php @@ -64,6 +64,8 @@ class AppServiceProvider extends ServiceProvider $this->app->bind(\App\Repositories\InterventionPractitionerRepositoryInterface::class, \App\Repositories\InterventionPractitionerRepository::class); $this->app->bind(\App\Repositories\FileRepositoryInterface::class, \App\Repositories\FileRepository::class); + + $this->app->bind(\App\Repositories\ProductCategoryRepositoryInterface::class, \App\Repositories\ProductCategoryRepository::class); } /** diff --git a/thanasoft-back/app/Repositories/ProductCategoryRepository.php b/thanasoft-back/app/Repositories/ProductCategoryRepository.php index e83bab9..0801619 100644 --- a/thanasoft-back/app/Repositories/ProductCategoryRepository.php +++ b/thanasoft-back/app/Repositories/ProductCategoryRepository.php @@ -5,14 +5,47 @@ namespace App\Repositories; use App\Models\ProductCategory; use Illuminate\Database\Eloquent\Collection; use Illuminate\Contracts\Pagination\LengthAwarePaginator; +use Illuminate\Database\Eloquent\Builder; -class ProductCategoryRepository implements ProductCategoryRepositoryInterface +class ProductCategoryRepository extends BaseRepository implements ProductCategoryRepositoryInterface { - protected $model; - + /** + * ProductCategoryRepository constructor. + * + * @param ProductCategory $model + */ public function __construct(ProductCategory $model) { - $this->model = $model; + parent::__construct($model); + } + + /** + * Get active categories only. + * Overriding or adding custom logic if needed, but BaseRepository might not have 'active' scope standard. + * Keeping it as a specific method for this repository. + */ + public function getActive(): Collection + { + return $this->model->active()->orderBy('name')->get(); + } + + /** + * Get root categories (no parent). + */ + public function getRoots(): Collection + { + return $this->model->roots()->orderBy('name')->get(); + } + + /** + * Get categories with their children. + */ + public function getWithChildren(): Collection + { + return $this->model->with('children') + ->roots() + ->orderBy('name') + ->get(); } /** @@ -22,25 +55,27 @@ class ProductCategoryRepository implements ProductCategoryRepositoryInterface { $query = $this->model->newQuery(); - // Apply filters - if (isset($filters['search']) && !empty($filters['search'])) { + if (!empty($filters['search'])) { $search = $filters['search']; - $query->where(function ($q) use ($search) { + $query->where(function (Builder $q) use ($search) { $q->where('name', 'like', "%{$search}%") ->orWhere('code', 'like', "%{$search}%") ->orWhere('description', 'like', "%{$search}%"); }); } - if (isset($filters['active']) && $filters['active'] !== null) { - $query->where('active', $filters['active']); + if (isset($filters['active']) && $filters['active'] !== '') { + $query->where('active', (bool) $filters['active']); } - if (isset($filters['parent_id']) && $filters['parent_id'] !== null) { - $query->where('parent_id', $filters['parent_id']); + if (isset($filters['parent_id']) && $filters['parent_id'] !== '') { + if ($filters['parent_id'] === 'null') { + $query->whereNull('parent_id'); + } else { + $query->where('parent_id', $filters['parent_id']); + } } - // Sorting $sortBy = $filters['sort_by'] ?? 'name'; $sortDirection = $filters['sort_direction'] ?? 'asc'; $query->orderBy($sortBy, $sortDirection); @@ -49,109 +84,22 @@ class ProductCategoryRepository implements ProductCategoryRepositoryInterface } /** - * Get all product categories - */ - public function all(array $filters = []): Collection - { - $query = $this->model->newQuery(); - - // Apply filters - if (isset($filters['active']) && $filters['active'] !== null) { - $query->where('active', $filters['active']); - } - - if (isset($filters['parent_id']) && $filters['parent_id'] !== null) { - $query->where('parent_id', $filters['parent_id']); - } - - $sortBy = $filters['sort_by'] ?? 'name'; - $sortDirection = $filters['sort_direction'] ?? 'asc'; - $query->orderBy($sortBy, $sortDirection); - - return $query->get(); - } - - /** - * Find a product category by ID - */ - public function find(string $id): ?ProductCategory - { - return $this->model->find($id); - } - - /** - * Create a new product category - */ - public function create(array $data): ProductCategory - { - return $this->model->create($data); - } - - /** - * Update a product category - */ - public function update(string $id, array $data): bool - { - $category = $this->find($id); - if ($category) { - return $category->update($data); - } - return false; - } - - /** - * Delete a product category - */ - public function delete(string $id): bool - { - $category = $this->find($id); - if ($category) { - return $category->delete(); - } - return false; - } - - /** - * Get active categories only - */ - public function getActive(): Collection - { - return $this->model->active()->orderBy('name')->get(); - } - - /** - * Get root categories (no parent) - */ - public function getRoots(): Collection - { - return $this->model->roots()->orderBy('name')->get(); - } - - /** - * Get categories with their children - */ - public function getWithChildren(): Collection - { - return $this->model->with('children') - ->roots() - ->orderBy('name') - ->get(); - } - - /** - * Search categories by name or code + * Search categories by name or code. + * BaseRepository usually has a general search, but this one is specific with 'code' and 'description'. */ public function search(string $term, int $perPage = 15): LengthAwarePaginator { - return $this->model->where('name', 'like', "%{$term}%") - ->orWhere('code', 'like', "%{$term}%") - ->orWhere('description', 'like', "%{$term}%") - ->orderBy('name') - ->paginate($perPage); + return $this->model->where(function ($query) use ($term) { + $query->where('name', 'like', "%{$term}%") + ->orWhere('code', 'like', "%{$term}%") + ->orWhere('description', 'like', "%{$term}%"); + }) + ->orderBy('name') + ->paginate($perPage); } /** - * Get category statistics + * Get category statistics. */ public function getStatistics(): array { @@ -171,11 +119,12 @@ class ProductCategoryRepository implements ProductCategoryRepositoryInterface } /** - * Check if category can be deleted + * Check if category can be deleted. */ - public function canDelete(string $id): bool + public function canDelete($id): bool { $category = $this->find($id); + if (!$category) { return false; } diff --git a/thanasoft-back/app/Repositories/ProductCategoryRepositoryInterface.php b/thanasoft-back/app/Repositories/ProductCategoryRepositoryInterface.php index 2fa68ba..3ed0034 100644 --- a/thanasoft-back/app/Repositories/ProductCategoryRepositoryInterface.php +++ b/thanasoft-back/app/Repositories/ProductCategoryRepositoryInterface.php @@ -3,41 +3,16 @@ namespace App\Repositories; use App\Models\ProductCategory; -use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Collection; use Illuminate\Contracts\Pagination\LengthAwarePaginator; -interface ProductCategoryRepositoryInterface +interface ProductCategoryRepositoryInterface extends BaseRepositoryInterface { /** * Get all product categories with pagination */ public function paginate(int $perPage = 15, array $filters = []): LengthAwarePaginator; - /** - * Get all product categories - */ - public function all(array $filters = []): Collection; - - /** - * Find a product category by ID - */ - public function find(string $id): ?ProductCategory; - - /** - * Create a new product category - */ - public function create(array $data): ProductCategory; - - /** - * Update a product category - */ - public function update(string $id, array $data): bool; - - /** - * Delete a product category - */ - public function delete(string $id): bool; - /** * Get active categories only */ diff --git a/thanasoft-back/app/Repositories/ProductRepository.php b/thanasoft-back/app/Repositories/ProductRepository.php index 2196950..1a07c12 100644 --- a/thanasoft-back/app/Repositories/ProductRepository.php +++ b/thanasoft-back/app/Repositories/ProductRepository.php @@ -47,6 +47,12 @@ class ProductRepository extends BaseRepository implements ProductRepositoryInter ->where('date_expiration', '>=', now()->toDateString()); } + if (isset($filters['is_intervention'])) { + $query->whereHas('category', function ($q) use ($filters) { + $q->where('intervention', filter_var($filters['is_intervention'], FILTER_VALIDATE_BOOLEAN)); + }); + } + // Apply sorting $sortField = $filters['sort_by'] ?? 'created_at'; $sortDirection = $filters['sort_direction'] ?? 'desc'; diff --git a/thanasoft-back/app/Repositories/ProductRepositoryInterface.php b/thanasoft-back/app/Repositories/ProductRepositoryInterface.php index a6dfc4d..0f8b152 100644 --- a/thanasoft-back/app/Repositories/ProductRepositoryInterface.php +++ b/thanasoft-back/app/Repositories/ProductRepositoryInterface.php @@ -14,7 +14,7 @@ interface ProductRepositoryInterface extends BaseRepositoryInterface public function searchByName(string $name, int $perPage = 15, bool $exactMatch = false); - public function getByCategory(string $category, int $perPage = 15): LengthAwarePaginator; + public function getByCategory(int $categoryId, int $perPage = 15): LengthAwarePaginator; public function getProductsByFournisseur(int $fournisseurId): LengthAwarePaginator; } diff --git a/thanasoft-back/config/cors.php b/thanasoft-back/config/cors.php index c718056..e4a281d 100644 --- a/thanasoft-back/config/cors.php +++ b/thanasoft-back/config/cors.php @@ -9,7 +9,7 @@ return [ // IMPORTANT: Do NOT use '*' when sending credentials. List explicit origins. // Set FRONTEND_URL in .env to override the default if needed. - 'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:8081')], + 'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:8081'), 'http://localhost:8080'], // Alternatively, use patterns (kept empty for clarity) 'allowed_origins_patterns' => [], diff --git a/thanasoft-back/database/migrations/2026_01_05_155500_add_intervention_to_product_categories_table.php b/thanasoft-back/database/migrations/2026_01_05_155500_add_intervention_to_product_categories_table.php new file mode 100644 index 0000000..2406abf --- /dev/null +++ b/thanasoft-back/database/migrations/2026_01_05_155500_add_intervention_to_product_categories_table.php @@ -0,0 +1,32 @@ +boolean('intervention')->default(false)->after('description'); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('product_categories', function (Blueprint $table) { + if (Schema::hasColumn('product_categories', 'intervention')) { + $table->dropColumn('intervention'); + } + }); + } +}; diff --git a/thanasoft-back/database/migrations/2026_01_05_164000_refactor_products_category_column.php b/thanasoft-back/database/migrations/2026_01_05_164000_refactor_products_category_column.php new file mode 100644 index 0000000..e23c1f6 --- /dev/null +++ b/thanasoft-back/database/migrations/2026_01_05_164000_refactor_products_category_column.php @@ -0,0 +1,35 @@ +dropColumn('categorie'); + } + + // Ensure categorie_id exists and is FK (It should be from previous migration) + // If strictly needed we could check !Schema::hasColumn('products', 'categorie_id') but we know it failed on that. + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('products', function (Blueprint $table) { + if (!Schema::hasColumn('products', 'categorie')) { + $table->string('categorie')->nullable(); + } + }); + } +}; diff --git a/thanasoft-back/database/migrations/2026_01_05_165400_add_product_id_to_interventions_table.php b/thanasoft-back/database/migrations/2026_01_05_165400_add_product_id_to_interventions_table.php new file mode 100644 index 0000000..fbe27e0 --- /dev/null +++ b/thanasoft-back/database/migrations/2026_01_05_165400_add_product_id_to_interventions_table.php @@ -0,0 +1,30 @@ +foreignId('product_id')->nullable()->after('location_id') + ->constrained('products')->nullOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('interventions', function (Blueprint $table) { + $table->dropForeign(['product_id']); + $table->dropColumn('product_id'); + }); + } +}; diff --git a/thanasoft-front/src/components/Organism/Agenda/InterventionMultiStepModal.vue b/thanasoft-front/src/components/Organism/Agenda/InterventionMultiStepModal.vue index bf06920..0acb9bc 100644 --- a/thanasoft-front/src/components/Organism/Agenda/InterventionMultiStepModal.vue +++ b/thanasoft-front/src/components/Organism/Agenda/InterventionMultiStepModal.vue @@ -1,18 +1,18 @@ - - diff --git a/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepClient.vue b/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepClient.vue new file mode 100644 index 0000000..b29109e --- /dev/null +++ b/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepClient.vue @@ -0,0 +1,231 @@ + + + diff --git a/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepDeceased.vue b/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepDeceased.vue new file mode 100644 index 0000000..66185fd --- /dev/null +++ b/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepDeceased.vue @@ -0,0 +1,153 @@ + + + diff --git a/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepDocuments.vue b/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepDocuments.vue new file mode 100644 index 0000000..63d45ed --- /dev/null +++ b/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepDocuments.vue @@ -0,0 +1,161 @@ + + + diff --git a/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepIntervention.vue b/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepIntervention.vue new file mode 100644 index 0000000..797124e --- /dev/null +++ b/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepIntervention.vue @@ -0,0 +1,175 @@ + + + diff --git a/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepLocation.vue b/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepLocation.vue new file mode 100644 index 0000000..d8dce87 --- /dev/null +++ b/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepLocation.vue @@ -0,0 +1,154 @@ + + + diff --git a/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepProductSelection.vue b/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepProductSelection.vue new file mode 100644 index 0000000..020290d --- /dev/null +++ b/thanasoft-front/src/components/Organism/Agenda/WizardSteps/StepProductSelection.vue @@ -0,0 +1,121 @@ + + + diff --git a/thanasoft-front/src/components/Organism/Agenda/WizardSteps/WizardProgress.vue b/thanasoft-front/src/components/Organism/Agenda/WizardSteps/WizardProgress.vue new file mode 100644 index 0000000..7a56762 --- /dev/null +++ b/thanasoft-front/src/components/Organism/Agenda/WizardSteps/WizardProgress.vue @@ -0,0 +1,41 @@ + + + diff --git a/thanasoft-front/src/components/Organism/Parametrage/ProductCategories/ProductCategoryList.vue b/thanasoft-front/src/components/Organism/Parametrage/ProductCategories/ProductCategoryList.vue new file mode 100644 index 0000000..a78fecc --- /dev/null +++ b/thanasoft-front/src/components/Organism/Parametrage/ProductCategories/ProductCategoryList.vue @@ -0,0 +1,119 @@ + + + diff --git a/thanasoft-front/src/components/Organism/Parametrage/ProductCategories/ProductCategoryModal.vue b/thanasoft-front/src/components/Organism/Parametrage/ProductCategories/ProductCategoryModal.vue new file mode 100644 index 0000000..85cffd6 --- /dev/null +++ b/thanasoft-front/src/components/Organism/Parametrage/ProductCategories/ProductCategoryModal.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/thanasoft-front/src/components/Organism/Stock/AddProductPresentation.vue b/thanasoft-front/src/components/Organism/Stock/AddProductPresentation.vue index 352895d..68fab1f 100644 --- a/thanasoft-front/src/components/Organism/Stock/AddProductPresentation.vue +++ b/thanasoft-front/src/components/Organism/Stock/AddProductPresentation.vue @@ -48,7 +48,7 @@
-
+
@@ -78,33 +78,31 @@ >
- {{ validationErrors.categorie[0] }} + {{ validationErrors.categorie_id[0] }}
-
+
-
+
@@ -150,7 +148,7 @@
-
+
@@ -173,7 +171,7 @@
-
+