feat: agregar soporte para almacenamiento en masa de subcategorías y validaciones en la solicitud de creación

This commit is contained in:
Juan Felipe Zapata Moreno 2026-03-02 13:12:54 -06:00
parent 48fe26899a
commit 3d5198a65a
5 changed files with 80 additions and 4 deletions

View File

@ -52,9 +52,23 @@ public function index(Request $request)
$products = $products->orderBy('name') $products = $products->orderBy('name')
->paginate(config('app.pagination')); ->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([ return ApiResponse::OK->response([
'products' => $products, 'products' => $products,
'total_inventory_value' => round($totalInventoryValue, 2) 'total_inventory_value' => round($totalInventoryValue, 2),
'main_warehouse_id' => $mainWarehouseId,
]); ]);
} }

View File

@ -29,6 +29,15 @@ public function show(Category $category, Subcategory $subcategory)
public function store(SubcategoryStoreRequest $request, Category $category) 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()); $subcategoria = $category->subcategories()->create($request->validated());
return ApiResponse::OK->response([ return ApiResponse::OK->response([
@ -47,6 +56,12 @@ public function update(SubcategoryUpdateRequest $request, Category $category, Su
public function destroy(Category $category, Subcategory $subcategory) 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(); $subcategory->delete();
return ApiResponse::OK->response(); return ApiResponse::OK->response();

View File

@ -11,6 +11,13 @@ public function authorize(): bool
public function rules(): array public function rules(): array
{ {
if($this->isBulk()) {
return [
'*.name' => ['required', 'string', 'max:100'],
'*.description' => ['nullable', 'string', 'max:255'],
'*.is_active' => ['nullable', 'boolean'],
];
}
return [ return [
'name' => ['required', 'string', 'max:100'], 'name' => ['required', 'string', 'max:100'],
'description' => ['nullable', 'string', 'max:255'], 'description' => ['nullable', 'string', 'max:255'],
@ -21,12 +28,24 @@ public function rules(): array
public function messages(): array public function messages(): array
{ {
return [ 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.required' => 'El nombre es obligatorio.',
'name.string' => 'El nombre debe ser una cadena de texto.', 'name.string' => 'El nombre debe ser una cadena de texto.',
'name.max' => 'El nombre no debe exceder los 100 caracteres.', 'name.max' => 'El nombre no debe exceder los 100 caracteres.',
'description.string' => 'La descripción debe ser una cadena de texto.', 'description.string' => 'La descripción debe ser una cadena de texto.',
'description.max' => 'La descripción no debe exceder los 255 caracteres.', '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());
}
} }

View File

@ -120,7 +120,7 @@ private function createNewProduct(array $row)
// Buscar unidad de medida (requerida) // Buscar unidad de medida (requerida)
$unitId = null; $unitId = null;
if (!empty($row['unidad_medida'])) { 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'])) ->orWhere('abbreviation', trim($row['unidad_medida']))
->first(); ->first();
@ -133,7 +133,7 @@ private function createNewProduct(array $row)
} }
} else { } else {
// Si no se proporciona, usar 'Pieza' por defecto // Si no se proporciona, usar 'Pieza' por defecto
$unit = \App\Models\UnitOfMeasurement::where('name', 'Pieza')->first(); $unit = UnitOfMeasurement::where('name', 'Pieza')->first();
if ($unit) { if ($unit) {
$unitId = $unit->id; $unitId = $unit->id;
} else { } else {

View File

@ -0,0 +1,28 @@
<?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::table('inventories', function (Blueprint $table) {
$table->string('key_sat')->nullable()->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('inventories', function (Blueprint $table) {
$table->integer('key_sat')->nullable()->change();
});
}
};