Passed
Push — main ( c109b9...4198d9 )
by Sat CFDI
04:48
created

InformacionGlobal.__init__()   A

Complexity

Conditions 1

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.125

Importance

Changes 0
Metric Value
cc 1
eloc 9
nop 4
dl 0
loc 18
ccs 1
cts 2
cp 0.5
crap 1.125
rs 9.95
c 0
b 0
f 0
1 1
from collections.abc import Sequence
2 1
from dataclasses import dataclass
3 1
from datetime import datetime
4 1
from decimal import Decimal
5 1
from typing import Iterator
6
7 1
from . import pago20
8 1
from ..compute import make_impuestos, rounder, make_impuestos_dr, make_impuesto, \
9
    make_impuestos_dr_parcial
10 1
from ... import CFDI, XElement, ScalarMap
11 1
from ...create import Issuer
12 1
from ...utils import iterate
13 1
from ...transform import get_timezone
14
15 1
_impuestos = {
16
    "ISR": "001",
17
    "IVA": "002",
18
    "IEPS": "003",
19
}
20
21
22 1
class Impuesto(ScalarMap):
23
    """
24
    http://www.sat.gob.mx/cfd/4
25
    Nodo requerido para la información detallada de un traslado de impuesto específico.
26
    """
27
28 1
    def __init__(
29
            self,
30
            impuesto: str,
31
            tipo_factor: str,
32
            tasa_o_cuota: Decimal | int = None,
33
            importe: Decimal | int = None,
34
            base: Decimal | int = None,
35
    ):
36
        """
37
        Nodo requerido para la información detallada de un impuesto específico.
38
39
        :param base: Atributo requerido para señalar la suma de los atributos Base de los conceptos del impuesto trasladado. No se permiten valores negativos.
40
        :param impuesto: Atributo requerido para señalar la clave del tipo de impuesto trasladado.
41
        :param tipo_factor: Atributo requerido para señalar la clave del tipo de factor que se aplica a la base del impuesto.
42
        :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.
43
        :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.
44
        """
45
46 1
        super().__init__({
47
            'Base': base,
48
            'Impuesto': _impuestos.get(impuesto, impuesto),
49
            'TipoFactor': tipo_factor,
50
            'TasaOCuota': tasa_o_cuota,
51
            'Importe': importe,
52
        })
53
54 1
    @classmethod
55 1
    def parse(cls, impuesto: str) -> 'Impuesto':
56 1
        parts = impuesto.split("|")
57 1
        return cls(
58
            impuesto=parts[0],
59
            tipo_factor=parts[1],
60
            tasa_o_cuota=Decimal(parts[2]) if len(parts) > 2 else None
61
        )
62
63
64 1
class CfdiRelacionados(ScalarMap):
65
    """
66
    http://www.sat.gob.mx/cfd/4
67
    Nodo opcional para precisar la información de los comprobantes relacionados.
68
    """
69
70 1
    def __init__(
71
            self,
72
            tipo_relacion: str,
73
            cfdi_relacionado: str | Sequence[str],
74
    ):
75
        """
76
        Nodo opcional para precisar la información de los comprobantes relacionados.
77
78
        :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.
79
        :param cfdi_relacionado: Nodo requerido para precisar la información de los comprobantes relacionados.
80
        """
81
82
        super().__init__({
83
            'TipoRelacion': tipo_relacion,
84
            'CfdiRelacionado': cfdi_relacionado,
85
        })
86
87
88 1
class InformacionGlobal(ScalarMap):
89
    """
90
    http://www.sat.gob.mx/cfd/4
91
    Nodo condicional para precisar la información relacionada con el comprobante global.
92
    """
93
94 1
    def __init__(
95
            self,
96
            periodicidad: str,
97
            meses: str,
98
            ano: int,
99
    ):
100
        """
101
        Nodo condicional para precisar la información relacionada con el comprobante global.
102
103
        :param periodicidad: Atributo requerido para expresar el período al que corresponde la información del comprobante global.
104
        :param meses: Atributo requerido para expresar el mes o los meses al que corresponde la información del comprobante global.
105
        :param ano: Atributo requerido para expresar el año al que corresponde la información del comprobante global.
106
        """
107
108
        super().__init__({
109
            'Periodicidad': periodicidad,
110
            'Meses': meses,
111
            'Año': ano,
112
        })
113
114
115 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...
116
    """
117
    http://www.sat.gob.mx/cfd/4
118
    Nodo opcional para expresar las partes o componentes que integran la totalidad del concepto expresado en el comprobante fiscal digital por Internet.
119
    """
120
121 1
    def __init__(
122
            self,
123
            clave_prod_serv: str,
124
            cantidad: Decimal | int,
125
            descripcion: str,
126
            no_identificacion: str = None,
127
            unidad: str = None,
128
            valor_unitario: Decimal | int = None,
129
            importe: Decimal | int = None,
130
            informacion_aduanera: str | Sequence[str] = None,
131
    ):
132
        """
133
        Nodo opcional para expresar las partes o componentes que integran la totalidad del concepto expresado en el comprobante fiscal digital por Internet.
134
135
        :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.
136
        :param cantidad: Atributo requerido para precisar la cantidad de bienes o servicios del tipo particular definido por la presente parte.
137
        :param descripcion: Atributo requerido para precisar la descripción del bien o servicio cubierto por la presente parte.
138
        :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.
139
        :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.
140
        :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.
141
        :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.
142
        :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.
143
        """
144
145
        super().__init__({
146
            'ClaveProdServ': clave_prod_serv,
147
            'Cantidad': cantidad,
148
            'Descripcion': descripcion,
149
            'NoIdentificacion': no_identificacion,
150
            'Unidad': unidad,
151
            'ValorUnitario': valor_unitario,
152
            'Importe': importe,
153
            'InformacionAduanera': informacion_aduanera,
154
        })
155
156
157 1
class ACuentaTerceros(ScalarMap):
158
    """
159
    http://www.sat.gob.mx/cfd/4
160
    Nodo opcional para registrar información del contribuyente Tercero, a cuenta del que se realiza la operación.
161
    """
162
163 1
    def __init__(
164
            self,
165
            rfc_a_cuenta_terceros: str,
166
            nombre_a_cuenta_terceros: str,
167
            regimen_fiscal_a_cuenta_terceros: str,
168
            domicilio_fiscal_a_cuenta_terceros: str,
169
    ):
170
        """
171
        Nodo opcional para registrar información del contribuyente Tercero, a cuenta del que se realiza la operación.
172
173
        :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.
174
        :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.
175
        :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.
176
        :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.
177
        """
178
179
        super().__init__({
180
            'RfcACuentaTerceros': rfc_a_cuenta_terceros,
181
            'NombreACuentaTerceros': nombre_a_cuenta_terceros,
182
            'RegimenFiscalACuentaTerceros': regimen_fiscal_a_cuenta_terceros,
183
            'DomicilioFiscalACuentaTerceros': domicilio_fiscal_a_cuenta_terceros,
184
        })
185
186
187 1
class Concepto(ScalarMap):
188
    """
189
    http://www.sat.gob.mx/cfd/4
190
    Nodo requerido para registrar la información detallada de un bien o servicio amparado en el comprobante.
191
    """
192
193 1
    def __init__(
194
            self,
195
            clave_prod_serv: str,
196
            cantidad: Decimal | int,
197
            clave_unidad: str,
198
            descripcion: str,
199
            valor_unitario: Decimal | int,
200
            objeto_imp: str = None,
201
            no_identificacion: str = None,
202
            unidad: str = None,
203
            descuento: Decimal | int = None,
204
            a_cuenta_terceros: ACuentaTerceros | dict = None,
205
            informacion_aduanera: str | Sequence[str] = None,
206
            cuenta_predial: str | Sequence[str] = None,
207
            complemento_concepto: CFDI | Sequence[CFDI] = None,
208
            parte: Parte | Sequence[Parte | dict] = None,
209
            traslados: Impuesto | str | Sequence[Impuesto | str | dict] = None,
210
            retenciones: Impuesto | str | Sequence[Impuesto | str | dict] = None,
211
            _traslados_incluidos: bool = False
212
    ):
213
        """
214
        Nodo requerido para registrar la información detallada de un bien o servicio amparado en el comprobante.
215
216
        :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.
217
        :param cantidad: Atributo requerido para precisar la cantidad de bienes o servicios del tipo particular definido por el presente concepto.
218
        :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.
219
        :param descripcion: Atributo requerido para precisar la descripción del bien o servicio cubierto por el presente concepto.
220
        :param valor_unitario: Atributo requerido para precisar el valor o precio unitario del bien o servicio cubierto por el presente concepto.
221
        :param objeto_imp: Atributo requerido para expresar si la operación comercial es objeto o no de impuesto.
222
        :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.
223
        :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.
224
        :param descuento: Atributo opcional para representar el importe de los descuentos aplicables al concepto. No se permiten valores negativos.
225
        :param a_cuenta_terceros: Nodo opcional para registrar información del contribuyente Tercero, a cuenta del que se realiza la operación.
226
        :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.
227
        :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.
228
        :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.
229
        :param parte: Nodo opcional para expresar las partes o componentes que integran la totalidad del concepto expresado en el comprobante fiscal digital por Internet.
230
        :param traslados: Traslados a aplicar.
231
        :param retenciones: Retenciones a aplicar.
232
        :param _traslados_incluidos: si el valor valor_unitario ya incluye traslados.
233
        """
234
235 1
        super().__init__({
236
            'ClaveProdServ': clave_prod_serv,
237
            'Cantidad': cantidad,
238
            'ClaveUnidad': clave_unidad,
239
            'Descripcion': descripcion,
240
            'ValorUnitario': valor_unitario,
241
            'ObjetoImp': objeto_imp,
242
            'NoIdentificacion': no_identificacion,
243
            'Unidad': unidad,
244
            'Descuento': descuento,
245
            'ACuentaTerceros': a_cuenta_terceros,
246
            'InformacionAduanera': informacion_aduanera,
247
            'CuentaPredial': cuenta_predial,
248
            'ComplementoConcepto': complemento_concepto,
249
            'Parte': parte,
250
            'Impuestos': {
251
                'Traslados': traslados,
252
                'Retenciones': retenciones
253
            },
254
            '_traslados_incluidos': _traslados_incluidos
255
        })
256
257
258 1
class Receptor(ScalarMap):
259
    """
260
    http://www.sat.gob.mx/cfd/4
261
    Nodo requerido para precisar la información del contribuyente receptor del comprobante.
262
    """
263
264 1
    def __init__(
265
            self,
266
            rfc: str,
267
            nombre: str,
268
            domicilio_fiscal_receptor: str,
269
            regimen_fiscal_receptor: str,
270
            uso_cfdi: str,
271
            residencia_fiscal: str = None,
272
            num_reg_id_trib: str = None,
273
    ):
274
        """
275
        Nodo requerido para precisar la información del contribuyente receptor del comprobante.
276
277
        :param rfc: Atributo requerido para registrar la Clave del Registro Federal de Contribuyentes correspondiente al contribuyente receptor del comprobante.
278
        :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.
279
        :param domicilio_fiscal_receptor: Atributo requerido para registrar el código postal del domicilio fiscal del receptor del comprobante.
280
        :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.
281
        :param uso_cfdi: Atributo requerido para expresar la clave del uso que dará a esta factura el receptor del CFDI.
282
        :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.
283
        :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.
284
        """
285
286 1
        super().__init__({
287
            'Rfc': rfc,
288
            'Nombre': nombre,
289
            'DomicilioFiscalReceptor': domicilio_fiscal_receptor,
290
            'RegimenFiscalReceptor': regimen_fiscal_receptor,
291
            'UsoCFDI': uso_cfdi,
292
            'ResidenciaFiscal': residencia_fiscal,
293
            'NumRegIdTrib': num_reg_id_trib,
294
        })
295
296
297 1
@dataclass
298 1
class PagoComprobante:
299 1
    comprobante: CFDI
300 1
    num_parcialidad: int = None
301 1
    imp_saldo_ant: Decimal | int = None
302 1
    imp_pagado: Decimal | int = None
303
304 1
    def __post_init__(self):
305 1
        if self.num_parcialidad is None and self.imp_saldo_ant is None and self.imp_pagado is None:
306 1
            self.num_parcialidad = 1
307 1
            self.imp_saldo_ant = self.comprobante['Total']
308 1
            self.imp_pagado = self.comprobante['Total']
309
310 1
        if self.imp_pagado > self.imp_saldo_ant:
311
            raise ValueError('Importe Pagado debe de ser menor o igual al Importe Saldo Anterior')
312
313
314 1
def _make_conceptos(conceptos, rnd_fn):
315 1
    def make_concepto(concepto):
316 1
        trasladados = [x if isinstance(x, Impuesto) else Impuesto.parse(x) for x in iterate(concepto.get("Impuestos", {}).get("Traslados"))]
317 1
        retenciones = [x if isinstance(x, Impuesto) else Impuesto.parse(x) for x in iterate(concepto.get("Impuestos", {}).get("Retenciones"))]
318
319 1
        if concepto.get('_traslados_incluidos'):
320 1
            s_tasa = sum(c["TasaOCuota"] for c in trasladados if c["TipoFactor"] == "Tasa")
321 1
            s_cuota = sum(c["TasaOCuota"] for c in trasladados if c["TipoFactor"] == "Cuota")
322 1
            if any(c for c in trasladados if c["TipoFactor"] in ('Tasa', 'Cuota') and (c.get('Base') is not None or c.get('Importe') is not None)):
323
                raise ValueError("Not possible to compute '_traslados_incluidos' if any 'trasladados' contains 'Base' or 'Importe'")
324
325 1
            valor_unitario = concepto['ValorUnitario']
326 1
            valor_unitario = (valor_unitario - s_cuota) / (s_tasa + 1)
327 1
            concepto['ValorUnitario'] = rnd_fn(valor_unitario)
328
        else:
329 1
            valor_unitario = concepto['ValorUnitario']
330
331 1
        importe = concepto["Cantidad"] * valor_unitario
332 1
        concepto["Importe"] = rnd_fn(importe)
333
334 1
        if concepto.get("ObjetoImp") in ("01", "03"):
335 1
            concepto['Impuestos'] = None
336
        else:
337 1
            base = importe - (concepto.get("Descuento") or 0)
338 1
            impuestos = {
339
                imp_t: [
340
                    make_impuesto(i, base=base, rnd_fn=rnd_fn) for i in imp
341
                ]
342
                for imp_t, imp in [('Traslados', trasladados), ('Retenciones', retenciones)] if imp
343
            }
344 1
            concepto['Impuestos'] = impuestos or None
345 1
            concepto["ObjetoImp"] = "02" if impuestos else "01"
346
347 1
        return concepto
348
349 1
    return [make_concepto(c) for c in iterate(conceptos)]
350
351
352
# MAIN #
353 1
class Comprobante(CFDI):
354
    """
355
    Estándar de Comprobante Fiscal Digital por Internet.
356
    """
357 1
    tag = '{http://www.sat.gob.mx/cfd/4}Comprobante'
358 1
    version = '4.0'
359 1
    complemento_pago = pago20.Pagos
360
361 1
    def __init__(
362
            self,
363
            emisor: Issuer,
364
            lugar_expedicion: str,
365
            receptor: Receptor | dict,
366
            conceptos: Concepto | Sequence[Concepto | dict],
367
            moneda: str = "MXN",
368
            tipo_de_comprobante: str = "I",
369
            exportacion: str = "01",
370
            serie: str = None,
371
            folio: str = None,
372
            forma_pago: str = None,
373
            condiciones_de_pago: str = None,
374
            tipo_cambio: Decimal | int = None,
375
            metodo_pago: str = None,
376
            confirmacion: str = None,
377
            informacion_global: InformacionGlobal | dict = None,
378
            cfdi_relacionados: CfdiRelacionados | Sequence[CfdiRelacionados | dict] = None,
379
            complemento: CFDI | Sequence[CFDI] = None,
380
            addenda: CFDI | Sequence[CFDI] = None,
381
            fecha: datetime = None,
382
    ):
383
        """
384
        Estándar de Comprobante Fiscal Digital por Internet.
385
386
        :param emisor: Nodo requerido para expresar la información del contribuyente emisor del comprobante.
387
        :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).
388
        :param receptor: Nodo requerido para precisar la información del contribuyente receptor del comprobante.
389
        :param conceptos: Nodo requerido para listar los conceptos cubiertos por el comprobante.
390
        :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.
391
        :param tipo_de_comprobante: Atributo requerido para expresar la clave del efecto del comprobante fiscal para el contribuyente emisor.
392
        :param exportacion: Atributo requerido para expresar si el comprobante ampara una operación de exportación.
393
        :param serie: Atributo opcional para precisar la serie para control interno del contribuyente. Este atributo acepta una cadena de caracteres.
394
        :param folio: Atributo opcional para control interno del contribuyente que expresa el folio del comprobante, acepta una cadena de caracteres.
395
        :param forma_pago: Atributo condicional para expresar la clave de la forma de pago de los bienes o servicios amparados por el comprobante.
396
        :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.
397
        :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.
398
        :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.
399
        :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.
400
        :param informacion_global: Nodo condicional para precisar la información relacionada con el comprobante global.
401
        :param cfdi_relacionados: Nodo opcional para precisar la información de los comprobantes relacionados.
402
        :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.
403
        :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.
404
        :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.
405
        :return: objeto CFDI
406
        """
407 1
        conceptos = _make_conceptos(conceptos, rnd_fn=rounder(moneda))
408 1
        sub_total = sum(c['Importe'] for c in conceptos)
409 1
        descuento = sum(c.get('Descuento') or 0 for c in conceptos)
410 1
        impuestos = make_impuestos(conceptos)
411
412 1
        total = sub_total - descuento
413 1
        if impuestos:
414 1
            total += impuestos.get('TotalImpuestosTrasladados', 0)
415 1
            total -= impuestos.get('TotalImpuestosRetenidos', 0)
416
417 1
        descuento = descuento or None
418 1
        fecha = fecha or datetime.now(tz=get_timezone(lugar_expedicion)).replace(tzinfo=None)
419
420 1
        super().__init__({
421
            'Version': self.version,
422
            'Fecha': fecha,
423
            'NoCertificado': emisor.certificate_number,
424
            'Certificado': emisor.signer.certificate_base64() if emisor.signer else '',
425
            'Sello': '',
426
            'SubTotal': sub_total,
427
            'Moneda': moneda,
428
            'Total': total,
429
            'TipoDeComprobante': tipo_de_comprobante,
430
            'Exportacion': exportacion,
431
            'LugarExpedicion': lugar_expedicion,
432
            'Serie': serie,
433
            'Folio': folio,
434
            'FormaPago': forma_pago,
435
            'CondicionesDePago': condiciones_de_pago,
436
            'Descuento': descuento,
437
            'TipoCambio': tipo_cambio,
438
            'MetodoPago': metodo_pago,
439
            'Confirmacion': confirmacion,
440
            'InformacionGlobal': informacion_global,
441
            'CfdiRelacionados': cfdi_relacionados,
442
            'Emisor': {
443
                "Rfc": emisor.rfc,
444
                "Nombre": emisor.legal_name,
445
                "RegimenFiscal": emisor.tax_system
446
            },
447
            'Receptor': receptor,
448
            'Conceptos': conceptos,
449
            'Impuestos': impuestos,
450
            'Complemento': complemento,
451
            'Addenda': addenda,
452
        })
453 1
        if emisor.signer:
454 1
            self['Sello'] = emisor.signer.sign_sha256(
455
                self.cadena_original().encode()
456
            )
457
458 1
    @classmethod
459 1
    def pago(
460
            cls,
461
            emisor: Issuer,
462
            lugar_expedicion: str,
463
            receptor: Receptor | dict,
464
            complemento_pago: CFDI,
465
            cfdi_relacionados: CfdiRelacionados | Sequence[CfdiRelacionados | dict] = None,
466
            confirmacion: str = None,
467
            serie: str = None,
468
            folio: str = None,
469
            addenda: CFDI | Sequence[CFDI] = None,
470
            fecha: datetime = None) -> CFDI:
471
        """
472
        Estándar de Comprobante Fiscal Digital por Internet de Tipo Pago.
473
474
        :param emisor: Nodo requerido para expresar la información del contribuyente emisor del comprobante.
475
        :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).
476
        :param receptor: Nodo requerido para precisar la información del contribuyente receptor del comprobante.
477
        :param complemento_pago: Pago
478
        :param serie: Atributo opcional para precisar la serie para control interno del contribuyente. Este atributo acepta una cadena de caracteres.
479
        :param folio: Atributo opcional para control interno del contribuyente que expresa el folio del comprobante, acepta una cadena de caracteres.
480
        :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.
481
        :param cfdi_relacionados: Nodo opcional para precisar la información de los comprobantes relacionados.
482
        :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.
483
        :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.
484
        :return: objeto CFDI
485
        """
486 1
        if cls.version == "3.3":
487 1
            receptor["UsoCFDI"] = "P01"
488
        else:
489 1
            receptor["UsoCFDI"] = "CP01"
490
491 1
        return cls(
492
            emisor=emisor,
493
            lugar_expedicion=lugar_expedicion,
494
            receptor=receptor,
495
            conceptos=Concepto(
496
                clave_prod_serv='84111506',
497
                cantidad=1,
498
                clave_unidad='ACT',
499
                descripcion='Pago',
500
                valor_unitario=Decimal(0),
501
                objeto_imp="01"
502
            ),
503
            complemento=complemento_pago,
504
            serie=serie,
505
            folio=folio,
506
            moneda='XXX',
507
            tipo_de_comprobante='P',
508
            cfdi_relacionados=cfdi_relacionados,
509
            confirmacion=confirmacion,
510
            exportacion="01",
511
            addenda=addenda,
512
            fecha=fecha
513
        )
514
515 1
    @classmethod
516 1
    def _pago_tipo_cambio(cls, moneda, tipo_cambio):
517
        # CRP204: El campo TipoCambioP no debe estar presente cuando el campo Moneda contenga ^MXN$ en el nodo Pago
518 1
        if cls.complemento_pago.version == "1.0":
519 1
            if moneda == 'MXN' and tipo_cambio == 1:
520
                tipo_cambio = None
521
        else:
522 1
            if moneda == 'MXN' and tipo_cambio is None:
523 1
                tipo_cambio = 1
524 1
        return tipo_cambio
525
526 1
    @classmethod
527 1
    def pago_comprobantes(
528
            cls,
529
            emisor: Issuer,
530
            lugar_expedicion: str,
531
            comprobantes: CFDI | PagoComprobante | Sequence[CFDI | PagoComprobante],
532
            fecha_pago: datetime,
533
            forma_pago: str,
534
            tipo_cambio: Decimal | int = None,
535
            cfdi_relacionados: CfdiRelacionados | Sequence[CfdiRelacionados | dict] = None,
536
            confirmacion: str = None,
537
            serie: str = None,
538
            folio: str = None,
539
            addenda: CFDI | Sequence[CFDI] = None,
540
            fecha: datetime = None) -> CFDI:
541
        """
542
        Estándar de Comprobante Fiscal Digital por Internet de Tipo Pago. Generado a partir de una lista de Comprobantes
543
        Se asume que los comprobantes se pagan en su totalidad en una sola exhibición
544
545
        :param emisor: Nodo requerido para expresar la información del contribuyente emisor del comprobante.
546
        :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).
547
        :param comprobantes: Sequence[Mapping[str, Any]]a de CFDIS de Comprobante de Ingreso para generar el pago por su monto total
548
        :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.
549
        :param serie: Atributo opcional para precisar la serie para control interno del contribuyente. Este atributo acepta una cadena de caracteres.
550
        :param folio: Atributo opcional para control interno del contribuyente que expresa el folio del comprobante, acepta una cadena de caracteres.
551
        :param forma_pago: Atributo condicional para expresar la clave de la forma de pago de los bienes o servicios amparados por el comprobante.
552
        :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.
553
        :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.
554
        :param cfdi_relacionados: Nodo opcional para precisar la información de los comprobantes relacionados.
555
        :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.
556
        :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.
557
        :return: objeto CFDI
558
        """
559 1
        comprobantes = [c if isinstance(c, PagoComprobante) else PagoComprobante(comprobante=c) for c in iterate(comprobantes)]
560 1
        first_cfdi = comprobantes[0].comprobante
561 1
        moneda = first_cfdi['Moneda']
562 1
        receptor = first_cfdi['Receptor'].copy()
563 1
        tipo_cambio = cls._pago_tipo_cambio(moneda, tipo_cambio)
564
565 1
        if not all(
566
                c.comprobante["Moneda"] == moneda
567
                and c.comprobante["Emisor"]["Rfc"] == emisor.rfc
568
                and c.comprobante["Emisor"]["RegimenFiscal"] == emisor.tax_system
569
                and c.comprobante["Receptor"]["Rfc"] == receptor["Rfc"]
570
                and c.comprobante["Receptor"].get("RegimenFiscalReceptor") == receptor.get("RegimenFiscalReceptor")
571
                for c in comprobantes
572
        ):
573
            raise ValueError("CFDIS are of different RFC's Emisor/Receptor o Moneda")
574
575 1
        return cls.pago(
576
            emisor=emisor,
577
            lugar_expedicion=lugar_expedicion,
578
            receptor=receptor,
579
            complemento_pago=cls.complemento_pago(
580
                pago=[{
581
                    'DoctoRelacionado': [
582
                        {
583
                            'IdDocumento': c.comprobante["Complemento"]["TimbreFiscalDigital"]["UUID"],
584
                            'Serie': c.comprobante.get("Serie"),
585
                            'Folio': c.comprobante.get("Folio"),
586
                            'MonedaDR': c.comprobante["Moneda"],
587
                            'EquivalenciaDR': 1,
588
                            'MetodoDePagoDR': c.comprobante["MetodoPago"],
589
                            'NumParcialidad': c.num_parcialidad,
590
                            'ImpSaldoAnt': c.imp_saldo_ant,
591
                            'ImpPagado': c.imp_pagado,
592
                            'ObjetoImpDR': '02' if 'Impuestos' in c.comprobante else '01',
593
                            'ImpuestosDR': make_impuestos_dr_parcial(
594
                                conceptos=c.comprobante['Conceptos'],
595
                                imp_saldo_ant=c.imp_saldo_ant,
596
                                imp_pagado=c.imp_pagado,
597
                                total=c.comprobante["Total"],
598
                                rnd_fn=rounder(c.comprobante["Moneda"])
599
                            ) if 'Impuestos' in c.comprobante else None
600
                        } for c in comprobantes
601
                    ],
602
                    'FechaPago': fecha_pago,
603
                    'FormaDePagoP': forma_pago,
604
                    'MonedaP': moneda,
605
                    'TipoCambioP': tipo_cambio
606
                }]
607
            ),
608
            cfdi_relacionados=cfdi_relacionados,
609
            confirmacion=confirmacion,
610
            serie=serie,
611
            folio=folio,
612
            addenda=addenda,
613
            fecha=fecha
614
        )
615
616 1
    @classmethod
617 1
    def nomina(
618
            cls,
619
            emisor: Issuer,
620
            lugar_expedicion: str,
621
            receptor: Receptor | dict,
622
            complemento_nomina: CFDI,
623
            cfdi_relacionados: CfdiRelacionados | Sequence[CfdiRelacionados | dict] = None,
624
            confirmacion: str = None,
625
            serie: str = None,
626
            folio: str = None,
627
            addenda: CFDI | Sequence[CFDI] = None,
628
            fecha: datetime = None) -> CFDI:
629
        """
630
        Estándar de Comprobante Fiscal Digital por Internet de Tipo Pago.
631
632
        :param emisor: Nodo requerido para expresar la información del contribuyente emisor del comprobante.
633
        :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).
634
        :param receptor: Nodo requerido para precisar la información del contribuyente receptor del comprobante.
635
        :param complemento_nomina: Pago
636
        :param serie: Atributo opcional para precisar la serie para control interno del contribuyente. Este atributo acepta una cadena de caracteres.
637
        :param folio: Atributo opcional para control interno del contribuyente que expresa el folio del comprobante, acepta una cadena de caracteres.
638
        :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.
639
        :param cfdi_relacionados: Nodo opcional para precisar la información de los comprobantes relacionados.
640
        :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.
641
        :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.
642
        :return: objeto CFDI
643
        """
644 1
        if cls.version == "3.3":
645 1
            receptor["UsoCFDI"] = "P01"
646
        else:
647 1
            receptor["UsoCFDI"] = "CN01"
648
649 1
        concepto = Concepto(
650
            clave_prod_serv='84111505',
651
            cantidad=1,
652
            clave_unidad='ACT',
653
            descripcion='Pago de nómina',
654
            valor_unitario=complemento_nomina.get('TotalPercepciones', 0) + complemento_nomina.get('TotalOtrosPagos', 0),
655
            descuento=complemento_nomina.get('TotalDeducciones'),
656
            objeto_imp="03"
657
        )
658 1
        return cls(
659
            emisor=emisor,
660
            lugar_expedicion=lugar_expedicion,
661
            receptor=receptor,
662
            conceptos=concepto,
663
            complemento=complemento_nomina,
664
            serie=serie,
665
            folio=folio,
666
            moneda='MXN',
667
            tipo_de_comprobante='N',
668
            metodo_pago="PUE",
669
            forma_pago="99",
670
            cfdi_relacionados=cfdi_relacionados,
671
            confirmacion=confirmacion,
672
            exportacion="01",
673
            addenda=addenda,
674
            fecha=fecha,
675
        )
676