Merge branch 'development' into feature/fraction-API
This commit is contained in:
@@ -58,7 +58,7 @@ def validate_update(
|
||||
|
||||
# Columna A: Pedimento (si no viene en CSV, usar el existente)
|
||||
if invoice_data.compliance_mx.pedimento_id:
|
||||
invoice_data.compliance_mx.pedimento_id = clean_str(invoice_data.compliance_mx.pedimento_id)
|
||||
invoice_data.compliance_mx.pedimento_id = invoice_data.compliance_mx.pedimento_id
|
||||
else:
|
||||
invoice_data.compliance_mx.pedimento_id = existing_invoice.compliance_mx.pedimento_id if existing_invoice.compliance_mx else None
|
||||
|
||||
|
||||
@@ -3,36 +3,31 @@ from core.exceptions import ErrorCollector
|
||||
from ..line_items import models
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
def item_exists(
|
||||
db: Session,
|
||||
item_line: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
):
|
||||
|
||||
def item_exists(db: Session, item_line: int, tenant_id: int, company_id: int):
|
||||
item_exists = (
|
||||
db.query(models.LineItem.id)
|
||||
db.query(models.LineItem)
|
||||
.filter(
|
||||
models.LineItem.line_number == item_line,
|
||||
models.LineItem.tenant_id == tenant_id,
|
||||
models.LineItem.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
return item_exists
|
||||
|
||||
|
||||
def count_items(db: Session, invoice_id: int, tenant_id: int, company_id: int):
|
||||
count = (
|
||||
db.query(func.count())
|
||||
.select_from(models.Item)
|
||||
.filter(
|
||||
models.Item.invoice_id == invoice_id,
|
||||
models.Item.tenant_id == tenant_id,
|
||||
models.Item.company_id == company_id,
|
||||
)
|
||||
.scalar()
|
||||
)
|
||||
|
||||
if item_exists:
|
||||
return item_exists
|
||||
return None
|
||||
|
||||
def count_items(
|
||||
db: Session,
|
||||
invoice_id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
):
|
||||
count = db.query(func.count()).select_from(models.Item).filter(
|
||||
models.Item.invoice_id == invoice_id,
|
||||
models.Item.tenant_id == tenant_id,
|
||||
models.Item.company_id == company_id,
|
||||
).scalar()
|
||||
|
||||
return count
|
||||
return count
|
||||
|
||||
@@ -42,9 +42,7 @@ def validate_common(
|
||||
invoice: InvoiceHeader = invoice_exists_by_id(
|
||||
db, invoice_id, tenant_id, company_id, errors
|
||||
)
|
||||
line_item: LineItem = item_exists(
|
||||
db, line.line_number, tenant_id, company_id
|
||||
)
|
||||
line_item: LineItem = item_exists(db, line.line_number, tenant_id, company_id)
|
||||
|
||||
fecha_factura = invoice.invoice_date if invoice else None
|
||||
fraction = None
|
||||
@@ -181,13 +179,15 @@ def validate_common(
|
||||
fraction = line.customs.fraction if line.customs.fraction else fraction
|
||||
|
||||
country = line.customs.origin_country
|
||||
if line_item:
|
||||
if line_item and line_item.customs:
|
||||
country = (
|
||||
line_item.customs.fraction if line_item.customs.origin_country else country
|
||||
line_item.customs.origin_country
|
||||
if line_item.customs.origin_country
|
||||
else country
|
||||
)
|
||||
|
||||
fraction_type = line.customs.fraction_type.upper()
|
||||
if line_item:
|
||||
if line_item and line_item.customs:
|
||||
fraction_type = (
|
||||
line_item.customs.fraction_type
|
||||
if line_item.customs.fraction_type
|
||||
@@ -195,7 +195,7 @@ def validate_common(
|
||||
)
|
||||
|
||||
sector = line.customs.sector
|
||||
if line_item:
|
||||
if line_item and line_item.customs:
|
||||
sector = line_item.customs.sector if line_item.customs.sector else sector
|
||||
|
||||
country_m3 = db.query(Country.m3_key).filter(Country.m3_key == country).scalar()
|
||||
|
||||
@@ -206,8 +206,8 @@ def validate_create(
|
||||
net_weight_input = line.quantity.net_weight or Decimal("0")
|
||||
|
||||
# Determinar si la unidad de medida es de peso
|
||||
unit_is_kgs = line.unit_of_measure and line.unit_of_measure.upper() == "KGS"
|
||||
unit_is_lbs = line.unit_of_measure and line.unit_of_measure.upper() == "LB"
|
||||
unit_is_kgs = line.unit_of_measure and line.unit_of_measure == "24" #KGS
|
||||
unit_is_lbs = line.unit_of_measure and line.unit_of_measure == "25" #LBS
|
||||
|
||||
# Calcular peso neto en kilogramos (estándar interno)
|
||||
if unit_is_kgs:
|
||||
@@ -237,11 +237,11 @@ def validate_create(
|
||||
package_weight_unit = Decimal("0")
|
||||
|
||||
# Obtener peso unitario del bulto si existe
|
||||
if line.quantity.package_key:
|
||||
if line.quantity.package_id:
|
||||
package: Package = (
|
||||
db.query(Package)
|
||||
.filter(
|
||||
Package.key == line.quantity.package_key,
|
||||
Package.id == line.quantity.package_id,
|
||||
Package.tenant_id == tenant_id,
|
||||
Package.company_id == company_id,
|
||||
)
|
||||
@@ -278,22 +278,22 @@ def validate_create(
|
||||
# ==========================================
|
||||
# ASIGNAR DESCRIPCIÓN DE BULTOS
|
||||
# ==========================================
|
||||
if package_quantity and package_quantity > 0 and line.quantity.package_key:
|
||||
if package_quantity and package_quantity > 0 and line.quantity.package_id:
|
||||
package: Package = (
|
||||
db.query(Package)
|
||||
.filter(
|
||||
Package.key == line.quantity.package_key,
|
||||
Package.id == line.quantity.package_id,
|
||||
Package.tenant_id == tenant_id,
|
||||
Package.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if package:
|
||||
line.quantity.package_description = package.description_es
|
||||
line.description.package_description = package.description_es
|
||||
else:
|
||||
line.quantity.package_quantity = 0
|
||||
line.quantity.package_key = None
|
||||
line.quantity.package_description = None
|
||||
line.quantity.package_id = None
|
||||
line.description.package_description = None
|
||||
|
||||
# ==========================================
|
||||
# ASIGNAR FRACCIÓN AMERICANA POR DEFECTO
|
||||
|
||||
@@ -24,7 +24,8 @@ class LineDescription(Base):
|
||||
description_english: Mapped[Optional[str]] = mapped_column(String(4999)) # DESCRIPCIONI
|
||||
extra_description: Mapped[Optional[str]] = mapped_column(Text) # DESCRIPCIONEEXTRA
|
||||
part_description: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONPARTE
|
||||
class_description: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONCLASE
|
||||
class_description: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONCLASE
|
||||
package_description: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONBULTO
|
||||
|
||||
# Product attributes
|
||||
brand: Mapped[Optional[str]] = mapped_column(String(50)) # MARCA
|
||||
|
||||
@@ -13,6 +13,7 @@ class LineDescriptionBase(BaseModel):
|
||||
extra_description: Optional[str] = Field(None, description="Extra description (DESCRIPCIONEEXTRA)")
|
||||
part_description: Optional[str] = Field(None, max_length=500, description="Part description (DESCRIPCIONPARTE)")
|
||||
class_description: Optional[str] = Field(None, max_length=500, description="Class description (DESCRIPCIONCLASE)")
|
||||
package_description: Optional[str] = Field(None, max_length=500, description="Package description (DESCRIPCIONBULTO)")
|
||||
|
||||
# Product attributes
|
||||
brand: Optional[str] = Field(None, max_length=50, description="Brand (MARCA)")
|
||||
|
||||
@@ -17,6 +17,7 @@ from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSec
|
||||
from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO
|
||||
from api.v1.modules.a76.customs_brokers.dto import CustomsBrokerResponseDTO
|
||||
from api.v1.modules.a76.clients_and_providers.dto import ClientProviderResponseDTO
|
||||
from .dtos.pedimentos import PedimentosResponse
|
||||
|
||||
from .schemas import PedimentoCatalogsResponse, PedimentoCreationResponse, PedimentoEditionResponse
|
||||
|
||||
@@ -111,10 +112,13 @@ class PedimentoCatalogService:
|
||||
|
||||
if not pedimento:
|
||||
return None
|
||||
|
||||
# Convert SQLAlchemy object to Pydantic DTO
|
||||
pedimento_dto = PedimentosResponse.model_validate(pedimento)
|
||||
|
||||
return PedimentoEditionResponse(
|
||||
**catalogs.model_dump(),
|
||||
is_create=False,
|
||||
pedimento=pedimento,
|
||||
pedimento=pedimento_dto,
|
||||
pedimento_id=pedimento_id
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Consolidated schemas for Pedimento catalog responses
|
||||
"""
|
||||
|
||||
from typing import List, Optional, Any
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Import DTOs for catalog items
|
||||
@@ -11,6 +11,7 @@ from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSec
|
||||
from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO
|
||||
from api.v1.modules.a76.customs_brokers.dto import CustomsBrokerResponseDTO
|
||||
from api.v1.modules.a76.clients_and_providers.dto import ClientProviderResponseDTO
|
||||
from .dtos.pedimentos import PedimentosResponse
|
||||
|
||||
|
||||
class PedimentoCatalogsResponse(BaseModel):
|
||||
@@ -33,5 +34,5 @@ class PedimentoEditionResponse(PedimentoCatalogsResponse):
|
||||
"""Response for editing an existing pedimento (catalogs + pedimento data)"""
|
||||
|
||||
is_create: bool = False
|
||||
pedimento: Optional[Any] = None # Will be PedimentosResponse but avoiding circular import
|
||||
pedimento: Optional[PedimentosResponse] = None
|
||||
pedimento_id: Optional[int] = None
|
||||
|
||||
155
backend/main.py
155
backend/main.py
@@ -29,7 +29,9 @@ from api.v1.modules.a76.parts.models import Part
|
||||
from api.v1.modules.a24.fa.fa_parts.models import FaPart
|
||||
from api.v1.modules.a24.inv.inv_parts.models import InvPart
|
||||
from api.v1.modules.a76.manifests.manifest.models import Manifest
|
||||
from api.v1.modules.a76.manifests.concept_manifestation.models import ConceptManifestation
|
||||
from api.v1.modules.a76.manifests.concept_manifestation.models import (
|
||||
ConceptManifestation,
|
||||
)
|
||||
from api.v1.modules.a76.manifests.value_manifestation.models import ValueManifestation
|
||||
|
||||
# Configurar logging
|
||||
@@ -93,7 +95,6 @@ async def on_startup():
|
||||
run_migrations()
|
||||
logger.info("Base de datos inicializada correctamente.")
|
||||
|
||||
|
||||
# Configurar CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
@@ -112,6 +113,7 @@ app.add_middleware(TenantMiddleware)
|
||||
|
||||
# Middleware de Contexto de Usuario (Audit Log)
|
||||
from api.v1.modules.a76.audit_log.middleware import UserContextMiddleware
|
||||
|
||||
app.add_middleware(UserContextMiddleware)
|
||||
|
||||
# Importar modelos para Audit Log
|
||||
@@ -130,100 +132,121 @@ from api.v1.modules.a76.general_catalogs.company.models import Company
|
||||
from api.v1.modules.public.reference_data.countries.models import Country
|
||||
from api.v1.modules.public.reference_data.currency_types.models import CurrencyType
|
||||
from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection
|
||||
from api.v1.modules.public.reference_data.customs_warehouses.models import CustomsWarehouse
|
||||
from api.v1.modules.public.reference_data.customs_warehouses.models import (
|
||||
CustomsWarehouse,
|
||||
)
|
||||
from api.v1.modules.public.reference_data.incoterms.models import Incoterm
|
||||
from api.v1.modules.public.reference_data.invoice_types.models import InvoiceType
|
||||
from api.v1.modules.public.reference_data.material_types.models import MaterialType
|
||||
from api.v1.modules.public.reference_data.payment_methods.models import PaymentMethod
|
||||
from api.v1.modules.public.reference_data.pedimento_codes.models import PedimentoCode
|
||||
from api.v1.modules.public.reference_data.pedimento_regimens.models import RegimenPedimento
|
||||
from api.v1.modules.public.reference_data.pedimento_regimens.models import (
|
||||
RegimenPedimento,
|
||||
)
|
||||
from api.v1.modules.public.reference_data.sectors.models import Sector
|
||||
from api.v1.modules.public.reference_data.states.models import State
|
||||
from api.v1.modules.public.reference_data.transport_modes.models import TransportMode
|
||||
from api.v1.modules.public.reference_data.transport_types.models import TransportType
|
||||
from api.v1.modules.public.reference_data.valuation_methods.models import ValuationMethod
|
||||
from api.v1.modules.public.reference_data.valuation_methods.models import (
|
||||
ValuationMethod,
|
||||
)
|
||||
from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure
|
||||
from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate
|
||||
from api.v1.modules.a76.general_catalogs.identifiers.models import Identifier
|
||||
from api.v1.modules.a76.classes.models import Class
|
||||
from api.v1.modules.a76.general_catalogs.classification_concepts.models import ClassificationConcept
|
||||
from api.v1.modules.a76.general_catalogs.classification_concepts.models import (
|
||||
ClassificationConcept,
|
||||
)
|
||||
from api.v1.modules.a76.general_catalogs.concepts.models import Concept
|
||||
from api.v1.modules.a76.general_catalogs.customs_broker_concepts.models import CustomsBrokerConcept
|
||||
from api.v1.modules.a76.general_catalogs.depreciation_catalog.models import DepreciationCatalog
|
||||
from api.v1.modules.a76.general_catalogs.customs_broker_concepts.models import (
|
||||
CustomsBrokerConcept,
|
||||
)
|
||||
from api.v1.modules.a76.general_catalogs.depreciation_catalog.models import (
|
||||
DepreciationCatalog,
|
||||
)
|
||||
from api.v1.modules.a76.general_catalogs.doda.models import Doda
|
||||
from api.v1.modules.a76.general_catalogs.electronic_notices.models import ElectronicNotice
|
||||
from api.v1.modules.a76.general_catalogs.electronic_notices.models import (
|
||||
ElectronicNotice,
|
||||
)
|
||||
from api.v1.modules.a76.general_catalogs.equivalencies.models import Equivalency
|
||||
from api.v1.modules.a76.general_catalogs.error_catalogs.models import ErrorCatalog
|
||||
from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDACatalog
|
||||
from api.v1.modules.a76.general_catalogs.inpc.models import INPC
|
||||
from api.v1.modules.a76.general_catalogs.legends.models import Legend
|
||||
from api.v1.modules.a76.general_catalogs.multi_currency_types.models import MultiCurrencyType
|
||||
from api.v1.modules.a76.general_catalogs.multi_currency_types.models import (
|
||||
MultiCurrencyType,
|
||||
)
|
||||
from api.v1.modules.a76.general_catalogs.packages.models import Package
|
||||
from api.v1.modules.a76.general_catalogs.ports.models import Port
|
||||
from api.v1.modules.a76.general_catalogs.prevalidators.models import Prevalidator
|
||||
from api.v1.modules.a76.general_catalogs.seal.models import Seal
|
||||
from api.v1.modules.a76.general_catalogs.signatures.models import Signature
|
||||
from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction
|
||||
from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import (
|
||||
TariffFraction,
|
||||
)
|
||||
from api.v1.modules.a76.general_catalogs.unit_conversions.models import UnitConversion
|
||||
from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.models import USTariffFraction
|
||||
from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.models import (
|
||||
USTariffFraction,
|
||||
)
|
||||
|
||||
|
||||
# Registrar Listeners de Auditoría
|
||||
@app.on_event("startup")
|
||||
def register_audit():
|
||||
register_audit_listeners([
|
||||
# Core Transactions
|
||||
Pedimentos,
|
||||
InvoiceHeader,
|
||||
InvoiceSalesDetails,
|
||||
Item,
|
||||
|
||||
# Sidebar Core Modules
|
||||
ClientProvider,
|
||||
CustomsBroker,
|
||||
Part,
|
||||
Company,
|
||||
|
||||
# Reference Data
|
||||
Country,
|
||||
CurrencyType,
|
||||
CustomsSection,
|
||||
CustomsWarehouse,
|
||||
Incoterm,
|
||||
InvoiceType,
|
||||
MaterialType,
|
||||
PaymentMethod,
|
||||
PedimentoCode,
|
||||
RegimenPedimento,
|
||||
Sector,
|
||||
State,
|
||||
TransportMode,
|
||||
TransportType,
|
||||
ValuationMethod,
|
||||
UnitOfMeasure,
|
||||
ExchangeRate,
|
||||
Identifier,
|
||||
Class,
|
||||
ClassificationConcept,
|
||||
Concept,
|
||||
CustomsBrokerConcept,
|
||||
DepreciationCatalog,
|
||||
Doda,
|
||||
ElectronicNotice,
|
||||
Equivalency,
|
||||
ErrorCatalog,
|
||||
FDACatalog,
|
||||
INPC,
|
||||
Legend,
|
||||
MultiCurrencyType,
|
||||
Package,
|
||||
Port,
|
||||
Prevalidator,
|
||||
Seal,
|
||||
Signature,
|
||||
TariffFraction,
|
||||
UnitConversion,
|
||||
USTariffFraction
|
||||
])
|
||||
register_audit_listeners(
|
||||
[
|
||||
# Core Transactions
|
||||
Pedimentos,
|
||||
InvoiceHeader,
|
||||
InvoiceSalesDetails,
|
||||
Item,
|
||||
# Sidebar Core Modules
|
||||
ClientProvider,
|
||||
CustomsBroker,
|
||||
Part,
|
||||
Company,
|
||||
# Reference Data
|
||||
Country,
|
||||
CurrencyType,
|
||||
CustomsSection,
|
||||
CustomsWarehouse,
|
||||
Incoterm,
|
||||
InvoiceType,
|
||||
MaterialType,
|
||||
PaymentMethod,
|
||||
PedimentoCode,
|
||||
RegimenPedimento,
|
||||
Sector,
|
||||
State,
|
||||
TransportMode,
|
||||
TransportType,
|
||||
ValuationMethod,
|
||||
UnitOfMeasure,
|
||||
ExchangeRate,
|
||||
Identifier,
|
||||
Class,
|
||||
ClassificationConcept,
|
||||
Concept,
|
||||
CustomsBrokerConcept,
|
||||
DepreciationCatalog,
|
||||
Doda,
|
||||
ElectronicNotice,
|
||||
Equivalency,
|
||||
ErrorCatalog,
|
||||
FDACatalog,
|
||||
INPC,
|
||||
Legend,
|
||||
MultiCurrencyType,
|
||||
Package,
|
||||
Port,
|
||||
Prevalidator,
|
||||
Seal,
|
||||
Signature,
|
||||
TariffFraction,
|
||||
UnitConversion,
|
||||
USTariffFraction,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# Crear directorio de uploads si no existe y montar archivos estáticos
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
id="broker_key"
|
||||
bind:value={formData.broker_key}
|
||||
placeholder="Ej: 01001"
|
||||
maxlength="5"
|
||||
maxlength={5}
|
||||
disabled={isEdit}
|
||||
/>
|
||||
</div>
|
||||
@@ -133,7 +133,7 @@
|
||||
id="concept"
|
||||
bind:value={formData.concept}
|
||||
placeholder="Ej: 001"
|
||||
maxlength="15"
|
||||
maxlength={15}
|
||||
disabled={isEdit}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { obtenerAtajosFormularioIdentificadores } from '$lib/config/shortcuts/dashboard/general_catalogs/identifiers/edit';
|
||||
import {
|
||||
createIdentifier,
|
||||
updateIdentifier,
|
||||
type Identifier
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/identifiers';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
credentials: 'include'
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const data = await response.json();
|
||||
if (Array.isArray(data)) {
|
||||
countries = data;
|
||||
} else if (data.items && Array.isArray(data.items)) {
|
||||
@@ -80,7 +80,7 @@
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="max-w-4xl w-full max-h-[85vh] p-0 flex flex-col">
|
||||
<Dialog.Content class="!max-w-[70vw] w-[70vw] max-h-[90vh] p-0 flex flex-col">
|
||||
<Dialog.Header class="px-6 py-4 border-b">
|
||||
<Dialog.Title class="text-lg font-semibold">CATALOGO DE PAISES</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
@@ -88,7 +88,11 @@
|
||||
<div class="px-6 py-3 border-b bg-zinc-50 dark:bg-zinc-900">
|
||||
<div class="flex items-center gap-2">
|
||||
<Search class="w-4 h-4 text-zinc-400" />
|
||||
<Input bind:value={searchTerm} placeholder="Buscando..." class="flex-1 h-9" />
|
||||
<Input
|
||||
bind:value={searchTerm}
|
||||
placeholder="Buscando..."
|
||||
class="flex-1 h-9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -106,7 +110,9 @@
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-zinc-900 dark:bg-zinc-800 text-white">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700">Clave M3</th>
|
||||
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
|
||||
>Clave M3</th
|
||||
>
|
||||
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
|
||||
>Clave Mexicana</th
|
||||
>
|
||||
@@ -125,11 +131,11 @@
|
||||
class="border-b hover:bg-zinc-100 dark:hover:bg-zinc-800 cursor-pointer transition-colors"
|
||||
onclick={() => handleSelect(country)}
|
||||
>
|
||||
<td class="px-3 py-2 border-r">{country.m3_key || ''}</td>
|
||||
<td class="px-3 py-2 border-r">{country.mex_key || ''}</td>
|
||||
<td class="px-3 py-2 border-r">{country.description_es || ''}</td>
|
||||
<td class="px-3 py-2 border-r text-center">{country.ame_key || ''}</td>
|
||||
<td class="px-3 py-2">{country.description_en || ''}</td>
|
||||
<td class="px-3 py-2 border-r">{country.m3_key || ''}</td>
|
||||
<td class="px-3 py-2 border-r">{country.mex_key || ''}</td>
|
||||
<td class="px-3 py-2 border-r">{country.description_es || ''}</td>
|
||||
<td class="px-3 py-2 border-r text-center">{country.ame_key || ''}</td>
|
||||
<td class="px-3 py-2">{country.description_en || ''}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{#if filteredCountries.length === 0}
|
||||
@@ -143,9 +149,7 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mt-4 flex items-center justify-between text-sm text-zinc-600 dark:text-zinc-400"
|
||||
>
|
||||
<div class="mt-4 flex items-center justify-between text-sm text-zinc-600 dark:text-zinc-400">
|
||||
<div class="flex items-center gap-4">
|
||||
<button class="px-3 py-1 border rounded hover:bg-zinc-100 dark:hover:bg-zinc-800">
|
||||
<<
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
<script lang="ts">
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
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';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosPestanasItemFa } from '$lib/config/shortcuts/dashboard/invoices/item/fixed_asset';
|
||||
|
||||
// Child components
|
||||
import MainData from './main-data.svelte';
|
||||
@@ -26,7 +24,6 @@
|
||||
invoice,
|
||||
onSave,
|
||||
onCancel,
|
||||
|
||||
isTargetingPreset = false,
|
||||
isSaving = false
|
||||
}: {
|
||||
@@ -36,87 +33,39 @@
|
||||
invoice: Invoice | null;
|
||||
onSave: () => void;
|
||||
onCancel?: () => void;
|
||||
|
||||
isTargetingPreset?: boolean;
|
||||
isSaving?: boolean;
|
||||
} = $props();
|
||||
|
||||
// Acceso directo a la primera línea para evitar repeticiones en el HTML
|
||||
let line = $derived(editingItem.lines?.[0]);
|
||||
|
||||
let activeTab = $state('generales');
|
||||
|
||||
const tabMapping: Record<string, string> = {
|
||||
'tab1': 'generales',
|
||||
'tab2': 'continuacion',
|
||||
'tab3': 'series',
|
||||
'tab4': 'etiquetado',
|
||||
'tab5': 'identificadores'
|
||||
};
|
||||
|
||||
useShortcuts(
|
||||
'Invoice Item Form (Fixed Asset)',
|
||||
obtenerAtajosPestanasItemFa({
|
||||
cambiarPestana: (target) => {
|
||||
const tab = tabMapping[target];
|
||||
if (tab) activeTab = tab;
|
||||
},
|
||||
manejarGuardar: onSave,
|
||||
manejarCancelar: () => onCancel?.()
|
||||
})
|
||||
);
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open={open}>
|
||||
<Dialog.Content class="sm:max-w-6xl max-h-[90vh] p-0 overflow-hidden z-[100] [&>button]:hidden">
|
||||
<div class="bg-white dark:bg-zinc-950 border-b dark:border-zinc-800 px-4 py-3 shadow-sm flex items-start justify-between gap-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="bg-zinc-900 p-1 rounded">
|
||||
<Package class="w-3.5 h-3.5 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<Dialog.Title class="text-sm font-semibold text-zinc-900 dark:text-zinc-100 leading-tight flex items-center gap-2">
|
||||
{#if isTargetingPreset}
|
||||
{isEditMode ? 'Editar Item de Plantilla' : 'Nuevo Item para Plantilla'}
|
||||
{:else}
|
||||
<Sheet.Root bind:open={open}>
|
||||
<Sheet.Content side="right" class="w-full sm:max-w-[95vw] lg:max-w-[85vw] xl:max-w-[75vw] p-0 flex flex-col h-full bg-slate-50 dark:bg-black">
|
||||
|
||||
<header class="bg-white dark:bg-zinc-950 border-b dark:border-zinc-800 px-3 py-1.5 shadow-sm shrink-0">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="bg-zinc-900 p-1 rounded">
|
||||
<Package class="w-3.5 h-3.5 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<Sheet.Title class="text-sm font-semibold text-zinc-900 dark:text-zinc-100 leading-tight">
|
||||
{isEditMode ? 'Editar Partida' : 'Nueva Partida - Activo Fijo'}
|
||||
{/if}
|
||||
{#if editingItem.lines && editingItem.lines.length > 1}
|
||||
<span class="px-1.5 py-0.5 rounded-full bg-blue-100 dark:bg-blue-900/30 text-[10px] text-blue-700 dark:text-blue-300 font-bold border border-blue-200 dark:border-blue-800">
|
||||
{editingItem.lines.length} lines
|
||||
</span>
|
||||
{/if}
|
||||
</Dialog.Title>
|
||||
{#if !isTargetingPreset}
|
||||
</Sheet.Title>
|
||||
<p class="text-[10px] text-muted-foreground flex items-center gap-1.5">
|
||||
Factura: <span class="font-medium text-zinc-700 dark:text-zinc-300">{invoice?.invoice_number || 'N/A'}</span>
|
||||
</p>
|
||||
{:else}
|
||||
<p class="text-[10px] text-muted-foreground flex items-center gap-1.5">
|
||||
<span class="font-medium text-blue-600 dark:text-blue-400 uppercase tracking-wider">Modo Plantilla</span>
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
|
||||
<Button variant="outline" size="sm" onclick={() => onCancel?.()} disabled={isSaving} class="h-7 text-xs px-2">
|
||||
Cancelar
|
||||
<Button variant="ghost" size="icon" onclick={() => onCancel?.()} class="h-7 w-7 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-800">
|
||||
<X class="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
<Button size="sm" onclick={onSave} disabled={isSaving} class="h-7 text-xs px-2 bg-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-700 text-white">
|
||||
{#if isSaving}
|
||||
<Loader2 class="w-3 h-3 mr-1 animate-spin" />
|
||||
Guardando...
|
||||
{:else}
|
||||
<Save class="w-3 h-3 mr-1" />
|
||||
{isEditMode ? 'Actualizar' : 'Crear'}
|
||||
{/if}
|
||||
</Button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="p-3 overflow-y-auto max-h-[calc(90vh-88px)] bg-slate-50/70 dark:bg-black">
|
||||
<div class="flex-1 overflow-y-auto px-2 py-1.5">
|
||||
<div class="space-y-2">
|
||||
|
||||
{#if line}
|
||||
@@ -152,7 +101,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs.Root bind:value={activeTab} class="w-full">
|
||||
<Tabs.Root value="generales" class="w-full">
|
||||
<Tabs.List class="grid w-full grid-cols-5 bg-zinc-100 dark:bg-zinc-800/50 rounded p-0.5 gap-0.5">
|
||||
<Tabs.Trigger value="generales" class="text-xs font-medium py-1.5 px-2 rounded transition-all data-[state=active]:bg-white dark:data-[state=active]:bg-zinc-700 data-[state=active]:shadow-sm data-[state=active]:text-black dark:data-[state=active]:text-white">
|
||||
General
|
||||
@@ -216,5 +165,23 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
<footer class="bg-white dark:bg-zinc-950 border-t border-zinc-200 dark:border-zinc-800 px-3 py-1.5 shadow-sm shrink-0">
|
||||
<div class="flex items-center justify-end gap-1.5">
|
||||
<Button variant="outline" size="sm" onclick={() => onCancel?.()} disabled={isSaving} class="h-7 text-xs px-2">
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button size="sm" onclick={onSave} disabled={isSaving} class="h-7 text-xs px-2 bg-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-700 text-white">
|
||||
{#if isSaving}
|
||||
<Loader2 class="w-3 h-3 mr-1 animate-spin" />
|
||||
Guardando...
|
||||
{:else}
|
||||
<Save class="w-3 h-3 mr-1" />
|
||||
{isEditMode ? 'Actualizar' : 'Crear'}
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</Sheet.Content>
|
||||
</Sheet.Root>
|
||||
@@ -24,12 +24,11 @@
|
||||
|
||||
const filteredParts = $derived(
|
||||
searchQuery
|
||||
? parts.filter(
|
||||
(p) =>
|
||||
p.part_number?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
p.description_spanish?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
p.description_english?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
? parts.filter(p =>
|
||||
p.part_number?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
p.description_spanish?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
p.description_english?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
: parts
|
||||
);
|
||||
|
||||
@@ -53,12 +52,15 @@
|
||||
|
||||
isSearching = true;
|
||||
try {
|
||||
const response = await fetch(`/api-sveltekit/parts?company_id=${activeCompanyId}&limit=100`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
const response = await fetch(
|
||||
`/api-sveltekit/parts?company_id=${activeCompanyId}&limit=100`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Error al buscar números de parte');
|
||||
@@ -85,9 +87,8 @@
|
||||
function handleScroll(e: Event) {
|
||||
const target = e.target as HTMLDivElement;
|
||||
const threshold = 100;
|
||||
const scrolledToBottom =
|
||||
target.scrollHeight - target.scrollTop - target.clientHeight < threshold;
|
||||
|
||||
const scrolledToBottom = target.scrollHeight - target.scrollTop - target.clientHeight < threshold;
|
||||
|
||||
if (scrolledToBottom && displayedParts.length < filteredParts.length) {
|
||||
currentPage++;
|
||||
loadMoreParts();
|
||||
@@ -103,10 +104,12 @@
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="max-w-5xl w-full max-h-[90vh] p-0 flex flex-col">
|
||||
<Dialog.Content class="!max-w-[50vw] w-[50vw] max-h-[80vh] flex flex-col">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Seleccionar Número de Parte</Dialog.Title>
|
||||
<Dialog.Description>Busca y selecciona un número de parte para la partida</Dialog.Description>
|
||||
<Dialog.Description>
|
||||
Busca y selecciona un número de parte para la partida
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="flex gap-2 mb-4">
|
||||
@@ -145,18 +148,15 @@
|
||||
</Table.Row>
|
||||
{:else}
|
||||
{#each displayedParts as part}
|
||||
<Table.Row
|
||||
class="cursor-pointer hover:bg-muted/50"
|
||||
onclick={() => handleSelect(part)}
|
||||
>
|
||||
<Table.Row class="cursor-pointer hover:bg-muted/50" onclick={() => handleSelect(part)}>
|
||||
<Table.Cell class="font-medium">{part.part_number}</Table.Cell>
|
||||
<Table.Cell>{part.description_spanish || '-'}</Table.Cell>
|
||||
<Table.Cell class="text-muted-foreground"
|
||||
>{part.description_english || '-'}</Table.Cell
|
||||
>
|
||||
<Table.Cell class="text-muted-foreground">{part.description_english || '-'}</Table.Cell>
|
||||
<Table.Cell class="text-muted-foreground">{part.part_class || '-'}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Button variant="ghost" size="sm" class="h-8">Seleccionar</Button>
|
||||
<Button variant="ghost" size="sm" class="h-8">
|
||||
Seleccionar
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
import type { LineFinancials, LineQuantities } from '$lib/api/dashboard/a76/items';
|
||||
|
||||
let { financials = $bindable(), quantities = $bindable() }: { financials: LineFinancials; quantities: LineQuantities } = $props();
|
||||
|
||||
// Helper function to safely format numbers
|
||||
function formatNumber(value: any, decimals: number = 8): string {
|
||||
const num = Number(value);
|
||||
return isNaN(num) ? '0.00000000' : num.toFixed(decimals);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
@@ -10,18 +16,18 @@
|
||||
|
||||
<div class="text-xs font-semibold">RETURN QUANTITY SUB-ITEMS</div>
|
||||
<div class="grid grid-cols-2 gap-2 text-xs">
|
||||
<div>Temporary: <span class="text-gray-900 dark:text-gray-100">{quantities.quantity_temp_export?.toFixed(8) || '0.00000000'}</span></div>
|
||||
<div>Temporary: <span class="text-gray-900 dark:text-gray-100">{formatNumber(quantities.quantity_temp_export)}</span></div>
|
||||
<div>Replacement or Change: <span class="text-gray-900 dark:text-gray-100">0.00000000</span></div>
|
||||
<div>Definitive: <span class="text-gray-900 dark:text-gray-100">{quantities.quantity_returned?.toFixed(8) || '0.00000000'}</span></div>
|
||||
<div>Returned Values: <span class="text-gray-900 dark:text-gray-100">{financials.value_returned_usd?.toFixed(8) || '0.00000000'}</span></div>
|
||||
<div class="col-span-2">Returned Values: <span class="text-gray-900 dark:text-gray-100">{financials.value_returned_mxn?.toFixed(8) || '0.00000000'}</span></div>
|
||||
<div>Definitive: <span class="text-gray-900 dark:text-gray-100">{formatNumber(quantities.quantity_returned)}</span></div>
|
||||
<div>Returned Values: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.value_returned_usd)}</span></div>
|
||||
<div class="col-span-2">Returned Values: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.value_returned_mxn)}</span></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2 text-xs pt-2 border-t">
|
||||
<div class="font-semibold">WEIGHTS (KILOS)</div>
|
||||
<div class="font-semibold">WEIGHTS (Pounds)</div>
|
||||
<div>Net: <span class="text-gray-900 dark:text-gray-100">{quantities.net_weight?.toFixed(8) || '0.00000000'}</span></div>
|
||||
<div>Net: <span class="text-gray-900 dark:text-gray-100">{formatNumber(quantities.net_weight)}</span></div>
|
||||
<div><span class="text-gray-900 dark:text-gray-100">0.00000000</span></div>
|
||||
<div>Whole: <span class="text-gray-900 dark:text-gray-100">{quantities.gross_weight?.toFixed(8) || '0.00000000'}</span></div>
|
||||
<div>Whole: <span class="text-gray-900 dark:text-gray-100">{formatNumber(quantities.gross_weight)}</span></div>
|
||||
<div><span class="text-gray-900 dark:text-gray-100">0.00000000</span></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -33,13 +39,13 @@
|
||||
<div class="grid grid-cols-2 gap-2 text-xs">
|
||||
<div class="font-semibold">(Dollars)</div>
|
||||
<div class="font-semibold">(Pesos)</div>
|
||||
<div>Cost: <span class="text-gray-900 dark:text-gray-100">{financials.unit_cost_usd?.toFixed(8) || '0.00000000'}</span></div>
|
||||
<div><span class="text-gray-900 dark:text-gray-100">{financials.unit_cost_mxn?.toFixed(8) || '0.00000000'}</span></div>
|
||||
<div>Value: <span class="text-gray-900 dark:text-gray-100">{financials.value_usd?.toFixed(8) || '0.00000000'}</span></div>
|
||||
<div><span class="text-gray-900 dark:text-gray-100">{financials.value_mxn?.toFixed(8) || '0.00000000'}</span></div>
|
||||
<div class="text-xs">Capture Cost: <span class="text-gray-900 dark:text-gray-100">{financials.unit_cost_capture?.toFixed(8) || '0.00000000'}</span> <span class="text-gray-900 dark:text-gray-100">USD</span></div>
|
||||
<div class="text-xs">Capture Value: <span class="text-gray-900 dark:text-gray-100">{financials.value_usd?.toFixed(8) || '0.00000000'}</span> <span class="text-gray-900 dark:text-gray-100">USD</span></div>
|
||||
<div class="text-xs">Customs Value: <span class="text-gray-900 dark:text-gray-100">{financials.customs_value_usd?.toFixed(8) || '0.00000000'}</span> <span class="text-gray-900 dark:text-gray-100">USD</span></div>
|
||||
<div>Cost: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.unit_cost_usd)}</span></div>
|
||||
<div><span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.unit_cost_mxn)}</span></div>
|
||||
<div>Value: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.value_usd)}</span></div>
|
||||
<div><span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.value_mxn)}</span></div>
|
||||
<div class="text-xs">Capture Cost: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.unit_cost_capture)}</span> <span class="text-gray-900 dark:text-gray-100">USD</span></div>
|
||||
<div class="text-xs">Capture Value: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.value_usd)}</span> <span class="text-gray-900 dark:text-gray-100">USD</span></div>
|
||||
<div class="text-xs">Customs Value: <span class="text-gray-900 dark:text-gray-100">{formatNumber(financials.customs_value_usd)}</span> <span class="text-gray-900 dark:text-gray-100">USD</span></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="max-w-4xl w-full max-h-[90vh] p-0 flex flex-col">
|
||||
<Dialog.Content class="!max-w-[70vw] w-[70vw] max-h-[90vh] p-0 flex flex-col">
|
||||
<Dialog.Header class="px-6 py-4 border-b">
|
||||
<Dialog.Title class="text-lg font-semibold">CATALOGOS DE UNIDADES DE MEDIDA</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
@@ -91,7 +91,11 @@
|
||||
<div class="px-6 py-3 border-b bg-zinc-50 dark:bg-zinc-900">
|
||||
<div class="flex items-center gap-2">
|
||||
<Search class="w-4 h-4 text-zinc-400" />
|
||||
<Input bind:value={searchTerm} placeholder="Buscando..." class="flex-1 h-9" />
|
||||
<Input
|
||||
bind:value={searchTerm}
|
||||
placeholder="Buscando..."
|
||||
class="flex-1 h-9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -109,7 +113,9 @@
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-zinc-900 dark:bg-zinc-800 text-white">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700">U.M.</th>
|
||||
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
|
||||
>U.M.</th
|
||||
>
|
||||
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
|
||||
>Descripción Español</th
|
||||
>
|
||||
@@ -150,9 +156,7 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mt-4 flex items-center justify-between text-sm text-zinc-600 dark:text-zinc-400"
|
||||
>
|
||||
<div class="mt-4 flex items-center justify-between text-sm text-zinc-600 dark:text-zinc-400">
|
||||
<div class="flex items-center gap-4">
|
||||
<button class="px-3 py-1 border rounded hover:bg-zinc-100 dark:hover:bg-zinc-800">
|
||||
<<
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import * as Sheet from '$lib/components/ui/sheet';
|
||||
import * as Tabs from '$lib/components/ui/tabs';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
@@ -60,25 +60,29 @@
|
||||
// Initialize missing nested objects if they don't exist
|
||||
$effect(() => {
|
||||
if (open && editingItem) {
|
||||
if (!editingItem.lines) editingItem.lines = [{}];
|
||||
if (!editingItem.lines[0].quantity) editingItem.lines[0].quantity = {};
|
||||
if (!editingItem.lines[0].financial) editingItem.lines[0].financial = {};
|
||||
if (!editingItem.lines[0].customs) editingItem.lines[0].customs = {};
|
||||
if (!editingItem.lines[0].description) editingItem.lines[0].description = {};
|
||||
if (!editingItem.lines) editingItem.lines = [{ line_number: 1 } as any];
|
||||
if (editingItem.lines[0] && !editingItem.lines[0].quantity)
|
||||
editingItem.lines[0].quantity = {} as any;
|
||||
if (editingItem.lines[0] && !editingItem.lines[0].financial)
|
||||
editingItem.lines[0].financial = {} as any;
|
||||
if (editingItem.lines[0] && !editingItem.lines[0].customs)
|
||||
editingItem.lines[0].customs = {} as any;
|
||||
if (editingItem.lines[0] && !editingItem.lines[0].description)
|
||||
editingItem.lines[0].description = {} as any;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-5xl max-h-[90vh] p-0 overflow-hidden z-[100] [&>button]:hidden">
|
||||
<Sheet.Root bind:open>
|
||||
<Sheet.Content side="right" class="w-[50vw] overflow-hidden p-0 sm:max-w-none">
|
||||
<div
|
||||
class="px-6 py-4 border-b bg-white dark:bg-zinc-950 flex items-start justify-between gap-3"
|
||||
class="flex items-start justify-between gap-3 border-b bg-white px-6 py-4 dark:bg-zinc-950"
|
||||
>
|
||||
<div class="space-y-1">
|
||||
<Dialog.Title class="text-lg font-semibold">
|
||||
<Sheet.Title class="text-lg font-semibold">
|
||||
{isEditMode ? 'Editar Item' : 'Agregar Nuevo Item'} - SCAII (Inventario)
|
||||
</Dialog.Title>
|
||||
<Dialog.Description class="text-sm text-muted-foreground">
|
||||
</Sheet.Title>
|
||||
<Sheet.Description class="text-sm text-muted-foreground">
|
||||
{isEditMode
|
||||
? 'Modifica los campos del inventario y guarda los cambios.'
|
||||
: 'Completa la información del nuevo item de inventario.'}
|
||||
@@ -87,13 +91,13 @@
|
||||
{editingItem.lines.length} items en esta partida
|
||||
</Badge>
|
||||
{/if}
|
||||
</Dialog.Description>
|
||||
</Sheet.Description>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<Button variant="outline" onclick={() => onCancel?.()} disabled={isSaving}>Cancelar</Button>
|
||||
<Button onclick={onSave} disabled={isSaving}>
|
||||
{#if isSaving}
|
||||
<Loader2 class="w-4 h-4 mr-2 animate-spin" />
|
||||
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
|
||||
Guardando...
|
||||
{:else}
|
||||
{isEditMode ? 'Guardar Cambios' : 'Agregar Item'}
|
||||
@@ -102,7 +106,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 overflow-auto max-h-[calc(90vh-96px)] bg-slate-50/60 dark:bg-black">
|
||||
<div class="max-h-[calc(90vh-96px)] overflow-auto bg-slate-50/60 p-6 dark:bg-black">
|
||||
<Tabs.Root bind:value={activeTab} class="mt-0">
|
||||
<Tabs.List class="grid w-full grid-cols-4">
|
||||
<Tabs.Trigger value="general">General</Tabs.Trigger>
|
||||
@@ -112,13 +116,13 @@
|
||||
</Tabs.List>
|
||||
|
||||
<!-- Tab: General -->
|
||||
<Tabs.Content value="general" class="space-y-4 mt-4">
|
||||
<Tabs.Content value="general" class="mt-4 space-y-4">
|
||||
<!-- Información de la Factura (Solo lectura) -->
|
||||
{#if !isTargetingPreset}
|
||||
<div class="rounded-lg border bg-muted/50 p-4 space-y-3">
|
||||
<div class="space-y-3 rounded-lg border bg-muted/50 p-4">
|
||||
<h4 class="text-sm font-medium">Información de la Factura (SCAII - Inventario)</h4>
|
||||
{#if !invoice?.id}
|
||||
<div class="text-sm text-amber-600 bg-amber-50 dark:bg-amber-950/20 p-3 rounded">
|
||||
<div class="rounded bg-amber-50 p-3 text-sm text-amber-600 dark:bg-amber-950/20">
|
||||
⚠️ Esta factura aún no se ha guardado. Los items se asociarán cuando guardes la
|
||||
factura.
|
||||
</div>
|
||||
@@ -139,7 +143,7 @@
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<span class="text-muted-foreground">Sistema:</span>
|
||||
<span class="ml-2 font-medium bg-blue-100 dark:bg-blue-900/30 px-2 py-1 rounded"
|
||||
<span class="ml-2 rounded bg-blue-100 px-2 py-1 font-medium dark:bg-blue-900/30"
|
||||
>SCAII (Inventory)</span
|
||||
>
|
||||
</div>
|
||||
@@ -207,7 +211,7 @@
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- Tab: Clasificación -->
|
||||
<Tabs.Content value="clasificacion" class="space-y-4 mt-4">
|
||||
<Tabs.Content value="clasificacion" class="mt-4 space-y-4">
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="tariff_fraction">Fracción Arancelaria</Label>
|
||||
@@ -223,21 +227,21 @@
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="product_type">Tipo de Producto</Label>
|
||||
{#if line}
|
||||
{#if line?.description}
|
||||
<Input
|
||||
id="product_type"
|
||||
placeholder="Materia prima, producto terminado, etc."
|
||||
bind:value={line.description.extra_description_2}
|
||||
bind:value={(line.description as any).extra_description_2}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="material_type">Tipo de Material</Label>
|
||||
{#if line}
|
||||
{#if line?.description}
|
||||
<Input
|
||||
id="material_type"
|
||||
placeholder="Metal, plástico, etc."
|
||||
bind:value={line.description.extra_description_3}
|
||||
bind:value={(line.description as any).extra_description_3}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -262,7 +266,7 @@
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- Tab: Cantidades -->
|
||||
<Tabs.Content value="cantidades" class="space-y-4 mt-4">
|
||||
<Tabs.Content value="cantidades" class="mt-4 space-y-4">
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
@@ -350,7 +354,7 @@
|
||||
id="packages"
|
||||
type="number"
|
||||
placeholder="0"
|
||||
bind:value={line.quantity.packages}
|
||||
bind:value={(line.quantity as any).packages}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -360,7 +364,7 @@
|
||||
<Input
|
||||
id="package_type"
|
||||
placeholder="Caja, pallet, etc."
|
||||
bind:value={line.quantity.package_type}
|
||||
bind:value={(line.quantity as any).package_type}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -374,7 +378,7 @@
|
||||
id="imported_quantity"
|
||||
type="number"
|
||||
placeholder="0"
|
||||
bind:value={line.quantity.quantity_imported}
|
||||
bind:value={(line.quantity as any).quantity_imported}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -385,7 +389,8 @@
|
||||
id="remaining_quantity"
|
||||
type="number"
|
||||
placeholder="0"
|
||||
value={(line.quantity.quantity || 0) - (line.quantity.quantity_imported || 0)}
|
||||
value={(line.quantity.quantity || 0) -
|
||||
((line.quantity as any).quantity_imported || 0)}
|
||||
disabled
|
||||
/>
|
||||
{/if}
|
||||
@@ -395,7 +400,7 @@
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- Tab: Otros -->
|
||||
<Tabs.Content value="otros" class="space-y-4 mt-4">
|
||||
<Tabs.Content value="otros" class="mt-4 space-y-4">
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="brand">Marca</Label>
|
||||
@@ -435,7 +440,7 @@
|
||||
{#if line?.description}
|
||||
<textarea
|
||||
id="observations"
|
||||
class="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
placeholder="Notas adicionales sobre el inventario..."
|
||||
bind:value={line.description.extra_description}
|
||||
></textarea>
|
||||
@@ -445,5 +450,5 @@
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
</Sheet.Content>
|
||||
</Sheet.Root>
|
||||
|
||||
@@ -1,123 +1,106 @@
|
||||
<script lang="ts" generics="TData, TValue">
|
||||
import { onMount } from 'svelte';
|
||||
import {
|
||||
type ColumnDef,
|
||||
getCoreRowModel
|
||||
} from "@tanstack/table-core";
|
||||
import { createSvelteTable, FlexRender } from "$lib/components/ui/data-table/index.js";
|
||||
import * as Table from "$lib/components/ui/table/index.js";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
|
||||
type DataTableProps<TData, TValue> = {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
data: TData[];
|
||||
loading: boolean;
|
||||
hasMore: boolean;
|
||||
loadMore: () => void;
|
||||
pageCount: number;
|
||||
totalItems: number;
|
||||
};
|
||||
|
||||
let {
|
||||
data,
|
||||
columns,
|
||||
loading,
|
||||
hasMore,
|
||||
loadMore
|
||||
pageCount,
|
||||
totalItems
|
||||
}: DataTableProps<TData, TValue> = $props();
|
||||
|
||||
const table = createSvelteTable({
|
||||
get data() {
|
||||
return data;
|
||||
},
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel()
|
||||
get data() { return data; },
|
||||
get columns() { return columns; },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
get pageCount() { return pageCount; },
|
||||
});
|
||||
|
||||
let scrollContainer = $state<HTMLDivElement>();
|
||||
let loadingTrigger = $state<HTMLDivElement>();
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
// Intersection Observer para detectar cuando el usuario llega al final
|
||||
onMount(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const [entry] = entries;
|
||||
if (entry.isIntersecting && hasMore && !loading) {
|
||||
loadMore();
|
||||
}
|
||||
},
|
||||
{
|
||||
root: scrollContainer,
|
||||
threshold: 0.1
|
||||
}
|
||||
);
|
||||
|
||||
if (loadingTrigger) {
|
||||
observer.observe(loadingTrigger);
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
});
|
||||
const currentPage = $derived(Number($page.url.searchParams.get('page') || 1));
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4 px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -120,7 +120,7 @@ class CompanyStore {
|
||||
* @param company - La compañía a establecer como activa
|
||||
* @param silent - Si es true, no dispara el evento companyChanged (para inicialización)
|
||||
*/
|
||||
setActiveCompany(company: Company, silent: boolean = false) {
|
||||
async setActiveCompany(company: Company, silent: boolean = false) {
|
||||
const previousCompanyId = this._activeCompany?.id;
|
||||
this._activeCompany = company;
|
||||
|
||||
@@ -130,8 +130,20 @@ class CompanyStore {
|
||||
}
|
||||
|
||||
// Guardar en cookie para acceso desde el servidor (SSR)
|
||||
if (typeof document !== 'undefined') {
|
||||
document.cookie = `active_company_id=${company.id}; path=/; max-age=${60 * 60 * 24 * 30}; SameSite=Lax`;
|
||||
// Usar el endpoint del servidor para garantizar que la cookie esté disponible en SSR
|
||||
if (browser) {
|
||||
try {
|
||||
await fetch('/api-sveltekit/company/set-active', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ companyId: company.id }),
|
||||
credentials: 'include'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error setting active company cookie:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Despachar evento personalizado solo si:
|
||||
|
||||
@@ -36,7 +36,6 @@ export const GET: RequestHandler = async ({ cookies, url }) => {
|
||||
|
||||
try {
|
||||
const fetchUrl = `${baseUrl}v1/a76/classes?${queryString}`;
|
||||
console.log('Fetching classes from:', fetchUrl);
|
||||
|
||||
const response = await fetch(
|
||||
fetchUrl,
|
||||
|
||||
@@ -32,7 +32,6 @@ export const GET: RequestHandler = async ({ cookies, url, params }) => {
|
||||
|
||||
try {
|
||||
const fetchUrl = `${baseUrl}v1/a76/classes/${id}?company_id=${companyId}`;
|
||||
console.log('Fetching class from:', fetchUrl);
|
||||
|
||||
const response = await fetch(
|
||||
fetchUrl,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* API route para establecer la compañía activa en una cookie
|
||||
*/
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const POST: RequestHandler = async ({ cookies, request }) => {
|
||||
try {
|
||||
const { companyId } = await request.json();
|
||||
|
||||
if (!companyId || typeof companyId !== 'number') {
|
||||
return json({ error: 'Invalid company ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Establecer la cookie desde el servidor
|
||||
cookies.set('active_company_id', companyId.toString(), {
|
||||
path: '/',
|
||||
maxAge: 60 * 60 * 24 * 30, // 30 días
|
||||
sameSite: 'lax',
|
||||
httpOnly: false, // Permitir acceso desde JavaScript
|
||||
secure: process.env.NODE_ENV === 'production'
|
||||
});
|
||||
|
||||
return json({ success: true, companyId });
|
||||
} catch (error) {
|
||||
console.error('Error setting active company:', error);
|
||||
return json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
@@ -35,8 +35,7 @@ export const GET: RequestHandler = async ({ cookies, url }) => {
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
try {
|
||||
const fetchUrl = `${baseUrl}v1/a76/parts?${queryString}`;
|
||||
console.log('Fetching parts from:', fetchUrl);
|
||||
const fetchUrl = `${baseUrl}v1/a76/parts?${queryString}`;
|
||||
|
||||
const response = await fetch(
|
||||
fetchUrl,
|
||||
|
||||
@@ -31,8 +31,7 @@ export const GET: RequestHandler = async ({ cookies, url, params }) => {
|
||||
const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`;
|
||||
|
||||
try {
|
||||
const fetchUrl = `${baseUrl}v1/a76/parts/${id}?company_id=${companyId}`;
|
||||
console.log('Fetching part from:', fetchUrl);
|
||||
const fetchUrl = `${baseUrl}v1/a76/parts/${id}?company_id=${companyId}`;
|
||||
|
||||
const response = await fetch(
|
||||
fetchUrl,
|
||||
|
||||
@@ -20,8 +20,6 @@ export const GET: RequestHandler = async ({ cookies, url }) => {
|
||||
|
||||
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,
|
||||
@@ -35,8 +33,6 @@ export const GET: RequestHandler = async ({ cookies, url }) => {
|
||||
);
|
||||
|
||||
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), {
|
||||
|
||||
@@ -36,8 +36,6 @@ export const GET: RequestHandler = async ({ cookies, url }) => {
|
||||
|
||||
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,
|
||||
@@ -51,8 +49,6 @@ export const GET: RequestHandler = async ({ cookies, url }) => {
|
||||
);
|
||||
|
||||
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), {
|
||||
|
||||
@@ -31,8 +31,7 @@ export const GET: RequestHandler = async ({ cookies, params, url }) => {
|
||||
const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`;
|
||||
|
||||
try {
|
||||
const fetchUrl = `${baseUrl}v1/a76/units-of-measure/${id}?company_id=${companyId}`;
|
||||
console.log('Fetching unit of measure from:', fetchUrl);
|
||||
const fetchUrl = `${baseUrl}v1/a76/units-of-measure/${id}?company_id=${companyId}`;
|
||||
|
||||
const response = await fetch(
|
||||
fetchUrl,
|
||||
|
||||
@@ -39,7 +39,7 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
const response = await authenticatedFetch(`v1/a76/classification-concepts?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
const response = await authenticatedFetch(`v1/a76/classification-concepts/?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', classifications: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
|
||||
@@ -89,7 +89,6 @@
|
||||
/>
|
||||
<CreateDialog
|
||||
bind:open={dialogOpen}
|
||||
title="Crear Nueva Clasificación de Concepto"
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -59,7 +59,7 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
});
|
||||
|
||||
const response = await authenticatedFetch(
|
||||
`v1/a76/concepts?${queryParams.toString()}`,
|
||||
`v1/a76/concepts/?${queryParams.toString()}`,
|
||||
{ method: 'GET' },
|
||||
cookies,
|
||||
fetch
|
||||
|
||||
@@ -2,20 +2,25 @@ import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const parentData = await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
return { error: 'No authenticated', concepts: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }, activeCompanyId: null };
|
||||
return { error: 'No authenticated', concepts: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id');
|
||||
|
||||
// Obtener company_id de la cookie o usar el primero disponible
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return { error: 'No company selected', concepts: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }, activeCompanyId: null };
|
||||
return { error: 'No company selected', concepts: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
const filters: Record<string, string> = {};
|
||||
@@ -28,22 +33,22 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId,
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
const response = await authenticatedFetch(`v1/a76/customs-broker-concepts?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
const response = await authenticatedFetch(`v1/a76/customs-broker-concepts/?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', concepts: { items: [], total: 0, page, page_size: pageSize, pages: 0 }, activeCompanyId: companyId };
|
||||
return { error: 'Failed to load', concepts: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
// Calculate pages if not provided by API
|
||||
const pages = data.pages || Math.ceil(data.total / pageSize);
|
||||
|
||||
return { concepts: { ...data, pages }, activeCompanyId: parseInt(companyId) };
|
||||
return { concepts: { ...data, pages } };
|
||||
} catch (error) {
|
||||
console.error('Error loading customs broker concepts:', error);
|
||||
return { error: 'Error loading', concepts: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }, activeCompanyId: null };
|
||||
return { error: 'Error loading', concepts: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaConceptosAA } from '$lib/config/shortcuts/dashboard/general_catalogs/customs_broker_concepts/list';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
const activeCompanyId = $derived(companyStore.activeCompany?.id);
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Atajos
|
||||
@@ -92,9 +95,11 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
companyId={data.activeCompanyId}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
{#if activeCompanyId}
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
companyId={activeCompanyId}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const parentData = await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
@@ -12,7 +12,12 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id');
|
||||
|
||||
// Obtener company_id de la cookie o usar el primero disponible
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return { error: 'No company selected', dodas: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
@@ -26,7 +31,7 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId,
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
const response = await authenticatedFetch(`v1/a76/doda?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const parentData = await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
@@ -12,7 +12,12 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id');
|
||||
|
||||
// Obtener company_id de la cookie o usar el primero disponible
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return { error: 'No company selected', notices: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
@@ -28,7 +33,7 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId,
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
const response = await authenticatedFetch(`v1/a76/electronic-notices?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const parentData = await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
@@ -12,7 +12,12 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id');
|
||||
|
||||
// Obtener company_id de la cookie o usar el primero disponible
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return { error: 'No company selected', equivalencies: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
@@ -28,7 +33,7 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId,
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const parentData = await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
@@ -12,7 +12,12 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id');
|
||||
|
||||
// Obtener company_id de la cookie o usar el primero disponible
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return { error: 'No company selected', identifiers: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
@@ -28,11 +33,11 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId,
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await authenticatedFetch(`v1/a76/identifiers?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
const response = await authenticatedFetch(`v1/a76/identifiers/?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', identifiers: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const parentData = await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
@@ -12,7 +12,12 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id');
|
||||
|
||||
// Obtener company_id de la cookie o usar el primero disponible
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return { error: 'No company selected', inpc: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
@@ -28,7 +33,7 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId,
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
});
|
||||
|
||||
const response = await authenticatedFetch(
|
||||
`v1/a76/packages?${queryParams.toString()}`,
|
||||
`v1/a76/packages/?${queryParams.toString()}`,
|
||||
{ method: 'GET' },
|
||||
cookies,
|
||||
fetch
|
||||
|
||||
@@ -2,7 +2,8 @@ import { getServerApiUrl, getAuthTokens } from '$lib/server/api';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url }) => {
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
const parentData = await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
if (!accessToken) {
|
||||
throw redirect(302, '/login');
|
||||
@@ -10,7 +11,12 @@ export const load: PageServerLoad = async ({ cookies, fetch, url }) => {
|
||||
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('page_size')) || 50;
|
||||
const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id');
|
||||
|
||||
// Obtener company_id de la cookie o usar el primero disponible
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return {
|
||||
@@ -27,7 +33,7 @@ export const load: PageServerLoad = async ({ cookies, fetch, url }) => {
|
||||
const query = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId
|
||||
company_id: companyId.toString()
|
||||
});
|
||||
|
||||
const endpoint = `${apiUrl}v1/a76/ports?${query.toString()}`;
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const parentData = await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
@@ -12,7 +12,12 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id');
|
||||
|
||||
// Obtener company_id de la cookie o usar el primero disponible
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return { error: 'No company selected', prevalidators: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
@@ -28,7 +33,7 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId,
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
const response = await authenticatedFetch(`v1/a76/prevalidators?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const parentData = await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
@@ -12,7 +12,12 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id');
|
||||
|
||||
// Obtener company_id de la cookie o usar el primero disponible
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return { error: 'No company selected', signatures: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
@@ -23,7 +28,7 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
|
||||
if (code) filters.code = code;
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), company_id: companyId, ...filters });
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), company_id: companyId.toString(), ...filters });
|
||||
const response = await authenticatedFetch(`v1/a76/signatures?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const parentData = await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
@@ -17,9 +17,11 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
|
||||
// Obtener company_id
|
||||
// Obtener company_id de la cookie o usar el primero disponible
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId ? parseInt(cookieCompanyId) : undefined;
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return {
|
||||
|
||||
@@ -187,8 +187,7 @@
|
||||
selectedInvoiceId = null;
|
||||
} else {
|
||||
selectedInvoiceId = invoice.id;
|
||||
}
|
||||
console.log('Selected Invoice ID:', selectedInvoiceId);
|
||||
}
|
||||
}
|
||||
|
||||
const selectedInvoice = $derived(
|
||||
|
||||
@@ -222,29 +222,199 @@
|
||||
return { ...skeleton, ...filtered };
|
||||
}
|
||||
|
||||
// Función para mapear la factura existente a los formData
|
||||
function mapInvoiceToTopFields(invoice: any) {
|
||||
if (!invoice) return topFieldsSkeleton;
|
||||
|
||||
let operationType: string | null = null;
|
||||
if (invoice.operation_type) {
|
||||
operationType = invoice.operation_type;
|
||||
} else if (data.filters?.operation_type !== undefined) {
|
||||
operationType = data.filters.operation_type ?? null;
|
||||
}
|
||||
|
||||
return {
|
||||
is_pedimento_pending: invoice.compliance_mx?.is_pedimento_pending || false,
|
||||
pedimento_id: invoice.compliance_mx?.pedimento_id || '',
|
||||
remesa: invoice.compliance_mx?.remesa || '',
|
||||
invoice_number: invoice.invoice_number || '',
|
||||
invoice_date: invoice.invoice_date || new Date().toISOString().split('T')[0],
|
||||
emission_date: invoice.emission_date || new Date().toISOString().split('T')[0],
|
||||
operation_type: operationType,
|
||||
invoice_type: invoice.invoice_type || (data.filters?.invoice_type ?? ''),
|
||||
fecha_pedimento_del: '',
|
||||
fecha_pedimento_al: '',
|
||||
clave_pedimento: '',
|
||||
regimen_pedimento: ''
|
||||
};
|
||||
}
|
||||
|
||||
function mapInvoiceToGeneral(invoice: any) {
|
||||
if (!invoice) return generalSkeleton;
|
||||
|
||||
return {
|
||||
provider_header: invoice.compliance_mx?.provider_header || 'proveedor',
|
||||
provider_id: invoice.compliance_mx?.provider_id || null,
|
||||
sold_to_header: invoice.compliance_mx?.sold_to_header || 'consignado_a',
|
||||
sold_to_id: invoice.compliance_mx?.sold_to_id || null,
|
||||
shipped_to_header: invoice.compliance_mx?.shipped_to_header || 'enviado_a',
|
||||
shipped_to_id: invoice.compliance_mx?.shipped_to_id || null,
|
||||
customs_broker_id: invoice.compliance_mx?.customs_broker_id || null,
|
||||
customs_broker_us_id: invoice.compliance_mx?.customs_broker_us_id || null,
|
||||
currency_type: invoice.financials?.currency_type || '',
|
||||
currency: invoice.financials?.currency || 'foreign',
|
||||
exchange_rate: invoice.financials?.exchange_rate || null,
|
||||
weight_type: 'kgs',
|
||||
iva_factor: invoice.financials?.iva_factor || null,
|
||||
carrier_id: invoice.logistics?.carrier_id || null,
|
||||
transport_id: invoice.logistics?.transport_id || '',
|
||||
driver_name: invoice.logistics?.driver_name || '',
|
||||
transport_type: invoice.logistics?.transport_type || '',
|
||||
transport_num: invoice.logistics?.vehicle_num || '',
|
||||
aduana: invoice.compliance_mx?.aduana || '',
|
||||
document_type: invoice.document_type || ''
|
||||
};
|
||||
}
|
||||
|
||||
function mapInvoiceToObservations(invoice: any) {
|
||||
if (!invoice) return observationSkeleton;
|
||||
|
||||
return {
|
||||
observation_es: invoice.observation_es || '',
|
||||
observation_en: invoice.observation_en || '',
|
||||
freight: invoice.financials?.freight || null,
|
||||
insurance_value: invoice.financials?.insurance_value || null,
|
||||
insurance: invoice.financials?.insurance || null,
|
||||
packaging: invoice.financials?.packaging || null,
|
||||
other_increments: invoice.financials?.other_increments || null,
|
||||
total_increments_mn: invoice.financials?.total_increments_mn || null,
|
||||
total_increments_me: invoice.financials?.total_increments_me || null,
|
||||
incoterm: invoice.logistics?.incoterm || null,
|
||||
enclosure: invoice.compliance_mx?.enclosure || null,
|
||||
num_seals: null,
|
||||
movement_type: invoice.compliance_mx?.movement_type || '',
|
||||
alternate_invoice: invoice.alternate_invoice || '',
|
||||
valuation_method: invoice.compliance_mx?.value_method || null
|
||||
};
|
||||
}
|
||||
|
||||
function mapInvoiceToItems(invoice: any) {
|
||||
if (!invoice) return ensureItemsFormData(null);
|
||||
|
||||
return {
|
||||
items: invoice.items || []
|
||||
};
|
||||
}
|
||||
|
||||
function mapInvoiceToOthers(invoice: any) {
|
||||
if (!invoice) return othersSkeleton;
|
||||
|
||||
return {
|
||||
comments_status: invoice.comments_status || '',
|
||||
transport_mode: invoice.logistics?.transport_mode || 'TRUCK',
|
||||
is_mixed: invoice.compliance_mx?.is_mixed || false,
|
||||
print_stamp: invoice.print_stamp || false,
|
||||
rule_3121_parties_ii: invoice.compliance_mx?.rule_3121_parties_ii || false,
|
||||
related_doc_id: invoice.related_doc_id || null,
|
||||
code_signature: invoice.compliance_mx?.code_signature || '',
|
||||
electronic_signature: invoice.compliance_mx?.electronic_signature || '',
|
||||
mandatory_person: invoice.compliance_mx?.mandatory_person || '',
|
||||
contingency_mode: invoice.compliance_mx?.contingency_mode || false,
|
||||
cove: invoice.compliance_mx?.cove || '',
|
||||
operation_num: invoice.compliance_mx?.operation_num || '',
|
||||
adendas: invoice.compliance_mx?.adendas || '',
|
||||
observations_vu: invoice.compliance_mx?.observations_vu || '',
|
||||
certified_number: invoice.compliance_mx?.certified_number || '',
|
||||
bill_number: invoice.logistics?.bill_number || '',
|
||||
guide_number: invoice.logistics?.guide_number || '',
|
||||
shipment_number: invoice.logistics?.shipment_number || '',
|
||||
option_iv18: invoice.compliance_mx?.option_iv18 || '',
|
||||
delivered_status: invoice.delivered_status || false,
|
||||
received_by: invoice.received_by || '',
|
||||
delivery_date: invoice.delivery_date || ''
|
||||
};
|
||||
}
|
||||
|
||||
function mapInvoiceToContinuation(invoice: any) {
|
||||
if (!invoice) return continuationSkeleton;
|
||||
|
||||
return {
|
||||
numero_tipo_transporte: invoice.logistics?.numero_tipo_transporte || '',
|
||||
es_ferrocarril: invoice.logistics?.es_ferrocarril || 'no',
|
||||
numero_bl: invoice.logistics?.numero_bl || '',
|
||||
cantidad_guias_embarque: invoice.logistics?.cantidad_guias_embarque || null,
|
||||
destino_origen: invoice.logistics?.destino_origen || '',
|
||||
puerto_entrada: invoice.logistics?.puerto_entrada || '',
|
||||
vehicle_data: invoice.logistics?.vehicle_data || '',
|
||||
fue_revisado_equipo: invoice.logistics?.fue_revisado_equipo || false,
|
||||
sub_division: invoice.compliance_mx?.subdivision || false,
|
||||
funge_como_cd: invoice.logistics?.acts_as_cd || false,
|
||||
llego_pedimento: invoice.compliance_mx?.llego_pedimento || false,
|
||||
errores_facturacion: invoice.errores_facturacion || [],
|
||||
semaforo_verde_aduana_mexicana: invoice.compliance_mx?.semaforo_verde_aduana_mexicana || false,
|
||||
semaforo_verde_aduana_americana: invoice.compliance_mx?.semaforo_verde_aduana_americana || false,
|
||||
semaforo_rojo_aduana_mexicana: invoice.compliance_mx?.semaforo_rojo_aduana_mexicana || false,
|
||||
semaforo_rojo_aduana_americana: invoice.compliance_mx?.semaforo_rojo_aduana_americana || false,
|
||||
is_mixed: invoice.compliance_mx?.is_mixed || false,
|
||||
reason_export: invoice.compliance_mx?.reason_export || '1',
|
||||
purchase_order: invoice.purchase_order || '',
|
||||
payment_terms: invoice.payment_terms || '',
|
||||
handling_fees: invoice.financials?.handling_fees || 0,
|
||||
cfdi_uuid: invoice.cfdi_uuid || '',
|
||||
path_pdf: invoice.path_pdf || '',
|
||||
path_xml: invoice.path_xml || ''
|
||||
};
|
||||
}
|
||||
|
||||
// Referencias a los componentes de formulario para obtener sus datos
|
||||
// Si estamos en modo edición (!data.isCreate) y tenemos una factura, usarla
|
||||
// Si estamos en modo creación, usar defaultSettings
|
||||
let InvoiceTopFieldsFormData = $state<any>(
|
||||
mergeDefaults(topFieldsSkeleton, data.defaultSettings?.InvoiceTopFieldsFormData)
|
||||
!data.isCreate && data.invoice
|
||||
? mapInvoiceToTopFields(data.invoice)
|
||||
: mergeDefaults(topFieldsSkeleton, data.defaultSettings?.InvoiceTopFieldsFormData)
|
||||
);
|
||||
let generalFormData = $state<any>(
|
||||
mergeDefaults(generalSkeleton, data.defaultSettings?.generalFormData)
|
||||
!data.isCreate && data.invoice
|
||||
? mapInvoiceToGeneral(data.invoice)
|
||||
: mergeDefaults(generalSkeleton, data.defaultSettings?.generalFormData)
|
||||
);
|
||||
let observationFormData = $state<any>(
|
||||
mergeDefaults(observationSkeleton, data.defaultSettings?.observationFormData)
|
||||
!data.isCreate && data.invoice
|
||||
? mapInvoiceToObservations(data.invoice)
|
||||
: mergeDefaults(observationSkeleton, data.defaultSettings?.observationFormData)
|
||||
);
|
||||
let itemsFormData = $state<any>(
|
||||
!data.isCreate && data.invoice
|
||||
? mapInvoiceToItems(data.invoice)
|
||||
: ensureItemsFormData(data.defaultSettings?.itemsFormData)
|
||||
);
|
||||
let itemsFormData = $state<any>(ensureItemsFormData(data.defaultSettings?.itemsFormData));
|
||||
let othersFormData = $state<any>(
|
||||
mergeDefaults(othersSkeleton, data.defaultSettings?.othersFormData)
|
||||
!data.isCreate && data.invoice
|
||||
? mapInvoiceToOthers(data.invoice)
|
||||
: mergeDefaults(othersSkeleton, data.defaultSettings?.othersFormData)
|
||||
);
|
||||
let continuationFormData = $state<any>(
|
||||
mergeDefaults(continuationSkeleton, data.defaultSettings?.continuationFormData)
|
||||
!data.isCreate && data.invoice
|
||||
? mapInvoiceToContinuation(data.invoice)
|
||||
: mergeDefaults(continuationSkeleton, data.defaultSettings?.continuationFormData)
|
||||
);
|
||||
|
||||
// Estados para saber si existen datos previos
|
||||
let observationExists = $state(!!data.defaultSettings?.observationFormData);
|
||||
let itemsExists = $state(!!data.defaultSettings?.itemsFormData?.items?.length);
|
||||
let othersExists = $state(!!data.defaultSettings?.othersFormData);
|
||||
let continuationExists = $state(!!data.defaultSettings?.continuationFormData);
|
||||
let observationExists = $state(
|
||||
!data.isCreate ? !!data.invoice : !!data.defaultSettings?.observationFormData
|
||||
);
|
||||
let itemsExists = $state(
|
||||
!data.isCreate
|
||||
? !!(data.invoice?.items && data.invoice.items.length > 0)
|
||||
: !!data.defaultSettings?.itemsFormData?.items?.length
|
||||
);
|
||||
let othersExists = $state(
|
||||
!data.isCreate ? !!data.invoice : !!data.defaultSettings?.othersFormData
|
||||
);
|
||||
let continuationExists = $state(
|
||||
!data.isCreate ? !!data.invoice : !!data.defaultSettings?.continuationFormData
|
||||
);
|
||||
|
||||
let calculatedExchangeRate = $state<number | null>(
|
||||
data.invoice?.financials?.exchange_rate ?? null
|
||||
@@ -314,8 +484,7 @@
|
||||
const actualResponse = response as any;
|
||||
const items = actualResponse.data?.items || [];
|
||||
|
||||
if (items.length === 0) {
|
||||
console.log('No exchange rate found for', date);
|
||||
if (items.length === 0) {
|
||||
if (!uiStore.isExchangeRateDialogOpen) {
|
||||
missingExchangeRateDate = date;
|
||||
showExchangeRateDialog = true;
|
||||
|
||||
@@ -496,7 +496,7 @@
|
||||
onclick={() => handleDeleteItem(i)}
|
||||
class="h-8 w-8 text-zinc-500 hover:text-destructive"
|
||||
>
|
||||
<Trash2 class="h-3.5 h-3.5" />
|
||||
<Trash2 class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
|
||||
@@ -24,7 +24,7 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
|
||||
// Usar authenticatedFetch para manejar automáticamente el refresh de tokens
|
||||
const response = await authenticatedFetch(
|
||||
`v1/public/reference_data/incoterms?page=${page}&page_size=${pageSize}`,
|
||||
`v1/public/reference_data/incoterms/?page=${page}&page_size=${pageSize}`,
|
||||
{},
|
||||
cookies,
|
||||
fetch
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import { createColumns } from '$lib/components/dashboard/reference_data/incoterms/columns.js';
|
||||
import { createColumns } from '$lib/components/dashboard/reference_data/incoterms/columns';
|
||||
import DataTable from '$lib/components/dashboard/reference_data/incoterms/data-table.svelte';
|
||||
import CreateEditDialog from '$lib/components/dashboard/reference_data/incoterms/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
@@ -59,11 +59,7 @@
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo de Incoterms
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={() => createDialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Incoterm
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { Dialog } from "bits-ui";
|
||||
console.log("Dialog is:", Dialog);
|
||||
try {
|
||||
console.log("Dialog.Root is:", Dialog.Root);
|
||||
} catch (e) {
|
||||
console.log("Error accessing Dialog.Root:", e.message);
|
||||
}
|
||||
Reference in New Issue
Block a user