Merge pull request 'feature/classes' (#48) from feature/classes into development

Reviewed-on: ADUANASOFT/anexo76#48
This commit is contained in:
2026-01-12 17:41:23 +00:00
14 changed files with 1869 additions and 115 deletions

View File

@@ -167,12 +167,151 @@ def upgrade() -> None:
sa.PrimaryKeyConstraint("id", name="clave_pedimento_regimens_pkey"),
schema="public",
)
# Tablas de Unidades de Medida (A76)
# 1. Tabla: unit_of_measure_ace (ACE Units)
op.create_table(
'unit_of_measure_ace',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('code', sa.String(length=4), nullable=False),
sa.Column('description', sa.String(length=49), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('deleted_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code', name='uq_uom_ace_code'),
schema='a76'
)
# 2. Tabla: unit_of_measure_oma (OMA Units)
op.create_table(
'unit_of_measure_oma',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('code', sa.String(length=10), nullable=False),
sa.Column('description', sa.String(length=200), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('deleted_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code', name='uq_uom_oma_code'),
schema='a76'
)
# 3. Tabla: unit_of_measure_american (American Units)
op.create_table(
'unit_of_measure_american',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('code', sa.String(length=3), nullable=False),
sa.Column('description', sa.String(length=40), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('deleted_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code', name='uq_uom_american_code'),
schema='a76'
)
# 4. Tabla: unit_of_measure_customs (Customs Units)
op.create_table(
'unit_of_measure_customs',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('code', sa.String(length=10), nullable=False),
sa.Column('description', sa.String(length=50), nullable=True),
sa.Column('scaii_unit_code', sa.String(length=5), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('deleted_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code', name='uq_uom_customs_code'),
schema='a76'
)
# 5. Tabla: units_of_measure (Main Unit of Measure)
op.create_table(
'units_of_measure',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('code', sa.String(length=10), nullable=False),
sa.Column('description', sa.String(length=100), nullable=True),
sa.Column('description_en', sa.String(length=100), nullable=True),
sa.Column('customs_code', sa.String(length=10), nullable=True),
sa.Column('american_code', sa.String(length=3), nullable=True),
sa.Column('ace_code', sa.String(length=4), nullable=True),
sa.Column('oma_code', sa.String(length=10), nullable=True),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('company_id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('deleted_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
['customs_code'],
['a76.unit_of_measure_customs.code'],
name='fk_uom_customs'
),
sa.ForeignKeyConstraint(
['american_code'],
['a76.unit_of_measure_american.code'],
name='fk_uom_american'
),
sa.ForeignKeyConstraint(
['ace_code'],
['a76.unit_of_measure_ace.code'],
name='fk_uom_ace'
),
sa.ForeignKeyConstraint(
['oma_code'],
['a76.unit_of_measure_oma.code'],
name='fk_uom_oma'
),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code', 'tenant_id', 'company_id', name='uq_uom_code'),
schema='a76'
)
# 6. Tabla: units_of_measure_general (General/Conversion Units)
op.create_table(
'units_of_measure_general',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('code', sa.String(length=10), nullable=False),
sa.Column('description', sa.String(length=100), nullable=True),
sa.Column('conversion_factor', sa.Numeric(precision=13, scale=6), nullable=True),
sa.Column('mexico_unit', sa.String(length=10), nullable=True),
sa.Column('american_unit_code', sa.String(length=5), nullable=True),
sa.Column('customs_code', sa.String(length=10), nullable=True),
sa.Column('ace_code', sa.String(length=4), nullable=True),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('company_id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.ForeignKeyConstraint(
['customs_code'],
['a76.unit_of_measure_customs.code'],
name='fk_uom_general_customs'
),
sa.ForeignKeyConstraint(
['ace_code'],
['a76.unit_of_measure_ace.code'],
name='fk_uom_general_ace'
),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code', 'tenant_id', 'company_id', name='uq_uom_general_code'),
schema='a76'
)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
# Eliminar tablas de unidades de medida
op.drop_table('units_of_measure_general', schema='a76')
op.drop_table('units_of_measure', schema='a76')
op.drop_table('unit_of_measure_customs', schema='a76')
op.drop_table('unit_of_measure_american', schema='a76')
op.drop_table('unit_of_measure_oma', schema='a76')
op.drop_table('unit_of_measure_ace', schema='a76')
# Eliminar tablas públicas
op.drop_table("code_pedimento_regimens", schema="public")
op.drop_table("valuation_methods", schema="public")
op.drop_table("transport_types", schema="public")

View File

@@ -53,16 +53,44 @@ from api.v1.modules.public.reference_data.transport_types.seed import (
from api.v1.modules.public.reference_data.valuation_methods.seed import (
seed as valuation_methods_seed,
)
from api.v1.modules.a76.general_catalogs.units_of_measure.seed_med import seed as units_of_measure_seed
from api.v1.modules.a76.general_catalogs.units_of_measure.seed_ace import seed as ace_seed
from api.v1.modules.a76.general_catalogs.units_of_measure.seed_oma import seed as oma_seed
from api.v1.modules.a76.general_catalogs.units_of_measure.seed_ame import seed as ame_seed
from api.v1.modules.a76.general_catalogs.units_of_measure.seed_adua import seed as adua_seed
# revision identifiers, used by Alembic.
revision: str = "7937209f9718"
down_revision: Union[str, Sequence[str], None] = "531bf8cdae06"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = "531bf8cdae06"
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# --- AMPLIAR COLUMNAS ANTES DE INSERTAR DATOS ---
op.execute("ALTER TABLE a76.unit_of_measure_ace ALTER COLUMN code TYPE VARCHAR(20);")
op.execute("ALTER TABLE a76.unit_of_measure_oma ALTER COLUMN code TYPE VARCHAR(20);")
op.execute("ALTER TABLE a76.unit_of_measure_american ALTER COLUMN code TYPE VARCHAR(20);")
op.execute("ALTER TABLE a76.unit_of_measure_customs ALTER COLUMN code TYPE VARCHAR(20);")
op.execute("ALTER TABLE a76.units_of_measure ALTER COLUMN code TYPE VARCHAR(20);")
op.execute("ALTER TABLE a76.units_of_measure ALTER COLUMN customs_code TYPE VARCHAR(20);")
op.execute("ALTER TABLE a76.units_of_measure ALTER COLUMN american_code TYPE VARCHAR(20);")
op.execute("ALTER TABLE a76.units_of_measure ALTER COLUMN ace_code TYPE VARCHAR(20);")
op.execute("ALTER TABLE a76.units_of_measure ALTER COLUMN oma_code TYPE VARCHAR(20);")
# --- AGREGAR COLUMNAS deleted_at ---
op.execute("ALTER TABLE a76.units_of_measure ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP;")
op.execute("ALTER TABLE a76.units_of_measure_general ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP;")
# --- UTILIDAD DE FORMATEO ---
def format_value(val):
if val is None or str(val).strip() == '' or str(val).upper() == 'NONE':
return 'NULL'
return f"'{str(val).replace(chr(39), chr(39)*2)}'"
# --- SEEDS PUBLIC (Tablas base) ---
# Seeds
values_pc = ", ".join(
[
@@ -288,10 +316,84 @@ def upgrade() -> None:
"""
)
# --- SEEDS A76 (Unidades de Medida) ---
# ACE
val_ace = ", ".join([f"({format_value(c)}, {format_value(d)})" for c, d in ace_seed])
op.execute(f"INSERT INTO a76.unit_of_measure_ace (code, description) VALUES {val_ace} ON CONFLICT ON CONSTRAINT uq_uom_ace_code DO NOTHING;")
# OMA
val_oma = ", ".join([f"({format_value(c)}, {format_value(d)})" for c, d in oma_seed])
op.execute(f"INSERT INTO a76.unit_of_measure_oma (code, description) VALUES {val_oma} ON CONFLICT ON CONSTRAINT uq_uom_oma_code DO NOTHING;")
# AME
val_ame = ", ".join([f"({format_value(c)}, {format_value(d)})" for c, d in ame_seed])
op.execute(f"INSERT INTO a76.unit_of_measure_american (code, description) VALUES {val_ame} ON CONFLICT ON CONSTRAINT uq_uom_american_code DO NOTHING;")
# ADUA (Customs)
val_adua = ", ".join([f"({format_value(c)}, {format_value(d)})" for c, d in adua_seed])
op.execute(f"INSERT INTO a76.unit_of_measure_customs (code, description) VALUES {val_adua} ON CONFLICT ON CONSTRAINT uq_uom_customs_code DO NOTHING;")
# Recolectar códigos adicionales que faltan en los catálogos
additional_customs = set()
additional_american = set()
additional_ace = set()
additional_oma = set()
existing_customs = {c for c, d in adua_seed}
existing_american = {c for c, d in ame_seed}
existing_ace = {c for c, d in ace_seed}
existing_oma = {c for c, d in oma_seed}
for code, desc, desc_en, customs, american, ace, oma in units_of_measure_seed:
if customs and customs.strip() and customs not in existing_customs:
additional_customs.add((customs, f'Auto-generated from {code}'))
if american and american.strip() and american not in existing_american:
additional_american.add((american, f'Auto-generated from {code}'))
if ace and ace.strip() and ace not in existing_ace:
additional_ace.add((ace, f'Auto-generated from {code}'))
if oma and oma.strip() and oma not in existing_oma:
additional_oma.add((oma, f'Auto-generated from {code}'))
# Insertar códigos adicionales
if additional_customs:
val_add_customs = ", ".join([f"({format_value(c)}, {format_value(d)})" for c, d in additional_customs])
op.execute(f"INSERT INTO a76.unit_of_measure_customs (code, description) VALUES {val_add_customs} ON CONFLICT ON CONSTRAINT uq_uom_customs_code DO NOTHING;")
if additional_american:
val_add_american = ", ".join([f"({format_value(c)}, {format_value(d)})" for c, d in additional_american])
op.execute(f"INSERT INTO a76.unit_of_measure_american (code, description) VALUES {val_add_american} ON CONFLICT ON CONSTRAINT uq_uom_american_code DO NOTHING;")
if additional_ace:
val_add_ace = ", ".join([f"({format_value(c)}, {format_value(d)})" for c, d in additional_ace])
op.execute(f"INSERT INTO a76.unit_of_measure_ace (code, description) VALUES {val_add_ace} ON CONFLICT ON CONSTRAINT uq_uom_ace_code DO NOTHING;")
if additional_oma:
val_add_oma = ", ".join([f"({format_value(c)}, {format_value(d)})" for c, d in additional_oma])
op.execute(f"INSERT INTO a76.unit_of_measure_oma (code, description) VALUES {val_add_oma} ON CONFLICT ON CONSTRAINT uq_uom_oma_code DO NOTHING;")
# TABLA MAESTRA UOM
val_uom = ", ".join([
f"({format_value(code)}, {format_value(desc)}, {format_value(desc_en)}, "
f"{format_value(customs)}, {format_value(american)}, {format_value(ace)}, {format_value(oma)}, 1, 1)"
for code, desc, desc_en, customs, american, ace, oma in units_of_measure_seed
])
op.execute(f"""
INSERT INTO a76.units_of_measure
(code, description, description_en, customs_code, american_code, ace_code, oma_code, tenant_id, company_id)
VALUES {val_uom}
ON CONFLICT (code, tenant_id, company_id) DO NOTHING;
""")
def downgrade() -> None:
"""Downgrade schema."""
op.execute("DELETE FROM a76.units_of_measure;")
op.execute("DELETE FROM a76.unit_of_measure_customs;")
op.execute("DELETE FROM a76.unit_of_measure_american;")
op.execute("DELETE FROM a76.unit_of_measure_oma;")
op.execute("DELETE FROM a76.unit_of_measure_ace;")
op.execute("DELETE FROM public.valuation_methods;")
op.execute("DELETE FROM public.transport_types;")
op.execute("DELETE FROM public.transport_modes;")

View File

@@ -21,29 +21,29 @@ class UnitOfMeasureAmericanBase(BaseModel):
class UnitOfMeasureCustomsBase(BaseModel):
code: str = Field(..., max_length=2, description="Customs Code")
description: Optional[str] = Field(None, max_length=20)
code: str = Field(..., max_length=10, description="Customs Code")
description: Optional[str] = Field(None, max_length=50)
scaii_unit_code: Optional[str] = Field(None, max_length=5)
class UnitOfMeasureBase(BaseModel):
code: str = Field(..., max_length=5, description="Unit Code")
code: str = Field(..., max_length=20, description="Unit Code")
description: Optional[str] = Field(None, max_length=100)
description_en: Optional[str] = Field(None, max_length=100)
customs_code: Optional[str] = Field(None, max_length=2)
american_code: Optional[str] = Field(None, max_length=3)
ace_code: Optional[str] = Field(None, max_length=4)
oma_code: Optional[str] = Field(None, max_length=10)
customs_code: Optional[str] = Field(None, max_length=20)
american_code: Optional[str] = Field(None, max_length=20)
ace_code: Optional[str] = Field(None, max_length=20)
oma_code: Optional[str] = Field(None, max_length=20)
class UnitOfMeasureGeneralBase(BaseModel):
code: str = Field(..., max_length=5, description="Unit Code")
code: str = Field(..., max_length=20, description="Unit Code")
description: Optional[str] = Field(None, max_length=100)
conversion_factor: Optional[Decimal] = None
mexico_unit: Optional[str] = Field(None, max_length=5)
american_unit_code: Optional[str] = Field(None, max_length=5)
customs_code: Optional[str] = Field(None, max_length=2)
ace_code: Optional[str] = Field(None, max_length=4)
mexico_unit: Optional[str] = Field(None, max_length=20)
american_unit_code: Optional[str] = Field(None, max_length=20)
customs_code: Optional[str] = Field(None, max_length=20)
ace_code: Optional[str] = Field(None, max_length=20)
# --- Create DTOs ---
@@ -90,29 +90,29 @@ class UnitOfMeasureAmericanUpdate(BaseModel):
class UnitOfMeasureCustomsUpdate(BaseModel):
code: Optional[str] = Field(None, max_length=2)
description: Optional[str] = Field(None, max_length=20)
code: Optional[str] = Field(None, max_length=10)
description: Optional[str] = Field(None, max_length=50)
scaii_unit_code: Optional[str] = Field(None, max_length=5)
class UnitOfMeasureUpdate(BaseModel):
code: Optional[str] = Field(None, max_length=5)
code: Optional[str] = Field(None, max_length=20)
description: Optional[str] = Field(None, max_length=100)
description_en: Optional[str] = Field(None, max_length=100)
customs_code: Optional[str] = Field(None, max_length=2)
american_code: Optional[str] = Field(None, max_length=3)
ace_code: Optional[str] = Field(None, max_length=4)
oma_code: Optional[str] = Field(None, max_length=10)
customs_code: Optional[str] = Field(None, max_length=20)
american_code: Optional[str] = Field(None, max_length=20)
ace_code: Optional[str] = Field(None, max_length=20)
oma_code: Optional[str] = Field(None, max_length=20)
class UnitOfMeasureGeneralUpdate(BaseModel):
code: Optional[str] = Field(None, max_length=5)
code: Optional[str] = Field(None, max_length=20)
description: Optional[str] = Field(None, max_length=100)
conversion_factor: Optional[Decimal] = None
mexico_unit: Optional[str] = Field(None, max_length=5)
american_unit_code: Optional[str] = Field(None, max_length=5)
customs_code: Optional[str] = Field(None, max_length=2)
ace_code: Optional[str] = Field(None, max_length=4)
mexico_unit: Optional[str] = Field(None, max_length=20)
american_unit_code: Optional[str] = Field(None, max_length=20)
customs_code: Optional[str] = Field(None, max_length=20)
ace_code: Optional[str] = Field(None, max_length=20)
# --- Response DTOs ---

View File

@@ -8,11 +8,10 @@ from core.database import Base
# 1. GUniMedACE
class UnitOfMeasureACE(Base, TenantScopedMixin, TimestampMixin):
class UnitOfMeasureACE(Base, TimestampMixin):
__tablename__ = "unit_of_measure_ace"
__table_args__ = (
UniqueConstraint("code", "tenant_id", "company_id",
name="uq_uom_ace_code"),
UniqueConstraint("code", name="uq_uom_ace_code"),
{"schema": "a76", "extend_existing": True}
)
@@ -25,11 +24,10 @@ class UnitOfMeasureACE(Base, TenantScopedMixin, TimestampMixin):
# 2. GUMOMA
class UnitOfMeasureOMA(Base, TenantScopedMixin, TimestampMixin):
class UnitOfMeasureOMA(Base, TimestampMixin):
__tablename__ = "unit_of_measure_oma"
__table_args__ = (
UniqueConstraint("code", "tenant_id", "company_id",
name="uq_uom_oma_code"),
UniqueConstraint("code", name="uq_uom_oma_code"),
{"schema": "a76", "extend_existing": True}
)
@@ -42,11 +40,10 @@ class UnitOfMeasureOMA(Base, TenantScopedMixin, TimestampMixin):
# 3. GUMAme
class UnitOfMeasureAmerican(Base, TenantScopedMixin, TimestampMixin):
class UnitOfMeasureAmerican(Base, TimestampMixin):
__tablename__ = "unit_of_measure_american"
__table_args__ = (
UniqueConstraint("code", "tenant_id", "company_id",
name="uq_uom_american_code"),
UniqueConstraint("code", name="uq_uom_american_code"),
{"schema": "a76", "extend_existing": True}
)
@@ -59,19 +56,18 @@ class UnitOfMeasureAmerican(Base, TenantScopedMixin, TimestampMixin):
# 4. GUMAduana
class UnitOfMeasureCustoms(Base, TenantScopedMixin, TimestampMixin):
class UnitOfMeasureCustoms(Base, TimestampMixin):
__tablename__ = "unit_of_measure_customs"
__table_args__ = (
UniqueConstraint("code", "tenant_id", "company_id",
name="uq_uom_customs_code"),
UniqueConstraint("code", name="uq_uom_customs_code"),
{"schema": "a76", "extend_existing": True}
)
id: Mapped[int] = mapped_column(
Integer, primary_key=True, autoincrement=True)
code: Mapped[str] = mapped_column(String(2), nullable=False) # CLAVE
code: Mapped[str] = mapped_column(String(10), nullable=False) # CLAVE
description: Mapped[Optional[str]] = mapped_column(
String(20), nullable=True)
String(50), nullable=True)
scaii_unit_code: Mapped[Optional[str]] = mapped_column(
String(5), nullable=True) # UNIDADSCAII
@@ -84,30 +80,26 @@ class UnitOfMeasure(Base, TenantScopedMixin, TimestampMixin):
UniqueConstraint("code", "tenant_id",
"company_id", name="uq_uom_code"),
ForeignKeyConstraint(
["customs_code", "tenant_id", "company_id"],
["a76.unit_of_measure_customs.code", "a76.unit_of_measure_customs.tenant_id",
"a76.unit_of_measure_customs.company_id"],
["customs_code"],
["a76.unit_of_measure_customs.code"],
use_alter=True,
name="fk_uom_customs"
),
ForeignKeyConstraint(
["american_code", "tenant_id", "company_id"],
["a76.unit_of_measure_american.code", "a76.unit_of_measure_american.tenant_id",
"a76.unit_of_measure_american.company_id"],
["american_code"],
["a76.unit_of_measure_american.code"],
use_alter=True,
name="fk_uom_american"
),
ForeignKeyConstraint(
["ace_code", "tenant_id", "company_id"],
["a76.unit_of_measure_ace.code", "a76.unit_of_measure_ace.tenant_id",
"a76.unit_of_measure_ace.company_id"],
["ace_code"],
["a76.unit_of_measure_ace.code"],
use_alter=True,
name="fk_uom_ace"
),
ForeignKeyConstraint(
["oma_code", "tenant_id", "company_id"],
["a76.unit_of_measure_oma.code", "a76.unit_of_measure_oma.tenant_id",
"a76.unit_of_measure_oma.company_id"],
["oma_code"],
["a76.unit_of_measure_oma.code"],
use_alter=True,
name="fk_uom_oma"
),
@@ -116,14 +108,14 @@ class UnitOfMeasure(Base, TenantScopedMixin, TimestampMixin):
id: Mapped[int] = mapped_column(
Integer, primary_key=True, autoincrement=True)
code: Mapped[str] = mapped_column(String(5), nullable=False) # CLAVEUNI
code: Mapped[str] = mapped_column(String(10), nullable=False) # CLAVEUNI
description: Mapped[Optional[str]] = mapped_column(
String(100), nullable=True)
description_en: Mapped[Optional[str]] = mapped_column(
String(100), nullable=True)
customs_code: Mapped[Optional[str]] = mapped_column(
String(2), nullable=True) # CLAVE_AMEX
String(10), nullable=True) # CLAVE_AMEX
american_code: Mapped[Optional[str]] = mapped_column(
String(3), nullable=True) # CLAVE_AAMER
ace_code: Mapped[Optional[str]] = mapped_column(
@@ -146,16 +138,14 @@ class UnitOfMeasureGeneral(Base, TenantScopedMixin, TimestampMixin):
UniqueConstraint("code", "tenant_id", "company_id",
name="uq_uom_general_code"),
ForeignKeyConstraint(
["customs_code", "tenant_id", "company_id"],
["a76.unit_of_measure_customs.code", "a76.unit_of_measure_customs.tenant_id",
"a76.unit_of_measure_customs.company_id"],
["customs_code"],
["a76.unit_of_measure_customs.code"],
use_alter=True,
name="fk_uom_general_customs"
),
ForeignKeyConstraint(
["ace_code", "tenant_id", "company_id"],
["a76.unit_of_measure_ace.code", "a76.unit_of_measure_ace.tenant_id",
"a76.unit_of_measure_ace.company_id"],
["ace_code"],
["a76.unit_of_measure_ace.code"],
use_alter=True,
name="fk_uom_general_ace"
),
@@ -164,19 +154,19 @@ class UnitOfMeasureGeneral(Base, TenantScopedMixin, TimestampMixin):
id: Mapped[int] = mapped_column(
Integer, primary_key=True, autoincrement=True)
code: Mapped[str] = mapped_column(String(5), nullable=False) # UNIDAD
code: Mapped[str] = mapped_column(String(10), nullable=False) # UNIDAD
description: Mapped[Optional[str]] = mapped_column(
String(100), nullable=True)
conversion_factor: Mapped[Optional[Decimal]] = mapped_column(
Numeric(13, 6), nullable=True)
mexico_unit: Mapped[Optional[str]] = mapped_column(
String(5), nullable=True)
String(10), nullable=True)
# UNIDAD_AME (Note: GUniMed has UNIDAD_AME varchar(5), but GUMAme has CLAVE varchar(3). Keeping as string for now)
american_unit_code: Mapped[Optional[str]
] = mapped_column(String(5), nullable=True)
customs_code: Mapped[Optional[str]] = mapped_column(
String(2), nullable=True) # CLAVE_ADUANA
String(10), nullable=True) # CLAVE_ADUANA
ace_code: Mapped[Optional[str]] = mapped_column(
String(4), nullable=True) # CLAVEACE

View File

@@ -0,0 +1,58 @@
seed = [
('BAG', 'Bag'),
('BBL', 'Barrel'),
('BDL', 'Bundle'),
('BIC', 'Bing Chest'),
('BIN', 'Bin'),
('BKT', 'Bucket'),
('BLE', 'Bale'),
('BLK', 'Bulk'),
('BOX', 'Box'),
('BSK', 'Basket'),
('CAN', 'Can'),
('CAR', 'Carcass'),
('CAS', 'Case'),
('CBC', 'Container Bulk Cargo'),
('CBY', 'Carboy'),
('CCS', 'Can Case'),
('CHS', 'Chest'),
('COL', 'Coil'),
('COR', 'Cord'),
('CRT', 'Crate'),
('CSK', 'Cask'),
('CTN', 'Carton'),
('CYL', 'Cylinder'),
('DBK', 'Bry Bulk'),
('DRM', 'Drum'),
('DZ', 'Dozen'),
('FT', 'Feet'),
('GAL', 'Gallon'),
('HED', 'Heads of Beef'),
('HMP', 'Hamper'),
('KEG', 'Keg'),
('L', 'Liter'),
('LBK', 'Liquid Bulk'),
('LOG', 'Logs'),
('LUG', 'Lugs'),
('LVN', 'Lift Van'),
('M', 'Meters'),
('PAA', 'Pairs'),
('PAL', 'Pail'),
('PCL', 'Parcel'),
('PCS', 'Pieces'),
('PKG', 'Package'),
('POV', 'Private Vehicle'),
('QTR', 'Quarters of Beef'),
('REL', 'Reel'),
('ROL', 'Roll'),
('SAK', 'Sack'),
('SFT', 'Square Feet'),
('SHT', 'Sheet'),
('SID', 'Sides of Beef'),
('SKD', 'Skid'),
('TBE', 'Tube'),
('TBN', 'Tote Bin'),
('TIN', 'Tin'),
('TNK', 'Tank'),
('UNT', 'Unit'),
]

View File

@@ -0,0 +1,24 @@
seed = [
('KGS', 'Kilo'),
('KW', 'Kilowatt'),
('MILLR', 'Millar'),
('JGO', 'Juego'),
('KWH', 'Kilowatt/Hora'),
('TON', 'Tonelada'),
('BARR', 'Barril'),
('GRN', 'Gramo Neto'),
('DEC', 'Decenas'),
('CIEN', 'Cientos'),
('DOCE', 'Decenas (Docenas)'),
('GR', 'Gramo'),
('CAJA', 'Caja'),
('PZA', 'Botella'),
('CARAT', 'Carat'),
('MT', 'Metro Lineal'),
('M2', 'Metro Cuadrado'),
('M3', 'Metro Cubico'),
('PZA', 'Pieza'),
('PZA', 'Cabeza'),
('LT', 'Litro'),
('PAR', 'Par'),
]

View File

@@ -0,0 +1,48 @@
seed = [
('BBL', 'Barrels (42 Gallons ea) (Volume)'),
('BOL', 'Boluses (Dosage)'),
('CAP', 'Capsules (Dosage)'),
('CAR', 'Carats (Weight)'),
('CFT', 'Cubic Feet (Volume)'),
('CGI', 'Centigrams (Weight)'),
('CM', 'Centimeters (Long)'),
('CM3', 'Cubic Centimeters (Volume)'),
('CYD', 'Cubic Yards (Volume)'),
('DOZ', 'Dozen (Count)'),
('DPC', 'Dozen Pieces (Count)'),
('DPR', 'Dozen Pairs (Count)'),
('FOZ', 'Ounces, fluid (Volume)'),
('FT', 'Feet (Length)'),
('G', 'Grams (Weight)'),
('GAL', 'Gallons (US)(Volume)'),
('GR', 'Gross (Count)'),
('KG2', 'Kilograms (Weight)'),
('KM', 'Kilometers (Length)'),
('KM2', '1000 sq Meter Area'),
('KM3', '1,000 Cubic Meters (Volume)'),
('L', 'Liters (Volume)'),
('LB', 'Pounds (Weight)'),
('LNM', 'Linear Meters (Length)'),
('M', 'Meters (Length)'),
('M2', 'SQ Meter (Area)'),
('M3', 'Cubic Meters (Volume)'),
('MG', 'Miligrams (Weight)'),
('ML', 'Milliliters (Volume)'),
('NO', 'Number (Count)'),
('OZ', 'Weight'),
('PCS', 'Pieces (Count)'),
('PRS', 'Pairs (Count)'),
('PTL', 'Pints, liquid (US)(Volume)'),
('QTL', 'Quarts, liquid (US)(Volume)'),
('SFT', 'SQ Feet (Area)'),
('SQI', 'Sq Inches (Area)'),
('STN', 'Short Ion (2000 LB)(Weight)'),
('SUP', 'Suppositories (Dosage)'),
('SYD', 'Sq. Yards (Area)'),
('T', 'Metric Ton (Weight)'),
('TAB', 'Tablets (Dosage)'),
('TON', 'Long Ton (2,240 LB) (Weight)'),
('TOZ', 'Ounces, Troy or Apoth (Weight)'),
('YD', 'Yards (Length)'),
]

View File

@@ -0,0 +1,44 @@
seed = [
# (code, desc_es, desc_en, customs, american, ace, oma)
('BARR', 'BARRIL', 'BARIEL', '', 'BBL', 'BLL', ''),
('BD FT', 'PIE TABLA', 'BD FEET', '', 'FT', 'BFT', ''),
('BOLS', 'BOLSA', 'BAG', '', 'PCS', 'BG', ''),
('BTL', 'BOTELLA', 'BOTTLE', '', 'PCS', 'BO', ''),
('BULT', 'BULTO', 'BULK', '', 'PCS', 'VQ', ''),
('CAJA', 'CAJA', 'BOX', '', '', 'BX', ''),
('CARAT', 'CARAT', 'CARAT', '', '', 'HE', ''),
('CBZA', 'CABEZA', 'HEAD', '', 'PCS', 'Z4', ''),
('CIEN', 'CIENTO', 'CIEN', '', '', 'CEN', ''),
('CM', 'CENTIMETRO', 'CM', 'CM', 'CM', 'CMT', ''),
('CM2', 'CENTIMETRO CUADRADO', 'CM2', 'CM2', 'CM2', 'CMK', ''),
('DEC', 'DECENA', '', '', '', 'DC', ''),
('DM', 'DECIMETRO', 'DM', '', '', 'DMT', ''),
('DM2', 'DECIMETRO CUADRADO', 'SQ DM', '', '', 'DMK', ''),
('DOCE', 'DOCENA', 'DOZ', '', 'DOZ', 'DZN', 'DZ'),
('FOZ', 'ONZA LIQUIDA', 'FOZ', 'FOZ', 'FOZ', 'OZA', ''),
('FT', 'PIES', 'FT', 'FT', 'FT', 'LF', ''),
('FT2', 'PIE CUADRADO', 'FT2', '', 'SFT', 'FTK', ''),
('GAL', 'GALON', 'GAL', 'GAL', 'GAL', 'GLL', ''),
('GR', 'GRAMO', 'GRAM', '', '', 'GRM', ''),
('IN', 'PULGADA', 'IN', '', '', 'LI', ''),
('IN2', 'PULGADA CUADRADA', 'IN2', '', '', 'INK', ''),
('JGO', 'JUEGO', 'SET', '', '', 'SET', ''),
('KGS', 'KILOGRAMOS', 'KGS', '', 'KG2', 'KGM', ''),
('LB', 'LIBRAS', 'LB', '', '', 'LBR', ''),
('LT', 'LITRO', 'LT', 'LT', 'L', 'LTR', ''),
('M2', 'METRO CUADRADO', 'M2', 'M2', 'M2', 'MTK', ''),
('M3', 'METRO CUBICO', 'M3', 'M3', 'M3', 'MTQ', ''),
('MI', 'MILLA', 'MILE', '', 'KM', 'SMI', ''),
('MILLR', 'MILLAR', 'MILLR', '', '', 'MIL', ''),
('MT', 'METROS', 'MT', 'MT', 'M', 'MTR', ''),
('OZ', 'ONZA', 'OZ', 'FOZ', 'FOZ', 'OZ', ''),
('PAR', 'PAR', 'PAIR', '', '', 'PB', ''),
('PQ', 'PAQUETE', 'PACKAGE', '', 'PCS', 'PK_1', ''),
('PZA', 'PIEZA', 'PCS', 'PCS', 'PCS', 'C62_1', ''),
('QGL', 'CUARTO DE GALON', 'QGL', '', '', 'QT', ''),
('ROLL', 'ROLLO', 'ROLL', '', '', 'RO', ''),
('TON', 'TONELADA', 'TON', 'TON', 'TON', 'TNE_1', ''),
('TOZ', 'ONZA TROY', 'TOZ', '', 'TOZ', 'APZ', ''),
('YD', 'YARDA', 'YD', 'YD', 'YD', 'YRD', ''),
('YD2', 'YARDA CUADRADA', 'YD2', '', 'SYD', 'YDK', ''),
]

File diff suppressed because it is too large Load Diff

View File

@@ -15,11 +15,17 @@ router = APIRouter(prefix="/material-types")
async def list_material_types(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
type: str = Query(None, description="Filtrar por tipo (ACTIVO FIJO, MATERIALES, PRODUCTOS)"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(MaterialType)
# Aplicar filtro por tipo si se proporciona
if type:
query = query.filter(MaterialType.type == type)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {

View File

@@ -0,0 +1,101 @@
/**
* API Client para Units of Measure
* Gestiona las operaciones CRUD para unidades de medida
*/
import { api } from '$lib/api';
export interface UnitOfMeasure {
id: number;
code: string;
description: string | null;
description_en: string | null;
customs_code: string | null;
american_code: string | null;
ace_code: string | null;
oma_code: string | null;
}
export interface UnitOfMeasureListResponse {
items: UnitOfMeasure[];
total: number;
page: number;
page_size: number;
}
export interface CreateUnitOfMeasureData {
code: string;
description?: string | null;
description_en?: string | null;
customs_code?: string | null;
american_code?: string | null;
ace_code?: string | null;
oma_code?: string | null;
}
export interface UpdateUnitOfMeasureData {
code?: string;
description?: string | null;
description_en?: string | null;
customs_code?: string | null;
american_code?: string | null;
ace_code?: string | null;
oma_code?: string | null;
}
/**
* API para Units of Measure
*/
export const unitsOfMeasureApi = {
/**
* Lista todas las unidades de medida con paginación
* @param companyId - ID de la compañía
* @param page - Número de página (por defecto 1)
* @param pageSize - Tamaño de página (por defecto 50)
*/
list: (companyId: number, page = 1, pageSize = 50) => {
const params = new URLSearchParams({
company_id: companyId.toString(),
page: page.toString(),
page_size: pageSize.toString()
});
return api.get<UnitOfMeasureListResponse>(
`/v1/a76/units-of-measure?${params.toString()}`
);
},
/**
* Obtiene una unidad de medida por ID
* @param companyId - ID de la compañía
* @param id - ID de la unidad de medida
*/
get: (companyId: number, id: number) =>
api.get<UnitOfMeasure>(`/v1/a76/units-of-measure/${id}?company_id=${companyId}`),
/**
* Crea una nueva unidad de medida
* @param companyId - ID de la compañía
* @param data - Datos de la unidad de medida a crear
*/
create: (companyId: number, data: CreateUnitOfMeasureData) =>
api.post<UnitOfMeasure>(`/v1/a76/units-of-measure?company_id=${companyId}`, data),
/**
* Actualiza una unidad de medida existente
* @param companyId - ID de la compañía
* @param id - ID de la unidad de medida a actualizar
* @param data - Datos a actualizar
*/
update: (companyId: number, id: number, data: UpdateUnitOfMeasureData) =>
api.put<UnitOfMeasure>(
`/v1/a76/units-of-measure/${id}?company_id=${companyId}`,
data
),
/**
* Elimina una unidad de medida
* @param companyId - ID de la compañía
* @param id - ID de la unidad de medida a eliminar
*/
delete: (companyId: number, id: number) =>
api.delete(`/v1/a76/units-of-measure/${id}?company_id=${companyId}`)
};

View File

@@ -37,24 +37,33 @@ export const materialTypesApi = {
* Lista todos los tipos de material con paginación
* @param page - Número de página (por defecto 1)
* @param pageSize - Tamaño de página (por defecto 50)
* @param type - Filtrar por tipo (ACTIVO FIJO, MATERIALES, PRODUCTOS)
*/
list: (page = 1, pageSize = 50) =>
api.get<MaterialTypeListResponse>(
`/v1/public/refrence_data/material-types?page=${page}&page_size=${pageSize}`
),
list: (page = 1, pageSize = 50, type?: string) => {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString()
});
if (type) {
params.append('type', type);
}
return api.get<MaterialTypeListResponse>(
`/v1/public/refrence_data/material-types/?${params.toString()}`
);
},
/**
* Obtiene un tipo de material por key
* @param key - Clave del tipo de material
*/
get: (key: string) => api.get<MaterialType>(`/v1/public/refrence_data/material-types/${key}`),
get: (key: string) => api.get<MaterialType>(`/v1/public/refrence_data/material-types/${key}/`),
/**
* Crea un nuevo tipo de material
* @param data - Datos del tipo de material a crear
*/
create: (data: CreateMaterialTypeData) =>
api.post<MaterialType>('/v1/public/refrence_data/material-types', data),
api.post<MaterialType>('/v1/public/refrence_data/material-types/', data),
/**
* Actualiza un tipo de material existente
@@ -62,11 +71,11 @@ export const materialTypesApi = {
* @param data - Datos a actualizar
*/
update: (key: string, data: UpdateMaterialTypeData) =>
api.put<MaterialType>(`/v1/public/refrence_data/material-types/${key}`, data),
api.put<MaterialType>(`/v1/public/refrence_data/material-types/${key}/`, data),
/**
* Elimina un tipo de material
* @param key - Clave del tipo de material a eliminar
*/
delete: (key: string) => api.delete(`/v1/public/refrence_data/material-types/${key}`)
delete: (key: string) => api.delete(`/v1/public/refrence_data/material-types/${key}/`)
};

View File

@@ -7,6 +7,7 @@
import * as Select from "$lib/components/ui/select";
import { classesApi, type A76Class, type A76ClassCreate, type A76ClassUpdate } from "$lib/api/dashboard/a76/classes";
import { materialTypesApi, type MaterialType } from "$lib/api/dashboard/refrence_data/material_types";
import { unitsOfMeasureApi, type UnitOfMeasure } from "$lib/api/dashboard/a76/units_of_measure";
import { clientsProvidersApi, type ClientProvider } from "$lib/api/dashboard/a76/clients-providers";
import { companyStore } from "$lib/stores/company.svelte";
import { onMount } from 'svelte';
@@ -43,6 +44,8 @@
let error = $state<string | null>(null);
let materialTypes = $state<MaterialType[]>([]);
let loadingMaterialTypes = $state(false);
let unitsOfMeasure = $state<UnitOfMeasure[]>([]);
let loadingUnitsOfMeasure = $state(false);
let clients = $state<ClientProvider[]>([]);
let loadingClients = $state(false);
@@ -51,15 +54,15 @@
let selectedMaterialValue = $state<string>('');
let selectedPhysicalReviewValue = $state<number>(0);
// Cargar tipos de materiales y clientes al montar
// Cargar tipos de materiales, unidades de medida y clientes al montar
onMount(async () => {
const companyId = companyStore.activeCompany?.id;
if (!companyId) return;
// Cargar tipos de materiales
// Cargar tipos de materiales (solo ACTIVO FIJO)
loadingMaterialTypes = true;
try {
const response = await materialTypesApi.list(1, 100);
const response = await materialTypesApi.list(1, 100, 'ACTIVO FIJO');
if (response.data) {
materialTypes = response.data.items;
}
@@ -69,6 +72,19 @@
loadingMaterialTypes = false;
}
// Cargar unidades de medida
loadingUnitsOfMeasure = true;
try {
const response = await unitsOfMeasureApi.list(companyId, 1, 100);
if (response.data) {
unitsOfMeasure = response.data.items;
}
} catch (e) {
console.error('Error loading units of measure:', e);
} finally {
loadingUnitsOfMeasure = false;
}
// Cargar clientes
loadingClients = true;
try {
@@ -216,31 +232,9 @@
}
}
// Opciones de unidades de medida (puedes expandir esto)
const unitOptions = [
{ value: 'KG', label: 'Kilogramos (KG)' },
{ value: 'LB', label: 'Libras (LB)' },
{ value: 'MT', label: 'Metros (MT)' },
{ value: 'PZ', label: 'Piezas (PZ)' },
{ value: 'LT', label: 'Litros (LT)' },
{ value: 'M3', label: 'Metros Cúbicos (M3)' },
{ value: 'TON', label: 'Toneladas (TON)' }
];
// Funciones para obtener valores seleccionados
function getSelectedMaterialType() {
if (!formData.material_key) return null;
const found = materialTypes.find(mt => mt.key === formData.material_key);
return found ? { value: found.key, label: `${found.key} - ${found.description}` } : null;
}
function getSelectedUnit() {
return unitOptions.find(opt => opt.value === formData.unit_of_measure) || unitOptions[0];
}
function getSelectedPhysicalReview() {
return { value: formData.physical_review, label: formData.physical_review === 1 ? 'Sí' : 'No' };
}
</script>
<Dialog.Root bind:open onOpenChange={handleOpenChange}>
@@ -409,18 +403,34 @@
</div>
</div>
<!-- Unidad de Medida -->
<!-- Unidad de Medida Comercial -->
<div class="space-y-2">
<Label for="unit_of_measure" class="required">Unidad de Medida</Label>
<select
bind:value={formData.unit_of_measure}
disabled={loading}
class="border-input bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive"
>
{#each unitOptions as unit}
<option value={unit.value}>{unit.label}</option>
{/each}
</select>
<Label for="unit_of_measure" class="required">U.M. Comercial</Label>
{#if loadingUnitsOfMeasure}
<div class="flex items-center gap-2 text-sm text-muted-foreground">
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
Cargando unidades de medida...
</div>
{:else if unitsOfMeasure.length > 0}
<select
bind:value={formData.unit_of_measure}
disabled={loading}
class="border-input bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive"
>
{#each unitsOfMeasure as unit}
<option value={unit.code}>
{unit.code}{unit.description ? ` - ${unit.description}` : ''}
</option>
{/each}
</select>
{:else}
<Input
id="unit_of_measure"
bind:value={formData.unit_of_measure}
placeholder="No hay unidades de medida disponibles"
disabled={true}
/>
{/if}
</div>
<!-- Revisión Física -->

View File

@@ -6,6 +6,7 @@
import { Folder } from 'lucide-svelte';
import { toast } from 'svelte-sonner';
import { materialTypesApi, type MaterialType } from '$lib/api/dashboard/refrence_data/material_types';
import { unitsOfMeasureApi } from '$lib/api/dashboard/a76/units_of_measure';
import { getTariffFractions, type TariffFraction } from '$lib/api/dashboard/a76/general_catalogs/tariff-fractions';
import { getUSTariffFractions, type USTariffFraction } from '$lib/api/dashboard/a76/general_catalogs/us-tariff-fractions';
import { getDepreciationCatalog, type DepreciationCatalog } from '$lib/api/dashboard/a76/general_catalogs/depreciation-catalog';
@@ -33,14 +34,8 @@
claveOMA: string;
}
// Datos de unidades de medida
const unitsOfMeasureData: UnitOfMeasure[] = [
{ code: 'BARR', description: 'BARRIL', descriptionEnglish: 'BARREL', claveMexicana: '8', claveAmericana: 'BBL', claveACE: '', claveOMA: 'BLL' },
{ code: 'BD FT', description: 'PIE TABLA', descriptionEnglish: 'BD FEET', claveMexicana: '5', claveAmericana: 'FT', claveACE: '', claveOMA: 'BFT' },
{ code: 'BOLS', description: 'BOLSA', descriptionEnglish: 'BAG', claveMexicana: '6', claveAmericana: 'PCS', claveACE: '', claveOMA: 'BG' },
{ code: 'KGS', description: 'KILOGRAMOS', descriptionEnglish: 'KGS', claveMexicana: '1', claveAmericana: 'KG2', claveACE: '', claveOMA: 'KGM' },
{ code: 'PZA', description: 'PIEZA', descriptionEnglish: 'PCS', claveMexicana: '6', claveAmericana: 'PCS', claveACE: '', claveOMA: 'C62_1' },
];
// Datos de unidades de medida (se cargan desde el API)
let unitsOfMeasureData: UnitOfMeasure[] = $state([]);
// Estado del formulario
let formData = $state({
@@ -191,6 +186,28 @@
);
// Funciones
async function loadUnitsOfMeasure() {
if (unitsOfMeasureData.length > 0) return;
try {
const companyId = companyStore.activeCompany?.id;
if (!companyId) return;
const response = await unitsOfMeasureApi.list(companyId, 1, 100);
if (response.data) {
unitsOfMeasureData = response.data.items.map(item => ({
code: item.code,
description: item.description || '',
descriptionEnglish: item.description_en || '',
claveMexicana: item.customs_code || '',
claveAmericana: item.american_code || '',
claveACE: item.ace_code || '',
claveOMA: item.oma_code || ''
}));
}
} catch (error) {
console.error('Error cargando units of measure:', error);
}
}
async function openMaterialSearch() {
showMaterialDialog = true;
searchMaterial = '';
@@ -198,7 +215,7 @@
try {
const companyId = companyStore.activeCompany?.id;
if (!companyId) return;
const response = await materialTypesApi.list(1, 1000);
const response = await materialTypesApi.list(1, 100, 'ACTIVO FIJO');
if (response.data) {
materialTypes = response.data.items;
}
@@ -218,6 +235,7 @@
async function openUnitOfMeasureSearch() {
showUnitDialog = true;
searchUnit = '';
await loadUnitsOfMeasure();
}
function selectUnit(unit: UnitOfMeasure) {