This commit is contained in:
David Arranz 2026-03-20 19:34:21 +01:00
parent a032383be3
commit a8e4f76df2
3 changed files with 184 additions and 68 deletions

View File

@ -7,6 +7,7 @@ from app.utils import (
create_invoice_header,
insert_invoice_REST_API_FACTUGES,
insert_item_and_taxes,
validar_nif_verifacti,
)
from . import normalizations as NORMALIZA
@ -58,10 +59,15 @@ def sync_invoices_from_FACTUGES(conn_mysql, filas, conn_factuges, config):
cursorMySQL = None
cursor_FactuGES = None
factuges_id_anterior = None
tin_anterior = None
num_fac_procesed = 0
customer_valid = True
customer_valid = False
invoice_id: str | None = None
factura_payload: dict | None = None
sync_result: str | None = None
sync_notes: str | None = None
sync_result_anterior: str | None = None
sync_notes_anterior: str | None = None
try:
cursorMySQL = conn_mysql.cursor()
@ -79,27 +85,52 @@ def sync_invoices_from_FACTUGES(conn_mysql, filas, conn_factuges, config):
)
factuges_id = int(facturas["ID"])
if factuges_id_anterior is None or factuges_id_anterior != factuges_id:
# Validamos que el cif de la factura exista en la AEAT si no es así no se hace la sincro de la factura
logger.info(">>>PASO")
logger.info(str(factuges_id_anterior))
logger.info(str(factuges_id))
# --- Si cambia la factura, primero persistimos la anterior
if factuges_id_anterior is not None and factuges_id_anterior != factuges_id:
if factura_payload is not None:
logger.info(">>>> Se cambia de factura y vamos a insertar la anterior")
resultado_rest = insert_invoice_REST_API_FACTUGES(factura_payload)
logger.info(resultado_rest)
num_fac_procesed += 1
if not resultado_rest.get("ok"):
logger.info(resultado_rest.get("error"))
else:
data = resultado_rest.get("data") or {}
invoice_id = data.get("proforma_id")
logger.info("Updating FACTURAS_CLIENTE %s %s %s",
sync_result_anterior, invoice_id, factuges_id_anterior,)
cursor_FactuGES.execute(
SQL.UPDATE_FACTUGES_LINK,
(
sync_result_anterior,
invoice_id,
sync_notes_anterior,
factuges_id_anterior,
),
)
factura_payload = None
# --- Si no hay payload, estamos arrancando una nueva factura
if factura_payload is None:
sync_result = int(config["CTE_SYNC_RESULT_OK"])
sync_notes = None
customer_valid = True # validar_nif_verifacti(
# customer_fields["tin"], customer_fields["name"], config
# )
if customer_valid:
# Validamos que el cif de la factura exista en la AEAT si no es así no se hace la sincro de la factura
if tin_anterior is None or tin_anterior != customer_fields["tin"]:
logger.info("VALIDO NIF>>>>>>>>>>>")
customer_valid = validar_nif_verifacti(
customer_fields["tin"], customer_fields["name"], config)
tin_anterior = customer_fields["tin"]
# ---- cabecera factura
factura_payload = create_invoice_header(
customer_fields, header_invoice_fields, facturas["ID_FORMA_PAGO"], str(facturas["DES_FORMA_PAGO"]))
# ---- detalles factura
factura_payload["items"].append(insert_item_and_taxes(details_invoice_fields))
logger.info(f"PAYLOAD {factura_payload}")
# InsertamosREST
Resultado_REST = insert_invoice_REST_API_FACTUGES(factura_payload)
logger.info(Resultado_REST)
else:
if not customer_valid:
sync_result = int(config["CTE_SYNC_RESULT_FAIL"])
sync_notes = (
f">>> Factura {header_invoice_fields['reference']} no cumple requisitos para ser mandada a Verifactu: "
@ -107,21 +138,50 @@ def sync_invoices_from_FACTUGES(conn_mysql, filas, conn_factuges, config):
f"El NIF/CIF debe estar registrado en la AEAT y el nombre debe ser suficientemente parecido al nombre registrado en la AEAT"
)
logger.info(sync_notes)
cursor_FactuGES.execute(SQL.UPDATE_FACTUGES_LINK, (sync_result,
invoice_id, sync_notes, factuges_id),)
continue
num_fac_procesed += 1
# ---- cabecera factura
logger.info("Creo cabecera>>>>>>")
factura_payload = create_invoice_header(
customer_fields, header_invoice_fields, facturas["ID_FORMA_PAGO"], str(facturas["DES_FORMA_PAGO"]))
# Guardamos en Factuges el id de la customer_invoice
logger.info(
f"Updating FACTURAS_CLIENTE {sync_result} {invoice_id} {factuges_id} {sync_notes}: ")
# cursor_FactuGES.execute(
# SQL.UPDATE_FACTUGES_LINK,
# (sync_result, invoice_id, sync_notes, factuges_id),
# )
# Guardamos el estado asociado al payload actual
sync_result_anterior = sync_result
sync_notes_anterior = sync_notes
# Asignamos el id factura anterior para no volver a inserta cabecera
factuges_id_anterior = factuges_id
# ---- detalles factura
logger.info("Creo detalle>>>>")
factura_payload["items"].append(insert_item_and_taxes(details_invoice_fields))
# Asignamos el id factura anterior para no volver a inserta cabecera
factuges_id_anterior = factuges_id
logger.info(f"FACTURAS_CLIENTE rows to be processed: {str(num_fac_procesed)}")
# --- Insertar última factura pendiente
if factura_payload is not None:
logger.info(">>>> Última factura y vamos a insertar")
resultado_rest = insert_invoice_REST_API_FACTUGES(factura_payload)
logger.info(resultado_rest)
num_fac_procesed += 1
if not resultado_rest.get("ok"):
logger.info(resultado_rest.get("error"))
else:
data = resultado_rest.get("data") or {}
invoice_id = data.get("proforma_id")
cursor_FactuGES.execute(
SQL.UPDATE_FACTUGES_LINK,
(
sync_result_anterior,
invoice_id,
sync_notes_anterior,
factuges_id_anterior,
),
)
logger.info("FACTURAS_CLIENTE rows to be processed: %s", num_fac_procesed)
except Exception as e:
# Escribir el error en el archivo de errores
@ -136,6 +196,57 @@ def sync_invoices_from_FACTUGES(conn_mysql, filas, conn_factuges, config):
cursor_FactuGES.close()
'''
if factuges_id_anterior is not None and factuges_id_anterior != factuges_id:
logger.info(">>>>Se cambia de factura y vamos a insertar")
# InsertamosREST
Resultado_REST = insert_invoice_REST_API_FACTUGES(factura_payload)
logger.info(Resultado_REST)
num_fac_procesed += 1
if not Resultado_REST.get("ok"):
# Solo informamos en logger porque al cliente final este error no le interesa saldría la causa en notes de FactuGES antiguo
logger.info(Resultado_REST.get("error"))
else:
logger.info(Resultado_REST)
data = Resultado_REST.get("data")
invoice_id = data.get("proforma_id")
# Guardamos en Factuges el id de la customer_invoice
logger.info(
f"Updating FACTURAS_CLIENTE {sync_result} {invoice_id} {factuges_id} {sync_notes}: ")
cursor_FactuGES.execute(SQL.UPDATE_FACTUGES_LINK, (sync_result,
invoice_id, sync_notes, factuges_id),)
# Quitamos payload de la factura insertada
factura_payload = None
if factura_payload is not None:
logger.info(">>>>Ultima factura y vamos a insertar")
# InsertamosREST
Resultado_REST = insert_invoice_REST_API_FACTUGES(factura_payload)
logger.info(Resultado_REST)
num_fac_procesed += 1
if not Resultado_REST.get("ok"):
# Solo informamos en logger porque al cliente final este error no le interesa saldría la causa en notes de FactuGES antiguo
logger.info(Resultado_REST.get("error"))
else:
logger.info(Resultado_REST)
data = Resultado_REST.get("data")
invoice_id = data.get("proforma_id")
# Guardamos en Factuges el id de la customer_invoice
logger.info(
f"Updating FACTURAS_CLIENTE {sync_result} {invoice_id} {factuges_id} {sync_notes}: ")
cursor_FactuGES.execute(SQL.UPDATE_FACTUGES_LINK, (sync_result,
invoice_id, sync_notes, factuges_id),)
logger.info(f"FACTURAS_CLIENTE rows to be processed: {str(num_fac_procesed)}")
factura_payload = None
'''
def insert_verifactu_record(
cur: str, hif: Dict[str, Any], invoice_id: str, config) -> str:
"""

View File

@ -15,7 +15,7 @@ def create_invoice_header(
) -> Dict[str, Any]:
return {
"id": str(uuid7()),
"factuges_id": hif.get("factuges_id"),
"factuges_id": str(hif.get("factuges_id")),
"company_id": hif.get("company_id"),
"is_proforma": hif.get("is_proforma"),
"status": hif.get("status"),
@ -27,13 +27,13 @@ def create_invoice_header(
"notes": none_to_empty(hif.get("notes")),
"language_code": cf.get("language_code"),
"subtotal_amount_value": none_to_empty(hif.get("subtotal_amount_value")),
"global_discount_percentage": none_to_empty(hif.get("discount_percentage_val")),
"discount_amount_value": none_to_empty(hif.get("discount_amount_value")),
"taxable_amount_value": hif.get("taxable_amount_value"),
"taxes_amount_value": hif.get("taxes_amount_value"),
"total_amount_value": hif.get("total_amount_value"),
"payment_method_id": payment_method_id,
"subtotal_amount_value": str(none_to_empty(hif.get("subtotal_amount_value"))),
"global_discount_percentage": str(none_to_empty(hif.get("discount_percentage_val"))),
"discount_amount_value": str(none_to_empty(hif.get("discount_amount_value"))),
"taxable_amount_value": str(hif.get("taxable_amount_value")),
"taxes_amount_value": str(hif.get("taxes_amount_value")),
"total_amount_value": str(hif.get("total_amount_value")),
"payment_method_id": str(payment_method_id),
"payment_method_description": payment_method_description,
"customer": {
@ -52,7 +52,7 @@ def create_invoice_header(
"mobile_secondary": none_to_empty(cf["mobile_secondary"]),
"email_primary": none_to_empty(cf["email_primary"]),
"email_secondary": none_to_empty(cf["email_secondary"]),
"website": cf["website"],
"website": str(none_to_empty(cf["website"])),
},
"items": [],
@ -65,24 +65,24 @@ def insert_item_and_taxes(fields) -> Dict[str, Any]:
return {
"item_id": str(uuid7()),
"position": str(fields.get("position")),
"description": fields.get("description"),
"quantity_value": str(fields.get("quantity_value")),
"unit_value": str(fields.get("unit_value")),
"subtotal_amount_value": fields.get("subtotal_amount_value"),
"item_discount_percentage_value": none_to_empty(fields.get("item_discount_percentage_value")),
"item_discount_amount_value": none_to_empty(fields.get("item_discount_amount_value")),
"global_discount_percentage_value": none_to_empty(fields.get("global_discount_percentage_value")),
"global_discount_amount_value": none_to_empty(fields.get("global_discount_amount_value")),
"total_discount_amount_value": fields.get("total_discount_amount_value"),
"taxable_amount_value": fields.get("taxable_amount_value"),
"total_value": fields.get("total_value"),
"iva_code": fields.get("iva_code"),
"iva_percentage_value": str(fields.get("iva_percentage_value")),
"iva_amount_value": fields.get("iva_amount_value"),
"rec_code": none_to_empty(fields.get("rec_code")),
"rec_percentage_value": none_to_empty(fields.get("rec_percentage_value")),
"rec_amount_value": fields.get("rec_amount_value"),
"taxes_amount_value": fields.get("taxes_amount_value"),
"description": str(none_to_empty(fields.get("description"))),
"quantity_value": str(none_to_empty(fields.get("quantity_value"))),
"unit_value": str(none_to_empty(fields.get("unit_value"))),
"subtotal_amount_value": str(fields.get("subtotal_amount_value")),
"item_discount_percentage_value": str(none_to_empty(fields.get("item_discount_percentage_value"))),
"item_discount_amount_value": str(none_to_empty(fields.get("item_discount_amount_value"))),
"global_discount_percentage_value": str(none_to_empty(fields.get("global_discount_percentage_value"))),
"global_discount_amount_value": str(none_to_empty(fields.get("global_discount_amount_value"))),
"total_discount_amount_value": str(fields.get("total_discount_amount_value")),
"taxable_amount_value": str(fields.get("taxable_amount_value")),
"total_value": str(fields.get("total_value")),
"iva_code": str(fields.get("iva_code")),
"iva_percentage_value": str(str(fields.get("iva_percentage_value"))),
"iva_amount_value": str(fields.get("iva_amount_value")),
"rec_code": str(none_to_empty(fields.get("rec_code"))),
"rec_percentage_value": str(none_to_empty(fields.get("rec_percentage_value"))),
"rec_amount_value": str(fields.get("rec_amount_value")),
"taxes_amount_value": str(fields.get("taxes_amount_value")),
}
# logger.info("Inserting item tax %s code=%s base=%s tax=%s",

View File

@ -4,6 +4,9 @@ import requests
from app.config import logger
SUCCESS_CODES = {200, 201}
VALIDATION_ERROR_CODES = {400, 422}
def estado_factura(uuid_str: str,
config,
@ -142,13 +145,11 @@ def validar_nif_verifacti(
resp = requests.post(
url, json=payload, headers=headers, timeout=timeout)
if resp.status_code != 200:
logger.info("ERRRRRROOOOOOORRRRR LLAMADA REST API")
# return False, None, f"HTTP {resp.status_code}: {resp.text}"
return False, None, f"HTTP {resp.status_code}: {resp.text}"
data = resp.json()
resultado = data.get("resultado", "NO IDENTIFICADO")
logger.info(f"Resultado Verifacti: {resultado}")
return resultado == "IDENTIFICADO"
except requests.RequestException as e:
@ -177,26 +178,30 @@ def insert_invoice_REST_API_FACTUGES(
"nombre": nombre,
}
"""
logger.info("-----------------------------------------------------------------------------")
logger.info(payload)
logger.info("-----------------------------------------------------------------------------")
try:
resp = requests.post(
url, json=payload, headers=headers, timeout=timeout)
if resp.status_code == 200:
if resp.status_code in SUCCESS_CODES:
location = resp.headers.get("Location")
try:
data = resp.json()
logger.info(data)
except ValueError:
return {"ok": False, "status": 200, "error": "Respuesta 200 sin JSON válido", "raw": resp.text}
return {"ok": True, "status": 200, "data": data}
data = None
return {"ok": True, "status": resp.status_code, "data": data, "location": location}
if resp.status_code == 400:
if resp.status_code in VALIDATION_ERROR_CODES:
try:
body = resp.json()
msg = body.get(
"error") or "Error de validación (400) sin detalle"
msg = (body.get("title") + str(body.get("errors"))
or f"Error de validación ({resp.status_code}) sin detalle")
except ValueError:
msg = f"Error de validación (400): {resp.text}"
return {"ok": False, "status": 400, "error": msg}
return {"ok": False, "status": resp.status_code, "error": msg}
# Otros códigos: devuelve mensaje genérico, intenta extraer JSON si existe
try:
@ -207,7 +212,7 @@ def insert_invoice_REST_API_FACTUGES(
return {"ok": False, "status": resp.status_code, "error": f"HTTP {resp.status_code}", "raw": resp.text}
except requests.RequestException as e:
logger.error("Error de conexión con la API Verifacti: %s", e)
logger.error("Error de conexión con la API Factuges: %s", e)
return {"ok": False, "status": 500, "error": str(e)}
except ValueError as e:
logger.error("Respuesta no es JSON válido: %s", e)