From 3d5198a65a329ad8ae052285c777137c9a077335 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Mon, 2 Mar 2026 13:12:54 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20agregar=20soporte=20para=20almacenamien?= =?UTF-8?q?to=20en=20masa=20de=20subcategor=C3=ADas=20y=20validaciones=20e?= =?UTF-8?q?n=20la=20solicitud=20de=20creaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/App/InventoryController.php | 16 ++++++++++- .../Controllers/App/SubcategoryController.php | 15 ++++++++++ .../Requests/App/SubcategoryStoreRequest.php | 21 +++++++++++++- app/Imports/ProductsImport.php | 4 +-- ...key_sat_to_string_in_inventories_table.php | 28 +++++++++++++++++++ 5 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 database/migrations/2026_03_02_084516_change_key_sat_to_string_in_inventories_table.php diff --git a/app/Http/Controllers/App/InventoryController.php b/app/Http/Controllers/App/InventoryController.php index cb5f5ea..4efa152 100644 --- a/app/Http/Controllers/App/InventoryController.php +++ b/app/Http/Controllers/App/InventoryController.php @@ -52,9 +52,23 @@ public function index(Request $request) $products = $products->orderBy('name') ->paginate(config('app.pagination')); + // Stock del almacén principal por producto + $mainWarehouseId = DB::table('warehouses')->where('is_main', true)->value('id'); + $mainWarehouseStocks = $mainWarehouseId + ? DB::table('inventory_warehouse') + ->where('warehouse_id', $mainWarehouseId) + ->whereIn('inventory_id', $products->pluck('id')->toArray()) + ->pluck('stock', 'inventory_id') + : collect(); + + $products->each(function ($product) use ($mainWarehouseStocks) { + $product->main_warehouse_stock = (float) ($mainWarehouseStocks[$product->id] ?? 0); + }); + return ApiResponse::OK->response([ 'products' => $products, - 'total_inventory_value' => round($totalInventoryValue, 2) + 'total_inventory_value' => round($totalInventoryValue, 2), + 'main_warehouse_id' => $mainWarehouseId, ]); } diff --git a/app/Http/Controllers/App/SubcategoryController.php b/app/Http/Controllers/App/SubcategoryController.php index 89a1a0d..388d127 100644 --- a/app/Http/Controllers/App/SubcategoryController.php +++ b/app/Http/Controllers/App/SubcategoryController.php @@ -29,6 +29,15 @@ public function show(Category $category, Subcategory $subcategory) public function store(SubcategoryStoreRequest $request, Category $category) { + if($request->isBulk()){ + $subcategorias = collect($request->validated()) + ->map(fn($data) => $category->subcategories()->create($data)); + + return ApiResponse::OK->response([ + 'models' => $subcategorias, + ]); + } + $subcategoria = $category->subcategories()->create($request->validated()); return ApiResponse::OK->response([ @@ -47,6 +56,12 @@ public function update(SubcategoryUpdateRequest $request, Category $category, Su public function destroy(Category $category, Subcategory $subcategory) { + if ($subcategory->inventories()->exists()) { + return ApiResponse::BAD_REQUEST->response([ + 'message' => 'No se puede eliminar la subclasificación porque tiene productos asociados.' + ]); + } + $subcategory->delete(); return ApiResponse::OK->response(); diff --git a/app/Http/Requests/App/SubcategoryStoreRequest.php b/app/Http/Requests/App/SubcategoryStoreRequest.php index 1891542..5a99127 100644 --- a/app/Http/Requests/App/SubcategoryStoreRequest.php +++ b/app/Http/Requests/App/SubcategoryStoreRequest.php @@ -11,6 +11,13 @@ public function authorize(): bool public function rules(): array { + if($this->isBulk()) { + return [ + '*.name' => ['required', 'string', 'max:100'], + '*.description' => ['nullable', 'string', 'max:255'], + '*.is_active' => ['nullable', 'boolean'], + ]; + } return [ 'name' => ['required', 'string', 'max:100'], 'description' => ['nullable', 'string', 'max:255'], @@ -21,12 +28,24 @@ public function rules(): array public function messages(): array { return [ + '*.name.required' => 'El nombre es obligatorio.', + '*.name.string' => 'El nombre debe ser una cadena de texto.', + '*.name.max' => 'El nombre no debe exceder los 100 caracteres.', + '*.description.string' => 'La descripción debe ser una cadena de texto.', + '*.description.max' => 'La descripción no debe exceder los 255 caracteres.', 'name.required' => 'El nombre es obligatorio.', 'name.string' => 'El nombre debe ser una cadena de texto.', 'name.max' => 'El nombre no debe exceder los 100 caracteres.', 'description.string' => 'La descripción debe ser una cadena de texto.', 'description.max' => 'La descripción no debe exceder los 255 caracteres.', - 'is_active.boolean' => 'El campo activo debe ser verdadero o falso.', ]; } + + /** + * Detecta si el payload es un arreglo de objetos + */ + public function isBulk(): bool + { + return is_array($this->all()) && array_is_list($this->all()); + } } diff --git a/app/Imports/ProductsImport.php b/app/Imports/ProductsImport.php index ded4459..84adf48 100644 --- a/app/Imports/ProductsImport.php +++ b/app/Imports/ProductsImport.php @@ -120,7 +120,7 @@ private function createNewProduct(array $row) // Buscar unidad de medida (requerida) $unitId = null; if (!empty($row['unidad_medida'])) { - $unit = \App\Models\UnitOfMeasurement::where('name', trim($row['unidad_medida'])) + $unit = UnitOfMeasurement::where('name', trim($row['unidad_medida'])) ->orWhere('abbreviation', trim($row['unidad_medida'])) ->first(); @@ -133,7 +133,7 @@ private function createNewProduct(array $row) } } else { // Si no se proporciona, usar 'Pieza' por defecto - $unit = \App\Models\UnitOfMeasurement::where('name', 'Pieza')->first(); + $unit = UnitOfMeasurement::where('name', 'Pieza')->first(); if ($unit) { $unitId = $unit->id; } else { diff --git a/database/migrations/2026_03_02_084516_change_key_sat_to_string_in_inventories_table.php b/database/migrations/2026_03_02_084516_change_key_sat_to_string_in_inventories_table.php new file mode 100644 index 0000000..5e7ed34 --- /dev/null +++ b/database/migrations/2026_03_02_084516_change_key_sat_to_string_in_inventories_table.php @@ -0,0 +1,28 @@ +string('key_sat')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('inventories', function (Blueprint $table) { + $table->integer('key_sat')->nullable()->change(); + }); + } +};