From 98c7fe3bb53b9cc5ae4866071dc36d8dab4cf74d Mon Sep 17 00:00:00 2001 From: Galindo97 Date: Tue, 13 Jan 2026 14:09:22 -0600 Subject: [PATCH] feat: Enhance invoice editing components with new dialogs and improved UI - Added country selection dialog with search functionality. - Introduced tariff fraction selection dialog with infinite scrolling and search. - Implemented unit of measure selection dialog with search capabilities. - Updated invoice editing UI to include new fields for line descriptions and identifiers. - Improved layout and spacing for better user experience in invoice editing forms. - Added API endpoints for fetching tariff fractions and units of measure with proper error handling. --- .../general_catalogs/tariff_fractions/dto.py | 6 +- .../tariff_fractions/models.py | 8 +- .../tariff_fractions/routes.py | 96 ++++-- .../tariff_fractions/service.py | 38 +-- frontend/src/lib/api/dashboard/a76/items.ts | 3 + .../edit/items/fa/country-dialog.svelte | 178 ++++++++++ .../edit/items/fa/item-configuration.svelte | 4 +- .../edit/items/fa/item-sheet-fa.svelte | 312 ++++++++++-------- .../invoices/edit/items/fa/main-data.svelte | 130 +++++--- .../edit/items/fa/packages-section.svelte | 2 +- .../edit/items/fa/summary-section.svelte | 66 ++-- .../edit/items/fa/tab-continuation.svelte | 87 ++--- .../edit/items/fa/tab-identifiers.svelte | 29 +- .../edit/items/fa/tab-labeling.svelte | 4 +- .../items/fa/tariff-fraction-dialog.svelte | 218 ++++++++++++ .../items/fa/unit-of-measure-dialog.svelte | 183 ++++++++++ .../api-sveltekit/tariff-fractions/+server.ts | 68 ++++ .../api-sveltekit/units-of-measure/+server.ts | 84 +++++ 18 files changed, 1163 insertions(+), 353 deletions(-) create mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/country-dialog.svelte create mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/tariff-fraction-dialog.svelte create mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/unit-of-measure-dialog.svelte create mode 100644 frontend/src/routes/api-sveltekit/tariff-fractions/+server.ts create mode 100644 frontend/src/routes/api-sveltekit/units-of-measure/+server.ts diff --git a/backend/api/v1/modules/a76/general_catalogs/tariff_fractions/dto.py b/backend/api/v1/modules/a76/general_catalogs/tariff_fractions/dto.py index f43e471a..1a8f15c2 100644 --- a/backend/api/v1/modules/a76/general_catalogs/tariff_fractions/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/tariff_fractions/dto.py @@ -38,11 +38,9 @@ class TariffFractionUpdateDTO(BaseModel): class TariffFractionResponseDTO(BaseModel): - """DTO para respuesta de fracción arancelaria""" + """DTO para respuesta de fracción arancelaria (catálogo global)""" id: int - tenant_id: int - company_id: int code: str fraction: str description: Optional[str] = None @@ -50,8 +48,6 @@ class TariffFractionResponseDTO(BaseModel): umt: Optional[str] = None adv_impo: Optional[str] = None adv_expo: Optional[str] = None - created_at: datetime - updated_at: datetime model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/tariff_fractions/models.py b/backend/api/v1/modules/a76/general_catalogs/tariff_fractions/models.py index 717706ff..71256e0e 100644 --- a/backend/api/v1/modules/a76/general_catalogs/tariff_fractions/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/tariff_fractions/models.py @@ -4,15 +4,15 @@ Modelos ORM para fracciones arancelarias (SITAR-SCAII) from typing import Optional -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin from core.database import Base from sqlalchemy import Integer, PrimaryKeyConstraint, String, Numeric from sqlalchemy.orm import Mapped, mapped_column -class TariffFraction(Base, TenantScopedMixin, TimestampMixin): +class TariffFraction(Base): """ Modelo para fracciones arancelarias mexicanas (SITAR-SCAII) + Catálogo de referencia global (no tenant-scoped) Corresponde a la tabla sFracciones """ @@ -22,10 +22,10 @@ class TariffFraction(Base, TenantScopedMixin, TimestampMixin): {"schema": "a76", "extend_existing": True}, ) - id: Mapped[int] = mapped_column(Integer, primary_key=True) + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # Código completo de la fracción (ej: 01012101) - code: Mapped[str] = mapped_column(String(10), unique=True, index=True) + code: Mapped[str] = mapped_column(String(10), unique=True, index=True, nullable=False) # Fracción formateada (ej: 0101.21.01) fraction: Mapped[str] = mapped_column(String(15), index=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/tariff_fractions/routes.py b/backend/api/v1/modules/a76/general_catalogs/tariff_fractions/routes.py index db4809e3..4e7bd2f3 100644 --- a/backend/api/v1/modules/a76/general_catalogs/tariff_fractions/routes.py +++ b/backend/api/v1/modules/a76/general_catalogs/tariff_fractions/routes.py @@ -1,5 +1,6 @@ """ Endpoints API para fracciones arancelarias +Catálogo de referencia global (no tenant-scoped) """ from typing import Any, Dict, Optional @@ -8,7 +9,6 @@ from sqlalchemy.orm import Session from core.database import get_core_db from core.security import get_current_user -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes, validate_access_to_resource from .dto import ( TariffFractionCreateDTO, @@ -17,22 +17,6 @@ from .dto import ( ) from .service import TariffFractionService -# Create base router with generic CRUD routes (disabled list because we'll create a custom one) -base_router = TenantCRUDRoutes( - service=TariffFractionService, - create_schema=TariffFractionCreateDTO, - update_schema=TariffFractionUpdateDTO, - response_schema=TariffFractionResponseDTO, - prefix="/tariff-fractions", - tags=["a76 / general catalogs / tariff fractions"], - resource_name="TariffFraction", - id_name="tariff_fraction_id", - enable_list=False, # Disable default list, we'll add custom one - enable_filters=False, - default_page_size=50, - max_page_size=10000, -) - router = APIRouter(prefix="/tariff-fractions", tags=["a76 / general catalogs / tariff fractions"]) # Custom list endpoint with search filter @@ -40,25 +24,22 @@ router = APIRouter(prefix="/tariff-fractions", tags=["a76 / general catalogs / t "/", response_model=Dict[str, Any], summary="List Tariff Fractions", - description="Get paginated list of Tariff Fractions with optional search filter", + 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"), db: Session = Depends(get_core_db), current_user: Dict[str, Any] = Depends(get_current_user), ): - tenant_id = validate_access_to_resource(db, company_id, current_user) - skip = (page - 1) * page_size filters = {} if search: filters["search"] = search items, total = TariffFractionService.get_all( - db, tenant_id, company_id, skip, page_size, filters + db, skip, page_size, filters ) return { @@ -69,6 +50,73 @@ async def list_tariff_fractions( "pages": (total + page_size - 1) // page_size, } -# Include other CRUD routes from base router -router.include_router(base_router.router) + +@router.get( + "/{tariff_fraction_id}", + response_model=TariffFractionResponseDTO, + summary="Get Tariff Fraction by ID", + description="Get a specific tariff fraction by ID", +) +async def get_tariff_fraction( + tariff_fraction_id: int, + db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), +): + item = TariffFractionService.get_by_id(db, tariff_fraction_id) + if not item: + from fastapi import HTTPException + raise HTTPException(status_code=404, detail="Tariff fraction not found") + return TariffFractionResponseDTO.model_validate(item) + + +@router.post( + "/", + response_model=TariffFractionResponseDTO, + summary="Create Tariff Fraction", + description="Create a new tariff fraction (admin only)", + status_code=201, +) +async def create_tariff_fraction( + data: TariffFractionCreateDTO, + db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), +): + item = TariffFractionService.create(db, data) + return TariffFractionResponseDTO.model_validate(item) + + +@router.put( + "/{tariff_fraction_id}", + response_model=TariffFractionResponseDTO, + summary="Update Tariff Fraction", + description="Update an existing tariff fraction (admin only)", +) +async def update_tariff_fraction( + tariff_fraction_id: int, + data: TariffFractionUpdateDTO, + db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), +): + item = TariffFractionService.update(db, tariff_fraction_id, data) + if not item: + from fastapi import HTTPException + raise HTTPException(status_code=404, detail="Tariff fraction not found") + return TariffFractionResponseDTO.model_validate(item) + + +@router.delete( + "/{tariff_fraction_id}", + summary="Delete Tariff Fraction", + description="Delete a tariff fraction (admin only)", + status_code=204, +) +async def delete_tariff_fraction( + tariff_fraction_id: int, + db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), +): + success = TariffFractionService.delete(db, tariff_fraction_id) + if not success: + from fastapi import HTTPException + raise HTTPException(status_code=404, detail="Tariff fraction not found") diff --git a/backend/api/v1/modules/a76/general_catalogs/tariff_fractions/service.py b/backend/api/v1/modules/a76/general_catalogs/tariff_fractions/service.py index 56162ae4..3dd13594 100644 --- a/backend/api/v1/modules/a76/general_catalogs/tariff_fractions/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/tariff_fractions/service.py @@ -1,5 +1,6 @@ """ Service para fracciones arancelarias +Catálogo de referencia global (no tenant-scoped) """ from typing import List, Optional, Tuple, Dict, Any @@ -15,23 +16,18 @@ logger = logging.getLogger(__name__) class TariffFractionService: - """Service para gestionar fracciones arancelarias""" + """Service para gestionar fracciones arancelarias (catálogo global)""" @staticmethod def get_all( db: Session, - tenant_id: int, - company_id: int, skip: int = 0, limit: int = 100, filters: Optional[Dict[str, Any]] = None, ) -> Tuple[List[TariffFraction], int]: """Obtiene todas las fracciones arancelarias con filtros opcionales""" - query = db.query(TariffFraction).filter( - TariffFraction.tenant_id == tenant_id, - TariffFraction.company_id == company_id, - ) + query = db.query(TariffFraction) # Aplicar filtros if filters: @@ -67,18 +63,12 @@ class TariffFractionService: def get_by_id( db: Session, tariff_fraction_id: int, - tenant_id: int, - company_id: int, ) -> Optional[TariffFraction]: """Obtiene una fracción arancelaria por ID""" return ( db.query(TariffFraction) - .filter( - TariffFraction.id == tariff_fraction_id, - TariffFraction.tenant_id == tenant_id, - TariffFraction.company_id == company_id, - ) + .filter(TariffFraction.id == tariff_fraction_id) .first() ) @@ -86,18 +76,12 @@ class TariffFractionService: def get_by_code( db: Session, code: str, - tenant_id: int, - company_id: int, ) -> Optional[TariffFraction]: """Obtiene una fracción arancelaria por código""" return ( db.query(TariffFraction) - .filter( - TariffFraction.code == code, - TariffFraction.tenant_id == tenant_id, - TariffFraction.company_id == company_id, - ) + .filter(TariffFraction.code == code) .first() ) @@ -105,16 +89,12 @@ class TariffFractionService: def create( db: Session, tariff_fraction_data: TariffFractionCreateDTO, - tenant_id: int, - company_id: int, ) -> TariffFraction: """Crea una nueva fracción arancelaria""" try: tariff_fraction = TariffFraction( **tariff_fraction_data.model_dump(), - tenant_id=tenant_id, - company_id=company_id, ) db.add(tariff_fraction) db.commit() @@ -133,13 +113,11 @@ class TariffFractionService: db: Session, tariff_fraction_id: int, tariff_fraction_data: TariffFractionUpdateDTO, - tenant_id: int, - company_id: int, ) -> Optional[TariffFraction]: """Actualiza una fracción arancelaria existente""" tariff_fraction = TariffFractionService.get_by_id( - db, tariff_fraction_id, tenant_id, company_id + db, tariff_fraction_id ) if not tariff_fraction: @@ -165,13 +143,11 @@ class TariffFractionService: def delete( db: Session, tariff_fraction_id: int, - tenant_id: int, - company_id: int, ) -> bool: """Elimina una fracción arancelaria""" tariff_fraction = TariffFractionService.get_by_id( - db, tariff_fraction_id, tenant_id, company_id + db, tariff_fraction_id ) if not tariff_fraction: diff --git a/frontend/src/lib/api/dashboard/a76/items.ts b/frontend/src/lib/api/dashboard/a76/items.ts index abfbf846..c576ba68 100644 --- a/frontend/src/lib/api/dashboard/a76/items.ts +++ b/frontend/src/lib/api/dashboard/a76/items.ts @@ -68,6 +68,8 @@ export interface LineDescriptions { brand?: string; model?: string; has_serial?: boolean; + lot?: string; + entry_number?: string; } export interface LineReferences { @@ -133,6 +135,7 @@ export interface LineItem { page_line?: string; has_certificate?: boolean; certificate_number?: string; + octave_permit?: string; // Flags is_subitem?: boolean; diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/country-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/country-dialog.svelte new file mode 100644 index 00000000..ddb6b92d --- /dev/null +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/country-dialog.svelte @@ -0,0 +1,178 @@ + + + + + + CATALOGO DE PAISES + + +
+
+ + +
+
+ +
+ {#if loading} +
+ +
+ {:else if error} +
+

{error}

+
+ {:else} +
+ + + + + + + + + + + + {#each filteredCountries as country, i} + handleSelect(country)} + > + + + + + + + {/each} + {#if filteredCountries.length === 0} + + + + {/if} + +
Clave M3Clave MexicanaDescripción EspañolClave AmericanaDescripción Inglés
{country.m3_key || ''}{country.mex_key || ''}{country.description_es || ''}{country.ame_key || ''}{country.description_en || ''}
+ No se encontraron resultados +
+
+ +
+
+ + + + +
+
+ {/if} +
+ +
+ +
+
+
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte index 43237574..937b4232 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte @@ -35,7 +35,7 @@
- Is + Is
- Contains Sub-Items + Contains Sub-Items - import * as Sheet from '$lib/components/ui/sheet'; - import * as Tabs from '$lib/components/ui/tabs'; - import { Button } from '$lib/components/ui/button'; - import { Loader2 } from 'lucide-svelte'; - import type { Invoice } from '$lib/api/dashboard/a76/invoices'; - import type { Item } from '$lib/api/dashboard/a76/items'; - - // Import child components - import MainData from './main-data.svelte'; - import ItemConfiguration from './item-configuration.svelte'; - import PackagesSection from './packages-section.svelte'; - import SummarySection from './summary-section.svelte'; - import TabContinuation from './tab-continuation.svelte'; - import TabSeries from './tab-series.svelte'; - import TabLabeling from './tab-labeling.svelte'; - import TabIdentifiers from './tab-identifiers.svelte'; + import * as Sheet from '$lib/components/ui/sheet'; + import * as Tabs from '$lib/components/ui/tabs'; + import { Button } from '$lib/components/ui/button'; + import { Separator } from '$lib/components/ui/separator'; + import { Loader2, Package, Save, X, FileText } from 'lucide-svelte'; + import type { Invoice } from '$lib/api/dashboard/a76/invoices'; + import type { Item } from '$lib/api/dashboard/a76/items'; + + // Child components + import MainData from './main-data.svelte'; + import ItemConfiguration from './item-configuration.svelte'; + import PackagesSection from './packages-section.svelte'; + import SummarySection from './summary-section.svelte'; + import TabContinuation from './tab-continuation.svelte'; + import TabSeries from './tab-series.svelte'; + import TabLabeling from './tab-labeling.svelte'; + import TabIdentifiers from './tab-identifiers.svelte'; - let { - open = $bindable(), - isEditMode = false, - editingItem = $bindable(), - invoice, - onSave, - isSaving = false - }: { - open: boolean; - isEditMode?: boolean; - editingItem: Partial; - invoice: Invoice | null; - onSave: () => void; - isSaving?: boolean; - } = $props(); + let { + open = $bindable(), + isEditMode = false, + editingItem = $bindable(), + invoice, + onSave, + isSaving = false + }: { + open: boolean; + isEditMode?: boolean; + editingItem: Partial; + invoice: Invoice | null; + onSave: () => void; + isSaving?: boolean; + } = $props(); - let isSubPartida = $state('partida'); - let continueSubPartidas = $state('no'); + // Acceso directo a la primera línea para evitar repeticiones en el HTML + let line = $derived(editingItem.lines?.[0]); - - - - - - Temporary Import Item - - - Order Number: {invoice?.invoice_number || 'N/A'} | Line: currentline - - + + +
+
+
+
+ +
+
+ + {isEditMode ? 'Editar Partida' : 'Nueva Partida - Activo Fijo'} + +

+ Factura: {invoice?.invoice_number || 'N/A'} +

+
+
+ +
+
-
- -
- -
- {#if editingItem.lines && editingItem.lines.length > 0} - - {/if} -
+
+
+ + {#if line} +
+
+
+
+

Datos Principales

+
+
+ +
+
+
- - {#if editingItem.lines && editingItem.lines.length > 0} - +
+
+
+

Configuración

+
+
+ +
+
+
+
+ + + + + General + + + Continuación + + + Series + + + Etiquetado + + + IDs + + + +
+ +
+ + +
+
+ + + + + + + + + + + + + + + + +
+
+ {:else} +
+ +

Cargando datos de la partida...

+
{/if} -
+
+
- - - - 1) General - 2) Continuation - 3) Series - 4) Labeling - 5) Identifiers - - - - -
- {#if editingItem.lines && editingItem.lines.length > 0} - - - {/if} -
-
- - - - {#if editingItem.lines && editingItem.lines.length > 0} - +
+
+ + +
+
- - - {#if editingItem.lines && editingItem.lines.length > 0} - - {/if} - - - - - {#if editingItem.lines && editingItem.lines.length > 0} - - {/if} - - - - - {#if editingItem.lines && editingItem.lines.length > 0} - - {/if} - -
-
- - -
- - -
-
-
+
\ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/main-data.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/main-data.svelte index a2522f3a..83a1ba37 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/main-data.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/main-data.svelte @@ -1,6 +1,11 @@ -
- Main Data - - -
-
- -
- -
-
-
+ + + - -
-
+
+ Main Data + +
+ +
+ + +
+ + +
-
+ +
-
- -
+ +
-
- -
-
+ +
- USD + USD
-
+ +
- + +
-
- -
-
+ +
- + +
-
- - -
-
- -
- + +
+
+ + +
+ + +
+ Advalorem: + + {customs.advalorem || '0'} +
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/packages-section.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/packages-section.svelte index 7cf2ad92..d3c6f785 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/packages-section.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/packages-section.svelte @@ -57,7 +57,7 @@
- KILOS + KILOS
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/summary-section.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/summary-section.svelte index 3c37fa81..27b66319 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/summary-section.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/summary-section.svelte @@ -10,40 +10,36 @@
RETURN QUANTITY SUB-ITEMS
-
Temporary: {quantities.quantity_temp_export?.toFixed(8) || '0.00000000'}
-
Replacement or Change: 0.00000000
-
Definitive: {quantities.quantity_returned?.toFixed(8) || '0.00000000'}
-
Returned Values: {financials.value_returned_usd?.toFixed(8) || '0.00000000'}
-
Returned Values: {financials.value_returned_mxn?.toFixed(8) || '0.00000000'}
-
+
Temporary: {quantities.quantity_temp_export?.toFixed(8) || '0.00000000'}
+
Replacement or Change: 0.00000000
+
Definitive: {quantities.quantity_returned?.toFixed(8) || '0.00000000'}
+
Returned Values: {financials.value_returned_usd?.toFixed(8) || '0.00000000'}
+
Returned Values: {financials.value_returned_mxn?.toFixed(8) || '0.00000000'}
+
+
+
WEIGHTS (KILOS)
+
WEIGHTS (Pounds)
+
Net: {quantities.net_weight?.toFixed(8) || '0.00000000'}
+
0.00000000
+
Whole: {quantities.gross_weight?.toFixed(8) || '0.00000000'}
+
0.00000000
+
+
-
-
WEIGHTS (KILOS)
-
WEIGHTS (Pounds)
-
Net: {quantities.net_weight?.toFixed(8) || '0.00000000'}
-
0.00000000
-
Whole: {quantities.gross_weight?.toFixed(8) || '0.00000000'}
-
0.00000000
-
-
- - -
- COSTS AND VALUES - -
-
(Dollars)
-
(Pesos)
-
Cost: {financials.unit_cost_usd?.toFixed(8) || '0.00000000'}
-
{financials.unit_cost_mxn?.toFixed(8) || '0.00000000'}
-
Value: {financials.value_usd?.toFixed(8) || '0.00000000'}
-
{financials.value_mxn?.toFixed(8) || '0.00000000'}
-
- -
-
Capture Cost: {financials.unit_cost_capture?.toFixed(8) || '0.00000000'} USD
-
Capture Value: {financials.value_usd?.toFixed(8) || '0.00000000'} USD
-
Customs Value: {financials.customs_value_usd?.toFixed(8) || '0.00000000'} USD
-
-
+ +
+ COSTS AND VALUES + +
+
(Dollars)
+
(Pesos)
+
Cost: {financials.unit_cost_usd?.toFixed(8) || '0.00000000'}
+
{financials.unit_cost_mxn?.toFixed(8) || '0.00000000'}
+
Value: {financials.value_usd?.toFixed(8) || '0.00000000'}
+
{financials.value_mxn?.toFixed(8) || '0.00000000'}
+
Capture Cost: {financials.unit_cost_capture?.toFixed(8) || '0.00000000'} USD
+
Capture Value: {financials.value_usd?.toFixed(8) || '0.00000000'} USD
+
Customs Value: {financials.customs_value_usd?.toFixed(8) || '0.00000000'} USD
+
+
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-continuation.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-continuation.svelte index b44702d5..0f514eba 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-continuation.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-continuation.svelte @@ -3,12 +3,14 @@ import { Input } from '$lib/components/ui/input'; import { Label } from '$lib/components/ui/label'; import { Checkbox } from '$lib/components/ui/checkbox'; - import type { LineItem } from '$lib/api/dashboard/a76/items'; + import type { LineItem, LineDescriptions } from '$lib/api/dashboard/a76/items'; let { - lineItem = $bindable() + lineItem = $bindable(), + descriptions = $bindable() }: { lineItem: LineItem; + descriptions: LineDescriptions; } = $props(); let taxPaidValue = $derived(lineItem.tax_payment ? 'si' : 'no'); @@ -22,32 +24,32 @@ } -
+
-
+
-
-
- TAX PAID +
+
+ TAX PAID -
+ class="flex gap-2"> +
-
+
-
-
+
+
- +
@@ -56,101 +58,102 @@
-
+
- +
-
-
- Has Certificate of Origin? +
+
+ Has Certificate of Origin? -
+ class="flex gap-2"> +
-
+
-
+
- +
-
-
+
+
- +
-
+
-
+
- +
-
+
- +
-
+
-
-
+
+
- +
-
+
- +
-
+
- +
-
+
-
+
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-identifiers.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-identifiers.svelte index 3a3034c3..31542f67 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-identifiers.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-identifiers.svelte @@ -9,34 +9,17 @@
Identifiers -
-
- - -
-
- - -
-
- -
-
- - -
-
- - -
+
+ +
- +
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-labeling.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-labeling.svelte index cacb0306..3e28e03e 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-labeling.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-labeling.svelte @@ -12,11 +12,11 @@
- +
- +
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tariff-fraction-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tariff-fraction-dialog.svelte new file mode 100644 index 00000000..02721e57 --- /dev/null +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tariff-fraction-dialog.svelte @@ -0,0 +1,218 @@ + + + + + + FRACCIONES ARANCELARIAS + + +
+
+ + +
+

+ Mostrando {fractions.length} de {currentPage * pageSize} resultados +

+
+ +
+ {#if loading} +
+ +
+ {:else if error} +
+

{error}

+
+ {:else} +
+ + + + + + + + + + + + {#each fractions as fraction, i} + handleSelect(fraction)} + > + + + + + + + {/each} + {#if fractions.length === 0 && !loading} + + + + {/if} + +
CódigoFracciónDescripciónNICOUMT
{fraction.code || ''}{fraction.fraction || ''}{fraction.description || ''}{fraction.nico || ''}{fraction.umt || ''}
+ No se encontraron resultados +
+
+ + {#if loadingMore} +
+ + Cargando más... +
+ {/if} + + {#if !hasMore && fractions.length > 0} +
+ Todos los resultados cargados +
+ {/if} + {/if} +
+ +
+ +
+
+
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/unit-of-measure-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/unit-of-measure-dialog.svelte new file mode 100644 index 00000000..55dab050 --- /dev/null +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/unit-of-measure-dialog.svelte @@ -0,0 +1,183 @@ + + + + + + CATALOGOS DE UNIDADES DE MEDIDA + + +
+
+ + +
+
+ +
+ {#if loading} +
+ +
+ {:else if error} +
+

{error}

+
+ {:else} +
+ + + + + + + + + + + + + {#each filteredUnits as unit, i} + handleSelect(unit)} + > + + + + + + + + {/each} + {#if filteredUnits.length === 0} + + + + {/if} + +
U.M.Descripción EspañolAbrév. InglésClave AduanaClave AmericanaClave O.M.A.
{unit.code || ''}{unit.description || ''}{unit.description_en || ''}{unit.customs_code || ''}{unit.american_code || ''}{unit.oma_code || ''}
+ No se encontraron resultados +
+
+ +
+
+ + + + +
+
+ {/if} +
+ +
+ +
+
+
diff --git a/frontend/src/routes/api-sveltekit/tariff-fractions/+server.ts b/frontend/src/routes/api-sveltekit/tariff-fractions/+server.ts new file mode 100644 index 00000000..2704aca5 --- /dev/null +++ b/frontend/src/routes/api-sveltekit/tariff-fractions/+server.ts @@ -0,0 +1,68 @@ +import type { RequestHandler } from './$types'; + +export const GET: RequestHandler = async ({ cookies, url }) => { + const token = cookies.get('access_token'); + + // Configurar la URL de la API usando las variables de entorno + let apiUrl = process.env.INTERNAL_API_URL; + if (!apiUrl) { + apiUrl = process.env.VITE_API_URL; + // Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR) + apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend'); + } + + // Normalizar la URL + const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`; + + // Get query parameters - tariff_fractions es catálogo global (no requiere company_id) + const searchParams = new URLSearchParams(url.search); + const queryString = searchParams.toString(); + + try { + const fetchUrl = `${baseUrl}v1/a76/tariff-fractions?${queryString}`; + console.log('Fetching tariff fractions from:', fetchUrl); + console.log('Token:', token ? 'Present' : 'Missing'); + + const response = await fetch( + fetchUrl, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + } + } + ); + + const data = await response.json(); + console.log('Response status:', response.status); + console.log('Response data:', JSON.stringify(data).substring(0, 200)); + + if (!response.ok) { + return new Response(JSON.stringify(data), { + status: response.status, + headers: { + 'Content-Type': 'application/json' + } + }); + } + + return new Response(JSON.stringify(data), { + status: 200, + headers: { + 'Content-Type': 'application/json' + } + }); + } catch (error) { + console.error('Error fetching tariff fractions:', error); + return new Response( + JSON.stringify({ error: 'Failed to fetch tariff fractions' }), + { + status: 500, + headers: { + 'Content-Type': 'application/json' + } + } + ); + } +}; diff --git a/frontend/src/routes/api-sveltekit/units-of-measure/+server.ts b/frontend/src/routes/api-sveltekit/units-of-measure/+server.ts new file mode 100644 index 00000000..51dda155 --- /dev/null +++ b/frontend/src/routes/api-sveltekit/units-of-measure/+server.ts @@ -0,0 +1,84 @@ +import type { RequestHandler } from './$types'; + +export const GET: RequestHandler = async ({ cookies, url }) => { + const token = cookies.get('access_token'); + + // Obtener company_id de la cookie + const companyId = cookies.get('active_company_id'); + + if (!companyId) { + return new Response( + JSON.stringify({ error: 'No company selected' }), + { + status: 400, + headers: { + 'Content-Type': 'application/json' + } + } + ); + } + + // Configurar la URL de la API usando las variables de entorno + let apiUrl = process.env.INTERNAL_API_URL; + if (!apiUrl) { + apiUrl = process.env.VITE_API_URL; + // Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR) + apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend'); + } + + // Normalizar la URL + const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`; + + // Get query parameters and add company_id + const searchParams = new URLSearchParams(url.search); + searchParams.set('company_id', companyId); + const queryString = searchParams.toString(); + + try { + const fetchUrl = `${baseUrl}v1/a76/units-of-measure?${queryString}`; + console.log('Fetching units from:', fetchUrl); + console.log('Token:', token ? 'Present' : 'Missing'); + + const response = await fetch( + fetchUrl, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + } + } + ); + + const data = await response.json(); + console.log('Response status:', response.status); + console.log('Response data:', JSON.stringify(data).substring(0, 200)); + + if (!response.ok) { + return new Response(JSON.stringify(data), { + status: response.status, + headers: { + 'Content-Type': 'application/json' + } + }); + } + + return new Response(JSON.stringify(data), { + status: 200, + headers: { + 'Content-Type': 'application/json' + } + }); + } catch (error) { + console.error('Error fetching units of measure:', error); + return new Response( + JSON.stringify({ error: 'Failed to fetch units of measure' }), + { + status: 500, + headers: { + 'Content-Type': 'application/json' + } + } + ); + } +};