Skip to content

Commit 7c0be52

Browse files
committed
Fixed Rounding Issue
1 parent ee97bca commit 7c0be52

33 files changed

+7642
-5
lines changed

satcfdi/create/cfd/cfdi40.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
from datetime import datetime
55
from decimal import Decimal
66

7-
from satcfdi.create.cfd.catalogos import Impuesto as CatImpuesto
7+
from ...transform.helpers import strcode
8+
9+
from ...create.cfd.catalogos import Impuesto as CatImpuesto
810
from . import pago20
911
from ..compute import make_impuestos, rounder, make_impuesto, \
10-
make_impuestos_dr_parcial, m_decimals, RoundTracker
12+
make_impuestos_dr_parcial, m_decimals, RoundTracker, RoundTrackerManager
1113
from ...cfdi import CFDI
1214
from ...transform import get_timezone
1315
from ...utils import ScalarMap
@@ -334,7 +336,7 @@ def __post_init__(self):
334336

335337
def _make_conceptos(conceptos, decimals):
336338
rnd_fn = lambda v: round(v, decimals)
337-
rnd_traslados_tracker = RoundTracker(decimals)
339+
rnd_tracker_instance = RoundTrackerManager(decimals)
338340

339341
def make_concepto(concepto):
340342
impuestos = concepto.get("Impuestos") or {}
@@ -352,9 +354,9 @@ def make_concepto(concepto):
352354
else:
353355
impuestos = {
354356
imp_t: [
355-
make_impuesto(i, base=base, rnd_fn=rnd_tracker) for i in imp
357+
make_impuesto(i, base=base, rnd_fn=rnd_tracker_instance[imp_t + strcode(i["Impuesto"])]) for i in imp
356358
]
357-
for imp_t, imp, rnd_tracker in [('Traslados', trasladados, rnd_traslados_tracker), ('Retenciones', retenciones, rnd_fn)] if imp
359+
for imp_t, imp in [('Traslados', trasladados), ('Retenciones', retenciones)] if imp
358360
}
359361
concepto['Impuestos'] = impuestos or None
360362
concepto["ObjetoImp"] = "02" if impuestos else "01"

satcfdi/create/compute.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ def __call__(self, value):
100100
return rounded
101101

102102

103+
class RoundTrackerManager(dict):
104+
def __init__(self, decimals):
105+
super().__init__()
106+
self.decimals = decimals
107+
108+
def __missing__(self, key):
109+
self[key] = RoundTracker(self.decimals)
110+
return self[key]
111+
103112
def group_impuestos(elements, pfx="", ofx=""):
104113
retenciones = aggregate(
105114
(t for c in iterate(elements) for t in iterate((c[f"Impuestos{pfx}"] or {}).get(f"Retenciones{pfx}"))),

tests/test_create_cfdi40.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,3 +490,89 @@ def test_suma_conceptos_iva(valor_unitario):
490490
invoice.sign(signer)
491491

492492
verify_invoice(invoice, f"iva_concepto_suma_pago_{valor_unitario}")
493+
494+
@pytest.mark.parametrize('valor_unitario', [Decimal('100.03'), Decimal('100.04'), Decimal('100.05'), Decimal('100.06'), Decimal('100.07')])
495+
def test_suma_conceptos_retenciones(valor_unitario):
496+
rfc = 'xiqb891116qe4'
497+
signer = get_signer(rfc)
498+
emisor = cfdi40.Emisor(
499+
rfc=signer.rfc,
500+
nombre=signer.legal_name,
501+
regimen_fiscal="601"
502+
)
503+
504+
invoice = cfdi40.Comprobante(
505+
emisor=emisor,
506+
lugar_expedicion="56820",
507+
fecha=datetime.fromisoformat("2020-01-01T22:40:38"),
508+
receptor=cfdi40.Receptor(
509+
rfc='KIJ0906199R1',
510+
nombre='KIJ, S.A DE C.V.',
511+
uso_cfdi='G03',
512+
domicilio_fiscal_receptor="59820",
513+
regimen_fiscal_receptor="601"
514+
),
515+
metodo_pago='PPD',
516+
serie="A",
517+
folio="123456",
518+
conceptos=[
519+
cfdi40.Concepto(
520+
clave_prod_serv='10101702',
521+
cantidad=1,
522+
clave_unidad='E48',
523+
descripcion='SERVICIOS DE FACTURACION',
524+
valor_unitario=valor_unitario,
525+
impuestos=cfdi40.Impuestos(
526+
traslados=cfdi40.Traslado(
527+
impuesto=Impuesto.IVA,
528+
tipo_factor=TipoFactor.TASA,
529+
tasa_o_cuota=Decimal('0.160000'),
530+
),
531+
retenciones=[
532+
cfdi40.Retencion(
533+
impuesto=Impuesto.ISR,
534+
tipo_factor=TipoFactor.TASA,
535+
tasa_o_cuota=Decimal('0.100000')
536+
),
537+
cfdi40.Retencion(
538+
impuesto=Impuesto.IVA,
539+
tipo_factor=TipoFactor.TASA,
540+
tasa_o_cuota=Decimal('0.106667')
541+
)
542+
]
543+
),
544+
) for _ in range(6)
545+
]
546+
)
547+
invoice.sign(signer)
548+
verify_invoice(invoice, f"rentenciones_concepto_suma_{valor_unitario}")
549+
550+
invoice["Complemento"] = {
551+
"TimbreFiscalDigital": {
552+
"UUID": "123e4567-e89b-12d3-a456-426614174000",
553+
}
554+
}
555+
556+
total_retencion_isr = valor_unitario * 6 * Decimal('0.100000')
557+
retencion_isr = invoice["Impuestos"]['Retenciones'][0]['Importe']
558+
assert retencion_isr <= total_retencion_isr.quantize(Decimal('0.01'), rounding=ROUND_CEILING)
559+
assert retencion_isr>= total_retencion_isr.quantize(Decimal('0.01'), rounding=ROUND_FLOOR)
560+
561+
total_retencion_iva = valor_unitario * 6 * Decimal('0.106667')
562+
retencion_iva = invoice["Impuestos"]['Retenciones'][1]['Importe']
563+
assert retencion_iva <= total_retencion_iva.quantize(Decimal('0.01'), rounding=ROUND_CEILING)
564+
assert retencion_iva >= total_retencion_iva.quantize(Decimal('0.01'), rounding=ROUND_FLOOR)
565+
566+
invoice = cfdi40.Comprobante.pago_comprobantes(
567+
emisor=emisor,
568+
lugar_expedicion="56820",
569+
fecha=datetime.fromisoformat("2020-01-01T22:40:38"),
570+
comprobantes=[invoice],
571+
fecha_pago=datetime.fromisoformat("2020-01-02T22:40:38"),
572+
forma_pago="03",
573+
serie="A",
574+
folio="123456",
575+
)
576+
invoice.sign(signer)
577+
578+
verify_invoice(invoice, f"rentenciones_concepto_suma_pago_{valor_unitario}")

0 commit comments

Comments
 (0)