Comprobante.pago_comprobantes()   C
last analyzed

Complexity

Conditions 9

Size

Total Lines 94
Code Lines 66

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 9.0468

Importance

Changes 0
Metric Value
cc 9
eloc 66
nop 14
dl 0
loc 94
ccs 11
cts 12
cp 0.9167
crap 9.0468
rs 5.7793
c 0
b 0
f 0

How to fix   Long Method    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
"""cfdi http://www.sat.gob.mx/cfd/4"""
2 1
from collections.abc import Sequence
3 1
from dataclasses import dataclass
4 1
from datetime import datetime
5 1
from decimal import Decimal
6
7 1
from . import pago20
8 1
from ..compute import make_impuestos, rounder, make_impuesto, \
9
    make_impuestos_dr_parcial, m_decimals, RoundTrackerManager
10 1
from ...cfdi import CFDI
11 1
from ...create.cfd.catalogos import Impuesto as CatImpuesto
12 1
from ...transform import get_timezone
13 1
from ...transform.helpers import strcode
14 1
from ...utils import ScalarMap
15 1
from ...utils import iterate
16
17
18 1
class CfdiRelacionados(ScalarMap):
19
    """
20
    Nodo opcional para precisar la información de los comprobantes relacionados.
21
22
    :param tipo_relacion: Atributo requerido para indicar la clave de la relación que existe entre éste que se está generando y el o los CFDI previos.
23
    :param cfdi_relacionado: Nodo requerido para precisar la información de los comprobantes relacionados.
24
    """
25
26 1
    def __init__(
27
            self,
28
            tipo_relacion: str,
29
            cfdi_relacionado: str | Sequence[str],
30
    ):
31
        super().__init__({
32
            'TipoRelacion': tipo_relacion,
33
            'CfdiRelacionado': cfdi_relacionado,
34
        })
35
36
37 1
class InformacionGlobal(ScalarMap):
38
    """
39
    Nodo condicional para precisar la información relacionada con el comprobante global.
40
41
    :param periodicidad: Atributo requerido para expresar el período al que corresponde la información del comprobante global.
42
    :param meses: Atributo requerido para expresar el mes o los meses al que corresponde la información del comprobante global.
43
    :param ano: Atributo requerido para expresar el año al que corresponde la información del comprobante global.
44
    """
45
46 1
    def __init__(
47
            self,
48
            periodicidad: str,
49
            meses: str,
50
            ano: int,
51
    ):
52
        super().__init__({
53
            'Periodicidad': periodicidad,
54
            'Meses': meses,
55
            'Año': ano,
56
        })
57
58
59 1 View Code Duplication
class Parte(ScalarMap):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
60
    """
61
    Nodo opcional para expresar las partes o componentes que integran la totalidad del concepto expresado en el comprobante fiscal digital por Internet.
62
63
    :param clave_prod_serv: Atributo requerido para expresar la clave del producto o del servicio amparado por la presente parte. Es requerido y deben utilizar las claves del catálogo de productos y servicios, cuando los conceptos que registren por sus actividades correspondan con dichos conceptos.
64
    :param cantidad: Atributo requerido para precisar la cantidad de bienes o servicios del tipo particular definido por la presente parte.
65
    :param descripcion: Atributo requerido para precisar la descripción del bien o servicio cubierto por la presente parte.
66
    :param no_identificacion: Atributo opcional para expresar el número de serie, número de parte del bien o identificador del producto o del servicio amparado por la presente parte. Opcionalmente se puede utilizar claves del estándar GTIN.
67
    :param unidad: Atributo opcional para precisar la unidad de medida propia de la operación del emisor, aplicable para la cantidad expresada en la parte. La unidad debe corresponder con la descripción de la parte.
68
    :param valor_unitario: Atributo opcional para precisar el valor o precio unitario del bien o servicio cubierto por la presente parte. No se permiten valores negativos.
69
    :param importe: Atributo opcional para precisar el importe total de los bienes o servicios de la presente parte. Debe ser equivalente al resultado de multiplicar la cantidad por el valor unitario expresado en la parte. No se permiten valores negativos.
70
    :param informacion_aduanera: Nodo opcional para introducir la información aduanera aplicable cuando se trate de ventas de primera mano de mercancías importadas o se trate de operaciones de comercio exterior con bienes o servicios.
71
    """
72
73 1
    def __init__(
74
            self,
75
            clave_prod_serv: str,
76
            cantidad: Decimal | int,
77
            descripcion: str,
78
            no_identificacion: str = None,
79
            unidad: str = None,
80
            valor_unitario: Decimal | int = None,
81
            importe: Decimal | int = None,
82
            informacion_aduanera: str | Sequence[str] = None,
83
    ):
84
        super().__init__({
85
            'ClaveProdServ': clave_prod_serv,
86
            'Cantidad': cantidad,
87
            'Descripcion': descripcion,
88
            'NoIdentificacion': no_identificacion,
89
            'Unidad': unidad,
90
            'ValorUnitario': valor_unitario,
91
            'Importe': importe,
92
            'InformacionAduanera': informacion_aduanera,
93
        })
94
95
96 1
class ACuentaTerceros(ScalarMap):
97
    """
98
    Nodo opcional para registrar información del contribuyente Tercero, a cuenta del que se realiza la operación.
99
100
    :param rfc_a_cuenta_terceros: Atributo requerido para registrar la Clave del Registro Federal de Contribuyentes del contribuyente Tercero, a cuenta del que se realiza la operación.
101
    :param nombre_a_cuenta_terceros: Atributo requerido para registrar el nombre, denominación o razón social del contribuyente Tercero correspondiente con el Rfc, a cuenta del que se realiza la operación.
102
    :param regimen_fiscal_a_cuenta_terceros: Atributo requerido para incorporar la clave del régimen del contribuyente Tercero, a cuenta del que se realiza la operación.
103
    :param domicilio_fiscal_a_cuenta_terceros: Atributo requerido para incorporar el código postal del domicilio fiscal del Tercero, a cuenta del que se realiza la operación.
104
    """
105
106 1
    def __init__(
107
            self,
108
            rfc_a_cuenta_terceros: str,
109
            nombre_a_cuenta_terceros: str,
110
            regimen_fiscal_a_cuenta_terceros: str,
111
            domicilio_fiscal_a_cuenta_terceros: str,
112
    ):
113
        super().__init__({
114
            'RfcACuentaTerceros': rfc_a_cuenta_terceros,
115
            'NombreACuentaTerceros': nombre_a_cuenta_terceros,
116
            'RegimenFiscalACuentaTerceros': regimen_fiscal_a_cuenta_terceros,
117
            'DomicilioFiscalACuentaTerceros': domicilio_fiscal_a_cuenta_terceros,
118
        })
119
120
121 1
def _find_impuesto(impuesto):
122 1
    try:
123 1
        return CatImpuesto[impuesto]
124 1
    except KeyError:
125 1
        return impuesto
126
127
128 1 View Code Duplication
class Traslado(ScalarMap):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
129
    """
130
    Nodo requerido para la información detallada de un traslado de impuesto específico.
131
132
    :param base: Atributo requerido para señalar la suma de los atributos Base de los conceptos del impuesto trasladado. No se permiten valores negativos.
133
    :param impuesto: Atributo requerido para señalar la clave del tipo de impuesto retencion.
134
    :param tipo_factor: Atributo requerido para señalar la clave del tipo de factor que se aplica a la base del impuesto.
135
    :param tasa_o_cuota: Atributo condicional para señalar el valor de la tasa o cuota del impuesto que se traslada por los conceptos amparados en el comprobante.
136
    :param importe: Atributo condicional para señalar la suma del importe del impuesto trasladado, agrupado por impuesto, TipoFactor y TasaOCuota. No se permiten valores negativos.
137
    """
138
139 1
    def __init__(
140
            self,
141
            impuesto: str,
142
            tipo_factor: str,
143
            tasa_o_cuota: Decimal | int = None,
144
            importe: Decimal | int = None,
145
            base: Decimal | int = None,
146
    ):
147 1
        super().__init__({
148
            'Base': base,
149
            'Impuesto': _find_impuesto(impuesto),
150
            'TipoFactor': tipo_factor,
151
            'TasaOCuota': tasa_o_cuota,
152
            'Importe': importe,
153
        })
154
155
156 1 View Code Duplication
class Retencion(ScalarMap):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
157
    """
158
    Nodo requerido para la información detallada de un traslado de impuesto específico.
159
160
    :param base: Atributo requerido para señalar la suma de los atributos Base de los conceptos del impuesto trasladado. No se permiten valores negativos.
161
    :param impuesto: Atributo requerido para señalar la clave del tipo de impuesto retencion.
162
    :param tipo_factor: Atributo requerido para señalar la clave del tipo de factor que se aplica a la base del impuesto.
163
    :param tasa_o_cuota: Atributo condicional para señalar el valor de la tasa o cuota del impuesto que se traslada por los conceptos amparados en el comprobante.
164
    :param importe: Atributo condicional para señalar la suma del importe del impuesto trasladado, agrupado por impuesto, TipoFactor y TasaOCuota. No se permiten valores negativos.
165
    """
166
167 1
    def __init__(
168
            self,
169
            impuesto: str,
170
            tipo_factor: str,
171
            tasa_o_cuota: Decimal | int = None,
172
            importe: Decimal | int = None,
173
            base: Decimal | int = None,
174
    ):
175 1
        super().__init__({
176
            'Base': base,
177
            'Impuesto': _find_impuesto(impuesto),
178
            'TipoFactor': tipo_factor,
179
            'TasaOCuota': tasa_o_cuota,
180
            'Importe': importe,
181
        })
182
183
184 1 View Code Duplication
class Impuestos(ScalarMap):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
185
    """
186
    Nodo condicional para expresar el resumen de los impuestos aplicables.
187
188
    :param retenciones: Nodo condicional para capturar los impuestos retenidos aplicables. Es requerido cuando en los conceptos se registre algún impuesto retenido.
189
    :param traslados: Nodo condicional para capturar los impuestos trasladados aplicables. Es requerido cuando en los conceptos se registre un impuesto trasladado.
190
    """
191
192 1
    def __init__(
193
            self,
194
            retenciones: Retencion | dict | str | Sequence[Retencion | dict | str] = None,
195
            traslados: Traslado | dict | str | Sequence[Traslado | dict | str] = None,
196
    ):
197 1
        super().__init__({
198
            'Retenciones': retenciones,
199
            'Traslados': traslados,
200
        })
201
202
203 1
class Concepto(ScalarMap):
204
    """
205
    Nodo requerido para registrar la información detallada de un bien o servicio amparado en el comprobante.
206
207
    :param clave_prod_serv: Atributo requerido para expresar la clave del producto o del servicio amparado por el presente concepto. Es requerido y deben utilizar las claves del catálogo de productos y servicios, cuando los conceptos que registren por sus actividades correspondan con dichos conceptos.
208
    :param cantidad: Atributo requerido para precisar la cantidad de bienes o servicios del tipo particular definido por el presente concepto.
209
    :param clave_unidad: Atributo requerido para precisar la clave de unidad de medida estandarizada aplicable para la cantidad expresada en el concepto. La unidad debe corresponder con la descripción del concepto.
210
    :param descripcion: Atributo requerido para precisar la descripción del bien o servicio cubierto por el presente concepto.
211
    :param valor_unitario: Atributo requerido para precisar el valor o precio unitario del bien o servicio cubierto por el presente concepto.
212
    :param objeto_imp: Atributo requerido para expresar si la operación comercial es objeto o no de impuesto.
213
    :param no_identificacion: Atributo opcional para expresar el número de parte, identificador del producto o del servicio, la clave de producto o servicio, SKU o equivalente, propia de la operación del emisor, amparado por el presente concepto. Opcionalmente se puede utilizar claves del estándar GTIN.
214
    :param unidad: Atributo opcional para precisar la unidad de medida propia de la operación del emisor, aplicable para la cantidad expresada en el concepto. La unidad debe corresponder con la descripción del concepto.
215
    :param descuento: Atributo opcional para representar el importe de los descuentos aplicables al concepto. No se permiten valores negativos.
216
    :param a_cuenta_terceros: Nodo opcional para registrar información del contribuyente Tercero, a cuenta del que se realiza la operación.
217
    :param informacion_aduanera: Nodo opcional para introducir la información aduanera aplicable cuando se trate de ventas de primera mano de mercancías importadas o se trate de operaciones de comercio exterior con bienes o servicios.
218
    :param cuenta_predial: Nodo opcional para asentar el número de cuenta predial con el que fue registrado el inmueble, en el sistema catastral de la entidad federativa de que trate, o bien para incorporar los datos de identificación del certificado de participación inmobiliaria no amortizable.
219
    :param complemento_concepto: Nodo opcional donde se incluyen los nodos complementarios de extensión al concepto definidos por el SAT, de acuerdo con las disposiciones particulares para un sector o actividad específica.
220
    :param parte: Nodo opcional para expresar las partes o componentes que integran la totalidad del concepto expresado en el comprobante fiscal digital por Internet.
221
    """
222
223 1
    def __init__(
224
            self,
225
            clave_prod_serv: str,
226
            cantidad: Decimal | int,
227
            clave_unidad: str,
228
            descripcion: str,
229
            valor_unitario: Decimal | int,
230
            objeto_imp: str = None,
231
            no_identificacion: str = None,
232
            unidad: str = None,
233
            descuento: Decimal | int = None,
234
            impuestos: Impuestos | dict = None,
235
            a_cuenta_terceros: ACuentaTerceros | dict = None,
236
            informacion_aduanera: str | Sequence[str] = None,
237
            cuenta_predial: str | Sequence[str] = None,
238
            complemento_concepto: CFDI | Sequence[CFDI] = None,
239
            parte: Parte | Sequence[Parte | dict] = None
240
    ):
241 1
        super().__init__({
242
            'ClaveProdServ': clave_prod_serv,
243
            'Cantidad': cantidad,
244
            'ClaveUnidad': clave_unidad,
245
            'Descripcion': descripcion,
246
            'ValorUnitario': valor_unitario,
247
            'ObjetoImp': objeto_imp,
248
            'NoIdentificacion': no_identificacion,
249
            'Unidad': unidad,
250
            'Descuento': descuento,
251
            'Impuestos': impuestos,
252
            'ACuentaTerceros': a_cuenta_terceros,
253
            'InformacionAduanera': informacion_aduanera,
254
            'CuentaPredial': cuenta_predial,
255
            'ComplementoConcepto': complemento_concepto,
256
            'Parte': parte
257
        })
258
259
260 1
class Receptor(ScalarMap):
261
    """
262
    Nodo requerido para precisar la información del contribuyente receptor del comprobante.
263
264
    :param rfc: Atributo requerido para registrar la Clave del Registro Federal de Contribuyentes correspondiente al contribuyente receptor del comprobante.
265
    :param nombre: Atributo requerido para registrar el nombre(s), primer apellido, segundo apellido, según corresponda, denominación o razón social del contribuyente, inscrito en el RFC, del receptor del comprobante.
266
    :param domicilio_fiscal_receptor: Atributo requerido para registrar el código postal del domicilio fiscal del receptor del comprobante.
267
    :param regimen_fiscal_receptor: Atributo requerido para incorporar la clave del régimen fiscal del contribuyente receptor al que aplicará el efecto fiscal de este comprobante.
268
    :param uso_cfdi: Atributo requerido para expresar la clave del uso que dará a esta factura el receptor del CFDI.
269
    :param residencia_fiscal: Atributo condicional para registrar la clave del país de residencia para efectos fiscales del receptor del comprobante, cuando se trate de un extranjero, y que es conforme con la especificación ISO 3166-1 alpha-3. Es requerido cuando se incluya el complemento de comercio exterior o se registre el atributo NumRegIdTrib.
270
    :param num_reg_id_trib: Atributo condicional para expresar el número de registro de identidad fiscal del receptor cuando sea residente en el extranjero. Es requerido cuando se incluya el complemento de comercio exterior.
271
    """
272
273 1
    def __init__(
274
            self,
275
            rfc: str,
276
            nombre: str,
277
            domicilio_fiscal_receptor: str,
278
            regimen_fiscal_receptor: str,
279
            uso_cfdi: str,
280
            residencia_fiscal: str = None,
281
            num_reg_id_trib: str = None,
282
    ):
283 1
        super().__init__({
284
            'Rfc': rfc,
285
            'Nombre': nombre,
286
            'DomicilioFiscalReceptor': domicilio_fiscal_receptor,
287
            'RegimenFiscalReceptor': regimen_fiscal_receptor,
288
            'UsoCFDI': uso_cfdi,
289
            'ResidenciaFiscal': residencia_fiscal,
290
            'NumRegIdTrib': num_reg_id_trib,
291
        })
292
293
294 1
class Emisor(ScalarMap):
295
    """
296
    Nodo requerido para expresar la información del contribuyente emisor del comprobante.
297
298
    :param rfc: Atributo requerido para registrar la Clave del Registro Federal de Contribuyentes correspondiente al contribuyente emisor del comprobante.
299
    :param nombre: Atributo requerido para registrar el nombre, denominación o razón social del contribuyente inscrito en el RFC, del emisor del comprobante.
300
    :param regimen_fiscal: Atributo requerido para incorporar la clave del régimen del contribuyente emisor al que aplicará el efecto fiscal de este comprobante.
301
    :param fac_atr_adquirente: Atributo condicional para expresar el número de operación proporcionado por el SAT cuando se trate de un comprobante a través de un PCECFDI o un PCGCFDISP.
302
    """
303
304 1
    def __init__(
305
            self,
306
            rfc: str,
307
            nombre: str,
308
            regimen_fiscal: str,
309
            fac_atr_adquirente: str = None,
310
    ):
311 1
        super().__init__({
312
            'Rfc': rfc,
313
            'Nombre': nombre,
314
            'RegimenFiscal': regimen_fiscal,
315
            'FacAtrAdquirente': fac_atr_adquirente,
316
        })
317
318
319 1
@dataclass
320 1
class PagoComprobante:
321 1
    comprobante: CFDI
322 1
    num_parcialidad: int = None
323 1
    imp_saldo_ant: Decimal | int = None
324 1
    imp_pagado: Decimal | int = None
325
326 1
    def __post_init__(self):
327 1
        if self.num_parcialidad is None and self.imp_saldo_ant is None and self.imp_pagado is None:
328 1
            self.num_parcialidad = 1
329 1
            self.imp_saldo_ant = self.comprobante['Total']
330 1
            self.imp_pagado = self.comprobante['Total']
331
332 1
        if self.imp_pagado > self.imp_saldo_ant:
333
            raise ValueError('Importe Pagado debe de ser menor o igual al Importe Saldo Anterior')
334
335
336 1
def _make_conceptos(conceptos, decimals):
337 1
    rnd_fn = lambda v: round(v, decimals)
338 1
    rnd_tracker_instance = RoundTrackerManager(decimals)
339
340 1
    def make_concepto(concepto):
341 1
        valor_unitario = concepto['ValorUnitario']
342
343 1
        importe = rnd_fn(concepto["Cantidad"] * valor_unitario)
344 1
        concepto["Importe"] = importe
345 1
        base = importe - (concepto.get("Descuento") or 0)
346
347 1
        impuestos = concepto['Impuestos'] = {
348
            imp_k: [
349
                make_impuesto(i, base=base,
350
                              rnd_fn=rnd_tracker_instance[imp_k + strcode(i["Impuesto"])]) for i
351
                in iterate(imp_v)
352
            ]
353
            for imp_k, imp_v in (concepto.get("Impuestos") or {}).items() if imp_v
354
        } or None
355
356 1
        if not concepto.get("ObjetoImp"):
357 1
            concepto["ObjetoImp"] = "02" if impuestos else "01"
358
359 1
        return concepto
360
361 1
    return [make_concepto(c) for c in iterate(conceptos)]
362
363
364
# MAIN #
365 1
class Comprobante(CFDI):
366
    """
367
    Estándar de Comprobante Fiscal Digital por Internet.
368
369
    :param emisor: Nodo requerido para expresar la información del contribuyente emisor del comprobante.
370
    :param lugar_expedicion: Atributo requerido para incorporar el código postal del lugar de expedición del comprobante (domicilio de la matriz o de la sucursal).
371
    :param receptor: Nodo requerido para precisar la información del contribuyente receptor del comprobante.
372
    :param conceptos: Nodo requerido para listar los conceptos cubiertos por el comprobante.
373
    :param moneda: Atributo requerido para identificar la clave de la moneda utilizada para expresar los montos, cuando se usa moneda nacional se registra MXN. Conforme con la especificación ISO 4217.
374
    :param tipo_de_comprobante: Atributo requerido para expresar la clave del efecto del comprobante fiscal para el contribuyente emisor.
375
    :param exportacion: Atributo requerido para expresar si el comprobante ampara una operación de exportación.
376
    :param serie: Atributo opcional para precisar la serie para control interno del contribuyente. Este atributo acepta una cadena de caracteres.
377
    :param folio: Atributo opcional para control interno del contribuyente que expresa el folio del comprobante, acepta una cadena de caracteres.
378
    :param forma_pago: Atributo condicional para expresar la clave de la forma de pago de los bienes o servicios amparados por el comprobante.
379
    :param condiciones_de_pago: Atributo condicional para expresar las condiciones comerciales aplicables para el pago del comprobante fiscal digital por Internet. Este atributo puede ser condicionado mediante atributos o complementos.
380
    :param tipo_cambio: Atributo condicional para representar el tipo de cambio FIX conforme con la moneda usada. Es requerido cuando la clave de moneda es distinta de MXN y de XXX. El valor debe reflejar el número de pesos mexicanos que equivalen a una unidad de la divisa señalada en el atributo moneda. Si el valor está fuera del porcentaje aplicable a la moneda tomado del catálogo c_Moneda, el emisor debe obtener del PAC que vaya a timbrar el CFDI, de manera no automática, una clave de confirmación para ratificar que el valor es correcto e integrar dicha clave en el atributo Confirmacion.
381
    :param metodo_pago: Atributo condicional para precisar la clave del método de pago que aplica para este comprobante fiscal digital por Internet, conforme al Artículo 29-A fracción VII incisos a y b del CFF.
382
    :param confirmacion: Atributo condicional para registrar la clave de confirmación que entregue el PAC para expedir el comprobante con importes grandes, con un tipo de cambio fuera del rango establecido o con ambos casos. Es requerido cuando se registra un tipo de cambio o un total fuera del rango establecido.
383
    :param informacion_global: Nodo condicional para precisar la información relacionada con el comprobante global.
384
    :param cfdi_relacionados: Nodo opcional para precisar la información de los comprobantes relacionados.
385
    :param complemento: Nodo opcional donde se incluye el complemento Timbre Fiscal Digital de manera obligatoria y los nodos complementarios determinados por el SAT, de acuerdo con las disposiciones particulares para un sector o actividad específica.
386
    :param addenda: Nodo opcional para recibir las extensiones al presente formato que sean de utilidad al contribuyente. Para las reglas de uso del mismo, referirse al formato origen.
387
    :param fecha: Atributo requerido para la expresión de la fecha y hora de expedición del Comprobante Fiscal Digital por Internet. Se expresa en la forma AAAA-MM-DDThh:mm:ss y debe corresponder con la hora local donde se expide el comprobante.
388
    """
389
390 1
    tag = '{http://www.sat.gob.mx/cfd/4}Comprobante'
391 1
    version = '4.0'
392 1
    complemento_pago = pago20.Pagos
393
394 1
    def __init__(
395
            self,
396
            emisor: Emisor | dict,
397
            lugar_expedicion: str,
398
            receptor: Receptor | dict,
399
            conceptos: Concepto | Sequence[Concepto | dict],
400
            moneda: str = "MXN",
401
            tipo_de_comprobante: str = "I",
402
            exportacion: str = "01",
403
            serie: str = None,
404
            folio: str = None,
405
            forma_pago: str = None,
406
            condiciones_de_pago: str = None,
407
            tipo_cambio: Decimal | int = None,
408
            metodo_pago: str = None,
409
            confirmacion: str = None,
410
            informacion_global: InformacionGlobal | dict = None,
411
            cfdi_relacionados: CfdiRelacionados | Sequence[CfdiRelacionados | dict] = None,
412
            complemento: CFDI | Sequence[CFDI] = None,
413
            addenda: CFDI | Sequence[CFDI] = None,
414
            fecha: datetime = None
415
    ):
416 1
        super().__init__({
417
            'Version': self.version,
418
            'Fecha': fecha or datetime.now(tz=get_timezone(lugar_expedicion)).replace(tzinfo=None),
419
            'Sello': '',
420
            'SubTotal': None,
421
            'Moneda': moneda,
422
            'Total': None,
423
            'TipoDeComprobante': tipo_de_comprobante,
424
            'Exportacion': exportacion,
425
            'LugarExpedicion': lugar_expedicion,
426
            'Serie': serie,
427
            'Folio': folio,
428
            'FormaPago': forma_pago,
429
            'CondicionesDePago': condiciones_de_pago,
430
            'Descuento': None,
431
            'TipoCambio': tipo_cambio,
432
            'MetodoPago': metodo_pago,
433
            'Confirmacion': confirmacion,
434
            'InformacionGlobal': informacion_global,
435
            'CfdiRelacionados': cfdi_relacionados,
436
            'Emisor': emisor,
437
            'Receptor': receptor,
438
            'Conceptos': conceptos,
439
            'Impuestos': None,
440
            'Complemento': complemento,
441
            'Addenda': addenda,
442
            'NoCertificado': '',
443
            'Certificado': '',
444
        })
445 1
        self.compute()
446
447 1
    def compute(self):
448 1
        round_decimals = m_decimals(self["Moneda"])
449 1
        self["Conceptos"] = conceptos = _make_conceptos(self["Conceptos"], decimals=round_decimals)
450 1
        self["SubTotal"] = sub_total = sum(c['Importe'] for c in conceptos)
451 1
        descuento = sum(c.get('Descuento') or 0 for c in conceptos)
452 1
        self['Descuento'] = descuento or None
453 1
        self['Impuestos'] = impuestos = make_impuestos(conceptos)
454 1
        total = sub_total - descuento
455 1
        if impuestos:
456 1
            total += impuestos.get('TotalImpuestosTrasladados') or 0
457 1
            total -= impuestos.get('TotalImpuestosRetenidos') or 0
458 1
        self['Total'] = total
459
460 1
    @classmethod
461 1
    def pago(
462
            cls,
463
            emisor: Emisor | dict,
464
            lugar_expedicion: str,
465
            receptor: Receptor | dict,
466
            complemento_pago: CFDI,
467
            cfdi_relacionados: CfdiRelacionados | Sequence[CfdiRelacionados | dict] = None,
468
            confirmacion: str = None,
469
            serie: str = None,
470
            folio: str = None,
471
            addenda: CFDI | Sequence[CFDI] = None,
472
            fecha: datetime = None) -> 'Comprobante':
473
        """
474
        Estándar de Comprobante Fiscal Digital por Internet de Tipo Pago.
475
476
        :param emisor: Nodo requerido para expresar la información del contribuyente emisor del comprobante.
477
        :param lugar_expedicion: Atributo requerido para incorporar el código postal del lugar de expedición del comprobante (domicilio de la matriz o de la sucursal).
478
        :param receptor: Nodo requerido para precisar la información del contribuyente receptor del comprobante.
479
        :param complemento_pago: Pago
480
        :param serie: Atributo opcional para precisar la serie para control interno del contribuyente. Este atributo acepta una cadena de caracteres.
481
        :param folio: Atributo opcional para control interno del contribuyente que expresa el folio del comprobante, acepta una cadena de caracteres.
482
        :param confirmacion: Atributo condicional para registrar la clave de confirmación que entregue el PAC para expedir el comprobante con importes grandes, con un tipo de cambio fuera del rango establecido o con ambos casos. Es requerido cuando se registra un tipo de cambio o un total fuera del rango establecido.
483
        :param cfdi_relacionados: Nodo opcional para precisar la información de los comprobantes relacionados.
484
        :param addenda: Nodo opcional para recibir las extensiones al presente formato que sean de utilidad al contribuyente. Para las reglas de uso del mismo, referirse al formato origen.
485
        :param fecha: Atributo requerido para la expresión de la fecha y hora de expedición del Comprobante Fiscal Digital por Internet. Se expresa en la forma AAAA-MM-DDThh:mm:ss y debe corresponder con la hora local donde se expide el comprobante.
486
        :return: Comprobante
487
        """
488 1
        if cls.version == "3.3":
489 1
            receptor["UsoCFDI"] = "P01"
490
        else:
491 1
            receptor["UsoCFDI"] = "CP01"
492
493 1
        return cls(
494
            emisor=emisor,
495
            lugar_expedicion=lugar_expedicion,
496
            receptor=receptor,
497
            conceptos=Concepto(
498
                clave_prod_serv='84111506',
499
                cantidad=1,
500
                clave_unidad='ACT',
501
                descripcion='Pago',
502
                valor_unitario=Decimal(0),
503
                objeto_imp="01"
504
            ),
505
            complemento=complemento_pago,
506
            serie=serie,
507
            folio=folio,
508
            moneda='XXX',
509
            tipo_de_comprobante='P',
510
            cfdi_relacionados=cfdi_relacionados,
511
            confirmacion=confirmacion,
512
            exportacion="01",
513
            addenda=addenda,
514
            fecha=fecha
515
        )
516
517 1
    @classmethod
518 1
    def _pago_tipo_cambio(cls, moneda, tipo_cambio):
519
        # CRP204: El campo TipoCambioP no debe estar presente cuando el campo Moneda contenga ^MXN$ en el nodo Pago
520 1
        if cls.complemento_pago.version == "1.0":
521 1
            if moneda == 'MXN' and tipo_cambio == 1:
522
                tipo_cambio = None
523
        else:
524 1
            if moneda == 'MXN' and tipo_cambio is None:
525 1
                tipo_cambio = 1
526 1
        return tipo_cambio
527
528 1
    @classmethod
529 1
    def pago_comprobantes(
530
            cls,
531
            comprobantes: CFDI | PagoComprobante | Sequence[CFDI | PagoComprobante],
532
            fecha_pago: datetime,
533
            forma_pago: str,
534
            emisor: Emisor | dict = None,
535
            lugar_expedicion: str = None,
536
            receptor: Receptor | dict = None,
537
            tipo_cambio: Decimal | int = None,
538
            cfdi_relacionados: CfdiRelacionados | Sequence[CfdiRelacionados | dict] = None,
539
            confirmacion: str = None,
540
            serie: str = None,
541
            folio: str = None,
542
            addenda: CFDI | Sequence[CFDI] = None,
543
            fecha: datetime = None) -> 'Comprobante':
544
        """
545
        Estándar de Comprobante Fiscal Digital por Internet de Tipo Pago. Generado a partir de una lista de Comprobantes
546
        Se asume que los comprobantes se pagan en su totalidad en una sola exhibición
547
548
        :param emisor: Nodo requerido para expresar la información del contribuyente emisor del comprobante.
549
        :param lugar_expedicion: Atributo requerido para incorporar el código postal del lugar de expedición del comprobante (domicilio de la matriz o de la sucursal).
550
        :param receptor: Nodo requerido para precisar la información del contribuyente receptor del comprobante.
551
        :param comprobantes: CFDI(s) de Comprobante de Ingreso para generar el pago por su monto total o parcial usando PagoComprobante
552
        :param fecha_pago: Atributo requerido para expresar la fecha y hora en la que el beneficiario recibe el pago. Se expresa en la forma aaaa-mm-ddThh:mm:ss, de acuerdo con la especificación ISO 8601.En caso de no contar con la hora se debe registrar 12:00:00.
553
        :param serie: Atributo opcional para precisar la serie para control interno del contribuyente. Este atributo acepta una cadena de caracteres.
554
        :param folio: Atributo opcional para control interno del contribuyente que expresa el folio del comprobante, acepta una cadena de caracteres.
555
        :param forma_pago: Atributo condicional para expresar la clave de la forma de pago de los bienes o servicios amparados por el comprobante.
556
        :param tipo_cambio: Atributo condicional para representar el tipo de cambio FIX conforme con la moneda usada. Es requerido cuando la clave de moneda es distinta de MXN y de XXX. El valor debe reflejar el número de pesos mexicanos que equivalen a una unidad de la divisa señalada en el atributo moneda. Si el valor está fuera del porcentaje aplicable a la moneda tomado del catálogo c_Moneda, el emisor debe obtener del PAC que vaya a timbrar el CFDI, de manera no automática, una clave de confirmación para ratificar que el valor es correcto e integrar dicha clave en el atributo Confirmacion.
557
        :param confirmacion: Atributo condicional para registrar la clave de confirmación que entregue el PAC para expedir el comprobante con importes grandes, con un tipo de cambio fuera del rango establecido o con ambos casos. Es requerido cuando se registra un tipo de cambio o un total fuera del rango establecido.
558
        :param cfdi_relacionados: Nodo opcional para precisar la información de los comprobantes relacionados.
559
        :param addenda: Nodo opcional para recibir las extensiones al presente formato que sean de utilidad al contribuyente. Para las reglas de uso del mismo, referirse al formato origen.
560
        :param fecha: Atributo requerido para la expresión de la fecha y hora de expedición del Comprobante Fiscal Digital por Internet. Se expresa en la forma AAAA-MM-DDThh:mm:ss y debe corresponder con la hora local donde se expide el comprobante.
561
        :return: Comprobante
562
        """
563 1
        comprobantes = [c if isinstance(c, PagoComprobante) else PagoComprobante(comprobante=c) for c in iterate(comprobantes)]
564 1
        first_cfdi = comprobantes[0].comprobante
565 1
        moneda = first_cfdi['Moneda']
566
567 1
        emisor = emisor or first_cfdi['Emisor'].copy()
568 1
        lugar_expedicion = lugar_expedicion or first_cfdi['LugarExpedicion']
569 1
        receptor = receptor or first_cfdi['Receptor'].copy()
570
571 1
        tipo_cambio = cls._pago_tipo_cambio(moneda, tipo_cambio)
572
573 1
        if not all(
574
                c.comprobante["Moneda"] == moneda
575
                and c.comprobante["Emisor"]["Rfc"] == emisor["Rfc"]
576
                and c.comprobante["Emisor"]["RegimenFiscal"] == emisor["RegimenFiscal"]
577
                and c.comprobante["Receptor"]["Rfc"] == receptor["Rfc"]
578
                and c.comprobante["Receptor"].get("RegimenFiscalReceptor") == receptor.get("RegimenFiscalReceptor")
579
                for c in comprobantes
580
        ):
581
            raise ValueError("CFDIS are of different RFC's Emisor/Receptor o Moneda")
582
583 1
        return cls.pago(
584
            emisor=emisor,
585
            lugar_expedicion=lugar_expedicion,
586
            receptor=receptor,
587
            complemento_pago=cls.complemento_pago(
588
                pago=[{
589
                    'DoctoRelacionado': [
590
                        {
591
                            'IdDocumento': c.comprobante["Complemento"]["TimbreFiscalDigital"]["UUID"],
592
                            'Serie': c.comprobante.get("Serie"),
593
                            'Folio': c.comprobante.get("Folio"),
594
                            'MonedaDR': c.comprobante["Moneda"],
595
                            'EquivalenciaDR': 1,
596
                            'MetodoDePagoDR': c.comprobante["MetodoPago"],
597
                            'NumParcialidad': c.num_parcialidad,
598
                            'ImpSaldoAnt': c.imp_saldo_ant,
599
                            'ImpPagado': c.imp_pagado,
600
                            'ObjetoImpDR': '02' if 'Impuestos' in c.comprobante else '01',
601
                            'ImpuestosDR': make_impuestos_dr_parcial(
602
                                conceptos=c.comprobante['Conceptos'],
603
                                imp_saldo_ant=c.imp_saldo_ant,
604
                                imp_pagado=c.imp_pagado,
605
                                total=c.comprobante["Total"],
606
                                rnd_fn=rounder(c.comprobante["Moneda"])
607
                            ) if 'Impuestos' in c.comprobante else None
608
                        } for c in comprobantes
609
                    ],
610
                    'FechaPago': fecha_pago,
611
                    'FormaDePagoP': forma_pago,
612
                    'MonedaP': moneda,
613
                    'TipoCambioP': tipo_cambio
614
                }]
615
            ),
616
            cfdi_relacionados=cfdi_relacionados,
617
            confirmacion=confirmacion,
618
            serie=serie,
619
            folio=folio,
620
            addenda=addenda,
621
            fecha=fecha
622
        )
623
624 1
    @classmethod
625 1
    def nomina(
626
            cls,
627
            emisor: Emisor | dict,
628
            lugar_expedicion: str,
629
            receptor: Receptor | dict,
630
            complemento_nomina: CFDI,
631
            cfdi_relacionados: CfdiRelacionados | Sequence[CfdiRelacionados | dict] = None,
632
            confirmacion: str = None,
633
            serie: str = None,
634
            folio: str = None,
635
            addenda: CFDI | Sequence[CFDI] = None,
636
            fecha: datetime = None) -> 'Comprobante':
637
        """
638
        Estándar de Comprobante Fiscal Digital por Internet de Tipo Pago.
639
640
        :param emisor: Nodo requerido para expresar la información del contribuyente emisor del comprobante.
641
        :param lugar_expedicion: Atributo requerido para incorporar el código postal del lugar de expedición del comprobante (domicilio de la matriz o de la sucursal).
642
        :param receptor: Nodo requerido para precisar la información del contribuyente receptor del comprobante.
643
        :param complemento_nomina: Pago
644
        :param serie: Atributo opcional para precisar la serie para control interno del contribuyente. Este atributo acepta una cadena de caracteres.
645
        :param folio: Atributo opcional para control interno del contribuyente que expresa el folio del comprobante, acepta una cadena de caracteres.
646
        :param confirmacion: Atributo condicional para registrar la clave de confirmación que entregue el PAC para expedir el comprobante con importes grandes, con un tipo de cambio fuera del rango establecido o con ambos casos. Es requerido cuando se registra un tipo de cambio o un total fuera del rango establecido.
647
        :param cfdi_relacionados: Nodo opcional para precisar la información de los comprobantes relacionados.
648
        :param addenda: Nodo opcional para recibir las extensiones al presente formato que sean de utilidad al contribuyente. Para las reglas de uso del mismo, referirse al formato origen.
649
        :param fecha: Atributo requerido para la expresión de la fecha y hora de expedición del Comprobante Fiscal Digital por Internet. Se expresa en la forma AAAA-MM-DDThh:mm:ss y debe corresponder con la hora local donde se expide el comprobante.
650
        :return: Comprobante
651
        """
652 1
        if cls.version == "3.3":
653 1
            receptor["UsoCFDI"] = "P01"
654
        else:
655 1
            receptor["UsoCFDI"] = "CN01"
656
657 1
        valor_unitario = (
658
            complemento_nomina.get('TotalPercepciones') or Decimal(0)
659
        ) + (
660
            complemento_nomina.get('TotalOtrosPagos') or Decimal(0)
661
        )
662
        
663 1
        concepto = Concepto(
664
            clave_prod_serv='84111505',
665
            cantidad=1,
666
            clave_unidad='ACT',
667
            descripcion='Pago de nómina',
668
            valor_unitario=valor_unitario,
669
            descuento=complemento_nomina.get('TotalDeducciones'),
670
            objeto_imp="03"
671
        )
672
        
673 1
        return cls(
674
            emisor=emisor,
675
            lugar_expedicion=lugar_expedicion,
676
            receptor=receptor,
677
            conceptos=concepto,
678
            complemento=complemento_nomina,
679
            serie=serie,
680
            folio=folio,
681
            moneda='MXN',
682
            tipo_de_comprobante='N',
683
            metodo_pago="PUE",
684
            forma_pago="99",
685
            cfdi_relacionados=cfdi_relacionados,
686
            confirmacion=confirmacion,
687
            exportacion="01",
688
            addenda=addenda,
689
            fecha=fecha,
690
        )
691