This commit is contained in:
David Arranz 2025-11-27 20:08:06 +01:00
parent a951be5b87
commit 634d04dc10
14 changed files with 256 additions and 157 deletions

28
.env
View File

@ -1,5 +1,6 @@
ENVIRONMENT = development ENVIRONMENT = development
LOCAL_TZ = Europe/Madrid LOCAL_TZ = Europe/Madrid
LAST_RUN_PATH = ./app.last_run.txt
#LOG_PATH = ./app.log #LOG_PATH = ./app.log
#DESARROLLO ACANA #DESARROLLO ACANA
@ -9,20 +10,6 @@ FACTUGES_DATABASE = C:\Codigo Acana\Output\Debug\Database\FACTUGES.FDB
FACTUGES_USER = sysdba FACTUGES_USER = sysdba
FACTUGES_PASSWORD = masterkey FACTUGES_PASSWORD = masterkey
# PRODUCCION RODAX FACTUGES FIREBIRD
#FACTUGES_HOST = 192.168.0.101
#FACTUGES_PORT = 3050
#FACTUGES_DATABASE = C:\FactuGES\FACTUGES.FDB
#FACTUGES_USER = sysdba
#FACTUGES_PASSWORD = masterkey
# PRODUCCION RODAX MYSQL
#UECKO_MYSQL_HOST = 192.168.0.250
#UECKO_MYSQL_PORT = 3306
#UECKO_MYSQL_DATABASE = rodax_db
#UECKO_MYSQL_USER = rodax_usr
#UECKO_MYSQL_PASSWORD = supersecret
#DESARROLLO #DESARROLLO
UECKO_MYSQL_HOST = localhost UECKO_MYSQL_HOST = localhost
UECKO_MYSQL_PORT = 3306 UECKO_MYSQL_PORT = 3306
@ -35,25 +22,16 @@ CTE_COMPANY_ID = '019a9667-6a65-767a-a737-48234ee50a3a'
CTE_SERIE = 'F25/' CTE_SERIE = 'F25/'
CTE_STATUS_INVOICE = 'issued' CTE_STATUS_INVOICE = 'issued'
CTE_IS_PROFORMA = 0 CTE_IS_PROFORMA = 0
CTE_STATUS_VERIFACTU = 'pendiente' CTE_STATUS_VERIFACTU = 'Pendiente'
CTE_LANGUAGE_CODE = 'es' #En uecko vendrá de su ficha CTE_LANGUAGE_CODE = 'es' #En uecko vendrá de su ficha
CTE_COUNTRY_CODE = 'es' #En uecko vendrá de su ficha CTE_COUNTRY_CODE = 'es' #En uecko vendrá de su ficha
CTE_IS_COMPANY = 1 CTE_IS_COMPANY = 1
#CONFIGURACION RODAX
#CTE_COMPANY_ID = '5e4dc5b3-96b9-4968-9490-14bd032fec5f'
#CTE_SERIE = 'F25/'
#CTE_STATUS_INVOICE = 'approved'
#CTE_IS_PROFORMA = 1
#CTE_STATUS_VERIFACTU = 'Pendiente'
VERIFACTU_BASE_URL = https://api.verifacti.com/ VERIFACTU_BASE_URL = https://api.verifacti.com/
VERIFACTU_API_KEY = vf_test_ei8WYAvEq5dhSdEyQVjgCS8NZaNpEK2BljSHSUXf+Y0= VERIFACTU_API_KEY = vf_test_ei8WYAvEq5dhSdEyQVjgCS8NZaNpEK2BljSHSUXf+Y0=
VERIFACTU_NIFS_API_KEY = vfn_osYpNdqSzAdTAHpazXG2anz4F3o0gfbSb5FFrCBZcno= VERIFACTU_NIFS_API_KEY = vfn_osYpNdqSzAdTAHpazXG2anz4F3o0gfbSb5FFrCBZcno=
#BREVO_API_KEY = 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 #BREVO_API_KEY = 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
#BREVO_EMAIL_TEMPLATE = 1 #BREVO_EMAIL_TEMPLATE = 1
#MAIL_FROM = 'no-reply@presupuestos.uecko.com' #MAIL_FROM = 'no-reply@presupuestos.uecko.com'
#MAIL_TO = 'soporte@rodax-software.com' #MAIL_TO = 'soporte@rodax-software.com'

View File

@ -1,57 +0,0 @@
ENVIRONMENT = production
LOCAL_TZ = Europe/Madrid
#LOG_PATH = /var/log/uecko_sync_app/uecko_sync_app.log
FACTUGES_HOST = 83.48.36.692
FACTUGES_PORT = 3050
FACTUGES_DATABASE = D:\RODAX\FACTUGES\BD\FACTUGES_FABRICA.FDB
FACTUGES_USER = sysdba
FACTUGES_PASSWORD = abeto2010
# DESARROLLO ALISO FACTUGES FIREBIRD
#FACTUGES_HOST = 192.168.0.105
#FACTUGES_PORT = 3050
#FACTUGES_DATABASE = C:\Codigo Acana\Output\Debug\Database\FACTUGES.FDB
#FACTUGES_USER = sysdba
#FACTUGES_PASSWORD = masterkey
# PRODUCCION RODAX FACTUGES FIREBIRD
#FACTUGES_HOST = 192.168.0.101
#FACTUGES_PORT = 3050
#FACTUGES_DATABASE = C:\FactuGES\FACTUGES.FDB
#FACTUGES_USER = sysdba
#FACTUGES_PASSWORD = masterkey
# PRODUCCION RODAX MYSQL
#UECKO_MYSQL_HOST = 192.168.0.250
#UECKO_MYSQL_PORT = 3306
#UECKO_MYSQL_DATABASE = rodax_db
#UECKO_MYSQL_USER = rodax_usr
#UECKO_MYSQL_PASSWORD = supersecret
UECKO_MYSQL_HOST = mariadb2
UECKO_MYSQL_PORT = 3306
UECKO_MYSQL_DATABASE = uecko
UECKO_MYSQL_USER = uecko
UECKO_MYSQL_PASSWORD = u8Ax5Nw3%sjd
BREVO_API_KEY = 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
BREVO_EMAIL_TEMPLATE = 1
MAIL_FROM = 'no-reply@presupuestos.uecko.com'
MAIL_TO = 'pedidos@uecko.com'
VERIFACTU_BASE_URL = https://api.verifacti.com/
VERIFACTU_API_KEY = vf_test_kY9FoI86dH+g1a5hmEnb/0YcLTlMFlu+tpp9iMZp020=
VERIFACTU_NIFS_API_KEY = vfn_osYpNdqSzAdTAHpazXG2anz4F3o0gfbSb5FFrCBZcno=
#VERIFACTU_RODAX_TEST_API_KEY = vf_test_C03HL2F0X5OXSDRunjNFoMxD4IrRfK3kCC8PfcvCENI=
#VERIFACTU_RODAX_PROD_API_KEY = vf_prod_yfjonNPv2E4Fij+5J0hct0zCgUeFYT2dZzb23UZlM+Q=
#VERIFACTU_ALISO_TEST_API_KEY = vf_test_ei8WYAvEq5dhSdEyQVjgCS8NZaNpEK2BljSHSUXf+Y0=

View File

@ -0,0 +1,33 @@
ENVIRONMENT = development
LOCAL_TZ = Europe/Madrid
LAST_RUN_PATH = ./app.last_run.txt
#LOG_PATH = ./app.log
#DESARROLLO ACANA
FACTUGES_HOST = 192.168.0.105
FACTUGES_PORT = 3050
FACTUGES_DATABASE = C:\Codigo Acana\Output\Debug\Database\FACTUGES.FDB
FACTUGES_USER = sysdba
FACTUGES_PASSWORD = masterkey
#DESARROLLO
UECKO_MYSQL_HOST = localhost
UECKO_MYSQL_PORT = 3306
UECKO_MYSQL_DATABASE = uecko_erp_sync
UECKO_MYSQL_USER = rodax
UECKO_MYSQL_PASSWORD = rodax
#CONFIGURACION ACANA
CTE_COMPANY_ID = '019a9667-6a65-767a-a737-48234ee50a3a'
CTE_SERIE = 'F25/'
CTE_STATUS_INVOICE = 'issued'
CTE_IS_PROFORMA = 0
CTE_STATUS_VERIFACTU = 'Pendiente'
CTE_LANGUAGE_CODE = 'es' #En uecko vendrá de su ficha
CTE_COUNTRY_CODE = 'es' #En uecko vendrá de su ficha
CTE_IS_COMPANY = 1
#BREVO_API_KEY = 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
#BREVO_EMAIL_TEMPLATE = 1
#MAIL_FROM = 'no-reply@presupuestos.uecko.com'
#MAIL_TO = 'soporte@rodax-software.com'

21
.env_V Normal file
View File

@ -0,0 +1,21 @@
ENVIRONMENT = development
LOCAL_TZ = Europe/Madrid
LAST_RUN_PATH = ./app.last_run.txt
#LOG_PATH = ./app.log
#DESARROLLO
UECKO_MYSQL_HOST = localhost
UECKO_MYSQL_PORT = 3306
UECKO_MYSQL_DATABASE = uecko_erp_sync
UECKO_MYSQL_USER = rodax
UECKO_MYSQL_PASSWORD = rodax
VERIFACTU_BASE_URL = https://api.verifacti.com/
VERIFACTU_API_KEY = vf_test_ei8WYAvEq5dhSdEyQVjgCS8NZaNpEK2BljSHSUXf+Y0=
VERIFACTU_NIFS_API_KEY = vfn_osYpNdqSzAdTAHpazXG2anz4F3o0gfbSb5FFrCBZcno=
#BREVO_API_KEY = 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
#BREVO_EMAIL_TEMPLATE = 1
#MAIL_FROM = 'no-reply@presupuestos.uecko.com'
#MAIL_TO = 'soporte@rodax-software.com'

1
app.last_run.txt Normal file
View File

@ -0,0 +1 @@
2025-11-27 18:58:46

View File

@ -0,0 +1 @@
__version__ = "1.0.0"

View File

@ -10,6 +10,7 @@ def load_config():
return { return {
'ENVIRONMENT': os.getenv('ENVIRONMENT'), 'ENVIRONMENT': os.getenv('ENVIRONMENT'),
'LOCAL_TZ': os.getenv('LOCAL_TZ', 'Europe/Madrid'), 'LOCAL_TZ': os.getenv('LOCAL_TZ', 'Europe/Madrid'),
'LAST_RUN_PATH': os.getenv('LAST_RUN_PATH'),
# 'LOG_PATH': os.getenv('LOG_PATH', 'app.log'), # 'LOG_PATH': os.getenv('LOG_PATH', 'app.log'),
'FACTUGES_HOST': os.getenv('FACTUGES_HOST'), 'FACTUGES_HOST': os.getenv('FACTUGES_HOST'),

View File

@ -1,4 +1,4 @@
from .db_connection import get_factuges_connection from .db_connection import get_factuges_connection
from .db_connection import get_mysql_connection from .db_connection import get_mysql_connection
from .sync_invoices import sync_invoices from .sync_invoices_factuges import sync_invoices_factuges
from .sync_invoices_verifactu import sync_invoices_verifactu from .sync_invoices_verifactu import sync_invoices_verifactu

View File

@ -121,14 +121,16 @@ SELECT_FACTUGES_FACTURAS_CLIENTE = (
f"LEFT JOIN TIPOS_IVA AS ti ON fac.ID_TIPO_IVA = ti.ID " f"LEFT JOIN TIPOS_IVA AS ti ON fac.ID_TIPO_IVA = ti.ID "
f"LEFT JOIN FACTURAS_CLIENTE_DETALLES as facdet ON fac.ID = facdet.ID_FACTURA " f"LEFT JOIN FACTURAS_CLIENTE_DETALLES as facdet ON fac.ID = facdet.ID_FACTURA "
f"WHERE " f"WHERE "
f"(fac.VERIFACTU > 0) " f"(fac.VERIFACTU = 1) "
f"AND (fac.ID_VERIFACTU is null)" f"AND (fac.ID_VERIFACTU is null)"
f"ORDER BY (fac.ID)" f"ORDER BY (fac.ID)"
) )
UPDATE_FACTUGES_LINK = ( UPDATE_FACTUGES_LINK = (
"UPDATE FACTURAS_CLIENTE " "UPDATE FACTURAS_CLIENTE "
"SET ID_VERIFACTU=? " "SET VERIFACTU=?, "
"ID_VERIFACTU=?, "
"VERIFACTU_NOTES =? "
"WHERE ID=?" "WHERE ID=?"
) )

View File

@ -4,9 +4,10 @@ from uuid6 import uuid7
from config import load_config from config import load_config
from . import sql_sentences as SQL from . import sql_sentences as SQL
from . import normalizations as NORMALIZA from . import normalizations as NORMALIZA
from utils import validar_nif
def sync_invoices(conn_factuges, conn_mysql, last_execution_date): def sync_invoices_factuges(conn_factuges, conn_mysql, last_execution_date):
config = load_config() config = load_config()
# LIMPIAMOS LAS FACTURAS DE FACTUGES QUE HAYAN SIDO ELIMINADAS DEL PROGRAMA NUEVO DE FACTURACION, PARA QUE PUEDAN SER MODIFICADAS # LIMPIAMOS LAS FACTURAS DE FACTUGES QUE HAYAN SIDO ELIMINADAS DEL PROGRAMA NUEVO DE FACTURACION, PARA QUE PUEDAN SER MODIFICADAS
@ -67,7 +68,7 @@ def sync_invoices(conn_factuges, conn_mysql, last_execution_date):
conn_mysql, tuplas_seleccionadas, conn_factuges, config) conn_mysql, tuplas_seleccionadas, conn_factuges, config)
else: else:
logging.info( logging.info(
"There are no new FACTURAS rows since the last run.") "There are NOT new FACTURAS rows since the last run.")
def sync_delete_invoices(conn_factuges, ids_verifactu_deleted, config): def sync_delete_invoices(conn_factuges, ids_verifactu_deleted, config):
@ -101,6 +102,8 @@ def sync_invoices_from_FACTUGES(conn_mysql, filas, conn_factuges, config):
cursor_FactuGES = None cursor_FactuGES = None
factuges_id_anterior = None factuges_id_anterior = None
num_fac_procesed = 0 num_fac_procesed = 0
customer_valid = True
invoice_id = None
try: try:
cursorMySQL = conn_mysql.cursor() cursorMySQL = conn_mysql.cursor()
@ -120,47 +123,56 @@ def sync_invoices_from_FACTUGES(conn_mysql, filas, conn_factuges, config):
factuges_id = int(factura_detalle['ID_FACTURA']) factuges_id = int(factura_detalle['ID_FACTURA'])
if factuges_id_anterior is None or factuges_id_anterior != factuges_id: if factuges_id_anterior is None or factuges_id_anterior != factuges_id:
# Comprobamos si existe el cliente del primer item de la factura # Validamos que el cif de la factura exista en la AEAT si no es así no se hace la sincro de la factura
customer_id = get_or_create_customer( sync_result = 1
cursorMySQL, customer_valid = validar_nif(customer_fields["tin"], customer_fields["name"], config)
config['CTE_COMPANY_ID'], if customer_valid:
str(factura_detalle["ID_CLIENTE"]), # Comprobamos si existe el cliente del primer item de la factura
customer_fields, customer_id = get_or_create_customer(
) cursorMySQL,
config['CTE_COMPANY_ID'],
str(factura_detalle["ID_CLIENTE"]),
customer_fields,
)
# ---- forma de pago # ---- forma de pago
pm_id = get_or_create_payment_method( pm_id = get_or_create_payment_method(
cursorMySQL, cursorMySQL,
str(factura_detalle["ID_FORMA_PAGO"]), str(factura_detalle["ID_FORMA_PAGO"]),
str(factura_detalle["DES_FORMA_PAGO"]), str(factura_detalle["DES_FORMA_PAGO"]),
) )
# campos pendiente de revisar en un futuro # campos pendiente de revisar en un futuro
# xxxxxxx = str(factura_detalle['ID_FORMA_PAGO']) según este id se debe de guardar en la factura los vencimiento asociados a la forma de pago # xxxxxxx = str(factura_detalle['ID_FORMA_PAGO']) según este id se debe de guardar en la factura los vencimiento asociados a la forma de pago
# ---- cabecera factura # ---- cabecera factura
invoice_id = insert_invoice_header(cursorMySQL, customer_fields, header_invoice_fields, customer_id, pm_id, str( invoice_id = insert_invoice_header(cursorMySQL, customer_fields, header_invoice_fields, customer_id, pm_id, str(
factura_detalle["DES_FORMA_PAGO"]), config factura_detalle["DES_FORMA_PAGO"]), config
) )
# ---- impuestos cabecera # ---- impuestos cabecera
insert_header_taxes_if_any( insert_header_taxes_if_any(
cursorMySQL, invoice_id, factura_detalle['IVA'], factura_detalle['RECARGO_EQUIVALENCIA'], header_invoice_fields) cursorMySQL, invoice_id, factura_detalle['IVA'], factura_detalle['RECARGO_EQUIVALENCIA'], header_invoice_fields)
# ---- registro verifactu # ---- registro verifactu
insert_verifactu_record( insert_verifactu_record(
cursorMySQL, header_invoice_fields, invoice_id, config) cursorMySQL, header_invoice_fields, invoice_id, config)
else:
sync_result = 2
cadena = (f">>> Factura {header_invoice_fields['reference']} no cumple requisitos para ser mandada a Verifactu: "
f">>>>>> El NIF/NOMBRE ({customer_fields['tin']}/{customer_fields['name']}) no está registrado en la AEAT. "
f"El NIF/CIF debe estar registrado en la AEAT y el nombre debe ser suficientemente parecido al nombre registrado en la AEAT")
logging.info(cadena)
# Guardamos en Factuges el id de la customer_invoice # Guardamos en Factuges el id de la customer_invoice
logging.info( logging.info(
f"Updating FACTURAS_CLIENTE {invoice_id} {factuges_id}") f"Updating FACTURAS_CLIENTE {sync_result} {invoice_id} {factuges_id} {cadena}")
cursor_FactuGES.execute( cursor_FactuGES.execute(SQL.UPDATE_FACTUGES_LINK, (sync_result, invoice_id, cadena, factuges_id))
SQL.UPDATE_FACTUGES_LINK, (invoice_id, factuges_id))
num_fac_procesed += 1 num_fac_procesed += 1
# Insertamos detalles y taxes correspondientes siempre # Insertamos detalles y taxes correspondientes siempre que hayamos insertado cabecera
# Siempre insertamos la línea if customer_valid:
insert_item_and_taxes(cursorMySQL, invoice_id, insert_item_and_taxes(cursorMySQL, invoice_id, details_invoice_fields)
details_invoice_fields)
# Asignamos el id factura anterior para no volver a inserta cabecera # Asignamos el id factura anterior para no volver a inserta cabecera
factuges_id_anterior = factuges_id factuges_id_anterior = factuges_id
@ -240,7 +252,7 @@ def insert_invoice_header(cur: str, cf: Dict[str, Any], hif: Dict[str, Any], cus
""" """
invoice_id = str(uuid7()) invoice_id = str(uuid7())
logging.info("Inserting invoice %s %s %s %s", logging.info("Inserting invoice %s %s %s %s %s",
invoice_id, hif.get('reference'), hif.get('invoice_date'), hif.get('operation_date'), config['CTE_STATUS_INVOICE']) invoice_id, hif.get('reference'), hif.get('invoice_date'), hif.get('operation_date'), config['CTE_STATUS_INVOICE'])
cur.execute( cur.execute(
SQL.INSERT_INVOICE, SQL.INSERT_INVOICE,
@ -298,8 +310,7 @@ def insert_item_and_taxes(cur, invoice_id: str, fields: Dict[str, Any]) -> None:
""" """
item_id = str(uuid7()) item_id = str(uuid7())
logging.info("Inserting item %s pos=%s qty=%s", # logging.info("Inserting item %s pos=%s qty=%s", item_id, fields.get('position'), fields.get('quantity_value'))
item_id, fields.get('position'), fields.get('quantity_value'))
cur.execute( cur.execute(
SQL.INSERT_INVOICE_ITEM, SQL.INSERT_INVOICE_ITEM,
(item_id, invoice_id, fields.get('position'), fields.get('description'), fields.get('quantity_value'), (item_id, invoice_id, fields.get('position'), fields.get('description'), fields.get('quantity_value'),

View File

@ -1,12 +1,12 @@
import sys import sys
import logging import logging
import __version__
from __version_sync_factuges__ import __version__
from datetime import datetime from datetime import datetime
from dateutil import tz from dateutil import tz
from config import setup_logging, load_config from config import setup_logging, load_config
from db import get_mysql_connection, get_factuges_connection, sync_invoices, sync_invoices_verifactu from db import get_mysql_connection, get_factuges_connection, sync_invoices_factuges
from utils import obtener_fecha_ultima_ejecucion, actualizar_fecha_ultima_ejecucion, log_system_metrics, send_orders_mail, limpiar_cadena from utils import obtener_fecha_ultima_ejecucion, actualizar_fecha_ultima_ejecucion, log_system_metrics, send_orders_mail
def main(): def main():
@ -19,8 +19,8 @@ def main():
# Logging # Logging
setup_logging() setup_logging()
logging.info("== START ==") logging.info("== START SYNC FACTUGES ==")
logging.info(f"Version: {__version__.__version__}") logging.info(f"Version: {__version__}")
logging.info(f"Environment: {config['ENVIRONMENT']}") logging.info(f"Environment: {config['ENVIRONMENT']}")
log_system_metrics() log_system_metrics()
@ -28,7 +28,7 @@ def main():
conn_mysql = None conn_mysql = None
try: try:
# Obtener la fecha de la última ejecución del programa # Obtener la fecha de la última ejecución del programa
last_execution_date_utc = obtener_fecha_ultima_ejecucion() last_execution_date_utc = obtener_fecha_ultima_ejecucion(config['LAST_RUN_PATH'])
last_execution_date_local_tz = last_execution_date_utc.astimezone( last_execution_date_local_tz = last_execution_date_utc.astimezone(
tz=local_tz).strftime("%Y-%m-%d %H:%M:%S") tz=local_tz).strftime("%Y-%m-%d %H:%M:%S")
@ -41,31 +41,19 @@ def main():
conn_factuges = get_factuges_connection(config) conn_factuges = get_factuges_connection(config)
conn_mysql = get_mysql_connection(config) conn_mysql = get_mysql_connection(config)
# Sync invoices # Sincronizamos
logging.info( logging.info(
f">>>>>>>>>>> Sync invoices FactuGES escritorio to FactuGES web") f">>>>>>>>>>> INI Sync invoices FactuGES escritorio to FactuGES web")
sync_invoices(conn_factuges, conn_mysql, last_execution_date_local_tz) sync_invoices_factuges(conn_factuges, conn_mysql, last_execution_date_local_tz)
# Confirmar los cambios # Confirmar los cambios
conn_mysql.commit() conn_mysql.commit()
conn_factuges.commit() conn_factuges.commit()
conn_factuges.close() conn_factuges.close()
conn_mysql.close() conn_mysql.close()
logging.info(f"FIN Sync FactuGES web >>>>>>>>>>") logging.info(f">>>>>>>>>>> FIN Sync invoices FactuGES escritorio to FactuGES web")
# ESTO OTRO DEBERIA SER OTRA TRANSACCION POR LO QUE HACEMOS NUEVA CONEXION actualizar_fecha_ultima_ejecucion(config['LAST_RUN_PATH'])
# Vamos que deberia ir en otro lado
conn_mysql = get_mysql_connection(config)
# Sync Verifactu
logging.info(
f">>>>>>>>>> Sync facturas emitidas en FactuGES web to Verifactu")
sync_invoices_verifactu(conn_mysql, last_execution_date_local_tz)
conn_mysql.commit()
conn_mysql.close()
logging.info(f"FIN Sync Verifactu >>>>>>>>>>")
# actualizar_fecha_ultima_ejecucion()
# Enviar email # Enviar email
# send_orders_mail(inserted_orders) # send_orders_mail(inserted_orders)

View File

@ -0,0 +1,81 @@
import sys
import logging
from __version_sync_factuges__ import __version__
from datetime import datetime
from dateutil import tz
from config import setup_logging, load_config
from db import get_mysql_connection, get_factuges_connection, sync_invoices_verifactu
from utils import obtener_fecha_ultima_ejecucion, actualizar_fecha_ultima_ejecucion, log_system_metrics, send_orders_mail
def main():
# Cargar la configuración
config = load_config()
local_tz = tz.gettz(config['LOCAL_TZ'])
# Logging
setup_logging()
logging.info("== START SYNC VERIFACTU ==")
logging.info(f"Version: {__version__}")
logging.info(f"Environment: {config['ENVIRONMENT']}")
log_system_metrics()
conn_factuges = None
conn_mysql = None
try:
# Obtener la fecha de la última ejecución del programa
last_execution_date_utc = obtener_fecha_ultima_ejecucion()
last_execution_date_local_tz = last_execution_date_utc.astimezone(
tz=local_tz).strftime("%Y-%m-%d %H:%M:%S")
logging.info("Last execution (UTC): %s",
last_execution_date_utc.strftime("%Y-%m-%d %H:%M:%S %Z"))
logging.info("Last execution (Local time): %s",
last_execution_date_local_tz)
# Abrimos conexión con una única transacción para que todo esté controlado
conn_mysql = get_mysql_connection(config)
# Sync Verifactu
logging.info(
f">>>>>>>>>> INI Sync facturas emitidas to Verifactu")
sync_invoices_verifactu(conn_mysql, last_execution_date_local_tz)
conn_mysql.commit()
conn_mysql.close()
logging.info(f">>>>>>>>>> FIN Sync facturas emitidas to Verifactu")
actualizar_fecha_ultima_ejecucion()
# Enviar email
# send_orders_mail(inserted_orders)
logging.info("== END (0) ==")
sys.exit(0)
except Exception as e:
logging.error("Se ha producido un error en la última ejecución.")
logging.error(e)
logging.error("Traceback:", exc_info=True)
logging.info("== END (1) ==")
if conn_mysql is not None:
conn_mysql.rollback()
if conn_factuges is not None:
conn_factuges.rollback()
sys.exit(1)
finally:
if conn_factuges:
conn_factuges.close()
if conn_mysql:
conn_mysql.close()
if __name__ == "__main__":
main()

View File

@ -1,21 +1,60 @@
from __future__ import annotations
from datetime import datetime, timezone from datetime import datetime, timezone
from dateutil import tz from typing import Optional
import os
# Función para obtener la fecha de la última ejecución del programa desde un archivo de texto DEFAULT_PATH = "./last_execution.txt"
FMT = "%Y-%m-%d %H:%M:%S"
def obtener_fecha_ultima_ejecucion(): def obtener_fecha_ultima_ejecucion(
path: str = DEFAULT_PATH,
*,
fallback: Optional[datetime] = None,
) -> datetime:
"""
Lee la última fecha de ejecución desde `path` y la devuelve como aware (UTC).
Si no existe o hay error de parseo, devuelve `fallback` (por defecto 2024-01-01 UTC).
"""
if fallback is None:
fallback = datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
try: try:
with open('./last_execution.txt', 'r', encoding="utf8") as f: with open(path, "r", encoding="utf8") as f:
fecha_str = f.read().strip() fecha_str = f.read().strip()
return datetime.strptime(fecha_str, '%Y-%m-%d %H:%M:%S').astimezone(tz=tz.UTC) # Se guarda como texto sin tz; interpretamos como UTC
dt_naive = datetime.strptime(fecha_str, FMT)
return dt_naive.replace(tzinfo=timezone.utc)
except FileNotFoundError: except FileNotFoundError:
# Si el archivo no existe, se asume que el programa nunca se ha ejecutado antes return fallback
return datetime(2024, 1, 1, 0, 0, 0).astimezone(tz=tz.UTC) except ValueError:
# Formato inválido en el archivo -> usar fallback
# Función para actualizar la fecha de la última ejecución del programa en el archivo de texto return fallback
def actualizar_fecha_ultima_ejecucion(): def actualizar_fecha_ultima_ejecucion(
with open('./last_execution.txt', 'w', encoding="utf8") as f: path: str = DEFAULT_PATH,
f.write(datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')) *,
momento: Optional[datetime] = None,
) -> None:
"""
Escribe en `path` la fecha/hora (UTC) en formato YYYY-MM-DD HH:MM:SS.
Si `momento` es None, usa ahora en UTC.
Crea directorios intermedios si no existen.
"""
if momento is None:
momento = datetime.now(timezone.utc)
else:
# Normalizamos a UTC si viene con tz; si es naive, asumimos UTC
if momento.tzinfo is None:
momento = momento.replace(tzinfo=timezone.utc)
else:
momento = momento.astimezone(timezone.utc)
# Asegurar carpeta si `path` incluye directorios
folder = os.path.dirname(os.path.abspath(path))
if folder and not os.path.exists(folder):
os.makedirs(folder, exist_ok=True)
with open(path, "w", encoding="utf8") as f:
f.write(momento.strftime(FMT))