Passed
Push — main ( 7eb0ae...8315de )
by Sat CFDI
01:43
created

  A

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nop 2
1
import logging
2
import os
3
import pickle
4
from datetime import datetime
5
from enum import Enum
6
from uuid import UUID
7
8
import diskcache
9
from satcfdi.accounting import SatCFDI
10
from satcfdi.accounting.models import EstadoComprobante
11
from satcfdi.pacs import sat
12
13
from . import PPD
14
from .log_tools import print_yaml
15
from .utils import estado_to_estatus
16
17
LIQUIDATED = 0
18
NOTIFIED = 2
19
STATUS_SAT = 3
20
FOLIO = 5
21
SERIE = 6
22
SOLICITUDES = 'solicitudes'
23
24
sat_manager = sat.SAT()
25
26
logger = logging.getLogger(__name__)
27
28
29
class LocalDB(diskcache.Cache):
30
    def __init__(self, base_path: str):
31
        super().__init__(directory=os.path.join(base_path, 'cache'))
32
        self.base_path = base_path
33
34
    def folio(self) -> int:
35
        return self.get(FOLIO, 1000)
36
37
    def folio_set(self, value: int):
38
        self[FOLIO] = value
39
40
    def serie(self) -> str:
41
        return self.get(SERIE, 'A')
42
43
    def serie_set(self, value: str):
44
        self[SERIE] = value
45
46
    def liquidated(self, uuid: UUID):
47
        return self.get((LIQUIDATED, uuid))
48
49
    def liquidated_set(self, uuid: UUID, value: bool):
50
        self[(LIQUIDATED, uuid)] = value
51
52
    def notified(self, uuid: UUID):
53
        return self.get((NOTIFIED, uuid))
54
55
    def notified_set(self, uuid: UUID, value: bool):
56
        self[(NOTIFIED, uuid)] = value
57
58
    def status(self, uuid: UUID):
59
        return self.get((STATUS_SAT, uuid), {})
60
61
    def status_merge(self, uuid: UUID, estatus: EstadoComprobante, es_cancelable=None,
62
                     estatus_cancelacion=None, fecha_cancelacion: datetime = None):
63
        value = {
64
            "Estatus": estatus,
65
        }
66
        if es_cancelable:
67
            value["EsCancelable"] = es_cancelable
68
        if estatus_cancelacion:
69
            value["EstatusCancelacion"] = estatus_cancelacion
70
        if fecha_cancelacion:
71
            value["FechaCancelacion"] = fecha_cancelacion
72
        value["UltimaConsulta"] = datetime.now().replace(microsecond=0),
73
        self[(STATUS_SAT, uuid)] = self.status(uuid) | value
74
75
    def get_solicitudes(self):
76
        return self.load_data(SOLICITUDES, {})
77
78
    def set_solicitudes(self, solicitudes):
79
        self.save_data(SOLICITUDES, solicitudes)
80
81
    def save_data(self, file, data):
82
        with open(os.path.join(self.base_path, file), 'wb') as f:
83
            pickle.dump(data, f)
84
85
    def load_data(self, file, default=None):
86
        try:
87
            with open(os.path.join(self.base_path, file), 'rb') as f:
88
                return pickle.load(f)
89
        except FileNotFoundError:
90
            return default
91
92
93
class StatusState(Enum):
94
    NONE = 1
95
    PAID = 2
96
    PENDING = 3
97
    IGNORED = 4
98
    CANCELLED = 5
99
100
    def __str__(self):
101
        if self.name == "NONE":
102
            return "💰"
103
        if self.name == "IGNORED":
104
            return "🚫"
105
        if self.name == "PAID":
106
            return "✔"
107
        if self.name == "PENDING":
108
            return ""
109
        if self.name == "CANCELLED":
110
            return "❌"
111
112
113
class LocalDBSatCFDI(LocalDB):
114
    def __init__(self, base_path, enviar_a_partir, pagar_a_partir):
115
        super().__init__(base_path)
116
        self.enviar_a_partir = enviar_a_partir
117
        self.pagar_a_partir = pagar_a_partir
118
119
    def notified(self, cfdi: SatCFDI):
120
        v = super().notified(cfdi.uuid)
121
        if v is None and cfdi["Fecha"] < self.enviar_a_partir:
122
            return True
123
        return v
124
125
    def notified_flip(self, cfdi: SatCFDI):
126
        v = not self.notified(cfdi)
127
        self.notified_set(cfdi.uuid, v)
128
        return v
129
130
    def liquidated(self, cfdi: SatCFDI):
131
        v = super().liquidated(cfdi.uuid)
132
        if v is None and cfdi["Fecha"] < self.pagar_a_partir[cfdi["MetodoPago"]]:
133
            return True
134
        return v
135
136
    def liquidated_flip(self, cfdi: SatCFDI):
137
        v = not self.liquidated(cfdi)
138
        self.liquidated_set(cfdi.uuid, v)
139
        return v
140
141
    def status_sat(self, cfdi: SatCFDI, update=False):
142
        if update:
143
            res = sat_manager.status(cfdi)
144
            if res["ValidacionEFOS"] == "200":
145
                self.status_merge(
146
                    cfdi.uuid,
147
                    estatus=estado_to_estatus(res["Estatus"]),
148
                    es_cancelable=res["EsCancelable"],
149
                    estatus_cancelacion=res["EstatusCancelacion"]
150
                )
151
            else:
152
                raise ValueError("Error al actualizar estado de %s: %s", cfdi.uuid, res)
153
            return res
154
        else:
155
            return super().status(cfdi.uuid)
156
157
    def liquidated_state(self, cfdi: SatCFDI):
158
        if cfdi.estatus == EstadoComprobante.Cancelado:
159
            return StatusState.CANCELLED
160
161
        if cfdi["TipoDeComprobante"] != "I":
162
            return StatusState.NONE
163
164
        mpago = cfdi["MetodoPago"]
165
        if cfdi['Total'] == 0 or (mpago == PPD and cfdi.saldo_pendiente == 0):
166
            return StatusState.PAID
167
168
        if self.liquidated(cfdi):
169
            if mpago == PPD:
170
                return StatusState.IGNORED
171
            return StatusState.PAID
172
173
        return StatusState.PENDING
174