diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/routes.py b/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/routes.py index 5f0f1600..21277c99 100644 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/routes.py +++ b/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/routes.py @@ -8,7 +8,7 @@ from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session from core.database import get_core_db -from core.security import get_current_user +from core.security import get_current_user, get_tenant_from_token from .dto import ( TariffFractionCreateDTO, @@ -27,6 +27,7 @@ router = APIRouter(prefix="/tariff-fractions", tags=["a76 / general catalogs / t description="Get paginated list of Tariff Fractions with optional search filter (global catalog)", ) async def list_tariff_fractions( + company_id: int = Query(..., description="Company ID"), page: int = Query(1, ge=1, description="Page number"), page_size: int = Query(50, ge=1, le=10000, description="Page size"), search: Optional[str] = Query(None, description="Search in code, fraction, description, nico, or umt"), @@ -47,8 +48,9 @@ async def list_tariff_fractions( # Service.get_all calls Sitar (async) or DB (sync). # This should be fine. - tenant_id = current_user.get("tenant_id") - company_id = current_user.get("company_id") # Assuming user is context-aware or we use a default? + tenant_id = get_tenant_from_token(current_user) + if tenant_id is None: + tenant_id = current_user.get("tenant_id") # If using headers for selected company, it might be in current_user context if middleware sets it. items, total = await TariffFractionService.get_all( @@ -121,7 +123,7 @@ async def create_tariff_fraction( pass us_dto = USTariffFractionCreateDTO( - code=fraction_data.code, + code=fraction_data.fraction, # Store the punctuated fraction in the DB description=fraction_data.description, unit_of_measure=fraction_data.umt, ad_valorem=ad_valorem, @@ -131,7 +133,7 @@ async def create_tariff_fraction( fixed_cost=None ) - created = USTariffFractionService.create(db, tenant_id, company_id, us_dto) + created = USTariffFractionService.create(db, us_dto, tenant_id, company_id) return TariffFractionService.to_domain_usa_local(created) else: @@ -171,12 +173,13 @@ async def update_tariff_fraction( pass us_dto = USTariffFractionUpdateDTO( + code=fraction_data.fraction, description=fraction_data.description, unit_of_measure=fraction_data.umt, ad_valorem=ad_valorem ) - updated = USTariffFractionService.update(db, tenant_id, company_id, tariff_fraction_id, us_dto) + updated = USTariffFractionService.update(db, tariff_fraction_id, tenant_id, us_dto, company_id) if not updated: raise HTTPException(status_code=404, detail="US Tariff fraction not found") return TariffFractionService.to_domain_usa_local(updated) @@ -202,7 +205,7 @@ async def delete_tariff_fraction( if catalog == "american": from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.service import USTariffFractionService - success = USTariffFractionService.delete(db, tenant_id, company_id, tariff_fraction_id) + success = USTariffFractionService.delete(db, tariff_fraction_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="US Tariff fraction not found") return {"ok": True} diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/service.py b/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/service.py index 7a00b364..65530eef 100644 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/service.py @@ -96,10 +96,14 @@ class TariffFractionService: # US format: 1234.56.78.90. For now return as is or use helper if available. # item is USTariffFraction (imported inside method to avoid circular import if needed, or assumed available) + # Remove formatting (e.g. dots) for the 'code' property + code_str = str(item.code) + clean_code = code_str.replace(".", "").replace("-", "") + return TariffFraction( id=item.id, - code=item.code, - fraction=item.code, # TODO: Format if needed + code=clean_code, + fraction=code_str, description=item.description or "(Sin descripción)", nico=None, umt=item.unit_of_measure, @@ -133,11 +137,11 @@ class TariffFractionService: from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.service import USTariffFractionService # Use local service directly - usa_items, total = USTariffFractionService._get_all_local( + usa_items, total = USTariffFractionService.get_all( db, tenant_id, company_id, skip, limit, filters ) - items = [TariffFractionMapper.to_domain_usa_local(item) for item in usa_items] + items = [TariffFractionService.to_domain_usa_local(item) for item in usa_items] return items, total # USA CATALOG HANDLING (API - 'Fracciones US') diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/dto.py b/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/dto.py index 1c943349..c316dc08 100644 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/dto.py @@ -3,9 +3,9 @@ DTOs para fracciones arancelarias americanas """ from datetime import datetime -from typing import Optional +from typing import Optional, Any -from pydantic import BaseModel, Field, ConfigDict +from pydantic import BaseModel, Field, ConfigDict, model_validator class USTariffFractionCreateDTO(BaseModel): @@ -23,6 +23,7 @@ class USTariffFractionCreateDTO(BaseModel): class USTariffFractionUpdateDTO(BaseModel): """DTO para actualizar fracción arancelaria americana""" + code: Optional[str] = Field(None, max_length=16) prefix: Optional[str] = Field(None, max_length=10) type_code: Optional[str] = Field(None, max_length=10) ad_valorem: Optional[float] = None @@ -38,6 +39,7 @@ class USTariffFractionResponseDTO(BaseModel): id: int code: str + fraction: Optional[str] = None prefix: Optional[str] = None type_code: Optional[str] = None ad_valorem: Optional[float] = None @@ -46,3 +48,36 @@ class USTariffFractionResponseDTO(BaseModel): description: Optional[str] = None created_at: datetime updated_at: datetime + + @model_validator(mode="before") + @classmethod + def format_code_and_fraction(cls, data: Any) -> Any: + # Check if data is an ORM model or dict + if hasattr(data, "code"): + raw_code = data.code + elif isinstance(data, dict): + raw_code = data.get("code") + else: + return data + + if raw_code: + code_str = str(raw_code) + # fraction keeps the original formatted string + fraction = code_str + # code strips dots and hyphens + code = code_str.replace(".", "").replace("-", "") + + if isinstance(data, dict): + data["code"] = code + data["fraction"] = fraction + else: + # If it's an ORM object, we can't easily modify the object's attribute + # cleanly without side effects for other things, so we convert it to dict + new_data = { + c.name: getattr(data, c.name) for c in data.__table__.columns + } + new_data["code"] = code + new_data["fraction"] = fraction + return new_data + + return data diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/routes.py b/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/routes.py index daabcd8a..f6521624 100644 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/routes.py +++ b/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/routes.py @@ -26,6 +26,7 @@ crud_router = TenantCRUDRoutes( prefix="/us-tariff-fractions", tags=["a76 / general catalogs / us tariff fractions"], resource_name="US Tariff Fraction", + id_name="id", enable_list=False, # We implement our custom list endpoint ) diff --git a/backend/test_debug.py b/backend/test_debug.py new file mode 100644 index 00000000..cfb342be --- /dev/null +++ b/backend/test_debug.py @@ -0,0 +1,44 @@ +import sys +import os +sys.path.append('/app') +sys.path.append('/home/josmar/dev/anexo76/backend') + +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +import asyncio +import logging + +# Disable logging to keep output clean +logging.basicConfig(level=logging.ERROR) + +from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.service import TariffFractionService +from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.dto import TariffFractionResponseDTO + +async def test(): + # Use the database URL from the environment or default + db_url = "postgresql://postgres:postgres@anexo76-postgres-a76:5432/anexo76_core" + engine = create_engine(db_url) + Session = sessionmaker(bind=engine) + db = Session() + + try: + print("Starting test for catalog='american'...") + items, total = await TariffFractionService.get_all( + db, skip=0, limit=10, filters=None, catalog="american", tenant_id=1, company_id=1 + ) + print(f"Service Success! Total: {total}") + + print("Validating items with TariffFractionResponseDTO...") + for item in items: + dto = TariffFractionResponseDTO.model_validate(item) + print(f"DTO: ID={dto.id}, Code={dto.code}, Fraction={dto.fraction}") + + except Exception as e: + print(f"Error caught: {type(e).__name__}: {e}") + import traceback + traceback.print_exc() + finally: + db.close() + +if __name__ == "__main__": + asyncio.run(test()) diff --git a/backend/test_user_info.py b/backend/test_user_info.py new file mode 100644 index 00000000..17f2cc2b --- /dev/null +++ b/backend/test_user_info.py @@ -0,0 +1,39 @@ +import sys +import os +sys.path.append('/app') +sys.path.append('/home/josmar/dev/anexo76/backend') + +from sqlalchemy import create_engine, text +from sqlalchemy.orm import sessionmaker + +def test(): + db_url = "postgresql://postgres:postgres@anexo76-postgres-a76:5432/anexo76_core" + engine = create_engine(db_url) + Session = sessionmaker(bind=engine) + db = Session() + + try: + print("Checking users and tenants...") + # Check users + users = db.execute(text("SELECT id, email, tenant_id FROM a76.users")).fetchall() + for u in users: + print(f"User ID: {u.id} | Email: {u.email} | Tenant ID: {u.tenant_id}") + + # Check companies + companies = db.execute(text("SELECT id, name, tenant_id FROM a76.companies")).fetchall() + for c in companies: + print(f"Company ID: {c.id} | Name: {c.name} | Tenant ID: {c.tenant_id}") + + # Check fractions + fractions = db.execute(text("SELECT id, code, tenant_id, company_id FROM a76.us_tariff_fractions")).fetchall() + print(f"Total US Fractions in DB: {len(fractions)}") + for f in fractions: + print(f"Fraction ID: {f.id} | Code: {f.code} | Tenant ID: {f.tenant_id} | Company ID: {f.company_id}") + + except Exception as e: + print(f"Error: {e}") + finally: + db.close() + +if __name__ == "__main__": + test() diff --git a/frontend/src/lib/components/dashboard/goods/fractions/TariffFractionFormDialog.svelte b/frontend/src/lib/components/dashboard/goods/fractions/TariffFractionFormDialog.svelte index 93eea293..d92d48e5 100644 --- a/frontend/src/lib/components/dashboard/goods/fractions/TariffFractionFormDialog.svelte +++ b/frontend/src/lib/components/dashboard/goods/fractions/TariffFractionFormDialog.svelte @@ -126,17 +126,25 @@
- - - {#if fraction} -

- El código no se puede modificar una vez creado. -

- {/if} + + +

+ {catalog === 'mex' + ? 'El código se genera automáticamente.' + : 'La clave se deriva de la fracción sin puntos.'} +

- - + +
diff --git a/frontend/src/lib/components/dashboard/goods/fractions/TariffFractionList.svelte b/frontend/src/lib/components/dashboard/goods/fractions/TariffFractionList.svelte index 15e75d87..ba3ea1cf 100644 --- a/frontend/src/lib/components/dashboard/goods/fractions/TariffFractionList.svelte +++ b/frontend/src/lib/components/dashboard/goods/fractions/TariffFractionList.svelte @@ -310,7 +310,7 @@ fraction={selectedFraction} {catalog} onSuccess={() => { - loadFractions(); + loadFractions(true); isFormDialogOpen = false; }} />