.
This commit is contained in:
parent
9a34e38557
commit
bb91c34ba7
@ -1,9 +1,10 @@
|
||||
import logging
|
||||
from datetime import date
|
||||
from config import load_config
|
||||
import textwrap
|
||||
from typing import Dict, Any
|
||||
from typing import Dict, Any, Optional
|
||||
from decimal import Decimal, ROUND_HALF_UP
|
||||
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
|
||||
from 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
|
||||
|
||||
|
||||
@ -66,7 +67,8 @@ def normalize_header_invoice_fields(fd: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
"factuges_id": int(fd['ID_FACTURA']),
|
||||
"reference": str(fd['REFERENCIA']),
|
||||
"invoice_date": str(fd['FECHA_FACTURA']),
|
||||
# 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="…"),
|
||||
@ -90,19 +92,51 @@ def normalize_header_invoice_fields(fd: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
def normalize_details_invoice_fields(fd: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Normaliza campos del registro de origen 'fd' (factura).
|
||||
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)
|
||||
- tax_amount: escala 2 (cents), calculado con redondeo a 2 decimales
|
||||
"""
|
||||
config = load_config()
|
||||
|
||||
tax_code = map_tax_code(str(fd.get("DES_TIPO_IVA")))
|
||||
disc_pct = fd.get('DESCUENTO_DET')
|
||||
|
||||
# Calcular cuota de IVA de la línea
|
||||
frac = tax_fraction_from_code(tax_code) # 0.21, 0.10, 0…
|
||||
# Ttotal_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())
|
||||
frac: Optional[Decimal] = tax_fraction_from_code(
|
||||
tax_code) # p.ej. Decimal('0.21') o Decimal('0')
|
||||
|
||||
# --- 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 frac is None:
|
||||
tax_amount = 0
|
||||
else:
|
||||
base = Decimal(str(total_det or 0))
|
||||
tax_amount = cents(money_round(base * frac, 2))
|
||||
|
||||
# Se calcula en el objeto de negocio de nuevo, comprobar si coincide
|
||||
# item_discount_amount = (
|
||||
@ -110,12 +144,12 @@ def normalize_details_invoice_fields(fd: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
return {
|
||||
'tax_code': tax_code,
|
||||
'position': int(fd['POSICION']),
|
||||
'description': rtf_a_texto_plano(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),
|
||||
'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': None if disc_pct in (None, 0) else int(Decimal(str(disc_pct)) * 100),
|
||||
'total_value': cents(fd.get('IMPORTE_TOTAL_DET')),
|
||||
'discount_percentage_value': discount_percentage_value,
|
||||
'total_value': total_value,
|
||||
'tax_amount': tax_amount
|
||||
}
|
||||
|
||||
@ -140,14 +140,13 @@ LIMPIAR_FACTUGES_LINK = (
|
||||
# OPCION A SACAMOS EL RESUMEN DE LA TAXES DE LA CABECERA
|
||||
consulta_sql_customer_invoices_issue = (
|
||||
f"SELECT ci.id, ci.series, ci.invoice_number, ci.invoice_date, ci.description, ci.customer_tin, ci.customer_name, ci.total_amount_value, ci.total_amount_scale, ci.reference, "
|
||||
f"cit.taxable_amount_scale, cit.taxes_amount_scale, cit.tax_code, sum(cit.taxable_amount_value) as taxable_amount_value, sum(cit.taxes_amount_value) as taxes_amount_value, "
|
||||
f"cit.taxable_amount_scale, cit.taxes_amount_scale, cit.tax_code, cit.taxable_amount_value, cit.taxes_amount_value, "
|
||||
f"vr.id as vrId, vr.uuid, vr.estado "
|
||||
f"FROM customer_invoices as ci "
|
||||
f"LEFT JOIN customer_invoice_taxes cit on (ci.id = cit.invoice_id) "
|
||||
f"LEFT JOIN verifactu_records vr on (ci.id = vr.invoice_id) "
|
||||
f"WHERE (ci.is_proforma = 0) AND (ci.status= 'issued') "
|
||||
f"AND (vr.estado <> 'Correcto') "
|
||||
f"group by 1,2,3,4,5,6,7,8,9,10,11 "
|
||||
f"order by reference"
|
||||
)
|
||||
|
||||
|
||||
@ -241,7 +241,7 @@ def insert_invoice_header(cur: str, cf: Dict[str, Any], hif: Dict[str, Any], cus
|
||||
invoice_id = str(uuid7())
|
||||
|
||||
logging.info("Inserting invoice %s %s %s %s",
|
||||
invoice_id, hif.get('reference'), hif.get('invoice_date'), config['CTE_STATUS_INVOICE'])
|
||||
invoice_id, hif.get('reference'), hif.get('invoice_date'), hif.get('operation_date'), config['CTE_STATUS_INVOICE'])
|
||||
cur.execute(
|
||||
SQL.INSERT_INVOICE,
|
||||
(
|
||||
|
||||
@ -108,8 +108,9 @@ def procesar_factura_verifactu(
|
||||
respuesta = crear_factura(factura, config)
|
||||
if respuesta.get("status") == 200 and respuesta.get("ok"):
|
||||
data = respuesta.get("data")
|
||||
qr_verifactu = f"data:image/png;base64,{data.get('qr', '')}"
|
||||
cursor_mysql.execute(SQL.update_verifactu_records_with_invoiceId, (data.get("estado"), data.get(
|
||||
"uuid"), data.get("url"), data.get("qr"), factura.get("id")))
|
||||
"uuid"), data.get("url"), qr_verifactu, factura.get("id")))
|
||||
logging.info(
|
||||
f">>> Factura {factura.get("reference")} registrada en Verifactu")
|
||||
return True
|
||||
|
||||
@ -5,7 +5,7 @@ from .send_orders_mail import send_orders_mail
|
||||
from .text_converter import text_converter, limpiar_cadena
|
||||
from .send_rest_api import validar_nif, estado_factura, crear_factura
|
||||
from .tax_catalog_helper import TaxCatalog, get_default_tax_catalog, map_tax_code, calc_item_tax_amount, tax_fraction_from_code
|
||||
from .importes_helper import unscale_to_str, unscale_to_decimal, cents, money_round
|
||||
from .importes_helper import unscale_to_str, unscale_to_decimal, cents, cents4, money_round
|
||||
from .telefonos_helper import normalizar_telefono_con_plus
|
||||
from .mails_helper import corregir_y_validar_email
|
||||
from .websites_helper import normalizar_url_para_insert
|
||||
|
||||
@ -2,6 +2,12 @@ from decimal import Decimal, ROUND_HALF_UP
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
def cents4(value: Optional[Decimal | float | int]) -> int:
|
||||
"""Convierte a centésimas (valor * 10000). Soporta None."""
|
||||
v = Decimal(str(value or 0))
|
||||
return int((v * 10000).to_integral_value(rounding=ROUND_HALF_UP))
|
||||
|
||||
|
||||
def cents(value: Optional[Decimal | float | int]) -> int:
|
||||
"""Convierte a centésimas (valor * 100). Soporta None."""
|
||||
v = Decimal(str(value or 0))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user