feat(api): add Units of Measure API client with CRUD operations

- Implemented interfaces for UnitOfMeasure, UnitOfMeasureListResponse, CreateUnitOfMeasureData, and UpdateUnitOfMeasureData.
- Added API methods for listing, retrieving, creating, updating, and deleting units of measure.
- Included pagination support for the list method.
This commit is contained in:
Galindo97
2026-01-12 10:36:43 -06:00
parent c2d1961994
commit 0aea981cf1
12 changed files with 1693 additions and 147 deletions

View File

@@ -176,12 +176,10 @@ def upgrade() -> None:
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('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.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code', 'tenant_id', 'company_id', name='uq_uom_ace_code'),
sa.UniqueConstraint('code', name='uq_uom_ace_code'),
schema='a76'
)
@@ -191,12 +189,10 @@ def upgrade() -> None:
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('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.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code', 'tenant_id', 'company_id', name='uq_uom_oma_code'),
sa.UniqueConstraint('code', name='uq_uom_oma_code'),
schema='a76'
)
@@ -206,12 +202,10 @@ def upgrade() -> None:
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('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.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code', 'tenant_id', 'company_id', name='uq_uom_american_code'),
sa.UniqueConstraint('code', name='uq_uom_american_code'),
schema='a76'
)
@@ -219,15 +213,13 @@ def upgrade() -> None:
op.create_table(
'unit_of_measure_customs',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('code', sa.String(length=2), nullable=False),
sa.Column('description', sa.String(length=20), nullable=True),
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('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.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code', 'tenant_id', 'company_id', name='uq_uom_customs_code'),
sa.UniqueConstraint('code', name='uq_uom_customs_code'),
schema='a76'
)
@@ -235,10 +227,10 @@ def upgrade() -> None:
op.create_table(
'units_of_measure',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('code', sa.String(length=5), 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=2), 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),
@@ -247,23 +239,23 @@ def upgrade() -> None:
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', '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'],
name='fk_uom_customs'
),
sa.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'],
name='fk_uom_american'
),
sa.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'],
name='fk_uom_ace'
),
sa.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'],
name='fk_uom_oma'
),
sa.PrimaryKeyConstraint('id'),
@@ -275,25 +267,25 @@ def upgrade() -> None:
op.create_table(
'units_of_measure_general',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('code', sa.String(length=5), 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=5), 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=2), 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', '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'],
name='fk_uom_general_customs'
),
sa.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'],
name='fk_uom_general_ace'
),
sa.PrimaryKeyConstraint('id'),

View File

@@ -53,7 +53,11 @@ 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 import seed as units_of_measure_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"
@@ -64,6 +68,25 @@ 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);")
# --- 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(
[
@@ -289,43 +312,72 @@ def upgrade() -> None:
"""
)
# Insertar seeds de Unidades de Medida
# Preparar valores únicos para cada tabla auxiliar
ace_codes = set()
oma_codes = set()
american_codes = set()
customs_codes = set()
# --- 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 ace and ace.strip():
ace_codes.add(ace.strip())
if oma and oma.strip():
oma_codes.add(oma.strip())
if american and american.strip():
american_codes.add(american.strip())
if customs and customs.strip():
customs_codes.add(customs.strip())
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 seeds (con tenant_id=1, company_id=1 por defecto)
# IMPORTANTE: En producción necesitarás ajustar estos valores
# 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;")
def format_value(val):
"""Formatea valores para SQL, manejando None y strings vacíos"""
if not val or val.strip() == '':
return 'NULL'
return f"'{val.replace(chr(39), chr(39)*2)}'"
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;")
values_uom = ", ".join([
f"('{code}', {format_value(desc)}, {format_value(desc_en)}, "
f"{format_value(customs)}, {format_value(american)}, "
f"{format_value(ace)}, {format_value(oma)}, 1, 1)"
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 {values_uom}
VALUES {val_uom}
ON CONFLICT (code, tenant_id, company_id) DO NOTHING;
""")
@@ -333,6 +385,11 @@ def upgrade() -> None:
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;")
@@ -349,4 +406,3 @@ def downgrade() -> None:
op.execute("DELETE FROM public.code_pedimento_regimens;")
op.execute("DELETE FROM public.pedimento_regimens;")
op.execute("DELETE FROM public.pedimento_codes;")
op.execute("DELETE FROM a76.units_of_measure;")

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

@@ -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

@@ -48,7 +48,7 @@ export const materialTypesApi = {
params.append('type', type);
}
return api.get<MaterialTypeListResponse>(
`/v1/public/refrence_data/material-types?${params.toString()}`
`/v1/public/refrence_data/material-types/?${params.toString()}`
);
},
@@ -56,14 +56,14 @@ export const materialTypesApi = {
* 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
@@ -71,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({
@@ -195,6 +190,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 = '';
@@ -202,7 +219,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;
}
@@ -222,6 +239,7 @@
async function openUnitOfMeasureSearch() {
showUnitDialog = true;
searchUnit = '';
await loadUnitsOfMeasure();
}
function selectUnit(unit: UnitOfMeasure) {