diff --git a/app/db/sync_invoices_factuges_REST_API.py b/app/db/sync_invoices_factuges_REST_API.py index b0965ce..c49acd2 100644 --- a/app/db/sync_invoices_factuges_REST_API.py +++ b/app/db/sync_invoices_factuges_REST_API.py @@ -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: """ diff --git a/app/utils/payloads_factuges.py b/app/utils/payloads_factuges.py index 9d33eab..1eee3a5 100644 --- a/app/utils/payloads_factuges.py +++ b/app/utils/payloads_factuges.py @@ -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", diff --git a/app/utils/send_rest_api.py b/app/utils/send_rest_api.py index cbd21e3..da119ef 100644 --- a/app/utils/send_rest_api.py +++ b/app/utils/send_rest_api.py @@ -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)