From dbf4b636759c95d69967b147211ce92cf5ad71df Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Thu, 5 Feb 2026 08:22:59 -0600 Subject: [PATCH] Se termino el modulo de reportes --- backend/api/v1/modules/a24/router.py | 1 + .../a76/items/line_quantities/models.py | 4 +- backend/api/v1/modules/a76/items/models.py | 2 + .../reports/exportacion/descargo/routes.py | 27 +- .../reports/exportacion/descargo/service.py | 309 ++-- .../a76/reports/exportacion/descargo/task.py | 2 +- .../descargo/templates/descarga.html | 67 +- backend/api/v1/router.py | 5 +- frontend/src/lib/api/dashboard/a24/inv.ts | 35 + .../dashboard/a76/reports/reports-descargo.ts | 25 + .../routes/dashboard/invoices/+page.svelte | 1280 +++++++++-------- 11 files changed, 992 insertions(+), 765 deletions(-) create mode 100644 frontend/src/lib/api/dashboard/a24/inv.ts diff --git a/backend/api/v1/modules/a24/router.py b/backend/api/v1/modules/a24/router.py index c6657bac..06a92dcc 100644 --- a/backend/api/v1/modules/a24/router.py +++ b/backend/api/v1/modules/a24/router.py @@ -8,6 +8,7 @@ from fastapi import APIRouter from .fa.fa_classes.routes import router as fa_classes_router from .fa.fa_item_lines.routes import router as fa_item_lines_router + # Router principal de A24 router = APIRouter() diff --git a/backend/api/v1/modules/a76/items/line_quantities/models.py b/backend/api/v1/modules/a76/items/line_quantities/models.py index 21baa88d..0e738d03 100644 --- a/backend/api/v1/modules/a76/items/line_quantities/models.py +++ b/backend/api/v1/modules/a76/items/line_quantities/models.py @@ -38,9 +38,9 @@ class LineQuantity(Base): gross_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # PESOBRUTO # Packaging - package_key: Mapped[Optional[str]] = mapped_column(String(5)) # CLAVEBULTOS + package_id: Mapped[Optional[int]] = mapped_column(Integer) # CLAVEBULTOS (Originally package_key) package_quantity: Mapped[Optional[int]] = mapped_column(Integer) # CANTBULTOS - package_description: Mapped[Optional[str]] = mapped_column(String(40)) # DESCBULTOS + # package_description removed as not in DB container_quantity: Mapped[Optional[int]] = mapped_column(SmallInteger) # CANTBULCONT container_description: Mapped[Optional[str]] = mapped_column(String(40)) # DESCCONTENEDOR box_count: Mapped[Optional[str]] = mapped_column(String(30)) # NOCAJAS diff --git a/backend/api/v1/modules/a76/items/models.py b/backend/api/v1/modules/a76/items/models.py index 6cb67175..53e75cfd 100644 --- a/backend/api/v1/modules/a76/items/models.py +++ b/backend/api/v1/modules/a76/items/models.py @@ -52,6 +52,8 @@ class Item(Base, TenantScopedMixin, TimestampMixin): # Relationships (one-to-many) lines: Mapped[List["LineItem"]] = relationship( "LineItem", back_populates="item", cascade="all, delete-orphan") + + invoice: Mapped["InvoiceHeader"] = relationship("InvoiceHeader") # ============================================================================ # SUPPORTING TABLES diff --git a/backend/api/v1/modules/a76/reports/exportacion/descargo/routes.py b/backend/api/v1/modules/a76/reports/exportacion/descargo/routes.py index 5c8ee8bc..2a3878f3 100644 --- a/backend/api/v1/modules/a76/reports/exportacion/descargo/routes.py +++ b/backend/api/v1/modules/a76/reports/exportacion/descargo/routes.py @@ -6,11 +6,36 @@ from typing import Dict, Any from core.database import get_core_db as get_db from core.security import get_current_user -from .task import generate_descarga_pdf_task +from .service import FIFOAssignmentService +from .task import generate_descarga_pdf_task # FORCE RELOAD from celery.result import AsyncResult router = APIRouter() +@router.post("/fifo-assign/{invoice_id}") +def run_fifo_assignment( + invoice_id: int, + db: Session = Depends(get_db), + current_user: Any = Depends(get_current_user), +): + """ + Executes FIFO (PEPS) calculation for an Export Invoice. + Returns the calculated discharges in JSON format. + """ + service = FIFOAssignmentService() + try: + discharges = service.calculate_fifo(db, invoice_id) + return { + "invoice_id": invoice_id, + "total_discharges": len(discharges), + "discharges": discharges + } + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"Error calculating FIFO: {str(e)}" + ) + @router.post("/{invoice_id}/download-async") async def trigger_descarga_generation( invoice_id: int, diff --git a/backend/api/v1/modules/a76/reports/exportacion/descargo/service.py b/backend/api/v1/modules/a76/reports/exportacion/descargo/service.py index 9b004101..57a8d479 100644 --- a/backend/api/v1/modules/a76/reports/exportacion/descargo/service.py +++ b/backend/api/v1/modules/a76/reports/exportacion/descargo/service.py @@ -9,17 +9,159 @@ from fastapi import HTTPException from sqlalchemy.orm import Session from pydantic import BaseModel from decimal import Decimal +from datetime import datetime + # --- MODELOS (Imported from system for Header info) --- -from api.v1.modules.a76.invoices.models import InvoiceHeader +from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceComplianceMx from api.v1.modules.a76.general_catalogs.company.models import Company from api.v1.modules.a76.items.line_items.models import LineItem from api.v1.modules.a76.items.line_quantities.models import LineQuantity from api.v1.modules.a76.parts.models import Part from api.v1.modules.a76.items.models import Item -from api.v1.modules.a76.pedmientos.models import Pedimentos +from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos +from api.v1.modules.a76.pedmientos.models.pedimento_dates import PedimentoDates +from api.v1.modules.core.tenants.models import Tenant +from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate from sqlalchemy.orm import joinedload, load_only +# --- FIFO SERVICE (Internalized) --- +from api.v1.modules.a76.items.series.models import Serie + +class FIFOAssignmentService: + """ + Service for calculating FIFO (PEPS) assignments in real-time. + Does not persist to database, returns calculated discharge objects. + """ + + def calculate_fifo(self, db: Session, invoice_id: int) -> List[Dict[str, Any]]: + """ + Calculates the FIFO trail for all lines in an export invoice. + Returns a list of calculated discharges. + """ + # 1. Get Export Lines + export_lines = db.query(LineItem).join(Item).filter( + Item.invoice_id == invoice_id + ).options( + joinedload(LineItem.quantity), + joinedload(LineItem.description), + joinedload(LineItem.customs), + joinedload(LineItem.unit_of_measure_info), + joinedload(LineItem.part_info) + ).all() + + results = [] + self._log(f"Starting FIFO for Invoice {invoice_id}. Export Lines: {len(export_lines)}") + + for exp_line in export_lines: + qty_needed = float(exp_line.quantity.quantity) if exp_line.quantity and exp_line.quantity.quantity is not None else 0.0 + if qty_needed <= 0: + continue + + part_number = exp_line.part_number + if not part_number: + self._log(f"Skipping line {exp_line.id}, no part number") + continue + + self._log(f"Processing Exp Line {exp_line.id}, Part: {part_number}, Qty Needed: {qty_needed}") + + # --- SERIES EXPO LOOKUP --- + series_desc = "" + series_count = db.query(Serie).filter(Serie.line_item_id == exp_line.id).count() + if series_count > 0: + series_list = db.query(Serie).filter(Serie.line_item_id == exp_line.id).all() + parts_str = [] + for idx, s in enumerate(series_list, 1): + line_parts = [f"{idx}) Serie: {s.serial_numbers or ''}", f"Modelo: {s.model or ''}", f"Parte: {exp_line.part_info.part_number if exp_line.part_info else ''}", f"Num ID Expo: {s.number_id or ''}", f"SubModelo: {s.sub_model or ''}"] + parts_str.append(", ".join([p for p in line_parts if p])) + if parts_str: + series_desc = "\nSeries:\n" + "\n".join(parts_str) + + # 2. Find Import Candidates (FIFO order by payment date) + # Use outerjoin for pedimento dates to avoid filtering out candidates with missing dates + candidates = db.query(LineItem).join(Item).join(InvoiceHeader)\ + .join(InvoiceComplianceMx).join(InvoiceComplianceMx.pedimento).outerjoin(Pedimentos.pedimento_dates)\ + .filter( + LineItem.part_number == part_number, + InvoiceHeader.operation_type == 'imp', # Assuming 'imp' is the value for Import based on Enum + ).order_by( + PedimentoDates.payment_date.asc() + ).options( + joinedload(LineItem.quantity), + joinedload(LineItem.customs), + joinedload(LineItem.financial), + joinedload(LineItem.item).joinedload(Item.invoice).joinedload(InvoiceHeader.compliance_mx).joinedload(InvoiceComplianceMx.pedimento).joinedload(Pedimentos.pedimento_dates) + ).all() + + self._log(f"Found {len(candidates)} candidates for {part_number}") + + for imp_line in candidates: + if qty_needed <= 0: + break + + imp_qty_total = float(imp_line.quantity.quantity) if imp_line.quantity and imp_line.quantity.quantity is not None else 0.0 + if imp_qty_total <= 0: + continue + + take = min(qty_needed, imp_qty_total) + ratio = take / imp_qty_total if imp_qty_total > 0 else 0 + + # Calculate Proportions + imp_weight = float(imp_line.quantity.net_weight) if imp_line.quantity and imp_line.quantity.net_weight is not None else 0.0 + # imp_val_mn = float(imp_line.customs.customs_value) if imp_line.customs and imp_line.customs.customs_value is not None else 0.0 + imp_val_me = float(imp_line.financial.customs_value_usd) if imp_line.financial and imp_line.financial.customs_value_usd is not None else 0.0 + imp_igi = float(imp_line.customs.igi_amount) if imp_line.customs and imp_line.customs.igi_amount is not None else 0.0 + + imp_inv = imp_line.item.invoice + ped = imp_inv.compliance_mx.pedimento if imp_inv and imp_inv.compliance_mx else None + + # --- EXCHANGE RATE VALIDATION --- + payment_date = ped.pedimento_dates.payment_date if ped and ped.pedimento_dates else None + exchange_rate_val = 1.0 + validation_error = None + + if payment_date: + er_obj = db.query(ExchangeRate).filter(ExchangeRate.date == payment_date).first() + if er_obj: + exchange_rate_val = float(er_obj.value) + else: + # Try previous day if strict match fails (mimicking SisGen:UtilizarFechaPagoPedDeUnDiaAnterior logic broadly or just flagging) + # For now, flag it. + validation_error = f"Tipo de Cambio no encontrado para fecha {payment_date}" + + # Calculate Valor MN based on Clarion logic: (CantDesc * ValorImpoME / CantImpo) * TC + # which simplifies to: Ratio * ValorImpoME * TC + val_mn_calc = (imp_val_me * ratio) * exchange_rate_val + + discharge = { + "export_line_id": exp_line.id, + "import_line_id": imp_line.id, + "quantity": take, + "net_weight": imp_weight * ratio, + "value_mxn": val_mn_calc, + "value_usd": imp_val_me * ratio, + "igi_amount": imp_igi * ratio, + "import_invoice": imp_inv.invoice_number if imp_inv else "N/A", + "pedimento": ped.pedimento_number if ped else "N/A", + "pedimento_clave": ped.pedimento_code if ped else "", + "pedimento_date": payment_date.isoformat() if payment_date else None, + "series_desc": series_desc, + "validation_error": validation_error + } + + self._log(f"MATCH: Taking {take} from Imp Line {imp_line.id}") + results.append(discharge) + qty_needed -= take + + return results + + def _log(self, msg): + try: + with open("/tmp/fifo_debug.log", "a") as f: + f.write(f"{datetime.now()}: {msg}\n") + except: pass + + # --- SCHEMAS FOR TEMPLATE CONTEXT --- class DischargeItemSchema(BaseModel): @@ -57,8 +199,10 @@ class DischargeItemSchema(BaseModel): # Let's assume Se Pago is a boolean/string. # Last col: "Linea Expo". - se_pago_val: str linea_expo: str + + # Errors + error_msg: Optional[str] = None # Helper for Jinja (if methods not allowed in pydantic models in template) def __init__(self, **data): @@ -104,115 +248,80 @@ class DescargaReportService: try: if progress_callback: progress_callback(10, "Buscando factura...") - # Fetch Header for basic info - header = db.query(InvoiceHeader).filter(InvoiceHeader.id == invoice_id, InvoiceHeader.company_id == company_id).first() + # --- 1. Obtener Cabeceras (Igual que antes) --- + header = db.query(InvoiceHeader).filter(InvoiceHeader.id == invoice_id).first() if not header: raise HTTPException(status_code=404, detail="Factura no encontrada") - company = db.query(Company).filter(Company.id == company_id).first() + tenant = db.query(Tenant).filter(Tenant.id == header.tenant_id).first() + company = db.query(Company).filter(Company.id == header.company_id).first() + + # --- 2. Obtener Líneas de Exportación (Lo que necesitamos cubrir) --- + if progress_callback: progress_callback(20, "Obteniendo items a exportar...") - if progress_callback: progress_callback(30, "Procesando descargas...") - - # --- REAL IMPLEMENTATION --- - # 1. Fetch Export Lines with FA Data export_lines = db.query(LineItem).filter( LineItem.item_id == Item.id, Item.invoice_id == invoice_id ).options( - joinedload(LineItem.fa_data), - joinedload(LineItem.quantity).load_only(LineQuantity.quantity, LineQuantity.net_weight), + joinedload(LineItem.quantity), joinedload(LineItem.customs), joinedload(LineItem.description), joinedload(LineItem.unit_of_measure_info), - joinedload(LineItem.part_info), # Fetch Part Relation - # joinedload(LineItem.item).joinedload(Item.invoice) # Removed due to missing relationship + joinedload(LineItem.part_info) ).join(Item).all() - # 2. Collect unique Import Invoices to bulk fetch - # fa_data.search_invoice stores the "FacturaImpo" number - import_inv_nums = set() - for line in export_lines: - if line.fa_data and line.fa_data.search_invoice: - import_inv_nums.add(line.fa_data.search_invoice) - - # Map InvoiceNumber -> (InvoiceHeader, Pedimento) - import_map = {} - if import_inv_nums: - # We need to find the invoices. Warning: search_invoice is just a string number. - # potentially non-unique across companies, but we filter by current Company. - imp_invoices = db.query(InvoiceHeader).filter( - InvoiceHeader.invoice_number.in_(import_inv_nums), - InvoiceHeader.company_id == company_id, - InvoiceHeader.invoice_type == 'Ingreso' # Assuming Imports are Ingreso/Import - ).options( - joinedload(InvoiceHeader.compliance_mx) - ).all() - - # Fetch Pedimentos for these invoices - ped_ids = {inv.compliance_mx.pedimento_id for inv in imp_invoices if inv.compliance_mx and inv.compliance_mx.pedimento_id} - peds = db.query(Pedimentos).filter(Pedimentos.id.in_(ped_ids)).all() - ped_map = {p.id: p for p in peds} - - for inv in imp_invoices: - ped = None - if inv.compliance_mx and inv.compliance_mx.pedimento_id: - ped = ped_map.get(inv.compliance_mx.pedimento_id) - import_map[inv.invoice_number] = (inv, ped) + items_reporte = [] - items = [] + # --- 3. EL ALGORITMO PEPS EN VIVO --- + if progress_callback: progress_callback(40, "Calculando PEPS en tiempo real...") + + fifo_service = FIFOAssignmentService() + discharges = fifo_service.calculate_fifo(db, invoice_id) + + print(f"DEBUG: Calculated {len(discharges)} discharges.") # FORCE PRINT - for line in export_lines: - # Defaults - ped_str = "" - ped_clave = "" - ped_fecha = "" - fac_impo = "" - se_pago = "" - valor_igi = 0.0 + # Create map for faster/safer lookup + exp_map = {l.id: l for l in export_lines} + + for d in discharges: + exp_id = d["export_line_id"] + exp_line = exp_map.get(exp_id) - # Linkage - if line.fa_data and line.fa_data.search_invoice: - fac_impo = line.fa_data.search_invoice - if fac_impo in import_map: - inv_imp, ped_imp = import_map[fac_impo] - - if ped_imp: - ped_str = f"{ped_imp.pedimento_number}" - ped_clave = f"{ped_imp.pedimento_code}" - # Format date if exists - # Simple date fallback from header if needed or Pedimento Date logic (revisit model if needed) - pass + if not exp_line: + print(f"DEBUG: Skipping discharge, Exp Line {exp_id} not found in map keys: {list(exp_map.keys())}") + continue - # Calculation logic (Prorate) - qty = float(line.quantity.quantity) if line.quantity else 0.0 + print(f"DEBUG: Adding item to report: Imp {d['import_line_id']} -> Exp {exp_id}") - valor_me = 0.0 - valor_mn = 0.0 - - # Create Schema - items.append(DischargeItemSchema( - pedimento_numero=ped_str, - pedimento_clave=ped_clave, - pedimento_fecha_pago=ped_fecha, - factura_impo=fac_impo, - numero_parte=line.part_info.part_number if hasattr(line, 'part_info') and line.part_info else (str(line.part_number) if line.part_number else "S/N"), - descripcion=line.description.description_spanish if line.description else "S/D", - fraccion=line.customs.fraction if line.customs else "", - origen_pref_sector=f"{line.customs.origin_country or ''} - {line.customs.sector or ''}" if line.customs else "", - cantidad=self.formatear_numero(qty, 3), - unidad_medida=line.unit_of_measure_info.code if line.unit_of_measure_info else "PZA", - peso_neto=self.formatear_numero(float(line.quantity.net_weight) if line.quantity else 0.0, 3), - valor_mn=self.formatear_numero(valor_mn), - valor_me=self.formatear_numero(valor_me), - valor_igi=self.formatear_numero(valor_igi), - se_pago=se_pago or "NO", - se_pago_val=se_pago, - linea_expo=str(line.line_number) + desc_final = exp_line.description.description_spanish if exp_line.description else "S/D" + items_reporte.append(DischargeItemSchema( + pedimento_numero=d["pedimento"], + pedimento_clave=d["pedimento_clave"], + pedimento_fecha_pago=d["pedimento_date"].split("T")[0] if d["pedimento_date"] else "", + + factura_impo=d["import_invoice"], + + numero_parte=exp_line.part_info.part_number if exp_line.part_info else "", + descripcion=desc_final, + fraccion=exp_line.customs.fraction if exp_line.customs else "", + origen_pref_sector=f"{exp_line.customs.origin_country or ''} - {exp_line.customs.sector or ''}" if exp_line.customs else "", + + cantidad=self.formatear_numero(d["quantity"], 3), + unidad_medida=exp_line.unit_of_measure_info.code if exp_line.unit_of_measure_info else "PZA", + + peso_neto=self.formatear_numero(d["net_weight"], 3), + valor_mn=self.formatear_numero(d["value_mxn"]), + valor_me=self.formatear_numero(d["value_usd"]), + valor_igi=self.formatear_numero(d["igi_amount"]), + + se_pago="", + linea_expo=str(exp_line.line_number), + error_msg=d.get("validation_error") )) - - # Totals - # Company Address Construction + print(f"DEBUG: Final Report Items Count: {len(items_reporte)}") + + # --- 4. Totales y Finalización (Igual que antes) --- addr_str = "DIRECCION NO REGISTRADA" immex_val = "" @@ -237,17 +346,17 @@ class DescargaReportService: if company.program and "IMMEX" in company.program and company.program_number: immex_val = company.program_number - # Calculate Totals - t_cant = sum(float(i.cantidad.replace(",","")) for i in items if i.cantidad) - t_peso = sum(float(i.peso_neto.replace(",","")) for i in items if i.peso_neto) - t_mn = sum(float(i.valor_mn.replace(",","")) for i in items if i.valor_mn) - t_me = sum(float(i.valor_me.replace(",","")) for i in items if i.valor_me) - t_igi = sum(float(i.valor_igi.replace(",","")) for i in items if i.valor_igi) + # Recalcular totales basados en la lista generada + t_cant = sum(float(i.cantidad.replace(",","")) for i in items_reporte) + t_peso = sum(float(i.peso_neto.replace(",","")) for i in items_reporte) + t_mn = sum(float(i.valor_mn.replace(",","")) for i in items_reporte) + t_me = sum(float(i.valor_me.replace(",","")) for i in items_reporte) + t_igi = sum(float(i.valor_igi.replace(",","")) for i in items_reporte) return DischargeContext( - items=items, + items=items_reporte, invoice_number=header.invoice_number or "SIN FOLIO", - company_name=company.name if company else "EMPRESA DESCONOCIDA", + company_name=company.name if company else (tenant.name if tenant else "EMPRESA DESCONOCIDA"), company_address=addr_str, company_rfc=company.rfc if company else "", company_immex=immex_val, diff --git a/backend/api/v1/modules/a76/reports/exportacion/descargo/task.py b/backend/api/v1/modules/a76/reports/exportacion/descargo/task.py index aa9d7b5e..3ed57fbb 100644 --- a/backend/api/v1/modules/a76/reports/exportacion/descargo/task.py +++ b/backend/api/v1/modules/a76/reports/exportacion/descargo/task.py @@ -2,7 +2,7 @@ from celery import shared_task from sqlalchemy.orm import Session from core.database import CoreSessionLocal as SessionLocal -from .service import DescargaReportService +from .service import DescargaReportService # FORCE RELOAD 2 import base64 import traceback diff --git a/backend/api/v1/modules/a76/reports/exportacion/descargo/templates/descarga.html b/backend/api/v1/modules/a76/reports/exportacion/descargo/templates/descarga.html index bb5b2c64..177847ba 100644 --- a/backend/api/v1/modules/a76/reports/exportacion/descargo/templates/descarga.html +++ b/backend/api/v1/modules/a76/reports/exportacion/descargo/templates/descarga.html @@ -103,10 +103,10 @@ - + - + + @@ -117,23 +117,28 @@ - - - - - {% for item in items %} - + {% endfor %} - + - - - - - - - - + + + + + + + + - + - - - - - - - - + + + + + + + + diff --git a/backend/api/v1/router.py b/backend/api/v1/router.py index d9b4d517..f893402c 100644 --- a/backend/api/v1/router.py +++ b/backend/api/v1/router.py @@ -1,6 +1,6 @@ """ Router principal de API v1 -Agrega todos los módulos de la aplicación +Agrega todos los módulos de la aplicación (Reload) """ from fastapi import APIRouter @@ -10,7 +10,6 @@ from .modules.core.router import router as core_router from .modules.a76.router import router as a76_router from .modules.a24.router import router as a24_router from .modules.public.router import router as public_router -from .modules.a24.router import router as a24_router # Router principal @@ -21,8 +20,6 @@ router.include_router(core_router) router.include_router(a76_router) router.include_router(a24_router) router.include_router(public_router) -# nuevas rutas de partes de anexo 24 -router.include_router(a24_router) # Health check diff --git a/frontend/src/lib/api/dashboard/a24/inv.ts b/frontend/src/lib/api/dashboard/a24/inv.ts new file mode 100644 index 00000000..cccdec43 --- /dev/null +++ b/frontend/src/lib/api/dashboard/a24/inv.ts @@ -0,0 +1,35 @@ + +import axios from 'axios'; +import { PUBLIC_API_URL } from '$env/static/public'; + +/** + * Cliente API para el módulo de Inventarios (A24) + */ +export const invApi = { + /** + * Ejecuta el proceso de asignación PEPS (FIFO) para una factura de exportación + * @param invoiceId ID de la factura de exportación + * @returns Promesa con la respuesta del servidor + */ + assignFifo: async (invoiceId: number) => { + try { + const token = localStorage.getItem('access_token'); + const response = await axios.post( + `${PUBLIC_API_URL}/api/v1/a76/reports/exportacion/descargo/fifo-assign/${invoiceId}`, + {}, + { + headers: { + Authorization: `Bearer ${token}` + } + } + ); + return { data: response.data, error: null }; + } catch (error: any) { + console.error('Error executing FIFO:', error); + return { + data: null, + error: error.response?.data?.detail || 'Error al ejecutar cálculo PEPS' + }; + } + } +}; diff --git a/frontend/src/lib/api/dashboard/a76/reports/reports-descargo.ts b/frontend/src/lib/api/dashboard/a76/reports/reports-descargo.ts index 493653fe..993e4ff9 100644 --- a/frontend/src/lib/api/dashboard/a76/reports/reports-descargo.ts +++ b/frontend/src/lib/api/dashboard/a76/reports/reports-descargo.ts @@ -32,5 +32,30 @@ export const dischargeReportsApi = { if (!response.ok) throw new Error('Error al consultar estado del Reporte de Descarga'); return await response.json(); + }, + + assignFifo: async (invoiceId: number) => { + // Endpoint: /a76/reports/exportacion/descargo/fifo-assign/{invoice_id} + const endpoint = `${BASE_URL}/v1/a76/reports/exportacion/descargo/fifo-assign/${invoiceId}`; + const token = localStorage.getItem('access_token'); + + try { + const response = await fetch(endpoint, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) { + const errData = await response.json().catch(() => ({})); + return { data: null, error: errData.detail || 'Error al ejecutar PEPS' }; + } + + return { data: await response.json(), error: null }; + } catch (e: any) { + return { data: null, error: e.message || 'Error de conexión PEPS' }; + } } }; diff --git a/frontend/src/routes/dashboard/invoices/+page.svelte b/frontend/src/routes/dashboard/invoices/+page.svelte index 63e76f31..847b83da 100644 --- a/frontend/src/routes/dashboard/invoices/+page.svelte +++ b/frontend/src/routes/dashboard/invoices/+page.svelte @@ -1,702 +1,730 @@
-
-
-

Facturas

-

- Gestiona las facturas del sistema -

-
- -
+
+
+

Facturas

+

Gestiona las facturas del sistema

+
+ +
- - - Filtros - Filtra las facturas por diferentes criterios (los filtros se aplican automáticamente) - - -
-
- - -
+ + + Filtros + Filtra las facturas por diferentes criterios (los filtros se aplican automáticamente) + + +
+
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
-
- - -
-
-
-
+
+ + +
+
+
+
- {#if error} - - - Error - {error} - - - {/if} + {#if error} + + + Error + {error} + + + {/if} - - -
-
- Listado de Facturas - - Mostrando {allItems.length} de {totalItems} registros - -
- -
-
- - - -
+ + +
+
+ Listado de Facturas + + Mostrando {allItems.length} de {totalItems} registros + +
+ +
+
+ + + +
+ - + +
+
+ +
+ + + + + +
+
+
- -
-
- -
- - - - - -
-
-
+ - - - - - {#if selectedInvoice && companyStore.activeCompany} - - {/if} -
\ No newline at end of file + {#if selectedInvoice && companyStore.activeCompany} + + {/if} +
No. Pedimento
Clave Fecha de Pago
No. Pedimento
Fecha de Pago
Import Invoice/
Factura de Impo.
Part Number/No. de Parte Componente
Description/Descripción - (Origen-Prefer.-Sector)
Part Number/No. de Parte
Description/Descripción
Fracción
(Origen-Prefer.-Sector)
Quantity/
Cantidad U.M.
Net Weight/
Peso Neto (KGS)
Value/Valor M.N.
MXP/Pesos
Comp. Temporales:
- {{ item.pedimento_numero }}
- {{ item.pedimento_clave }}    {{ item.pedimento_fecha_pago }} +
{{ item.pedimento_numero }}
+ + + + + +
{{ + item.pedimento_clave }}{{ + item.pedimento_fecha_pago }}
{{ item.factura_impo }} + {{ item.numero_parte }}
- {{ item.descripcion }}
- {{ item.fraccion }}
{{ item.origen_pref_sector }}
+ {{ item.descripcion }} +
+ {{ item.fraccion }}
+ {{ item.origen_pref_sector }}
{{ item.cantidad }} {{ item.unidad_medida }} @@ -147,28 +152,28 @@
Totales de los Comp. Temporales:{{ total_cantidad }}{{ total_peso }}{{ total_valor_mn }}{{ total_valor_me }}{{ total_igi }}Totales de los Comp. Temporales:{{ total_cantidad }}{{ total_peso }}{{ total_valor_mn }}{{ total_valor_me }}{{ total_igi }}
TOTALES:{{ total_cantidad }}{{ total_peso }}{{ total_valor_mn }}{{ total_valor_me }}{{ total_igi }}TOTALES:{{ total_cantidad }}{{ total_peso }}{{ total_valor_mn }}{{ total_valor_me }}{{ total_igi }}