smallIncrements('id'); $table->unsignedSmallInteger('store_id')->index(); // Relación con sucursal $table->unsignedSmallInteger('work_center_id')->nullable()->index(); $table->string('code', 16)->unique(); $table->string('name', 96)->index(); $table->mediumText('description')->nullable(); $table->unsignedMediumInteger('manager_id')->nullable()->index(); // sat_codigo_postal. $table->string('tel')->nullable(); $table->string('tel2')->nullable(); $table->unsignedTinyInteger('priority')->nullable(); $table->boolean('status')->default(true)->index(); $table->timestamps(); // Indices $table->unique(['store_id', 'name']); $table->foreign('store_id')->references('id')->on('stores')->onDelete('cascade'); $table->foreign('work_center_id')->references('id')->on('store_work_centers')->onDelete('cascade'); $table->foreign('manager_id')->references('id')->on('users')->onUpdate('restrict')->onDelete('restrict'); }); Schema::create('warehouse_movements', function (Blueprint $table) { $table->mediumIncrements('id'); $table->unsignedSmallInteger('store_id')->index(); $table->unsignedSmallInteger('warehouse_id')->index(); // Almacén involucrado // Tipo de ajuste (ajuste de inventario o traspaso) $table->unsignedTinyInteger('movement_type')->index(); // Tipo de movimiento: ajuste o traspaso $table->unsignedMediumInteger('movement_id')->nullable(); // UID específico por sucursal $table->unsignedMediumInteger('created_by')->index(); // Usuario que registró el movimiento $table->unsignedMediumInteger('approved_by')->nullable()->index(); // Usuario que autorizó el movimiento // Cantidad ajustada $table->decimal('adjusted_quantity', 13, 6); // Cantidad ajustada para el ajuste o transferencia // Costo asociado $table->decimal('cost', 9, 2)->unsigned()->default(0); // Costo de la operación // Notas sobre el ajuste $table->mediumText('notes')->nullable(); // Notas adicionales // Campos para el traspaso $table->unsignedSmallInteger('to_warehouse_id')->nullable()->index(); // Almacén de destino (solo para traspasos) $table->unsignedTinyInteger('status')->index(); // Estatus de la orden ('pending', 'approved', 'received', 'cancelled') $table->softDeletes(); $table->timestamps(); // Claves foráneas $table->foreign('store_id')->references('id')->on('stores')->onDelete('restrict'); $table->foreign('warehouse_id')->references('id')->on('warehouses')->onDelete('restrict'); $table->foreign('to_warehouse_id')->references('id')->on('warehouses')->onDelete('restrict'); $table->foreign('created_by')->references('id')->on('users')->onDelete('restrict'); $table->foreign('approved_by')->references('id')->on('users')->onDelete('restrict'); }); Schema::create('lot_numbers', function (Blueprint $table) { $table->mediumIncrements('id'); $table->unsignedMediumInteger('product_id')->index(); $table->unsignedSmallInteger('store_id')->index(); $table->unsignedSmallInteger('warehouse_id')->index(); $table->string('lot_number', 50)->unique()->comment('Número único del lote'); $table->date('production_date')->nullable(); $table->date('expiry_date')->nullable(); $table->decimal('initial_quantity', 13, 6)->unsigned()->default(0); $table->decimal('remaining_quantity', 13, 6)->unsigned()->default(0); $table->decimal('cost', 9, 2)->unsigned()->default(0); $table->timestamps(); $table->foreign('product_id')->references('id')->on('products')->onDelete('restrict'); $table->foreign('store_id')->references('id')->on('stores')->onDelete('restrict'); $table->foreign('warehouse_id')->references('id')->on('warehouses')->onDelete('restrict'); }); Schema::create('inventory_stock_levels', function (Blueprint $table) { $table->mediumIncrements('id'); $table->unsignedMediumInteger('product_id')->index(); $table->unsignedSmallInteger('store_id')->nullable()->index(); $table->unsignedSmallInteger('warehouse_id')->index(); $table->decimal('quantity', 13, 6)->unsigned()->comment('Stock total disponible en el almacén'); // Stock separado por área $table->decimal('pos_stock', 13, 6)->unsigned()->default(0)->comment('Stock destinado a ventas en POS'); $table->decimal('ecommerce_stock', 13, 6)->unsigned()->default(0)->comment('Stock destinado a eCommerce'); $table->decimal('purchase_reserved_stock', 13, 6)->unsigned()->default(0)->comment('Stock reservado para órdenes de compra'); $table->decimal('asset_stock', 13, 6)->unsigned()->default(0)->comment('Stock reservado para uso interno'); // Alertas de Stock mínimo y máximo $table->unsignedTinyInteger('alert_minimum_stock')->nullable()->index(); $table->decimal('minimum_stock', 13, 6)->unsigned()->nullable(); $table->unsignedTinyInteger('alert_maximum_stock')->nullable()->index(); $table->decimal('maximum_stock', 13, 6)->unsigned()->nullable(); // Costos asociados $table->decimal('last_cost', 9, 2)->unsigned()->default(0); // Último costo registrado $table->decimal('average_cost', 9, 2)->unsigned()->default(0); // Costo promedio ponderado $table->decimal('total_last_cost', 11, 2)->unsigned()->default(0); // Costo total último costo registrado $table->decimal('total_average_cost', 11, 2)->unsigned()->default(0); // Costo total promedio ponderado $table->decimal('total_identified_cost', 11, 2)->unsigned()->default(0); // Costo total identificado $table->unsignedTinyInteger('costing_method')->index(); // Método de costeo: 'average', 'last', 'identified' $table->timestamps(); $table->unique(['warehouse_id', 'product_id']); $table->foreign('product_id')->references('id')->on('products')->onDelete('restrict'); $table->foreign('store_id')->references('id')->on('stores')->onDelete('restrict'); $table->foreign('warehouse_id')->references('id')->on('warehouses')->onDelete('restrict'); }); Schema::create('inventory_movements', function (Blueprint $table) { $table->integerIncrements('id'); $table->unsignedMediumInteger('product_id')->index(); $table->unsignedSmallInteger('store_id')->index(); $table->unsignedSmallInteger('warehouse_id')->index(); $table->enum('movement_type', ['in', 'out'])->index(); // Tipo de movimiento (entrada/salida) $table->decimal('quantity', 13, 6)->unsigned(); $table->decimal('cost', 9, 2)->unsigned(); // Costo asociado a la transacción $table->decimal('cost_before', 9, 2)->unsigned()->nullable(); $table->decimal('cost_after', 9, 2)->unsigned()->nullable(); $table->enum('cost_type', ['average', 'last', 'specific'])->default('last'); $table->text('notes')->nullable(); // Notas sobre el movimiento // Define el campo para la relación polimórfica manualmente $table->unsignedMediumInteger('transactionable_id')->index(); $table->string('transactionable_type')->index(); $table->unsignedMediumInteger('created_by')->index(); // Usuario que registró el movimiento $table->timestamps(); // Agrega el índice con un nombre específico para evitar el problema de longitud $table->index(['transactionable_type', 'transactionable_id'], 'inventory_movements_transactionable_index'); $table->foreign('product_id')->references('id')->on('products')->onDelete('restrict'); $table->foreign('store_id')->references('id')->on('stores')->onDelete('restrict'); $table->foreign('warehouse_id')->references('id')->on('warehouses')->onDelete('restrict'); $table->foreign('created_by')->references('id')->on('users')->onDelete('restrict'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('warehouses'); Schema::dropIfExists('warehouse_movements'); Schema::dropIfExists('lot_numbers'); Schema::dropIfExists('inventory_stock_levels'); Schema::dropIfExists('inventory_movements'); } };