diff --git a/backend/api/v1/modules/a76/factura_cove/routes.py b/backend/api/v1/modules/a76/factura_cove/routes.py index 341e90b6..9c20bb3a 100644 --- a/backend/api/v1/modules/a76/factura_cove/routes.py +++ b/backend/api/v1/modules/a76/factura_cove/routes.py @@ -59,11 +59,8 @@ def trigger_cove_for_invoice( task=factura_cove_generate, tenant_id=tenant_id, company_id=body.company_id, - requested_by_user=( - current_user.get("preferred_username") - or current_user.get("email") - or current_user.get("sub") - ), + # Hardcodeado a petición: siempre registrar esta tarea con el correo de Hugo Reyes. + requested_by_user="hreyes@aduanasoft.com.mx", task_name="factura_cove_generate", task_group="factura_cove", task_origin="a76/factura_cove/invoices/cove", diff --git a/backend/api/v1/modules/a76/factura_cove/tasks.py b/backend/api/v1/modules/a76/factura_cove/tasks.py index c4b95130..c0f2b4c5 100644 --- a/backend/api/v1/modules/a76/factura_cove/tasks.py +++ b/backend/api/v1/modules/a76/factura_cove/tasks.py @@ -9,6 +9,7 @@ from celery import Task from core.celery_app import celery_app from core.database import CoreSessionLocal from core.exceptions import ValidationException +from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceComplianceMx from .service import FacturaCoveDomainService from .schemas import GenerateCoveResult @@ -21,6 +22,50 @@ def _progress(task: Task, current: int, status: str) -> None: task.update_state(state="PROGRESS", meta={"current": current, "status": status}) +def _save_cove_result( + db: "Session", invoice_id: int, final_external: CoveExternalResult +) -> None: + """ + Persiste en la factura el número de COVE y el número de operación VUCEM + cuando el servicio externo reporta SUCCESS. + """ + if final_external.status != "success": + return + + if not final_external.cove_number and not final_external.vucem_operation_num: + return + + invoice = db.get(InvoiceHeader, invoice_id) + if not invoice: + logger.error("No se encontró la factura %s para guardar COVE", invoice_id) + return + + compliance = invoice.compliance_mx + if not compliance: + # Creamos un registro mínimo de compliance ligado a la factura. + compliance = InvoiceComplianceMx( + invoice_id=invoice.id, + tenant_id=invoice.tenant_id, + company_id=invoice.company_id, + ) + db.add(compliance) + + # Idempotencia básica: solo sobrescribir si está vacío o coincide. + if final_external.cove_number: + current_cove = compliance.edocument or "" + new_cove = final_external.cove_number or "" + if not current_cove or current_cove == new_cove: + compliance.edocument = new_cove + + if final_external.vucem_operation_num: + current_op = compliance.vucem_operation_num or "" + new_op = final_external.vucem_operation_num or "" + if not current_op or current_op == new_op: + compliance.vucem_operation_num = new_op + + db.commit() + + def _poll_external_status( task: Task, external: CoveExternalService, external_task_id: str, timeout_seconds: int = 300 ) -> CoveExternalResult: @@ -171,6 +216,13 @@ def factura_cove_generate(self: Task, invoice_id: int, tenant_id: int, company_i # Fallback: usamos el resultado tal cual devolvió el endpoint de generación final_external = external_result + # Intentar persistir COVE / número de operación en la factura cuando sea éxito. + try: + _save_cove_result(db, invoice_id, final_external) + except Exception: + # No fallamos la tarea por errores de persistencia; solo los registramos. + logger.exception("Error guardando COVE en la factura %s", invoice_id) + _progress(self, 100, "Proceso de COVE finalizado.") result = GenerateCoveResult( diff --git a/frontend/src/routes/dashboard/invoices/+page.svelte b/frontend/src/routes/dashboard/invoices/+page.svelte index 85d94657..389a2874 100644 --- a/frontend/src/routes/dashboard/invoices/+page.svelte +++ b/frontend/src/routes/dashboard/invoices/+page.svelte @@ -217,6 +217,17 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Submenú contextual para Interface VU (click derecho) + let showVuSubmenu = $state(false); + let vuSubmenuPosition = $state({ x: 0, y: 0 }); + + // Si se pierde la selección, cerramos el submenú contextual VU + $effect(() => { + if (!hasSelection) { + showVuSubmenu = false; + } + }); + // Estado para selección de filas (múltiple) let selectedInvoiceIds = $state([]); // Estado para los diálogos de acciones @@ -1129,6 +1140,65 @@ + {#if showVuSubmenu} + +
(showVuSubmenu = false)} + on:contextmenu|preventDefault={() => (showVuSubmenu = false)} + /> + +
+ + + + +
+ {/if} +
- + { + if (!open) { + showVuSubmenu = false; + } + }} + > {#snippet child({ props })}