2025-09-04 16:54:32 +00:00
import logging
2025-10-29 16:08:14 +00:00
import textwrap
2025-11-06 19:18:37 +00:00
from typing import Dict , Any , Optional , Tuple
2025-11-05 17:43:40 +00:00
from uuid6 import uuid7
2025-09-04 16:54:32 +00:00
from config import load_config
2025-10-03 18:22:15 +00:00
from decimal import Decimal , ROUND_HALF_UP
2025-11-06 19:18:37 +00:00
from . import sql_sentences as SQL
from utils import limpiar_cadena , normalizar_telefono_con_plus , corregir_y_validar_email , normalizar_url_para_insert , map_tax_code , cents , money_round , tax_fraction_from_code
2025-09-04 16:54:32 +00:00
def sync_invoices ( conn_factuges , conn_mysql , last_execution_date ) :
config = load_config ( )
2025-11-05 17:43:40 +00:00
# LIMPIAMOS LAS FACTURAS DE FACTUGES QUE HAYAN SIDO ELIMINADAS DEL PROGRAMA NUEVO DE FACTURACION, PARA QUE PUEDAN SER MODIFICADAS
2025-09-10 17:38:41 +00:00
# Crear un cursor para ejecutar consultas SQL
cursor_mysql = None
try :
cursor_mysql = conn_mysql . cursor ( )
2025-11-06 19:18:37 +00:00
cursor_mysql . execute ( SQL . SELECT_INVOICES_DELETED )
2025-09-10 17:38:41 +00:00
filas = cursor_mysql . fetchall ( )
cursor_mysql . close ( )
2025-11-05 17:43:40 +00:00
# Crear un conjunto con los IDs [0] de los customer_inovices que debo liberar en FactuGES, porque han sido eliminadas en programa de facturación nuevo
2025-09-10 17:38:41 +00:00
ids_verifactu_deleted = { str ( fila [ 0 ] ) for fila in filas }
2025-11-06 19:18:37 +00:00
# logging.info(f"Customer invoices rows to be deleted: {len(ids_verifactu_deleted)}")
2025-09-10 17:38:41 +00:00
# Verificar si hay filas en el resultado
if ids_verifactu_deleted :
eliminar_datos ( conn_factuges , ids_verifactu_deleted , config )
else :
2025-11-06 19:18:37 +00:00
logging . info ( f " There are customer invoices deleted " )
2025-09-10 17:38:41 +00:00
except Exception as e :
if cursor_mysql is not None :
cursor_mysql . close ( )
logging . error ( f " (ERROR) Failed to fetch from database: {
config [ ' UECKO_MYSQL_DATABASE ' ] } - using user : { config [ ' UECKO_MYSQL_USER ' ] } " )
logging . error ( e )
raise e
2025-11-05 17:43:40 +00:00
# BUSCAMOS FACTURAS ENVIADAS A VERIFACTU EN FACTUGES, PARA SUBIRLAS AL NUEVO PROGRAMA DE FACTURACIÓN
2025-09-04 16:54:32 +00:00
# Crear un cursor para ejecutar consultas SQL
cursor_FactuGES = None
try :
cursor_FactuGES = conn_factuges . cursor ( )
# Ejecutar la consulta de FACTURAS_CLIENTE
2025-11-06 19:18:37 +00:00
cursor_FactuGES . execute ( SQL . SELECT_FACTUGES_FACTURAS_CLIENTE )
2025-09-04 16:54:32 +00:00
filas = cursor_FactuGES . fetchall ( )
except Exception as e :
if cursor_FactuGES is not None :
cursor_FactuGES . close ( )
logging . error ( f " (ERROR) Failed to fetch from database: {
config [ ' FACTUGES_DATABASE ' ] } - using user : { config [ ' FACTUGES_USER ' ] } " )
logging . 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 :
insertar_datos ( conn_mysql , tuplas_seleccionadas , conn_factuges , config )
else :
logging . info (
2025-09-10 17:38:41 +00:00
" There are no new FACTURAS rows since the last run. " )
2025-09-04 16:54:32 +00:00
2025-09-10 17:38:41 +00:00
def eliminar_datos ( conn_factuges , ids_verifactu_deleted , config ) :
2025-11-06 19:18:37 +00:00
# Eliminamos todos los IDs asociados en FactuGES que han sido eliminados así liberaremos la factura borrador y podermos modificarla de nuevo, para volverla a subir una vez hechos los cambios.
# VERIFACTU = 0 and ID_VERIFACTU = NULL
2025-09-10 17:38:41 +00:00
cursor_FactuGES = None
2025-09-04 16:54:32 +00:00
try :
2025-09-10 17:38:41 +00:00
cursor_FactuGES = conn_factuges . cursor ( )
if ids_verifactu_deleted :
logging . info ( f " Liberate factures: { ids_verifactu_deleted } " )
2025-11-05 17:43:40 +00:00
cursor_FactuGES . executemany ( SQL . LIMPIAR_FACTUGES_LINK , [ (
2025-09-10 17:38:41 +00:00
id_verifactu , ) for id_verifactu in ids_verifactu_deleted ] )
2025-09-04 16:54:32 +00:00
else :
logging . info ( " No articles to delete. " )
except Exception as e :
# Escribir el error en el archivo de errores
logging . error ( str ( e ) )
raise e # Re-lanzar la excepción para detener el procesamiento
finally :
# Cerrar la conexión
2025-09-10 17:38:41 +00:00
if cursor_FactuGES is not None :
cursor_FactuGES . close ( )
2025-09-04 16:54:32 +00:00
def insertar_datos ( conn_mysql , filas , conn_factuges , config ) :
2025-11-05 17:43:40 +00:00
# Insertaremos cada factura existente en las filas a la nueva estructura de tablas del programa nuevo de facturacion.
2025-11-06 19:18:37 +00:00
# logging.info(f"FACTURAS_CLIENTE_DETALLE rows to be processed: {len(filas)}")
2025-09-04 16:54:32 +00:00
2025-11-05 17:43:40 +00:00
# Compañia RODAX
cte_company_id = ' 5e4dc5b3-96b9-4968-9490-14bd032fec5f '
2025-09-04 16:54:32 +00:00
cursorMySQL = None
cursor_FactuGES = None
2025-09-10 17:38:41 +00:00
factuges_id_anterior = None
id_customer_invoice = None
num_fac_procesed = 0
2025-11-06 19:18:37 +00:00
2025-09-04 16:54:32 +00:00
try :
cursorMySQL = conn_mysql . cursor ( )
cursor_FactuGES = conn_factuges . cursor ( )
2025-11-06 19:18:37 +00:00
2025-09-04 16:54:32 +00:00
# Insertar datos en la tabla 'customer_invoices'
2025-09-10 17:38:41 +00:00
for factura_detalle in filas :
2025-09-18 10:41:06 +00:00
# Preparamos el tipo de IVA, en FactuGES es único
2025-11-06 19:18:37 +00:00
# Map tax code (cabecera)
tax_code = map_tax_code ( str ( factura_detalle . get ( " DES_TIPO_IVA " ) ) )
2025-09-18 10:41:06 +00:00
# La cuota de impuestos es el IVA + RE
tax_amount_value = (
( factura_detalle [ ' IMPORTE_IVA ' ] or 0 ) + ( factura_detalle [ ' IMPORTE_RE ' ] or 0 ) ) * 100
2025-09-22 17:32:39 +00:00
factuges_payment_method_id = str ( factura_detalle [ ' ID_FORMA_PAGO ' ] )
2025-09-10 17:38:41 +00:00
payment_method_description = str ( factura_detalle [ ' DES_FORMA_PAGO ' ] )
factuges_customer_id = str ( factura_detalle [ ' ID_CLIENTE ' ] )
2025-10-29 16:08:14 +00:00
customer_tin = limpiar_cadena ( str ( factura_detalle [ ' NIF_CIF ' ] ) )
2025-09-04 16:54:32 +00:00
2025-09-10 17:38:41 +00:00
item_position = int ( factura_detalle [ ' POSICION ' ] )
item_description = str ( factura_detalle [ ' CONCEPTO ' ] )
2025-09-18 10:41:06 +00:00
item_quantity_value = None if factura_detalle [ ' CANTIDAD ' ] is None else (
factura_detalle [ ' CANTIDAD ' ] or 0 ) * 100
item_unit_amount_value = None if factura_detalle [ ' IMPORTE_UNIDAD ' ] is None else (
2025-09-10 17:38:41 +00:00
factura_detalle [ ' IMPORTE_UNIDAD ' ] or 0 ) * 10000
2025-11-05 18:01:52 +00:00
Descuento = factura_detalle [ ' DESCUENTO_DET ' ]
2025-09-22 17:32:39 +00:00
item_discount_percentage_value = None if Descuento is None else None if Descuento == 0 else (
2025-11-05 18:01:52 +00:00
factura_detalle [ ' DESCUENTO_DET ' ] ) * 100
2025-10-03 18:22:15 +00:00
item_total_amount = ( factura_detalle [ ' IMPORTE_TOTAL_DET ' ] or 0 ) * 100
# None if factura_detalle['IMPORTE_TOTAL_DET'] is None else (
# factura_detalle['IMPORTE_TOTAL_DET'] or 0)*100
2025-09-10 17:38:41 +00:00
2025-09-04 16:54:32 +00:00
# campos pendiente de revisar en un futuro
2025-09-10 17:38:41 +00:00
# xxxxxxx = str(factura_detalle['ID_EMPRESA'])
# 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['OBSERVACIONES'])
2025-09-04 16:54:32 +00:00
2025-11-06 19:18:37 +00:00
factuges_id = int ( factura_detalle [ ' ID_FACTURA ' ] )
2025-09-10 17:38:41 +00:00
if factuges_id_anterior is None or factuges_id_anterior != factuges_id :
2025-09-22 17:32:39 +00:00
# Comprobamos si existe el cliente del primer item de la factura
2025-11-06 19:18:37 +00:00
# cursorMySQL.execute(SQL.SELECT_CUSTOMER_BY_FACTUGES,
# (factuges_customer_id, ))
# row = cursorMySQL.fetchone()
# is_new = (row is None) or (row[0] is None)
# ---- cliente
customer_fields = normalize_customer_fields ( factura_detalle )
customer_id = get_or_create_customer (
cursorMySQL ,
cte_company_id ,
str ( factura_detalle [ " ID_CLIENTE " ] ) ,
customer_fields ,
)
# ---- forma de pago
pm_id = get_or_create_payment_method (
cursorMySQL ,
str ( factura_detalle [ " ID_FORMA_PAGO " ] ) ,
str ( factura_detalle [ " DES_FORMA_PAGO " ] ) ,
)
# ---- cabecera factura
invoice_id , tax_code = insert_invoice_header (
cursorMySQL , cte_company_id , factura_detalle , customer_id , pm_id , str (
factura_detalle [ " DES_FORMA_PAGO " ] )
)
# ---- impuestos cabecera
insert_header_taxes_if_any (
cursorMySQL , invoice_id , factura_detalle , tax_code )
2025-09-10 17:38:41 +00:00
# Guardamos en Factuges el id de la customer_invoice
2025-09-04 16:54:32 +00:00
logging . info (
2025-11-06 19:18:37 +00:00
f " Updating FACTURAS_CLIENTE { invoice_id } { factuges_id } " )
2025-09-10 17:38:41 +00:00
cursor_FactuGES . execute (
2025-11-06 19:18:37 +00:00
SQL . UPDATE_FACTUGES_LINK , ( invoice_id , factuges_id ) )
2025-09-10 17:38:41 +00:00
num_fac_procesed + = 1
# Insertamos detalles y taxes correspondientes siempre
2025-11-06 19:18:37 +00:00
# Siempre insertamos la línea
insert_item_and_taxes ( cursorMySQL , invoice_id ,
factura_detalle , tax_code )
# Asignamos el id factura anterior para no volver a inserta cabecera
factuges_id_anterior = factuges_id
logging . info (
f " FACTURAS_CLIENTE rows to be processed: { str ( num_fac_procesed ) } " )
except Exception as e :
# Escribir el error en el archivo de errores
logging . 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 ( )
def normalize_customer_fields ( fd : Dict [ str , Any ] ) - > Dict [ str , Any ] :
"""
Normaliza campos de cliente del registro de origen ' fd ' ( factura_detalle ) .
"""
# >>> aquí usa tus helpers reales:
def clean_phone ( v ) : return normalizar_telefono_con_plus ( v )
def clean_web ( v ) : return normalizar_url_para_insert ( v )
def clean_tin ( v ) : return limpiar_cadena ( v )
email1_ok , email1 = corregir_y_validar_email ( fd . get ( " EMAIL_1 " ) )
email2_ok , email2 = corregir_y_validar_email ( fd . get ( " EMAIL_2 " ) )
return {
" tin " : clean_tin ( str ( fd . get ( " NIF_CIF " ) ) ) ,
" name " : str ( fd . get ( " NOMBRE " ) ) ,
" street " : str ( fd . get ( " CALLE " ) ) ,
" city " : str ( fd . get ( " POBLACION " ) ) ,
" province " : str ( fd . get ( " PROVINCIA " ) ) ,
" postal_code " : str ( fd . get ( " CODIGO_POSTAL " ) ) ,
" country " : " es " ,
" phone_primary " : clean_phone ( fd . get ( " TELEFONO_1 " ) ) ,
" phone_secondary " : clean_phone ( fd . get ( " TELEFONO_2 " ) ) ,
" mobile_primary " : clean_phone ( fd . get ( " MOVIL_1 " ) ) ,
" mobile_secondary " : clean_phone ( fd . get ( " MOVIL_2 " ) ) ,
" email_primary " : email1 if email1_ok else None ,
" email_secondary " : email2 if email2_ok else None ,
" website " : clean_web ( str ( fd . get ( " PAGINA_WEB " ) ) ) ,
}
def get_or_create_customer ( cur , company_id : str , factuges_customer_id : str , fields : Dict [ str , Any ] ) - > str :
cur . execute ( SQL . SELECT_CUSTOMER_BY_FACTUGES , ( factuges_customer_id , ) )
row = cur . fetchone ( )
if not row or not row [ 0 ] :
customer_id = str ( uuid7 ( ) )
logging . info ( " Inserting customer %s %s %s " ,
factuges_customer_id , fields [ " tin " ] , fields [ " name " ] )
cur . execute (
SQL . INSERT_CUSTOMER ,
(
customer_id , fields [ " name " ] , fields [ " tin " ] , fields [ " street " ] , fields [ " city " ] , fields [ " province " ] ,
fields [ " postal_code " ] , fields [ " country " ] , fields [ " phone_primary " ] , fields [ " phone_secondary " ] ,
fields [ " mobile_primary " ] , fields [ " mobile_secondary " ] , fields [ " email_primary " ] , fields [ " email_secondary " ] ,
fields [ " website " ] , factuges_customer_id , company_id ,
) ,
)
return customer_id
customer_id = str ( row [ 0 ] )
logging . info ( " Updating customer %s %s " , factuges_customer_id , customer_id )
cur . execute (
SQL . UPDATE_CUSTOMER ,
(
fields [ " name " ] , fields [ " tin " ] , fields [ " street " ] , fields [ " city " ] , fields [ " province " ] , fields [ " postal_code " ] ,
fields [ " country " ] , fields [ " phone_primary " ] , fields [ " phone_secondary " ] ,
fields [ " mobile_primary " ] , fields [ " mobile_secondary " ] , fields [ " email_primary " ] , fields [ " email_secondary " ] ,
fields [ " website " ] , customer_id ,
) ,
)
return customer_id
def get_or_create_payment_method ( cur , factuges_payment_id : str , description : str ) - > str :
cur . execute ( SQL . SELECT_PAYMENT_METHOD_BY_FACTUGES , ( factuges_payment_id , ) )
row = cur . fetchone ( )
if not row or not row [ 0 ] :
pm_id = str ( uuid7 ( ) )
logging . info ( " Inserting payment method %s %s %s " ,
factuges_payment_id , pm_id , description )
cur . execute ( SQL . INSERT_PAYMENT_METHOD ,
( pm_id , description , factuges_payment_id ) )
return pm_id
pm_id = str ( row [ 0 ] )
logging . info ( " Payment method exists %s -> %s " , factuges_payment_id , pm_id )
return pm_id
def insert_invoice_header ( cur , company_id : str , fd : Dict [ str , Any ] , customer_id : str ,
payment_method_id : str , payment_method_description : str ) - > Tuple [ str , str ] :
"""
Inserta cabecera y devuelve ( invoice_id , tax_code_normalized )
"""
invoice_id = str ( uuid7 ( ) )
reference = str ( fd [ ' REFERENCIA ' ] )
invoice_date = str ( fd [ ' FECHA_FACTURA ' ] )
operation_date = str ( fd [ ' FECHA_FACTURA ' ] )
description = textwrap . shorten (
f " { reference or ' ' } - { str ( fd . get ( ' NOMBRE ' ) ) or ' ' } " , width = 50 , placeholder = " … " )
# siempre tendrán 2 decimales
subtotal_amount_value = cents ( fd . get ( ' IMPORTE_NETO ' ) )
discount_amount_value = cents ( fd . get ( ' IMPORTE_DESCUENTO ' ) )
discount_percentage_val = int ( ( Decimal ( str ( fd . get ( ' DESCUENTO ' ) or 0 ) ) * 100 ) . to_integral_value ( ) ) \
if fd . get ( ' DESCUENTO ' ) is not None else 0
taxable_amount_value = cents ( fd . get ( ' BASE_IMPONIBLE ' ) )
taxes_amount_value = cents (
( fd . get ( ' IMPORTE_IVA ' ) or 0 ) + ( fd . get ( ' IMPORTE_RE ' ) or 0 ) )
total_amount_value = cents ( fd . get ( ' IMPORTE_TOTAL ' ) )
# Mapea tax_code cabecera
tax_code = map_tax_code ( str ( fd . get ( " DES_TIPO_IVA " ) ) )
logging . info ( " Inserting invoice %s %s %s " ,
invoice_id , reference , invoice_date )
cur . execute (
SQL . INSERT_INVOICE ,
(
invoice_id , company_id , ' draft ' , ' F25/ ' , reference , invoice_date , operation_date , description ,
subtotal_amount_value , discount_amount_value , discount_percentage_val , taxable_amount_value ,
taxes_amount_value , total_amount_value ,
customer_id , fd . get ( ' NIF_CIF ' ) , fd . get (
' NOMBRE ' ) , fd . get ( ' CALLE ' ) , fd . get ( ' POBLACION ' ) ,
fd . get ( ' PROVINCIA ' ) , fd . get ( ' CODIGO_POSTAL ' ) , ' es ' ,
payment_method_id , payment_method_description , fd . get (
' ID_FACTURA ' ) , company_id
) ,
)
return invoice_id , tax_code
def insert_header_taxes_if_any ( cur , invoice_id : str , fd : Dict [ str , Any ] , tax_code : str ) - > None :
base = cents ( fd . get ( ' BASE_IMPONIBLE ' ) )
iva = cents ( fd . get ( ' IMPORTE_IVA ' ) )
re = cents ( fd . get ( ' IMPORTE_RE ' ) )
# IVA (>= 0 acepta 0% también, si no quieres registrar 0, cambia condición a > 0)
if ( fd . get ( ' IVA ' ) or 0 ) > = 0 :
cur . execute ( SQL . INSERT_INVOICE_TAX ,
( str ( uuid7 ( ) ) , invoice_id , tax_code , base , iva ) )
# Recargo equivalencia
if ( fd . get ( ' RECARGO_EQUIVALENCIA ' ) or 0 ) > 0 :
cur . execute ( SQL . INSERT_INVOICE_TAX ,
( str ( uuid7 ( ) ) , invoice_id , ' rec_5_2 ' , base , re ) )
def insert_item_and_taxes ( cur , invoice_id : str , fd : Dict [ str , Any ] , tax_code : str ) - > None :
"""
Inserta línea y sus impuestos derivados .
"""
item_id = str ( uuid7 ( ) )
position = int ( fd [ ' POSICION ' ] )
description = str ( fd [ ' CONCEPTO ' ] )
quantity_value = None if fd [ ' CANTIDAD ' ] is None else int (
Decimal ( str ( fd [ ' CANTIDAD ' ] or 0 ) ) * 100 )
unit_value = None if fd [ ' IMPORTE_UNIDAD ' ] is None else int (
Decimal ( str ( fd [ ' IMPORTE_UNIDAD ' ] or 0 ) ) * 10000 )
disc_pct = fd . get ( ' DESCUENTO_DET ' )
discount_percentage_value = None if disc_pct in (
None , 0 ) else int ( Decimal ( str ( disc_pct ) ) * 100 )
total_value = cents ( fd . get ( ' IMPORTE_TOTAL_DET ' ) )
logging . info ( " Inserting item %s pos= %s qty= %s " ,
item_id , position , quantity_value )
cur . execute (
SQL . INSERT_INVOICE_ITEM ,
( item_id , invoice_id , position , description , quantity_value ,
unit_value , discount_percentage_value , None , total_value )
)
# Se calcula en el objeto de negocio de nuevo, comprobar si coincide
# item_discount_amount = (
# (factura_detalle['IMPORTE_UNIDAD'] or 0)*((factura_detalle['DESCUENTO'] or 0)/100))*100
# Calcular cuota de IVA de la línea
frac = tax_fraction_from_code ( tax_code ) # 0.21, 0.10, 0…
# si tu total_det ya incluye descuento
line_base = Decimal ( str ( fd . get ( ' IMPORTE_TOTAL_DET ' ) or 0 ) )
tax_amount = int ( ( money_round ( line_base * frac , 2 )
* 100 ) . to_integral_value ( ) )
logging . info ( " Inserting item tax %s code= %s base= %s tax= %s " ,
item_id , tax_code , total_value , tax_amount )
cur . execute (
SQL . INSERT_INVOICE_ITEM_TAX ,
( str ( uuid7 ( ) ) , item_id , tax_code , total_value , tax_amount )
)
"""
logging . info (
f " Inserting customer_invoice_item_taxes { item_id } { item_position } { tax_code } { item_total_amount } { tax_amount_value } " )
cursorMySQL . execute ( SQL . INSERT_INVOICE_ITEM_TAX , ( str ( uuid7 ( ) ) , item_id , tax_code ,
item_total_amount , tax_amount_value ) )
2025-09-10 17:38:41 +00:00
2025-10-03 18:22:15 +00:00
if tax_code == ' iva_21 ' :
2025-09-10 17:38:41 +00:00
tax_amount_value = (
2025-10-03 18:22:15 +00:00
( Decimal ( str ( factura_detalle [ ' IMPORTE_TOTAL_DET ' ] or 0 ) ) * Decimal ( ' 0.21 ' ) ) . quantize ( Decimal ( ' 0.01 ' ) , rounding = ROUND_HALF_UP ) ) * 100
elif tax_code == ' iva_18 ' :
2025-09-10 17:38:41 +00:00
tax_amount_value = (
2025-10-03 18:22:15 +00:00
( Decimal ( str ( factura_detalle [ ' IMPORTE_TOTAL_DET ' ] or 0 ) ) * Decimal ( ' 0.18 ' ) ) . quantize ( Decimal ( ' 0.01 ' ) , rounding = ROUND_HALF_UP ) ) * 100
elif tax_code == ' iva_16 ' :
2025-09-10 17:38:41 +00:00
tax_amount_value = (
2025-10-03 18:22:15 +00:00
( Decimal ( str ( factura_detalle [ ' IMPORTE_TOTAL_DET ' ] or 0 ) ) * Decimal ( ' 0.16 ' ) ) . quantize ( Decimal ( ' 0.01 ' ) , rounding = ROUND_HALF_UP ) ) * 100
elif tax_code == ' iva_10 ' :
2025-09-10 17:38:41 +00:00
tax_amount_value = (
2025-10-03 18:22:15 +00:00
( Decimal ( str ( factura_detalle [ ' IMPORTE_TOTAL_DET ' ] or 0 ) ) * Decimal ( ' 0.10 ' ) ) . quantize ( Decimal ( ' 0.01 ' ) , rounding = ROUND_HALF_UP ) ) * 100
2025-10-29 16:08:14 +00:00
elif tax_code == ' iva_exenta ' :
tax_amount_value = (
( Decimal ( str ( factura_detalle [ ' IMPORTE_TOTAL_DET ' ] or 0 ) ) * Decimal ( ' 0 ' ) ) . quantize ( Decimal ( ' 0.01 ' ) , rounding = ROUND_HALF_UP ) ) * 100
2025-09-10 17:38:41 +00:00
else :
2025-10-03 18:22:15 +00:00
tax_amount_value = (
factura_detalle [ ' IMPORTE_TOTAL_DET ' ] or 0 ) * 100
2025-09-04 16:54:32 +00:00
2025-11-06 19:18:37 +00:00
"""