167 lines
6.3 KiB
Python
167 lines
6.3 KiB
Python
from app.config import logger
|
|
from datetime import date
|
|
from app.config import load_config
|
|
import textwrap
|
|
from typing import Dict, Any, Optional
|
|
from decimal import Decimal, ROUND_HALF_UP
|
|
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
|
|
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']),
|
|
# 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"),
|
|
"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]:
|
|
"""
|
|
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)
|
|
- iva_amount: escala 2 (cents), calculado con redondeo a 2 decimales
|
|
-
|
|
"""
|
|
config = load_config()
|
|
|
|
iva_code = map_tax_code(str(fd.get("DES_TIPO_IVA")))
|
|
# Calcular cuota de IVA de la línea
|
|
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
|
|
|
|
# --- 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
|
|
if iva_frac is None:
|
|
iva_amount = 0
|
|
else:
|
|
base = Decimal(str(total_det or 0))
|
|
iva_amount = cents(money_round(base * iva_frac, 2))
|
|
|
|
# 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 {
|
|
'position': int(fd.get("POSICION") or 0),
|
|
'description': rtf_a_texto_plano(str(concepto_rtf or "")),
|
|
'quantity_value': quantity_value,
|
|
'unit_value': unit_value,
|
|
'disc_pct': disc_pct,
|
|
'discount_percentage_value': discount_percentage_value,
|
|
'total_value': total_value,
|
|
'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,
|
|
|
|
}
|