Se termino el modulo de reportes
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -103,10 +103,10 @@
|
||||
<table class="main-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 12%;">No. Pedimento<br>Clave Fecha de Pago</th>
|
||||
<th style="width: 12%;">No. Pedimento<br>Fecha de Pago</th>
|
||||
<th style="width: 10%;">Import Invoice/<br>Factura de Impo.</th>
|
||||
<th style="width: 25%;">Part Number/No. de Parte Componente<br>Description/Descripción
|
||||
(Origen-Prefer.-Sector)</th>
|
||||
<th style="width: 15%; border-right: none;">Part Number/No. de Parte<br>Description/Descripción</th>
|
||||
<th style="width: 10%; border-left: none;">Fracción<br>(Origen-Prefer.-Sector)</th>
|
||||
<th style="width: 8%;">Quantity/<br>Cantidad U.M.</th>
|
||||
<th style="width: 8%;">Net Weight/<br>Peso Neto (KGS)</th>
|
||||
<th style="width: 8%;">Value/Valor M.N.<br>MXP/Pesos</th>
|
||||
@@ -117,23 +117,28 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Sub Header -->
|
||||
<tr>
|
||||
<td colspan="10" class="sub-header">Comp. Temporales:</td>
|
||||
</tr>
|
||||
|
||||
<!-- Loops Items -->
|
||||
{% for item in items %}
|
||||
<tr class="{{ 'row-border-bottom' if loop.last else '' }}">
|
||||
<td>
|
||||
<b>{{ item.pedimento_numero }}</b><br>
|
||||
{{ item.pedimento_clave }} {{ item.pedimento_fecha_pago }}
|
||||
<div style="font-weight: bold;">{{ item.pedimento_numero }}</div>
|
||||
<table style="width: 100%; border: none; margin-top: 2px; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style="border: none; padding: 0; font-size: 6.5pt; text-align: left; width: 30%;">{{
|
||||
item.pedimento_clave }}</td>
|
||||
<td style="border: none; padding: 0; font-size: 6.5pt; text-align: right; width: 70%;">{{
|
||||
item.pedimento_fecha_pago }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td>{{ item.factura_impo }}</td>
|
||||
<td>
|
||||
<td style="border-right: none;">
|
||||
<b>{{ item.numero_parte }}</b><br>
|
||||
{{ item.descripcion }}<br>
|
||||
<span style="font-size: 6pt;">{{ item.fraccion }}<br>{{ item.origen_pref_sector }}</span>
|
||||
{{ item.descripcion }}
|
||||
</td>
|
||||
<td style="font-size: 6pt; border-left: none;">
|
||||
{{ item.fraccion }}<br>
|
||||
{{ item.origen_pref_sector }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ item.cantidad }} {{ item.unidad_medida }}
|
||||
@@ -147,28 +152,28 @@
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Totals -->
|
||||
<!-- Sub Totals -->
|
||||
<tr class="totals-row">
|
||||
<td colspan="3" class="text-center">Totales de los Comp. Temporales:</td>
|
||||
<td>{{ total_cantidad }}</td>
|
||||
<td>{{ total_peso }}</td>
|
||||
<td>{{ total_valor_mn }}</td>
|
||||
<td>{{ total_valor_me }}</td>
|
||||
<td>{{ total_igi }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td colspan="4" class="text-center">Totales de los Comp. Temporales:</td>
|
||||
<td class="text-right">{{ total_cantidad }}</td>
|
||||
<td class="text-right">{{ total_peso }}</td>
|
||||
<td class="text-right">{{ total_valor_mn }}</td>
|
||||
<td class="text-right">{{ total_valor_me }}</td>
|
||||
<td class="text-right">{{ total_igi }}</td>
|
||||
<td class="text-center"></td>
|
||||
<td class="text-center"></td>
|
||||
</tr>
|
||||
|
||||
<!-- Grand Totals (Same as above for this example) -->
|
||||
<!-- Grand Totals -->
|
||||
<tr class="totals-row" style="border-top: 2px solid black;">
|
||||
<td colspan="3" class="text-center" style="font-size: 9pt;">TOTALES:</td>
|
||||
<td>{{ total_cantidad }}</td>
|
||||
<td>{{ total_peso }}</td>
|
||||
<td>{{ total_valor_mn }}</td>
|
||||
<td>{{ total_valor_me }}</td>
|
||||
<td>{{ total_igi }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td colspan="4" class="text-center" style="font-size: 9pt;">TOTALES:</td>
|
||||
<td class="text-right">{{ total_cantidad }}</td>
|
||||
<td class="text-right">{{ total_peso }}</td>
|
||||
<td class="text-right">{{ total_valor_mn }}</td>
|
||||
<td class="text-right">{{ total_valor_me }}</td>
|
||||
<td class="text-right">{{ total_igi }}</td>
|
||||
<td class="text-center"></td>
|
||||
<td class="text-center"></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
|
||||
@@ -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
|
||||
|
||||
35
frontend/src/lib/api/dashboard/a24/inv.ts
Normal file
35
frontend/src/lib/api/dashboard/a24/inv.ts
Normal file
@@ -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'
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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' };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user