first commit

This commit is contained in:
2025-03-05 20:44:45 -06:00
commit 06dbf8e2a7
74 changed files with 9681 additions and 0 deletions

View File

@ -0,0 +1,84 @@
<?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('product_categories', function (Blueprint $table) {
$table->mediumIncrements('id');
$table->unsignedMediumInteger('parent_id')->nullable()->index();
$table->string('parent_slug')->nullable()->index();
$table->string('name')->index();
$table->string('slug')->nullable()->index();
$table->string('icon')->nullable();
$table->mediumText('description')->nullable();
$table->boolean('show_in_pos')->index();
$table->boolean('show_in_purchases')->index();
$table->boolean('show_in_ecommerce')->index();
$table->boolean('show_in_manufacturing')->index();
$table->boolean('show_in_quality')->index();
$table->boolean('show_in_assets')->index();
$table->unsignedTinyInteger('priority')->nullable()->index();
// Aditoria
$table->timestamps();
// Index
$table->unique(['parent_id', 'slug']);
// Relaciones
//$table->foreign('parent_id')->references('id')->on('product_categories')->onUpdate('restrict')->onDelete('cascade');
});
DB::unprepared("CREATE TRIGGER before_delete_category
BEFORE DELETE ON product_categories
FOR EACH ROW
BEGIN
-- Verificar si la categoría tiene hijos
IF (SELECT COUNT(*) FROM product_categories WHERE parent_id = OLD.id) > 0 THEN
-- Generar un error para evitar la eliminación
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'No se puede eliminar la categoría porque tiene categorías hijas.';
END IF;
END");
Schema::create('category_properties', function (Blueprint $table) {
$table->mediumIncrements('id');
$table->unsignedMediumInteger('category_id')->index();
$table->unsignedSmallInteger('property_id')->index();
$table->boolean('is_required'); // Si es obligatorio en esta categoría
$table->boolean('is_filterable'); // Si se puede usar en filtros
$table->timestamps();
// Relaciones
$table->foreign('category_id')->references('id')->on('product_categories')->onUpdate('restrict')->onDelete('cascade');
//$table->foreign('property_id')->references('id')->on('product_properties')->onUpdate('restrict')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('product_categories');
DB::unprepared("DROP TRIGGER IF EXISTS before_delete_category");
Schema::dropIfExists('category_properties');
}
};

View File

@ -0,0 +1,149 @@
<?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('products', function (Blueprint $table) {
$table->mediumIncrements('id');
$table->unsignedTinyInteger('type')->index();
$table->unsignedMediumInteger('category_id')->index(); // product_categories.id
$table->string('descripcion')->fulltext();
$table->mediumText('descripcion_completa')->nullable()->index();
$table->string('no_identificacion', 40)->nullable()->unique();
$table->string('slug')->nullable()->unique();
$table->boolean('available_in_pos')->index();
$table->boolean('available_in_purchases')->index();
$table->boolean('available_in_ecommerce')->index();
$table->boolean('available_in_maanufacturing')->index();
$table->boolean('available_in_quality')->index();
$table->boolean('available_in_assets')->index();
$table->string('c_clave_unidad', 3)->nullable()->index(); // sat_clave_unidad.
$table->unsignedInteger('c_clave_prod_serv')->nullable()->index(); // sat_clave_prod_serv.
$table->unsignedBigInteger('ean_code')->nullable()->unique();
$table->decimal('costo', 9, 2)->unsigned()->nullable();
$table->char('c_moneda', 3)->charset('ascii')->collation('ascii_general_ci')->nullable()->index();
$table->unsignedTinyInteger('c_objeto_imp')->nullable()->index(); // sat_objeto_imp.
$table->json('impuestos')->nullable();
$table->decimal('traslados', 9, 6)->unsigned()->nullable();
$table->decimal('retenciones', 9, 6)->unsigned()->nullable();
$table->unsignedTinyInteger('data_lot_enable')->nullable()->index();
$table->unsignedTinyInteger('data_lot_require')->nullable()->index();
$table->unsignedTinyInteger('data_series_enable')->nullable()->index();
$table->unsignedTinyInteger('data_series_require')->nullable()->index();
$table->unsignedTinyInteger('data_expiration_enable')->nullable()->index();
$table->unsignedTinyInteger('data_expiration_require')->nullable()->index();
$table->unsignedTinyInteger('data_warranty_enable')->nullable()->index();
$table->unsignedMediumInteger('warranty')->nullable()->index();
$table->unsignedTinyInteger('data_best_before_enable')->nullable()->index();
$table->unsignedTinyInteger('data_best_before_require')->nullable()->index();
$table->unsignedTinyInteger('data_observations_enable')->nullable()->index();
$table->decimal('minimum_unit', 7, 6)->unsigned()->nullable(); // Decimales soportados en la Unidad de Medida
$table->unsignedTinyInteger('affects_inventory')->nullable()->index();
$table->unsignedTinyInteger('status')->index();
// Auditoría
$table->unsignedMediumInteger('created_by')->nullable()->index(); // users.id
$table->timestamps();
// Relaciones
$table->foreign('category_id')->references('id')->on('product_categories')->onUpdate('restrict')->onDelete('restrict');
$table->foreign('c_moneda')->references('c_moneda')->on('sat_moneda')->onUpdate('restrict')->onDelete('restrict');
$table->foreign('c_clave_unidad')->references('c_clave_unidad')->on('sat_clave_unidad')->onUpdate('restrict')->onDelete('restrict');
$table->foreign('c_clave_prod_serv')->references('c_clave_prod_serv')->on('sat_clave_prod_serv')->onUpdate('restrict')->onDelete('restrict');
$table->foreign('created_by')->references('id')->on('users')->onUpdate('restrict')->onDelete('restrict');
});
Schema::create('product_properties', function (Blueprint $table) {
$table->smallIncrements('id');
$table->string('name')->index(); // Ej: "Marca", "Potencia", "Color"
$table->enum('type', ['text', 'number', 'boolean', 'select'])->default('text');
$table->timestamps();
});
Schema::create('product_store_prices', function (Blueprint $table) {
$table->mediumIncrements('id');
$table->unsignedMediumInteger('product_id')->index();
$table->unsignedSmallInteger('store_id')->index();
$table->decimal('price', 9, 2)->unsigned()->default(0);
$table->char('currency', 3)->charset('ascii')->collation('ascii_general_ci');
$table->boolean('is_discounted')->index();
$table->decimal('discount_price', 9, 2)->unsigned()->nullable();
$table->date('discount_start')->nullable();
$table->date('discount_end')->nullable();
// Auditoría
$table->timestamps();
// Relaciones
$table->foreign('product_id')->references('id')->on('products')->onUpdate('restrict')->onDelete('cascade');
$table->foreign('store_id')->references('id')->on('stores')->onUpdate('restrict')->onDelete('cascade');
});
Schema::create('product_property', function (Blueprint $table) {
$table->mediumIncrements('id');
$table->unsignedMediumInteger('product_id')->index();
$table->unsignedSmallInteger('property_id')->index();
$table->string('value')->index(); // Valor de la propiedad, ej: 'Intel', 'DDR4', '500W'
// Auditoria
$table->timestamps();
// Relaciones
$table->foreign('product_id')->references('id')->on('products')->onUpdate('restrict')->onDelete('cascade');
$table->foreign('property_id')->references('id')->on('product_properties')->onUpdate('restrict')->onDelete('cascade');
});
Schema::create('product_property_values', function (Blueprint $table) {
$table->mediumIncrements('id');
$table->unsignedMediumInteger('product_id')->index();
$table->unsignedSmallInteger('property_id')->index();
$table->string('value_text')->nullable()->index();
$table->decimal('value_number', 9, 2)->nullable();
$table->boolean('value_boolean')->nullable();
$table->timestamps();
// Relaciones
$table->foreign('product_id')->references('id')->on('products')->onUpdate('restrict')->onDelete('cascade');
$table->foreign('property_id')->references('id')->on('product_properties')->onUpdate('restrict')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('products');
Schema::dropIfExists('product_properties');
Schema::dropIfExists('product_store_prices');
Schema::dropIfExists('product_property');
Schema::dropIfExists('product_property_values');
}
};

View File

@ -0,0 +1,193 @@
<?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('warehouses', function (Blueprint $table) {
$table->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');
}
};