1
|
1 |
|
from dataclasses import dataclass |
2
|
1 |
|
from enum import Flag, auto, Enum |
3
|
1 |
|
from ..cfdi import CFDI |
4
|
1 |
|
from ..models import Signer |
5
|
1 |
|
from ..create.cancela import cancelacion |
6
|
1 |
|
from ..create.cancela import cancelacionretencion |
7
|
1 |
|
from ..create.cancela.aceptacionrechazo import SolicitudAceptacionRechazo |
8
|
|
|
|
9
|
1 |
|
__all__ = [ |
10
|
|
|
'PAC', |
11
|
|
|
'Accept', |
12
|
|
|
'Document', |
13
|
|
|
'CancelationAcknowledgment', |
14
|
|
|
'AcceptRejectAcknowledgment', |
15
|
|
|
'CancelReason', |
16
|
|
|
'TaxpayerStatus', |
17
|
|
|
'Environment' |
18
|
|
|
] |
19
|
|
|
|
20
|
|
|
|
21
|
1 |
|
class Accept(Flag): |
22
|
1 |
|
NOTHING = 0 |
23
|
1 |
|
XML = auto() |
24
|
1 |
|
PDF = auto() |
25
|
1 |
|
XML_PDF = XML | PDF |
26
|
|
|
|
27
|
|
|
|
28
|
1 |
|
@dataclass(init=True) |
29
|
1 |
|
class Document: |
30
|
1 |
|
document_id: str |
31
|
1 |
|
xml: bytes = None |
32
|
1 |
|
pdf: bytes = None |
33
|
|
|
|
34
|
1 |
|
@property |
35
|
1 |
|
def cfdi(self) -> CFDI: |
36
|
|
|
return CFDI.from_string(self.xml) |
37
|
|
|
|
38
|
|
|
|
39
|
1 |
|
@dataclass(init=True) |
40
|
1 |
|
class CancelationAcknowledgment: |
41
|
1 |
|
code: str | dict |
42
|
1 |
|
acuse: bytes = None |
43
|
|
|
|
44
|
|
|
|
45
|
1 |
|
@dataclass(init=True) |
46
|
1 |
|
class AcceptRejectAcknowledgment: |
47
|
1 |
|
folios: dict |
48
|
1 |
|
acuse: bytes = None |
49
|
|
|
|
50
|
|
|
|
51
|
1 |
|
def _enum_value(val): |
52
|
|
|
if isinstance(val, Enum): |
53
|
|
|
return val.value |
54
|
|
|
return val |
55
|
|
|
|
56
|
|
|
|
57
|
1 |
|
class CancelReason(Enum): |
58
|
1 |
|
COMPROBANTE_EMITIDO_CON_ERRORES_CON_RELACION = "01" |
59
|
1 |
|
""" |
60
|
|
|
-- Comprobante emitido con errores con relación -- |
61
|
|
|
|
62
|
|
|
Este supuesto aplica cuando la factura generada contiene un error en |
63
|
|
|
la clave del producto, valor unitario, descuento o cualquier otro dato, por lo que se |
64
|
|
|
debe reexpedir. En este caso, primero se sustituye la factura y cuando se solicita la |
65
|
|
|
cancelación, se incorpora el folio de la factura que sustituye a la cancelada. |
66
|
|
|
|
67
|
|
|
¿Qué debo hacer si uso el motivo de cancelación “01” Comprobantes emitidos con |
68
|
|
|
errores con relación y el comprobante no se cancela o presenta error? |
69
|
|
|
|
70
|
|
|
Se podrá utilizar la clave 02 para realizar la cancelación de los CFDI |
71
|
|
|
relacionados incluyendo el que sustituye al CFDI a cancelar, esto con la finalidad de |
72
|
|
|
que no se genere un estatus de “No cancelable” |
73
|
|
|
|
74
|
|
|
En los casos en los que subsista la operación, se deberá emitir un nuevo comprobante |
75
|
|
|
con la información correcta y la clave de tipo relación 04 sustitución de CFDI previos |
76
|
|
|
relacionando el folio fiscal del comprobante que se sustituye |
77
|
|
|
""" |
78
|
|
|
|
79
|
1 |
|
COMPROBANTE_EMITIDO_CON_ERRORES_SIN_RELACION = "02" |
80
|
1 |
|
""" |
81
|
|
|
-- Comprobante emitido con errores sin relación -- |
82
|
|
|
|
83
|
|
|
Este supuesto aplica cuando la factura generada contiene un error en |
84
|
|
|
la clave del producto, valor unitario, descuento o cualquier otro dato y no se requiera |
85
|
|
|
relacionar con otra factura generada. |
86
|
|
|
""" |
87
|
|
|
|
88
|
1 |
|
NO_SE_LLEVO_A_CABO_LA_OPERACION = "03" |
89
|
1 |
|
""" |
90
|
|
|
-- No se llevó a cabo la operación -- |
91
|
|
|
|
92
|
|
|
Este supuesto aplica cuando se facturó una operación que no se |
93
|
|
|
concreta. |
94
|
|
|
""" |
95
|
1 |
|
OPERACION_NORMATIVA_RELACIONADA_EN_LA_FACTURA_GLOBAL = "04" |
96
|
1 |
|
""" |
97
|
|
|
-- Operación nominativa relacionada en la factura global -- |
98
|
|
|
|
99
|
|
|
Este supuesto aplica cuando se incluye una venta en la factura global |
100
|
|
|
de operaciones con el público en general y posterior a ello, el cliente solicita su factura |
101
|
|
|
nominativa, lo que conlleva a cancelar la factura global y reexpedirla, así como |
102
|
|
|
generar la factura nominativa al cliente. |
103
|
|
|
""" |
104
|
|
|
|
105
|
|
|
|
106
|
1 |
|
class TaxpayerStatus(Enum): |
107
|
1 |
|
PRESUNTO = 'Presunto' |
108
|
1 |
|
""" |
109
|
|
|
PRESUNTO: Cuando la autoridad fiscal detecta que un contribuyente ha estado emitiendo comprobantes de manera irregular, |
110
|
|
|
se presumirá la inexistencia de las operaciones amparadas en tales comprobantes. |
111
|
|
|
|
112
|
|
|
En este supuesto, procederá a notificar a los contribuyentes que se encuentren en dicha situación a través de su buzón |
113
|
|
|
tributario, de la página de internet del Servicio de Administración Tributaria, así como mediante una publicación en el |
114
|
|
|
Diario Oficial de la Federación. |
115
|
|
|
""" |
116
|
|
|
|
117
|
1 |
|
SENTENCIA_FAVORABLE = 'Sentencia Favorable' |
118
|
1 |
|
""" |
119
|
|
|
SENTENCIA FAVORABLE: El contribuyente tiene treinta días para acreditar que efectivamente sus operaciones son reales, si |
120
|
|
|
logra corregir su situación se publica este estado. |
121
|
|
|
""" |
122
|
|
|
|
123
|
1 |
|
DEFINITIVO = 'Definitivo' |
124
|
1 |
|
""" |
125
|
|
|
DEFINITIVO: Si el contribuyente no atiende el llamado de la autoridad en quince días a partir de la última de las |
126
|
|
|
notificaciones o el contribuyente no pueda desvirtuar la existencia de sus operaciones se publica este estado. |
127
|
|
|
""" |
128
|
|
|
|
129
|
1 |
|
DESVIRTUADO = 'Desvirtuado' |
130
|
1 |
|
""" |
131
|
|
|
DESVIRTUADO: Cuando el contribuyente aporte a la autoridad fiscal la documentación e información que consideren |
132
|
|
|
pertinentes para desvirtuar los hechos que llevaron a notificarlos con un plazo de quince días contados a partir de la |
133
|
|
|
última de las notificaciones que se hayan efectuado. |
134
|
|
|
""" |
135
|
|
|
|
136
|
|
|
|
137
|
1 |
|
class Environment(Enum): |
138
|
1 |
|
PRODUCTION = auto() |
139
|
1 |
|
TEST = auto() |
140
|
|
|
|
141
|
|
|
|
142
|
1 |
|
class PAC: |
143
|
1 |
|
RFC = None |
144
|
|
|
|
145
|
1 |
|
def __init__(self, environment: Environment): |
146
|
1 |
|
self.environment = environment |
147
|
|
|
|
148
|
1 |
|
def status(self, cfdi: CFDI) -> dict: |
149
|
|
|
""" |
150
|
|
|
Consulta el estado de un CFDI |
151
|
|
|
:return: Respuesta de la consulta |
152
|
|
|
""" |
153
|
|
|
raise NotImplementedError() |
154
|
|
|
|
155
|
1 |
|
def validate(self, cfdi: CFDI): |
156
|
|
|
raise NotImplementedError() |
157
|
|
|
|
158
|
1 |
|
def issue(self, cfdi: CFDI, accept: Accept = Accept.XML) -> Document: |
159
|
|
|
""" |
160
|
|
|
Operation to request CFDI be sealed and stamped by PAC |
161
|
|
|
:param accept: |
162
|
|
|
:param cfdi: |
163
|
|
|
:return: |
164
|
|
|
""" |
165
|
|
|
raise NotImplementedError() |
166
|
|
|
|
167
|
1 |
|
def stamp(self, cfdi: CFDI, accept: Accept = Accept.XML) -> Document: |
168
|
|
|
""" |
169
|
|
|
Operation to request sealed CFDI be stamped by PAC |
170
|
|
|
:param accept: |
171
|
|
|
:param cfdi: |
172
|
|
|
:return: |
173
|
|
|
document_id and bytes of stamped xml |
174
|
|
|
""" |
175
|
|
|
raise NotImplementedError() |
176
|
|
|
|
177
|
1 |
|
def recover(self, document_id: str, accept: Accept = Accept.XML) -> Document: |
178
|
|
|
""" |
179
|
|
|
|
180
|
|
|
:param accept: |
181
|
|
|
:param document_id: |
182
|
|
|
:return: |
183
|
|
|
""" |
184
|
|
|
raise NotImplementedError() |
185
|
|
|
|
186
|
1 |
|
def cancel(self, cfdi: CFDI, reason: CancelReason, substitution_id: str = None, signer: Signer = None) -> CancelationAcknowledgment: |
187
|
|
|
""" |
188
|
|
|
Operation to request single cfdi to be canceled |
189
|
|
|
:param signer: |
190
|
|
|
:param cfdi: |
191
|
|
|
:param substitution_id: |
192
|
|
|
:param reason: |
193
|
|
|
:return: |
194
|
|
|
""" |
195
|
|
|
raise NotImplementedError() |
196
|
|
|
|
197
|
1 |
|
def cancel_comprobante(self, cancelation: cancelacion.Cancelacion) -> CancelationAcknowledgment: |
198
|
|
|
""" |
199
|
|
|
Operation to Cancel a Comprobante |
200
|
|
|
""" |
201
|
|
|
raise NotImplementedError() |
202
|
|
|
|
203
|
1 |
|
def cancel_retencion(self, cancelation: cancelacionretencion.Cancelacion) -> CancelationAcknowledgment: |
204
|
|
|
""" |
205
|
|
|
Operation to Cancel a Retencion |
206
|
|
|
""" |
207
|
|
|
raise NotImplementedError() |
208
|
|
|
|
209
|
1 |
|
def accept_reject(self, request: SolicitudAceptacionRechazo) -> AcceptRejectAcknowledgment: |
210
|
|
|
""" |
211
|
|
|
Operation to Accept Reject a Cancellation Request |
212
|
|
|
""" |
213
|
|
|
raise NotImplementedError() |
214
|
|
|
|
215
|
1 |
|
def pending(self, rfc: str) -> list[str]: |
216
|
|
|
""" |
217
|
|
|
Operation to get pending cancellations |
218
|
|
|
""" |
219
|
|
|
raise NotImplementedError() |
220
|
|
|
|
221
|
1 |
|
def list_69b(self, rfc: str) -> TaxpayerStatus | None: |
222
|
|
|
""" |
223
|
|
|
Operation to get list69b status |
224
|
|
|
""" |
225
|
|
|
raise NotImplementedError() |
226
|
|
|
|