Uecko_ERP_FactuGES_sync/app/db/normalizations.py

167 lines
6.3 KiB
Python
Raw Normal View History

2025-11-30 18:28:44 +00:00
from app.config import logger
2025-11-21 18:42:03 +00:00
from datetime import date
2025-11-30 09:43:57 +00:00
from app.config import load_config
2025-11-20 18:51:03 +00:00
import textwrap
2025-11-21 18:42:03 +00:00
from typing import Dict, Any, Optional
2025-11-20 18:51:03 +00:00
from decimal import Decimal, ROUND_HALF_UP
2025-11-30 09:43:57 +00:00
from app.utils import limpiar_cadena, normalizar_telefono_con_plus, corregir_y_validar_email, normalizar_url_para_insert, map_tax_code, cents, cents4, money_round, tax_fraction_from_code
2025-11-20 18:51:03 +00:00
from striprtf.striprtf import rtf_to_text
def rtf_a_texto_plano(rtf: str) -> str:
"""
Convierte RTF a texto plano usando striprtf.
"""
return rtf_to_text(rtf).strip()
def normalize_customer_fields(fd: Dict[str, Any]) -> Dict[str, Any]:
"""
Normaliza campos del registro de origen 'fd' (factura).
"""
config = load_config()
# >>> 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 {
"is_company": config["CTE_IS_COMPANY"],
"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": config['CTE_COUNTRY_CODE'],
"language_code": config['CTE_LANGUAGE_CODE'],
"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 normalize_header_invoice_fields(fd: Dict[str, Any]) -> Dict[str, Any]:
"""
Normaliza campos del registro de origen 'fd' (factura).
# campos pendiente de revisar en un futuro
# xxxxxxx = str(factura_detalle['ID_EMPRESA'])
# xxxxxxx = str(factura_detalle['OBSERVACIONES'])
"""
config = load_config()
return {
"company_id": config['CTE_COMPANY_ID'],
"is_proforma": config["CTE_IS_PROFORMA"],
"status": config["CTE_STATUS_INVOICE"],
"series": config['CTE_SERIE'],
"factuges_id": int(fd['ID_FACTURA']),
"reference": str(fd['REFERENCIA']),
2025-11-21 18:42:03 +00:00
# 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"),
2025-11-20 18:51:03 +00:00
"operation_date": str(fd['FECHA_FACTURA']),
"description": textwrap.shorten(
f"{str(fd['REFERENCIA'])} - {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')),
'tax_code': map_tax_code(str(fd.get("DES_TIPO_IVA"))),
'taxes_amount_value': cents((fd.get('IMPORTE_IVA') or 0) + (fd.get('IMPORTE_RE') or 0)),
'total_amount_value': cents(fd.get('IMPORTE_TOTAL')),
'base': cents(fd.get('BASE_IMPONIBLE')),
'iva': cents(fd.get('IMPORTE_IVA')),
're': cents(fd.get('IMPORTE_RE'))
}
def normalize_details_invoice_fields(fd: Dict[str, Any]) -> Dict[str, Any]:
"""
2025-11-21 18:42:03 +00:00
Normaliza campos de detalle de factura desde el registro de origen `fd`.
Escalas:
- quantity_value: escala 2 (cents)
- unit_value: escala 4 (cents4)
- discount_percentage_value: escala 2 (10 -> 1000)
- total_value: escala 4 (cents4)
2025-11-24 12:13:54 +00:00
- iva_amount: escala 2 (cents), calculado con redondeo a 2 decimales
-
2025-11-20 18:51:03 +00:00
"""
config = load_config()
2025-11-24 12:13:54 +00:00
iva_code = map_tax_code(str(fd.get("DES_TIPO_IVA")))
2025-11-20 18:51:03 +00:00
# Calcular cuota de IVA de la línea
2025-11-24 12:13:54 +00:00
iva_frac: Optional[Decimal] = tax_fraction_from_code(iva_code) # p.ej. Decimal('0.21') o Decimal('0')
iva_percentage_value = None if (
iva_frac is None or iva_frac == 0) else cents4(iva_frac)
rec_code = None
rec_percentage_value = None
rec_amount = None
2025-11-21 18:42:03 +00:00
# --- Campos base del origen ---
cantidad = fd.get("CANTIDAD")
importe_unidad = fd.get("IMPORTE_UNIDAD")
# total de línea (ya con descuento)
total_det = fd.get("IMPORTE_TOTAL_DET")
concepto_rtf = fd.get("CONCEPTO")
# --- cantidad y precio unitario ---
quantity_value = None if cantidad is None else cents(
cantidad) # escala 2
unit_value = None if importe_unidad is None else cents4(
importe_unidad) # escala 4
# --- descuento % (escala 2) ---
disc_raw = fd.get("DESCUENTO_DET")
disc_pct: Optional[Decimal] = None if disc_raw is None else Decimal(
str(disc_raw))
discount_percentage_value = None if (
disc_pct is None or disc_pct == 0) else cents(disc_pct)
# --- total de línea (escala 4) ---
total_value = cents4(total_det)
# --- cuota (escala 2) ---
# calculamos sobre el total en unidades monetarias con redondeo bancario a 2 decimales
2025-11-24 12:13:54 +00:00
if iva_frac is None:
iva_amount = 0
2025-11-21 18:42:03 +00:00
else:
base = Decimal(str(total_det or 0))
2025-11-24 12:13:54 +00:00
iva_amount = cents(money_round(base * iva_frac, 2))
2025-11-20 18:51:03 +00:00
# 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
return {
2025-11-21 18:42:03 +00:00
'position': int(fd.get("POSICION") or 0),
'description': rtf_a_texto_plano(str(concepto_rtf or "")),
'quantity_value': quantity_value,
'unit_value': unit_value,
2025-11-20 18:51:03 +00:00
'disc_pct': disc_pct,
2025-11-21 18:42:03 +00:00
'discount_percentage_value': discount_percentage_value,
'total_value': total_value,
2025-11-24 12:13:54 +00:00
'iva_code': iva_code,
'iva_percentage_value': iva_percentage_value,
'iva_amount': iva_amount,
'rec_code': rec_code,
'rec_percentage_value': rec_percentage_value,
'rec_amount': rec_amount,
2025-11-20 18:51:03 +00:00
}