Passed
Push — main ( f079c1...710f94 )
by Sat CFDI
01:42
created

satdigitalinvoice.localdb.LocalDB.status_set()   A

Complexity

Conditions 3

Size

Total Lines 8
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 3
nop 3
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 CFDIEstatus
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, value: dict):
62
        self[(STATUS_SAT, uuid)] = self.status(uuid) | value
63
64
    def get_solicitudes(self):
65
        return self.load_data(SOLICITUDES, {})
66
67
    def set_solicitudes(self, solicitudes):
68
        self.save_data(SOLICITUDES, solicitudes)
69
70
    def save_data(self, file, data):
71
        with open(os.path.join(self.base_path, file), 'wb') as f:
72
            pickle.dump(data, f)
73
74
    def load_data(self, file, default=None):
75
        try:
76
            with open(os.path.join(self.base_path, file), 'rb') as f:
77
                return pickle.load(f)
78
        except FileNotFoundError:
79
            return default
80
81
82
class StatusState(Enum):
83
    NONE = 1
84
    PAID = 2
85
    PENDING = 3
86
    IGNORED = 4
87
    CANCELLED = 5
88
89
    def __str__(self):
90
        if self.name == "NONE":
91
            return "💰"
92
        if self.name == "IGNORED":
93
            return "🚫"
94
        if self.name == "PAID":
95
            return "✔"
96
        if self.name == "PENDING":
97
            return ""
98
        if self.name == "CANCELLED":
99
            return "❌"
100
101
102
class LocalDBSatCFDI(LocalDB):
103
    def __init__(self, base_path, enviar_a_partir, pagar_a_partir):
104
        super().__init__(base_path)
105
        self.enviar_a_partir = enviar_a_partir
106
        self.pagar_a_partir = pagar_a_partir
107
108
    def notified(self, cfdi: SatCFDI):
109
        v = super().notified(cfdi.uuid)
110
        if v is None and cfdi["Fecha"] < self.enviar_a_partir:
111
            return True
112
        return v
113
114
    def notified_flip(self, cfdi: SatCFDI):
115
        v = not self.notified(cfdi)
116
        self.notified_set(cfdi.uuid, v)
117
        return v
118
119
    def liquidated(self, cfdi: SatCFDI):
120
        v = super().liquidated(cfdi.uuid)
121
        if v is None and cfdi["Fecha"] < self.pagar_a_partir[cfdi["MetodoPago"]]:
122
            return True
123
        return v
124
125
    def liquidated_flip(self, cfdi: SatCFDI):
126
        v = not self.liquidated(cfdi)
127
        self.liquidated_set(cfdi.uuid, v)
128
        return v
129
130
    def status_sat(self, cfdi: SatCFDI, update=False):
131
        if update:
132
            res = sat_manager.status(cfdi)
133
            if res["ValidacionEFOS"] == "200":
134
                self.status_merge(cfdi.uuid, {
135
                    'Estatus': estado_to_estatus(res["Estado"]),
136
                    'EsCancelable': res["EsCancelable"],
137
                    'EstatusCancelacion': res["EstatusCancelacion"],
138
                    "UltimaConsulta": datetime.now().replace(microsecond=0),
139
                })
140
            else:
141
                raise ValueError("Error al actualizar estado de %s: %s", cfdi.uuid, res)
142
            return res
143
        else:
144
            return super().status(cfdi.uuid)
145
146
    def liquidated_state(self, cfdi: SatCFDI):
147
        if cfdi.estatus == CFDIEstatus.Cancelado:
148
            return StatusState.CANCELLED
149
150
        if cfdi["TipoDeComprobante"] != "I":
151
            return StatusState.NONE
152
153
        mpago = cfdi["MetodoPago"]
154
        if cfdi['Total'] == 0 or (mpago == PPD and cfdi.saldo_pendiente == 0):
155
            return StatusState.PAID
156
157
        if self.liquidated(cfdi):
158
            if mpago == PPD:
159
                return StatusState.IGNORED
160
            return StatusState.PAID
161
162
        return StatusState.PENDING
163
164
    def describe(self, cfdi: SatCFDI):
165
        print_yaml({
166
            'saldada': self.liquidated(cfdi),
167
            'enviada': self.notified(cfdi),
168
            'status_sat': self.status_sat(cfdi)
169
        })
170