Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 615b309718 | |||
| eedb555096 | |||
| 4d890814a6 | |||
| 34fc389da9 | |||
| 3d52b0c112 | |||
| 6c73958307 | |||
| daf57066db | |||
| fbd294098d | |||
| ef315b1479 | |||
| 4b27315372 | |||
| a85a7afec4 | |||
| a7868ba6c8 | |||
| 14c898b13d | |||
| a8e4f76df2 | |||
| a032383be3 |
@ -24,7 +24,7 @@ def load_config() -> Dict[str, Any]:
|
|||||||
Carga la configuración desde variables de entorno.
|
Carga la configuración desde variables de entorno.
|
||||||
|
|
||||||
- En dev: carga dev.env y luego valida.
|
- En dev: carga dev.env y luego valida.
|
||||||
- En production: NO carga ningún .env, solo usa entorno del sistema/contendor.
|
- En production: NO carga ningún .env, solo usa entorno del sistema/contenedor.
|
||||||
- Si falta alguna variable requerida -> RuntimeError.
|
- Si falta alguna variable requerida -> RuntimeError.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -60,6 +60,10 @@ def load_config() -> Dict[str, Any]:
|
|||||||
"FWEB_MYSQL_USER": _required("FWEB_MYSQL_USER"),
|
"FWEB_MYSQL_USER": _required("FWEB_MYSQL_USER"),
|
||||||
"FWEB_MYSQL_PASSWORD": _required("FWEB_MYSQL_PASSWORD"),
|
"FWEB_MYSQL_PASSWORD": _required("FWEB_MYSQL_PASSWORD"),
|
||||||
|
|
||||||
|
"FWEB_API_URL": _required("FWEB_API_URL"),
|
||||||
|
"FWEB_API_KEY": _required("FWEB_API_KEY"),
|
||||||
|
"FWEB_API_TIMEOUT": int(os.getenv("FWEB_API_TIMEOUT", "120")),
|
||||||
|
|
||||||
# Constantes/CTE (requeridas)
|
# Constantes/CTE (requeridas)
|
||||||
"CTE_COMPANY_ID": _required("CTE_COMPANY_ID"),
|
"CTE_COMPANY_ID": _required("CTE_COMPANY_ID"),
|
||||||
"CTE_SERIE": _required("CTE_SERIE"),
|
"CTE_SERIE": _required("CTE_SERIE"),
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
from .db_connection import get_factuges_connection, get_mysql_connection
|
from .db_connection import get_factuges_connection, get_mysql_connection
|
||||||
from .sync_invoices_deleted_factuges import sync_invoices_deleted_factuges
|
from .sync_invoices_deleted_factuges import sync_invoices_deleted_factuges
|
||||||
from .sync_invoices_factuges import sync_invoices_factuges
|
from .sync_invoices_factuges import sync_invoices_factuges
|
||||||
|
from .sync_invoices_factuges_REST_API import sync_invoices_factuges_REST_API
|
||||||
from .sync_invoices_verifactu import sync_invoices_verifactu
|
from .sync_invoices_verifactu import sync_invoices_verifactu
|
||||||
|
|||||||
@ -17,7 +17,7 @@ def get_factuges_connection(config) -> fdb.Connection:
|
|||||||
database=config['FACTUGES_DATABASE'],
|
database=config['FACTUGES_DATABASE'],
|
||||||
user=config['FACTUGES_USER'],
|
user=config['FACTUGES_USER'],
|
||||||
password=config['FACTUGES_PASSWORD'],
|
password=config['FACTUGES_PASSWORD'],
|
||||||
charset='UTF8',
|
charset='ISO8859_1'
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
"Conexión a la base de datos FactuGES establecida: %s with database:%s - using user:%s",
|
"Conexión a la base de datos FactuGES establecida: %s with database:%s - using user:%s",
|
||||||
|
|||||||
@ -14,10 +14,10 @@ from app.utils import (
|
|||||||
limpiar_cadena,
|
limpiar_cadena,
|
||||||
map_iva_code,
|
map_iva_code,
|
||||||
map_rec_by_iva_code,
|
map_rec_by_iva_code,
|
||||||
money_round,
|
|
||||||
normalizar_telefono_con_plus,
|
normalizar_telefono_con_plus,
|
||||||
normalizar_url_para_insert,
|
normalizar_url_para_insert,
|
||||||
tax_fraction_from_code,
|
tax_fraction_from_code,
|
||||||
|
text_converter,
|
||||||
unscale_to_decimal,
|
unscale_to_decimal,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -46,12 +46,11 @@ def normalize_customer_fields(fd: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
return {
|
return {
|
||||||
"is_company": config["CTE_IS_COMPANY"],
|
"is_company": config["CTE_IS_COMPANY"],
|
||||||
"tin": clean_tin(str(fd.get("NIF_CIF"))),
|
"tin": clean_tin(str(fd.get("NIF_CIF"))),
|
||||||
"name": str(fd.get("NOMBRE")),
|
"name": fd.get("NOMBRE"),
|
||||||
|
"street": text_converter(fd.get("CALLE")),
|
||||||
"street": str(fd.get("CALLE")),
|
"city": fd.get("POBLACION"),
|
||||||
"city": str(fd.get("POBLACION")),
|
"province": fd.get("PROVINCIA"),
|
||||||
"province": str(fd.get("PROVINCIA")),
|
"postal_code": fd.get("CODIGO_POSTAL"),
|
||||||
"postal_code": str(fd.get("CODIGO_POSTAL")),
|
|
||||||
"country": config['CTE_COUNTRY_CODE'],
|
"country": config['CTE_COUNTRY_CODE'],
|
||||||
"language_code": config['CTE_LANGUAGE_CODE'],
|
"language_code": config['CTE_LANGUAGE_CODE'],
|
||||||
"phone_primary": clean_phone(fd.get("TELEFONO_1")),
|
"phone_primary": clean_phone(fd.get("TELEFONO_1")),
|
||||||
@ -103,7 +102,7 @@ def normalize_header_invoice_fields(fd: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
"series": config['CTE_SERIE'],
|
"series": config['CTE_SERIE'],
|
||||||
|
|
||||||
"factuges_id": int(fd['ID_FACTURA']),
|
"factuges_id": int(fd['ID_FACTURA']),
|
||||||
"reference": str(fd['REFERENCIA']),
|
"reference": fd['REFERENCIA'],
|
||||||
# Se asigna la fecha de la subida que es cuando se va a presentar a la AEAT
|
# Se asigna la fecha de la subida que es cuando se va a presentar a la AEAT
|
||||||
"invoice_date": date.today().strftime("%Y-%m-%d"),
|
"invoice_date": date.today().strftime("%Y-%m-%d"),
|
||||||
"operation_date": str(fd['FECHA_FACTURA']),
|
"operation_date": str(fd['FECHA_FACTURA']),
|
||||||
@ -137,9 +136,9 @@ def normalize_details_invoice_fields(fd: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
Normaliza campos de detalle de factura desde el registro de origen `fd`.
|
Normaliza campos de detalle de factura desde el registro de origen `fd`.
|
||||||
Escalas:
|
Escalas:
|
||||||
- quantity_value: escala 2 (cents)
|
- quantity_value: escala 2 (cents)
|
||||||
- unit_value: escala 4 (cents4)
|
- unit_amount_value: escala 4 (cents4)
|
||||||
- discount_percentage_value: escala 2 (10 -> 1000)
|
- discount_percentage_value: escala 2 (10 -> 1000)
|
||||||
- total_value: escala 4 (cents4)
|
- total_amount_value: escala 4 (cents4)
|
||||||
- iva_amount: escala 2 (cents), calculado con redondeo a 2 decimales
|
- iva_amount: escala 2 (cents), calculado con redondeo a 2 decimales
|
||||||
-
|
-
|
||||||
"""
|
"""
|
||||||
@ -159,7 +158,7 @@ def normalize_details_invoice_fields(fd: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
# --- cantidad y precio unitario ---
|
# --- cantidad y precio unitario ---
|
||||||
quantity_value = None if cantidad is None else cents(
|
quantity_value = None if cantidad is None else cents(
|
||||||
cantidad) # escala 2
|
cantidad) # escala 2
|
||||||
unit_value = None if importe_unidad is None else cents4(
|
unit_amount_value = None if importe_unidad is None else cents4(
|
||||||
importe_unidad) # escala 4
|
importe_unidad) # escala 4
|
||||||
|
|
||||||
# --- descuento de linea % (escala 2) ---
|
# --- descuento de linea % (escala 2) ---
|
||||||
@ -175,22 +174,26 @@ def normalize_details_invoice_fields(fd: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
disc_global_pct: Optional[Decimal] = None if disc_global_raw is None else Decimal(str(disc_global_raw))
|
disc_global_pct: Optional[Decimal] = None if disc_global_raw is None else Decimal(str(disc_global_raw))
|
||||||
global_discount_percentage_value = None if (
|
global_discount_percentage_value = None if (
|
||||||
disc_global_pct is None or disc_global_pct == 0) else cents(disc_global_pct)
|
disc_global_pct is None or disc_global_pct == 0) else cents(disc_global_pct)
|
||||||
global_discount_amount_value, taxable_amount_value = apply_discount_cents4(
|
|
||||||
subtotal_amount_with_dto, disc_global_pct)
|
|
||||||
|
|
||||||
total_discount_amount_value = discount_amount_value + global_discount_amount_value
|
if (global_discount_percentage_value is None):
|
||||||
|
global_discount_amount_value, taxable_amount_value, total_discount_amount_value = None, subtotal_amount_value, None
|
||||||
|
else:
|
||||||
|
global_discount_amount_value, taxable_amount_value = apply_discount_cents4(
|
||||||
|
subtotal_amount_with_dto, disc_global_pct)
|
||||||
|
total_discount_amount_value = discount_amount_value + global_discount_amount_value
|
||||||
|
|
||||||
# --- total de línea (escala 4) ---
|
# --- total de línea (escala 4) ---
|
||||||
total_value = cents4(total_det)
|
total_amount_value = cents4(total_det)
|
||||||
|
|
||||||
if total_value > 0:
|
if total_amount_value > 0:
|
||||||
# DEBE SER LO MISMO LO CALCULADO QUE LO QUE NOS VIENE POR BD DE FACTUGES
|
# DEBE SER LO MISMO LO CALCULADO QUE LO QUE NOS VIENE POR BD DE FACTUGES
|
||||||
logger.info("total con dto linea calculado: %s - total que llega: %s", subtotal_amount_with_dto, total_value)
|
logger.info("total con dto linea calculado: %s - total que llega: %s",
|
||||||
|
subtotal_amount_with_dto, total_amount_value)
|
||||||
|
|
||||||
# la base imponible sobre la que calcular impuestos es el neto menos el dto de linea y dto global
|
# la base imponible sobre la que calcular impuestos es el neto menos el dto de linea y dto global
|
||||||
base = unscale_to_decimal(taxable_amount_value, 4)
|
base = unscale_to_decimal(taxable_amount_value, 4)
|
||||||
|
|
||||||
if total_value > 0:
|
if total_amount_value > 0:
|
||||||
logger.info("base imponible calculada: %s - subtotal: %s - descuentodto: %s - descuentoglobal: %s",
|
logger.info("base imponible calculada: %s - subtotal: %s - descuentodto: %s - descuentoglobal: %s",
|
||||||
base, subtotal_amount_value, discount_amount_value, global_discount_amount_value)
|
base, subtotal_amount_value, discount_amount_value, global_discount_amount_value)
|
||||||
|
|
||||||
@ -201,7 +204,7 @@ def normalize_details_invoice_fields(fd: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
iva_frac is None or iva_frac == 0) else cents4(iva_frac)
|
iva_frac is None or iva_frac == 0) else cents4(iva_frac)
|
||||||
iva_amount_c4 = 0
|
iva_amount_c4 = 0
|
||||||
if iva_frac:
|
if iva_frac:
|
||||||
iva_amount_c4 = cents4(money_round(base * iva_frac, 2)) # escala 4
|
iva_amount_c4 = cents4(base * iva_frac) # escala 4
|
||||||
|
|
||||||
# Cuota cuota RE de la línea
|
# Cuota cuota RE de la línea
|
||||||
rec_code = None
|
rec_code = None
|
||||||
@ -218,13 +221,13 @@ def normalize_details_invoice_fields(fd: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
|
|
||||||
taxes_amount_value = iva_amount_c4 + rec_amount_c4
|
taxes_amount_value = iva_amount_c4 + rec_amount_c4
|
||||||
|
|
||||||
total_value = taxable_amount_value + taxes_amount_value
|
total_amount_value = taxable_amount_value + taxes_amount_value
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'position': int(fd.get("POSICION") or 0),
|
'position': int(fd.get("POSICION") or 0),
|
||||||
'description': rtf_a_texto_plano(str(concepto_rtf or "")),
|
'description': rtf_a_texto_plano(str(concepto_rtf or "")),
|
||||||
'quantity_value': quantity_value,
|
'quantity_value': quantity_value,
|
||||||
'unit_value': unit_value,
|
'unit_amount_value': unit_amount_value,
|
||||||
'subtotal_amount_value': subtotal_amount_value,
|
'subtotal_amount_value': subtotal_amount_value,
|
||||||
'disc_pct': disc_pct,
|
'disc_pct': disc_pct,
|
||||||
'discount_percentage_value': discount_percentage_value,
|
'discount_percentage_value': discount_percentage_value,
|
||||||
@ -233,7 +236,7 @@ def normalize_details_invoice_fields(fd: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
'global_discount_amount_value': global_discount_amount_value,
|
'global_discount_amount_value': global_discount_amount_value,
|
||||||
'total_discount_amount_value': total_discount_amount_value,
|
'total_discount_amount_value': total_discount_amount_value,
|
||||||
'taxable_amount_value': taxable_amount_value,
|
'taxable_amount_value': taxable_amount_value,
|
||||||
'total_value': total_value,
|
'total_amount_value': total_amount_value,
|
||||||
'iva_code': iva_code,
|
'iva_code': iva_code,
|
||||||
'iva_percentage_value': iva_percentage_value,
|
'iva_percentage_value': iva_percentage_value,
|
||||||
'iva_amount_value': iva_amount_c4,
|
'iva_amount_value': iva_amount_c4,
|
||||||
|
|||||||
@ -131,6 +131,32 @@ SELECT_FACTUGES_FACTURAS_CLIENTE = (
|
|||||||
"ORDER BY fac.ID, facdet.POSICION"
|
"ORDER BY fac.ID, facdet.POSICION"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SELECT_FACTUGES_FACTURAS_CLIENTE_CAB = (
|
||||||
|
"SELECT fac.VERIFACTU, fac.ID_VERIFACTU, fac.ID || '' AS ID, fac.ID_EMPRESA || '' AS ID_EMPRESA, fac.REFERENCIA, fac.FECHA_FACTURA, fac.ID_CLIENTE || '' as ID_CLIENTE, fac.NIF_CIF, fac.NOMBRE, "
|
||||||
|
"fac.CALLE, fac.POBLACION, fac.PROVINCIA, fac.CODIGO_POSTAL, fac.FECHA_ALTA, "
|
||||||
|
"fac.IMPORTE_NETO, fac.DESCUENTO, fac.IMPORTE_DESCUENTO, fac.BASE_IMPONIBLE, fac.IVA, fac.IMPORTE_IVA, fac.IMPORTE_TOTAL, "
|
||||||
|
"fac.OBSERVACIONES, fac.ID_FORMA_PAGO, fp.DESCRIPCION as DES_FORMA_PAGO, fac.ID_TIPO_IVA, ti.REFERENCIA as DES_TIPO_IVA, fac.RECARGO_EQUIVALENCIA, fac.RE, fac.IMPORTE_RE, "
|
||||||
|
"fac.ID_CLIENTE, fac.NIF_CIF, fac.NOMBRE, fac.CALLE, fac.POBLACION, fac.PROVINCIA, fac.CODIGO_POSTAL, "
|
||||||
|
"cc.TELEFONO_1, cc.TELEFONO_2, cc.MOVIL_1, cc.MOVIL_2, cc.EMAIL_1, cc.EMAIL_2, cc.PAGINA_WEB "
|
||||||
|
"FROM FACTURAS_CLIENTE AS fac "
|
||||||
|
"LEFT JOIN CONTACTOS AS cc ON fac.ID_CLIENTE = cc.ID "
|
||||||
|
"LEFT JOIN FORMAS_PAGO AS fp ON fac.ID_FORMA_PAGO = fp.ID "
|
||||||
|
"LEFT JOIN TIPOS_IVA AS ti ON fac.ID_TIPO_IVA = ti.ID "
|
||||||
|
"WHERE "
|
||||||
|
"(fac.VERIFACTU = 1) "
|
||||||
|
"AND (fac.ID_VERIFACTU is null)"
|
||||||
|
"ORDER BY fac.ID"
|
||||||
|
)
|
||||||
|
|
||||||
|
SELECT_FACTUGES_FACTURAS_CLIENTE_DET = (
|
||||||
|
"SELECT facdet.ID || '' as ID_DET, facdet.ID_FACTURA, facdet.POSICION, facdet.TIPO_DETALLE, facdet.ID_ARTICULO, facdet.CONCEPTO, facdet.CANTIDAD, "
|
||||||
|
"facdet.IMPORTE_UNIDAD, facdet.DESCUENTO as DESCUENTO_DET, facdet.IMPORTE_TOTAL as IMPORTE_TOTAL_DET, facdet.VISIBLE, facdet.FECHA_ALTA as FECHA_ALTA_DET, facdet.FECHA_MODIFICACION as FECHA_MODIFICACION_DET "
|
||||||
|
"FROM FACTURAS_CLIENTE_DETALLES AS facdet "
|
||||||
|
"WHERE "
|
||||||
|
"(facdet.ID = ?) "
|
||||||
|
"ORDER BY facdet.POSICION"
|
||||||
|
)
|
||||||
|
|
||||||
UPDATE_FACTUGES_LINK = (
|
UPDATE_FACTUGES_LINK = (
|
||||||
"UPDATE FACTURAS_CLIENTE "
|
"UPDATE FACTURAS_CLIENTE "
|
||||||
"SET VERIFACTU=?, "
|
"SET VERIFACTU=?, "
|
||||||
@ -160,6 +186,7 @@ consulta_sql_customer_invoices_issue = (
|
|||||||
"LEFT JOIN customer_invoice_taxes cit on (ci.id = cit.invoice_id) "
|
"LEFT JOIN customer_invoice_taxes cit on (ci.id = cit.invoice_id) "
|
||||||
"LEFT JOIN verifactu_records vr on (ci.id = vr.invoice_id) "
|
"LEFT JOIN verifactu_records vr on (ci.id = vr.invoice_id) "
|
||||||
"WHERE (ci.is_proforma = 0) AND (ci.status= 'issued') "
|
"WHERE (ci.is_proforma = 0) AND (ci.status= 'issued') "
|
||||||
|
"AND (company_id = %s) "
|
||||||
"AND (vr.estado <> 'Correcto') "
|
"AND (vr.estado <> 'Correcto') "
|
||||||
"order by reference"
|
"order by reference"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -376,7 +376,7 @@ def insert_item_and_taxes(cur, invoice_id: str, fields: Dict[str, Any]) -> None:
|
|||||||
fields.get("position"),
|
fields.get("position"),
|
||||||
fields.get("description"),
|
fields.get("description"),
|
||||||
fields.get("quantity_value"),
|
fields.get("quantity_value"),
|
||||||
fields.get("unit_value"),
|
fields.get("unit_amount_value"),
|
||||||
fields.get("subtotal_amount_value"),
|
fields.get("subtotal_amount_value"),
|
||||||
fields.get("discount_percentage_value"),
|
fields.get("discount_percentage_value"),
|
||||||
fields.get("discount_amount_value"),
|
fields.get("discount_amount_value"),
|
||||||
@ -384,7 +384,7 @@ def insert_item_and_taxes(cur, invoice_id: str, fields: Dict[str, Any]) -> None:
|
|||||||
fields.get("global_discount_amount_value"),
|
fields.get("global_discount_amount_value"),
|
||||||
fields.get("total_discount_amount_value"),
|
fields.get("total_discount_amount_value"),
|
||||||
fields.get("taxable_amount_value"),
|
fields.get("taxable_amount_value"),
|
||||||
fields.get("total_value"),
|
fields.get("total_amount_value"),
|
||||||
fields.get("iva_code"),
|
fields.get("iva_code"),
|
||||||
fields.get("iva_percentage_value"),
|
fields.get("iva_percentage_value"),
|
||||||
fields.get("iva_amount_value"),
|
fields.get("iva_amount_value"),
|
||||||
@ -396,8 +396,8 @@ def insert_item_and_taxes(cur, invoice_id: str, fields: Dict[str, Any]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# logger.info("Inserting item tax %s code=%s base=%s tax=%s",
|
# logger.info("Inserting item tax %s code=%s base=%s tax=%s",
|
||||||
# item_id, fields.get('tax_code'), fields.get('total_value'), fields.get('tax_amount'))
|
# item_id, fields.get('tax_code'), fields.get('total_amount_value'), fields.get('tax_amount'))
|
||||||
# cur.execute(
|
# cur.execute(
|
||||||
# SQL.INSERT_INVOICE_ITEM_TAX, (str(uuid7()), item_id, fields.get('tax_code'),
|
# SQL.INSERT_INVOICE_ITEM_TAX, (str(uuid7()), item_id, fields.get('tax_code'),
|
||||||
# fields.get('total_value'), fields.get('tax_amount'))
|
# fields.get('total_amount_value'), fields.get('tax_amount'))
|
||||||
# )
|
# )
|
||||||
|
|||||||
267
app/db/sync_invoices_factuges_REST_API.py
Normal file
267
app/db/sync_invoices_factuges_REST_API.py
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from uuid6 import uuid7
|
||||||
|
|
||||||
|
from app.config import load_config, logger
|
||||||
|
from app.utils import (
|
||||||
|
create_invoice_header,
|
||||||
|
insert_invoice_REST_API_FACTUGES,
|
||||||
|
insert_item_and_taxes,
|
||||||
|
validar_nif_verifacti,
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import normalizations as NORMALIZA
|
||||||
|
from . import sql_sentences as SQL
|
||||||
|
|
||||||
|
|
||||||
|
def sync_invoices_factuges_REST_API(conn_factuges, conn_mysql, last_execution_date):
|
||||||
|
config = load_config()
|
||||||
|
|
||||||
|
# BUSCAMOS FACTURAS ENVIADAS A VERIFACTU EN FACTUGES, PARA SUBIRLAS AL NUEVO PROGRAMA DE FACTURACIÓN
|
||||||
|
# Crear un cursor para ejecutar consultas SQL
|
||||||
|
cursor_FactuGES = None
|
||||||
|
try:
|
||||||
|
cursor_FactuGES = conn_factuges.cursor()
|
||||||
|
# Ejecutar la consulta de FACTURAS_CLIENTE
|
||||||
|
cursor_FactuGES.execute(SQL.SELECT_FACTUGES_FACTURAS_CLIENTE)
|
||||||
|
filas = cursor_FactuGES.fetchall()
|
||||||
|
except Exception as e:
|
||||||
|
if cursor_FactuGES is not None:
|
||||||
|
cursor_FactuGES.close()
|
||||||
|
logger.error(
|
||||||
|
f"(ERROR) Failed to fetch from database:{config['FACTUGES_DATABASE']} - using user:{config['FACTUGES_USER']}"
|
||||||
|
)
|
||||||
|
logger.error(e)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
# Obtener los nombres de las columnas
|
||||||
|
columnas = [desc[0] for desc in cursor_FactuGES.description]
|
||||||
|
cursor_FactuGES.close()
|
||||||
|
|
||||||
|
# Convertir las filas en diccionarios con nombres de columnas como claves
|
||||||
|
tuplas_seleccionadas = []
|
||||||
|
for fila in filas:
|
||||||
|
tupla = dict(zip(columnas, fila))
|
||||||
|
tuplas_seleccionadas.append(tupla)
|
||||||
|
# Verificar si hay filas en el resultado
|
||||||
|
if tuplas_seleccionadas:
|
||||||
|
sync_invoices_from_FACTUGES(
|
||||||
|
conn_mysql, tuplas_seleccionadas, conn_factuges, config
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.info("There are NOT new FACTURAS rows since the last run.")
|
||||||
|
|
||||||
|
|
||||||
|
def sync_invoices_from_FACTUGES(conn_mysql, filas, conn_factuges, config):
|
||||||
|
# Insertaremos cada factura existente en las filas a la nueva estructura de tablas del programa nuevo de facturacion.
|
||||||
|
# logger.info(f"FACTURAS_CLIENTE_DETALLE rows to be processed: {len(filas)}")
|
||||||
|
|
||||||
|
cursorMySQL = None
|
||||||
|
cursor_FactuGES = None
|
||||||
|
factuges_id_anterior = None
|
||||||
|
tin_anterior = None
|
||||||
|
num_fac_procesed = 0
|
||||||
|
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()
|
||||||
|
cursor_FactuGES = conn_factuges.cursor()
|
||||||
|
|
||||||
|
# Insertar datos en la tabla 'customer_invoices'
|
||||||
|
for facturas in filas:
|
||||||
|
# Preparamos los campos para evitar errores
|
||||||
|
customer_fields = NORMALIZA.normalize_customer_fields(facturas)
|
||||||
|
header_invoice_fields = NORMALIZA.normalize_header_invoice_fields(
|
||||||
|
facturas
|
||||||
|
)
|
||||||
|
details_invoice_fields = NORMALIZA.normalize_details_invoice_fields(
|
||||||
|
facturas
|
||||||
|
)
|
||||||
|
|
||||||
|
factuges_id = int(facturas["ID"])
|
||||||
|
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, config)
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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"]
|
||||||
|
|
||||||
|
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: "
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
logger.info(sync_notes)
|
||||||
|
cursor_FactuGES.execute(SQL.UPDATE_FACTUGES_LINK, (sync_result,
|
||||||
|
invoice_id, sync_notes, factuges_id),)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ---- 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 el estado asociado al payload actual
|
||||||
|
sync_result_anterior = sync_result
|
||||||
|
sync_notes_anterior = sync_notes
|
||||||
|
|
||||||
|
# ---- 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
|
||||||
|
|
||||||
|
# --- 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, config)
|
||||||
|
logger.info(resultado_rest)
|
||||||
|
num_fac_procesed += 1
|
||||||
|
|
||||||
|
if not resultado_rest.get("ok"):
|
||||||
|
logger.error(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
|
||||||
|
logger.error(str(e))
|
||||||
|
raise e # Re-lanzar la excepción para detener el procesamiento
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Cerrar la conexión
|
||||||
|
if cursorMySQL is not None:
|
||||||
|
cursorMySQL.close()
|
||||||
|
if cursor_FactuGES is not None:
|
||||||
|
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:
|
||||||
|
"""
|
||||||
|
Inserta registro verifactu vacio y devuelve id
|
||||||
|
"""
|
||||||
|
id = str(uuid7())
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Inserting verifactu record %s %s %s",
|
||||||
|
id,
|
||||||
|
hif.get("reference"),
|
||||||
|
hif.get("invoice_date"),
|
||||||
|
)
|
||||||
|
cur.execute( # type: ignore
|
||||||
|
SQL.INSERT_VERIFACTU_RECORD,
|
||||||
|
(id, invoice_id, config["CTE_STATUS_VERIFACTU"]),
|
||||||
|
)
|
||||||
|
return id
|
||||||
@ -19,7 +19,7 @@ def sync_invoices_verifactu(conn_mysql, last_execution_date):
|
|||||||
try:
|
try:
|
||||||
cursor_mysql = conn_mysql.cursor()
|
cursor_mysql = conn_mysql.cursor()
|
||||||
# Ejecutar la consulta de customer invoices a enviar
|
# Ejecutar la consulta de customer invoices a enviar
|
||||||
cursor_mysql.execute(SQL.consulta_sql_customer_invoices_issue)
|
cursor_mysql.execute(SQL.consulta_sql_customer_invoices_issue, (str(config["CTE_COMPANY_ID"]),))
|
||||||
filas = cursor_mysql.fetchall()
|
filas = cursor_mysql.fetchall()
|
||||||
|
|
||||||
# Obtener los nombres de las columnas
|
# Obtener los nombres de las columnas
|
||||||
@ -119,7 +119,7 @@ def procesar_factura_verifactu(
|
|||||||
|
|
||||||
# Creamos registro de factura en verifactu
|
# Creamos registro de factura en verifactu
|
||||||
if factura.get('uuid') == '':
|
if factura.get('uuid') == '':
|
||||||
# logger.info(f"Send to create Verifactu: {factura}")
|
# logger.info(f"Send to create Verifactu: {factura}")
|
||||||
respuesta = crear_factura_verifacti(factura, config)
|
respuesta = crear_factura_verifacti(factura, config)
|
||||||
|
|
||||||
if respuesta.get("status") == 200 and respuesta.get("ok"):
|
if respuesta.get("status") == 200 and respuesta.get("ok"):
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from app.db import (
|
|||||||
get_factuges_connection,
|
get_factuges_connection,
|
||||||
get_mysql_connection,
|
get_mysql_connection,
|
||||||
sync_invoices_deleted_factuges,
|
sync_invoices_deleted_factuges,
|
||||||
sync_invoices_factuges,
|
sync_invoices_factuges_REST_API,
|
||||||
)
|
)
|
||||||
from app.utils import actualizar_fecha_ultima_ejecucion, obtener_fecha_ultima_ejecucion
|
from app.utils import actualizar_fecha_ultima_ejecucion, obtener_fecha_ultima_ejecucion
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ def main():
|
|||||||
logger.info(
|
logger.info(
|
||||||
">>>>>>>>>>> INI Sync invoices FactuGES escritorio to FactuGES web")
|
">>>>>>>>>>> INI Sync invoices FactuGES escritorio to FactuGES web")
|
||||||
sync_invoices_deleted_factuges(conn_factuges, conn_mysql, last_execution_date_local_tz)
|
sync_invoices_deleted_factuges(conn_factuges, conn_mysql, last_execution_date_local_tz)
|
||||||
sync_invoices_factuges(conn_factuges, conn_mysql, last_execution_date_local_tz)
|
sync_invoices_factuges_REST_API(conn_factuges, conn_mysql, last_execution_date_local_tz)
|
||||||
|
|
||||||
# Confirmar los cambios
|
# Confirmar los cambios
|
||||||
conn_mysql.commit()
|
conn_mysql.commit()
|
||||||
|
|||||||
@ -67,7 +67,7 @@ def calc_discount_cents4(subtotal_cents4: int, disc_pct: Optional[Decimal | floa
|
|||||||
pct = Decimal(str(disc_pct or 0))
|
pct = Decimal(str(disc_pct or 0))
|
||||||
# descuento = round(subtotal * pct / 100) en la MISMA escala (×10000)
|
# descuento = round(subtotal * pct / 100) en la MISMA escala (×10000)
|
||||||
disc = (Decimal(subtotal_cents4) * pct / Decimal(100)).to_integral_value(rounding=ROUND_HALF_UP)
|
disc = (Decimal(subtotal_cents4) * pct / Decimal(100)).to_integral_value(rounding=ROUND_HALF_UP)
|
||||||
return disc
|
return int(disc)
|
||||||
|
|
||||||
|
|
||||||
def apply_discount_cents4(subtotal_cents4: int, disc_pct: Optional[Decimal | float | int]) -> Tuple[int, int]:
|
def apply_discount_cents4(subtotal_cents4: int, disc_pct: Optional[Decimal | float | int]) -> Tuple[int, int]:
|
||||||
|
|||||||
93
app/utils/payloads_factuges.py
Normal file
93
app/utils/payloads_factuges.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from uuid6 import uuid7
|
||||||
|
|
||||||
|
|
||||||
|
def none_to_empty(value: Any) -> str:
|
||||||
|
return "" if value is None else value
|
||||||
|
|
||||||
|
|
||||||
|
def create_invoice_header(
|
||||||
|
cf,
|
||||||
|
hif,
|
||||||
|
payment_method_id,
|
||||||
|
payment_method_description
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"id": str(uuid7()),
|
||||||
|
"factuges_id": str(hif.get("factuges_id")),
|
||||||
|
"company_id": hif.get("company_id"),
|
||||||
|
"is_proforma": hif.get("is_proforma"),
|
||||||
|
"status": hif.get("status"),
|
||||||
|
"series": hif.get("series"),
|
||||||
|
"reference": hif.get("reference"),
|
||||||
|
"description": hif.get("description"),
|
||||||
|
"invoice_date": hif.get("invoice_date"),
|
||||||
|
"operation_date": hif.get("operation_date"),
|
||||||
|
"notes": none_to_empty(hif.get("notes")),
|
||||||
|
"language_code": cf.get("language_code"),
|
||||||
|
|
||||||
|
"subtotal_amount_value": str(none_to_empty(hif.get("subtotal_amount_value"))),
|
||||||
|
"global_discount_percentage_value": 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": {
|
||||||
|
"is_company": cf["is_company"],
|
||||||
|
"name": cf["name"],
|
||||||
|
"tin": cf["tin"],
|
||||||
|
"street": cf["street"],
|
||||||
|
"city": cf["city"],
|
||||||
|
"province": cf["province"],
|
||||||
|
"postal_code": cf["postal_code"],
|
||||||
|
"country": cf["country"],
|
||||||
|
"language_code": cf["language_code"],
|
||||||
|
"phone_primary": none_to_empty(cf["phone_primary"]),
|
||||||
|
"phone_secondary": none_to_empty(cf["phone_secondary"]),
|
||||||
|
"mobile_primary": none_to_empty(cf["mobile_primary"]),
|
||||||
|
"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": str(none_to_empty(cf["website"])),
|
||||||
|
},
|
||||||
|
|
||||||
|
"items": [],
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def insert_item_and_taxes(fields) -> Dict[str, Any]:
|
||||||
|
# logger.info(fields)
|
||||||
|
return {
|
||||||
|
"item_id": str(uuid7()),
|
||||||
|
"position": str(fields.get("position")),
|
||||||
|
"description": str(none_to_empty(fields.get("description"))),
|
||||||
|
"quantity_value": str(none_to_empty(fields.get("quantity_value"))),
|
||||||
|
"unit_amount_value": str(none_to_empty(fields.get("unit_amount_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(none_to_empty(fields.get("total_discount_amount_value"))),
|
||||||
|
"taxable_amount_value": str(fields.get("taxable_amount_value")),
|
||||||
|
"total_amount_value": str(fields.get("total_amount_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",
|
||||||
|
# item_id, fields.get('tax_code'), fields.get('total_amount_value'), fields.get('tax_amount'))
|
||||||
|
# cur.execute(
|
||||||
|
# SQL.INSERT_INVOICE_ITEM_TAX, (str(uuid7()), item_id, fields.get('tax_code'),
|
||||||
|
# fields.get('total_amount_value'), fields.get('tax_amount'))
|
||||||
|
# )
|
||||||
@ -4,6 +4,9 @@ import requests
|
|||||||
|
|
||||||
from app.config import logger
|
from app.config import logger
|
||||||
|
|
||||||
|
SUCCESS_CODES = {200, 201}
|
||||||
|
VALIDATION_ERROR_CODES = {400, 422}
|
||||||
|
|
||||||
|
|
||||||
def estado_factura(uuid_str: str,
|
def estado_factura(uuid_str: str,
|
||||||
config,
|
config,
|
||||||
@ -18,7 +21,7 @@ def estado_factura(uuid_str: str,
|
|||||||
- error: mensaje de error si algo falló
|
- error: mensaje de error si algo falló
|
||||||
"""
|
"""
|
||||||
url = config['VERIFACTU_BASE_URL'] + "/verifactu/status"
|
url = config['VERIFACTU_BASE_URL'] + "/verifactu/status"
|
||||||
timeout: int = 10
|
timeout: int = 120
|
||||||
headers = {"Content-Type": "application/json",
|
headers = {"Content-Type": "application/json",
|
||||||
"Accept": "application/json"}
|
"Accept": "application/json"}
|
||||||
headers["Authorization"] = "Bearer " + config['VERIFACTU_API_KEY']
|
headers["Authorization"] = "Bearer " + config['VERIFACTU_API_KEY']
|
||||||
@ -73,7 +76,7 @@ def crear_factura_verifacti(payload,
|
|||||||
- error: mensaje de error si algo falló
|
- error: mensaje de error si algo falló
|
||||||
"""
|
"""
|
||||||
url = config['VERIFACTU_BASE_URL'] + "/verifactu/create"
|
url = config['VERIFACTU_BASE_URL'] + "/verifactu/create"
|
||||||
timeout: int = 10
|
timeout: int = 120
|
||||||
headers = {"Content-Type": "application/json",
|
headers = {"Content-Type": "application/json",
|
||||||
"Accept": "application/json"}
|
"Accept": "application/json"}
|
||||||
headers["Authorization"] = "Bearer " + config['VERIFACTU_API_KEY']
|
headers["Authorization"] = "Bearer " + config['VERIFACTU_API_KEY']
|
||||||
@ -130,7 +133,7 @@ def validar_nif_verifacti(
|
|||||||
- error: mensaje de error si algo falló
|
- error: mensaje de error si algo falló
|
||||||
"""
|
"""
|
||||||
url = config['VERIFACTU_BASE_URL'] + "/nifs/validar"
|
url = config['VERIFACTU_BASE_URL'] + "/nifs/validar"
|
||||||
timeout: int = 10
|
timeout: int = 120
|
||||||
headers = {"Content-Type": "application/json",
|
headers = {"Content-Type": "application/json",
|
||||||
"Accept": "application/json"}
|
"Accept": "application/json"}
|
||||||
headers["Authorization"] = "Bearer " + config['VERIFACTU_NIFS_API_KEY']
|
headers["Authorization"] = "Bearer " + config['VERIFACTU_NIFS_API_KEY']
|
||||||
@ -142,13 +145,11 @@ def validar_nif_verifacti(
|
|||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
url, json=payload, headers=headers, timeout=timeout)
|
url, json=payload, headers=headers, timeout=timeout)
|
||||||
if resp.status_code != 200:
|
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()
|
data = resp.json()
|
||||||
resultado = data.get("resultado", "NO IDENTIFICADO")
|
resultado = data.get("resultado", "NO IDENTIFICADO")
|
||||||
logger.info(f"Resultado Verifacti: {resultado}")
|
logger.info(f"Resultado Verifacti: {resultado}")
|
||||||
|
|
||||||
return resultado == "IDENTIFICADO"
|
return resultado == "IDENTIFICADO"
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
@ -160,7 +161,8 @@ def validar_nif_verifacti(
|
|||||||
|
|
||||||
|
|
||||||
def insert_invoice_REST_API_FACTUGES(
|
def insert_invoice_REST_API_FACTUGES(
|
||||||
payload
|
payload,
|
||||||
|
config,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Llama a la REST API de FACTUGES para insertar una proforma aprobada.
|
Llama a la REST API de FACTUGES para insertar una proforma aprobada.
|
||||||
@ -168,35 +170,38 @@ def insert_invoice_REST_API_FACTUGES(
|
|||||||
Retorna:
|
Retorna:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
url = "http://192.168.0.104:3002/api/v1/proformas"
|
url = config['FWEB_API_URL']
|
||||||
timeout: int = 10
|
timeout: int = config['FWEB_API_TIMEOUT']
|
||||||
headers = {"Content-Type": "application/json",
|
headers = {
|
||||||
"Accept": "application/json"}
|
"Content-Type": "application/json",
|
||||||
"""headers["Authorization"] = "Bearer " + config['VERIFACTU_NIFS_API_KEY']
|
"Accept": "application/json"
|
||||||
payload = {"nif": nif,
|
"Bearer " + config['FWEB_API_KEY']
|
||||||
"nombre": nombre,
|
}
|
||||||
}
|
|
||||||
"""
|
logger.info("-----------------------------------------------------------------------------")
|
||||||
|
logger.info(payload)
|
||||||
|
logger.info("-----------------------------------------------------------------------------")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
url, json=payload, headers=headers, timeout=timeout)
|
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:
|
try:
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
logger.info(data)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return {"ok": False, "status": 200, "error": "Respuesta 200 sin JSON válido", "raw": resp.text}
|
data = None
|
||||||
return {"ok": True, "status": 200, "data": data}
|
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:
|
try:
|
||||||
body = resp.json()
|
body = resp.json()
|
||||||
msg = body.get(
|
msg = (body.get("title") + str(body.get("errors"))
|
||||||
"error") or "Error de validación (400) sin detalle"
|
or f"Error de validación ({resp.status_code}) sin detalle")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
msg = f"Error de validación (400): {resp.text}"
|
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
|
# Otros códigos: devuelve mensaje genérico, intenta extraer JSON si existe
|
||||||
try:
|
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}
|
return {"ok": False, "status": resp.status_code, "error": f"HTTP {resp.status_code}", "raw": resp.text}
|
||||||
|
|
||||||
except requests.RequestException as e:
|
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)}
|
return {"ok": False, "status": 500, "error": str(e)}
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.error("Respuesta no es JSON válido: %s", e)
|
logger.error("Respuesta no es JSON válido: %s", e)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from app.config import logger
|
from app.config import logger
|
||||||
|
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ def text_converter(texto, charset_destino='ISO8859_1', longitud_maxima=None):
|
|||||||
try:
|
try:
|
||||||
# Convertir el texto al charset especificado
|
# Convertir el texto al charset especificado
|
||||||
texto_convertido = texto.encode(
|
texto_convertido = texto.encode(
|
||||||
charset_destino, 'ignore').decode(charset_destino)
|
charset_destino, 'ignore').decode("cp1252")
|
||||||
|
|
||||||
# Si se especifica una longitud máxima, truncar el texto
|
# Si se especifica una longitud máxima, truncar el texto
|
||||||
if longitud_maxima and len(texto_convertido) > longitud_maxima:
|
if longitud_maxima and len(texto_convertido) > longitud_maxima:
|
||||||
|
|||||||
@ -1,50 +0,0 @@
|
|||||||
ENV = development
|
|
||||||
LOCAL_TZ = Europe/Madrid
|
|
||||||
STATE_PATH = ./
|
|
||||||
#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
|
|
||||||
CONFIGURACION ACANA
|
|
||||||
CTE_COMPANY_ID = '019a9667-6a65-767a-a737-48234ee50a3a'
|
|
||||||
VERIFACTU_API_KEY = vf_test_ei8WYAvEq5dhSdEyQVjgCS8NZaNpEK2BljSHSUXf+Y0=
|
|
||||||
|
|
||||||
#DESARROLLO ALONSO Y SAL
|
|
||||||
#FACTUGES_HOST = 192.168.0.144
|
|
||||||
#FACTUGES_PORT = 3050
|
|
||||||
#FACTUGES_DATABASE = C:\Codigo Arribas2\Output\Debug\Database\FACTUGES.FDB
|
|
||||||
#FACTUGES_USER = sysdba
|
|
||||||
#FACTUGES_PASSWORD = masterkey
|
|
||||||
#CONFIGURACION ACANA
|
|
||||||
#CTE_COMPANY_ID = '019a9667-6a65-767a-a737-48234ee50a3a'
|
|
||||||
#CTE_COMPANY_ID_PRODUCCION = '019ac4f2-9502-731a-a6db-525475e85bc7'
|
|
||||||
#VERIFACTU_API_KEY = vf_test_C03HL2F0X5OXSDRunjNFoMxD4IrRfK3kCC8PfcvCENI=
|
|
||||||
|
|
||||||
#DESARROLLO
|
|
||||||
FWEB_MYSQL_HOST = localhost
|
|
||||||
FWEB_MYSQL_PORT = 3306
|
|
||||||
FWEB_MYSQL_DATABASE = uecko_erp_sync
|
|
||||||
FWEB_MYSQL_USER = rodax
|
|
||||||
FWEB_MYSQL_PASSWORD = rodax
|
|
||||||
|
|
||||||
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
|
|
||||||
CTE_SYNC_RESULT_OK = 1
|
|
||||||
CTE_SYNC_RESULT_FAIL = 2
|
|
||||||
|
|
||||||
VERIFACTU_BASE_URL = https://api.verifacti.com/
|
|
||||||
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,41 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
ENV = development
|
|
||||||
LOCAL_TZ = Europe/Madrid
|
|
||||||
#LOG_PATH = ./app.log
|
|
||||||
|
|
||||||
FACTUGES_HOST = 192.168.0.101
|
|
||||||
FACTUGES_PORT = 3050
|
|
||||||
FACTUGES_DATABASE = C:\FactuGES\FACTUGES.FDB
|
|
||||||
FACTUGES_USER = sysdba
|
|
||||||
FACTUGES_PASSWORD = masterkey
|
|
||||||
|
|
||||||
FACTUGES_ID_EMPRESA = 1
|
|
||||||
FACTUGES_CONTRATO_ID_TIENDA = 1
|
|
||||||
FACTUGES_CONTRATO_SITUACION = "PENDIENTE"
|
|
||||||
FACTUGES_CONTRATO_ENVIADA_REVISADA = 10
|
|
||||||
FACTUGES_CONTRATO_TIPO_DETALLE = "Concepto"
|
|
||||||
FACTUGES_NOMBRE_TARIFA = TARIFA 2024
|
|
||||||
FACTUGES_PRECIO_PUNTO = 3.31
|
|
||||||
|
|
||||||
PRO_FWEB_MYSQL_HOST = 192.168.0.250
|
|
||||||
PRO_FWEB_MYSQL_PORT = 3306
|
|
||||||
PRO_FWEB_MYSQL_DATABASE = rodax_db
|
|
||||||
PRO_FWEB_MYSQL_USER = rodax_usr
|
|
||||||
PRO_FWEB_MYSQL_PASSWORD = supersecret
|
|
||||||
|
|
||||||
DEV_FWEB_MYSQL_HOST = 192.168.0.104
|
|
||||||
DEV_FWEB_MYSQL_PORT = 3306
|
|
||||||
DEV_FWEB_MYSQL_DATABASE = uecko_erp_sync
|
|
||||||
DEV_FWEB_MYSQL_USER = rodax
|
|
||||||
DEV_FWEB_MYSQL_PASSWORD = rodax
|
|
||||||
|
|
||||||
BREVO_API_KEY = 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
|
|
||||||
BREVO_EMAIL_TEMPLATE = 1
|
|
||||||
MAIL_FROM = 'no-reply@presupuestos.uecko.com'
|
|
||||||
MAIL_TO = 'soporte@rodax-software.com'
|
|
||||||
|
|
||||||
VERIFACTU_BASE_URL = https://api.verifacti.com/
|
|
||||||
VERIFACTU_API_KEY = vf_test_kY9FoI86dH+g1a5hmEnb/0YcLTlMFlu+tpp9iMZp020=
|
|
||||||
VERIFACTU_NIFS_API_KEY = vfn_osYpNdqSzAdTAHpazXG2anz4F3o0gfbSb5FFrCBZcno=
|
|
||||||
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
ENV = development
|
|
||||||
LOCAL_TZ = Europe/Madrid
|
|
||||||
STATE_PATH = ./
|
|
||||||
#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
|
|
||||||
FWEB_MYSQL_HOST = localhost
|
|
||||||
FWEB_MYSQL_PORT = 3306
|
|
||||||
FWEB_MYSQL_DATABASE = uecko_erp_sync
|
|
||||||
FWEB_MYSQL_USER = rodax
|
|
||||||
FWEB_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'
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
ENV = development
|
|
||||||
LOCAL_TZ = Europe/Madrid
|
|
||||||
STATE_PATH = ./
|
|
||||||
#LOG_PATH = ./app.log
|
|
||||||
|
|
||||||
#DESARROLLO
|
|
||||||
FWEB_MYSQL_HOST = localhost
|
|
||||||
FWEB_MYSQL_PORT = 3306
|
|
||||||
FWEB_MYSQL_DATABASE = uecko_erp_sync
|
|
||||||
FWEB_MYSQL_USER = rodax
|
|
||||||
FWEB_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'
|
|
||||||
@ -14,17 +14,27 @@ STATE_PATH = ./
|
|||||||
#VERIFACTU_API_KEY = vf_prod_yfjonNPv2E4Fij+5J0hct0zCgUeFYT2dZzb23UZlM+Q=
|
#VERIFACTU_API_KEY = vf_prod_yfjonNPv2E4Fij+5J0hct0zCgUeFYT2dZzb23UZlM+Q=
|
||||||
#CTE_SERIE = 'F25'
|
#CTE_SERIE = 'F25'
|
||||||
|
|
||||||
#DESARROLLO ACANA
|
#DESARROLLO RODAX
|
||||||
FACTUGES_HOST = 192.168.0.105
|
FACTUGES_HOST = 192.168.0.156
|
||||||
FACTUGES_PORT = 3050
|
FACTUGES_PORT = 3050
|
||||||
FACTUGES_DATABASE = C:\Codigo Acana\Output\Debug\Database\FACTUGES.FDB
|
FACTUGES_DATABASE = C:\Codigo\Output\Debug\Database\FACTUGES.FDB
|
||||||
FACTUGES_USER = sysdba
|
FACTUGES_USER = sysdba
|
||||||
FACTUGES_PASSWORD = masterkey
|
FACTUGES_PASSWORD = masterkey
|
||||||
#CONFIGURACION ACANA
|
CTE_COMPANY_ID = '5e4dc5b3-96b9-4968-9490-14bd032fec5f'
|
||||||
CTE_COMPANY_ID = '019a9667-6a65-767a-a737-48234ee50a3a'
|
|
||||||
VERIFACTU_API_KEY = vf_test_ei8WYAvEq5dhSdEyQVjgCS8NZaNpEK2BljSHSUXf+Y0=
|
VERIFACTU_API_KEY = vf_test_ei8WYAvEq5dhSdEyQVjgCS8NZaNpEK2BljSHSUXf+Y0=
|
||||||
CTE_SERIE = 'F25/'
|
CTE_SERIE = 'F25/'
|
||||||
|
|
||||||
|
#DESARROLLO ACANA
|
||||||
|
#FACTUGES_HOST = 192.168.0.109
|
||||||
|
#FACTUGES_PORT = 3050
|
||||||
|
#FACTUGES_DATABASE = C:\Codigo Acana\Output\Debug\Database\FACTUGES.FDB
|
||||||
|
#FACTUGES_USER = sysdba
|
||||||
|
#FACTUGES_PASSWORD = masterkey
|
||||||
|
#CONFIGURACION ACANA
|
||||||
|
#CTE_COMPANY_ID = '019a9667-6a65-767a-a737-48234ee50a3a'
|
||||||
|
#VERIFACTU_API_KEY = vf_test_ei8WYAvEq5dhSdEyQVjgCS8NZaNpEK2BljSHSUXf+Y0=
|
||||||
|
#CTE_SERIE = 'F25/'
|
||||||
|
|
||||||
#DESARROLLO ALONSO Y SAL
|
#DESARROLLO ALONSO Y SAL
|
||||||
#FACTUGES_HOST = 192.168.0.146
|
#FACTUGES_HOST = 192.168.0.146
|
||||||
#FACTUGES_PORT = 3050
|
#FACTUGES_PORT = 3050
|
||||||
@ -37,13 +47,20 @@ CTE_SERIE = 'F25/'
|
|||||||
#VERIFACTU_API_KEY = vf_test_C03HL2F0X5OXSDRunjNFoMxD4IrRfK3kCC8PfcvCENI=
|
#VERIFACTU_API_KEY = vf_test_C03HL2F0X5OXSDRunjNFoMxD4IrRfK3kCC8PfcvCENI=
|
||||||
#CTE_SERIE = 'F25/'
|
#CTE_SERIE = 'F25/'
|
||||||
|
|
||||||
#DESARROLLO
|
|
||||||
|
|
||||||
|
|
||||||
|
#DESARROLLO DESTINO FACTUGES WEB
|
||||||
FWEB_MYSQL_HOST = localhost
|
FWEB_MYSQL_HOST = localhost
|
||||||
FWEB_MYSQL_PORT = 3306
|
FWEB_MYSQL_PORT = 3306
|
||||||
FWEB_MYSQL_DATABASE = uecko_erp_sync
|
FWEB_MYSQL_DATABASE = uecko_erp_sync
|
||||||
FWEB_MYSQL_USER = rodax
|
FWEB_MYSQL_USER = rodax
|
||||||
FWEB_MYSQL_PASSWORD = rodax
|
FWEB_MYSQL_PASSWORD = rodax
|
||||||
|
|
||||||
|
FWEB_API_URL=http://192.168.0.104:3002/api/v1/factuges
|
||||||
|
FWEB_API_KEY=XXXX
|
||||||
|
FWEB_API_TIMEOUT=120
|
||||||
|
|
||||||
#PRODUCCION RODAX
|
#PRODUCCION RODAX
|
||||||
#FWEB_MYSQL_HOST = 192.168.0.250
|
#FWEB_MYSQL_HOST = 192.168.0.250
|
||||||
#FWEB_MYSQL_PORT = 3306
|
#FWEB_MYSQL_PORT = 3306
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = factuges-sync
|
name = factuges-sync
|
||||||
version = 0.1.7
|
version = 0.2.1
|
||||||
description = ETL job to sync data from legacy DB to MariaDB
|
description = ETL job to sync data from legacy DB to MariaDB
|
||||||
author = Rodax Software
|
author = Rodax Software
|
||||||
author_email = info@rodax-software.com
|
author_email = info@rodax-software.com
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user