MambuPy 2.3.3__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/__init__.py +1 -1
- {mambupy-2.3.3/mambupy → mambupy-2.4.0/MambuPy}/api/connector/mambuconnector.py +31 -0
- {mambupy-2.3.3/mambupy → mambupy-2.4.0/MambuPy}/api/connector/rest.py +45 -54
- mambupy-2.4.0/MambuPy/api/connector/rest_helpers.py +63 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambuloan.py +59 -3
- {mambupy-2.3.3/mambupy → mambupy-2.4.0/MambuPy}/api/mambustruct.py +14 -2
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy.egg-info/PKG-INFO +1 -1
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy.egg-info/SOURCES.txt +2 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/PKG-INFO +1 -1
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/__init__.py +1 -1
- {mambupy-2.3.3/MambuPy → mambupy-2.4.0/mambupy}/api/connector/mambuconnector.py +31 -0
- {mambupy-2.3.3/MambuPy → mambupy-2.4.0/mambupy}/api/connector/rest.py +45 -54
- mambupy-2.4.0/mambupy/api/connector/rest_helpers.py +63 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambuloan.py +59 -3
- {mambupy-2.3.3/MambuPy → mambupy-2.4.0/mambupy}/api/mambustruct.py +14 -2
- {mambupy-2.3.3 → mambupy-2.4.0}/LICENSE +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/__init__.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/classes.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/connector/__init__.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/entities.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/interfaces.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambubranch.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambucentre.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambuclient.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambucustomfield.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambugroup.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambuproduct.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mamburole.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambutask.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambutransaction.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambuuser.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/vos.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/mambuconfig.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/mambugeturl.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/mambuutil.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/__init__.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_activities.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_addresses.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_branches.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_centres.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_clients.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_customfields.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_dummies.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_groups.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_loans.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_mambu.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_orm.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_tasks.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_users.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/rest/__init__.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/rest/mambuactivity.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/utils/__init__.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/utils/update_user_roles.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/utils/userdeactivate.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy.egg-info/dependency_links.txt +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy.egg-info/requires.txt +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy.egg-info/top_level.txt +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/README.md +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/__init__.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/classes.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/connector/__init__.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/entities.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/interfaces.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambubranch.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambucentre.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambuclient.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambucustomfield.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambugroup.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambuproduct.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mamburole.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambutask.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambutransaction.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambuuser.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/vos.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/mambuconfig.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/mambugeturl.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/mambuutil.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/__init__.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_activities.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_addresses.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_branches.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_centres.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_clients.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_customfields.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_dummies.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_groups.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_loans.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_mambu.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_orm.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_tasks.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_users.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/rest/__init__.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/rest/mambuactivity.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/utils/__init__.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/utils/update_user_roles.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/utils/userdeactivate.py +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/pyproject.toml +0 -0
- {mambupy-2.3.3 → mambupy-2.4.0}/setup.cfg +0 -0
|
@@ -286,6 +286,37 @@ class MambuConnectorWriter(ABC):
|
|
|
286
286
|
"""
|
|
287
287
|
raise NotImplementedError
|
|
288
288
|
|
|
289
|
+
@abstractmethod
|
|
290
|
+
def mambu_loanaccount_updateSchedule(self, loanid, installments):
|
|
291
|
+
"""Updates the installments schedule of a loan account
|
|
292
|
+
|
|
293
|
+
Mambu replaces the whole schedule with the given installments, so the
|
|
294
|
+
complete list must be sent, not just the modified ones.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
loanid (str): the id or encoded key of the loan account
|
|
298
|
+
installments (list of dict): the installments of the schedule,
|
|
299
|
+
complying with Mambu's schema
|
|
300
|
+
"""
|
|
301
|
+
raise NotImplementedError
|
|
302
|
+
|
|
303
|
+
@abstractmethod
|
|
304
|
+
def mambu_loanaccount_changeDueDatesSettings(
|
|
305
|
+
self, loanid, fixedDaysOfMonth, notes=None, valueDate=None
|
|
306
|
+
):
|
|
307
|
+
"""Changes the due dates settings of a loan account
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
loanid (str): the id or encoded key of the loan account
|
|
311
|
+
fixedDaysOfMonth (list of int): the new fixed days of month to be used
|
|
312
|
+
on the loan account
|
|
313
|
+
notes (str): notes to associate to the change due dates settings
|
|
314
|
+
operation in Mambu
|
|
315
|
+
valueDate (str or :py:obj:`datetime`): the date when to change the due
|
|
316
|
+
dates settings
|
|
317
|
+
"""
|
|
318
|
+
raise NotImplementedError
|
|
319
|
+
|
|
289
320
|
@abstractmethod
|
|
290
321
|
def mambu_loantransaction_adjust(self, transactionid, notes):
|
|
291
322
|
"""Adjust a loan transaction
|
|
@@ -9,22 +9,18 @@ Currently supports REST.
|
|
|
9
9
|
|
|
10
10
|
import base64
|
|
11
11
|
import copy
|
|
12
|
-
import datetime
|
|
13
|
-
import enum
|
|
14
12
|
import json
|
|
15
13
|
import mimetypes
|
|
16
14
|
import orjson
|
|
17
15
|
import os
|
|
18
16
|
import re
|
|
19
17
|
import uuid
|
|
20
|
-
from decimal import Decimal
|
|
21
18
|
|
|
22
19
|
import requests
|
|
23
20
|
from requests_toolbelt.multipart.encoder import MultipartEncoder
|
|
24
|
-
from requests.adapters import HTTPAdapter
|
|
25
|
-
from requests.packages.urllib3.util.retry import Retry
|
|
26
21
|
|
|
27
22
|
from .mambuconnector import MambuConnector, MambuConnectorReader, MambuConnectorWriter
|
|
23
|
+
from .rest_helpers import _MambuJSONEncoder, _configure_retry_strategy
|
|
28
24
|
from MambuPy.mambuutil import (
|
|
29
25
|
ALLOWED_UPLOAD_MIMETYPES,
|
|
30
26
|
DETAILSLEVEL,
|
|
@@ -47,55 +43,6 @@ from MambuPy.mambuutil import (
|
|
|
47
43
|
logger = setup_logging(__name__)
|
|
48
44
|
|
|
49
45
|
|
|
50
|
-
class _MambuJSONEncoder(json.JSONEncoder):
|
|
51
|
-
"""JSON encoder that handles Python types not natively serializable by json.dumps."""
|
|
52
|
-
|
|
53
|
-
def default(self, obj):
|
|
54
|
-
if isinstance(obj, (datetime.datetime, datetime.date)):
|
|
55
|
-
return obj.isoformat()
|
|
56
|
-
if isinstance(obj, enum.Enum):
|
|
57
|
-
return obj.value
|
|
58
|
-
if isinstance(obj, Decimal):
|
|
59
|
-
return float(obj)
|
|
60
|
-
return super().default(obj)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def _configure_retry_strategy(session, retries=5):
|
|
64
|
-
"""Configure retry strategy for a session.
|
|
65
|
-
|
|
66
|
-
El HTTPAdapter se construye con pool_maxsize=50 para que los flujos
|
|
67
|
-
que despachan multiples requests concurrentes contra Mambu (p.ej.
|
|
68
|
-
fan-out de aprobaciones, cargar_datos paralelo) no queden encolados
|
|
69
|
-
sobre el default de urllib3 de 10 conexiones por host.
|
|
70
|
-
|
|
71
|
-
Args:
|
|
72
|
-
session (requests.Session): The session to configure
|
|
73
|
-
retries (int, optional): Number of retries. Defaults to 5.
|
|
74
|
-
"""
|
|
75
|
-
retry_strategy = Retry(
|
|
76
|
-
total=retries,
|
|
77
|
-
status_forcelist=[429, 500, 502, 503, 504],
|
|
78
|
-
backoff_factor=1,
|
|
79
|
-
allowed_methods=[
|
|
80
|
-
"HEAD",
|
|
81
|
-
"GET",
|
|
82
|
-
"OPTIONS",
|
|
83
|
-
"POST",
|
|
84
|
-
"PUT",
|
|
85
|
-
"DELETE",
|
|
86
|
-
"TRACE",
|
|
87
|
-
"PATCH",
|
|
88
|
-
],
|
|
89
|
-
)
|
|
90
|
-
adapter = HTTPAdapter(
|
|
91
|
-
max_retries=retry_strategy,
|
|
92
|
-
pool_connections=50,
|
|
93
|
-
pool_maxsize=50,
|
|
94
|
-
)
|
|
95
|
-
session.mount("https://", adapter)
|
|
96
|
-
session.mount("http://", adapter)
|
|
97
|
-
|
|
98
|
-
|
|
99
46
|
class SessionSingleton:
|
|
100
47
|
"""Singleton class to manage HTTP sessions for MambuPy.
|
|
101
48
|
|
|
@@ -775,6 +722,50 @@ url %s, params %s, data %s, headers %s",
|
|
|
775
722
|
data = {"notes": notes}
|
|
776
723
|
return self.__request("POST", url, data=data)
|
|
777
724
|
|
|
725
|
+
def mambu_loanaccount_updateSchedule(self, loanid, installments):
|
|
726
|
+
"""Updates the installments schedule of a loan account
|
|
727
|
+
|
|
728
|
+
Mambu replaces the whole schedule with the given installments, so the
|
|
729
|
+
complete list must be sent, not just the modified ones.
|
|
730
|
+
|
|
731
|
+
Args:
|
|
732
|
+
loanid (str): the id or encoded key of the loan account
|
|
733
|
+
installments (list of dict): the installments of the schedule,
|
|
734
|
+
complying with Mambu's schema
|
|
735
|
+
"""
|
|
736
|
+
url = "https://{}/api/{}/{}/schedule".format(self._tenant, "loans", loanid)
|
|
737
|
+
|
|
738
|
+
return self.__request("PUT", url, data=installments)
|
|
739
|
+
|
|
740
|
+
def mambu_loanaccount_changeDueDatesSettings(
|
|
741
|
+
self, loanid, fixedDaysOfMonth, notes=None, valueDate=None
|
|
742
|
+
):
|
|
743
|
+
"""Changes the due dates settings of a loan account
|
|
744
|
+
|
|
745
|
+
Only available for loan accounts whose product uses the
|
|
746
|
+
FIXED_DAYS_OF_MONTH repayment methodology. Mambu rejects the request
|
|
747
|
+
otherwise.
|
|
748
|
+
|
|
749
|
+
Args:
|
|
750
|
+
loanid (str): the id or encoded key of the loan account
|
|
751
|
+
fixedDaysOfMonth (list of int): the new fixed days of month to be used
|
|
752
|
+
on the loan account
|
|
753
|
+
notes (str): notes to associate to the change due dates settings
|
|
754
|
+
operation in Mambu
|
|
755
|
+
valueDate (str or :py:obj:`datetime`): the date when to change the due
|
|
756
|
+
dates settings
|
|
757
|
+
"""
|
|
758
|
+
url = "https://{}/api/{}/{}:changeDueDatesSettings".format(
|
|
759
|
+
self._tenant, "loans", loanid
|
|
760
|
+
)
|
|
761
|
+
data = {"fixedDaysOfMonth": fixedDaysOfMonth}
|
|
762
|
+
if notes is not None:
|
|
763
|
+
data["notes"] = notes
|
|
764
|
+
if valueDate is not None:
|
|
765
|
+
data["valueDate"] = valueDate
|
|
766
|
+
|
|
767
|
+
return self.__request("POST", url, data=data)
|
|
768
|
+
|
|
778
769
|
def mambu_change_state(self, entid, prefix, action, notes):
|
|
779
770
|
"""change state of mambu entity
|
|
780
771
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Helpers for the REST connector.
|
|
2
|
+
|
|
3
|
+
.. autosummary::
|
|
4
|
+
:nosignatures:
|
|
5
|
+
:toctree: _autosummary
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import datetime
|
|
9
|
+
import enum
|
|
10
|
+
import json
|
|
11
|
+
from decimal import Decimal
|
|
12
|
+
|
|
13
|
+
from requests.adapters import HTTPAdapter
|
|
14
|
+
from requests.packages.urllib3.util.retry import Retry
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _MambuJSONEncoder(json.JSONEncoder):
|
|
18
|
+
"""JSON encoder that handles Python types not natively serializable by json.dumps."""
|
|
19
|
+
|
|
20
|
+
def default(self, obj):
|
|
21
|
+
if isinstance(obj, (datetime.datetime, datetime.date)):
|
|
22
|
+
return obj.isoformat()
|
|
23
|
+
if isinstance(obj, enum.Enum):
|
|
24
|
+
return obj.value
|
|
25
|
+
if isinstance(obj, Decimal):
|
|
26
|
+
return float(obj)
|
|
27
|
+
return super().default(obj)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _configure_retry_strategy(session, retries=5):
|
|
31
|
+
"""Configure retry strategy for a session.
|
|
32
|
+
|
|
33
|
+
El HTTPAdapter se construye con pool_maxsize=50 para que los flujos
|
|
34
|
+
que despachan multiples requests concurrentes contra Mambu (p.ej.
|
|
35
|
+
fan-out de aprobaciones, cargar_datos paralelo) no queden encolados
|
|
36
|
+
sobre el default de urllib3 de 10 conexiones por host.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
session (requests.Session): The session to configure
|
|
40
|
+
retries (int, optional): Number of retries. Defaults to 5.
|
|
41
|
+
"""
|
|
42
|
+
retry_strategy = Retry(
|
|
43
|
+
total=retries,
|
|
44
|
+
status_forcelist=[429, 500, 502, 503, 504],
|
|
45
|
+
backoff_factor=1,
|
|
46
|
+
allowed_methods=[
|
|
47
|
+
"HEAD",
|
|
48
|
+
"GET",
|
|
49
|
+
"OPTIONS",
|
|
50
|
+
"POST",
|
|
51
|
+
"PUT",
|
|
52
|
+
"DELETE",
|
|
53
|
+
"TRACE",
|
|
54
|
+
"PATCH",
|
|
55
|
+
],
|
|
56
|
+
)
|
|
57
|
+
adapter = HTTPAdapter(
|
|
58
|
+
max_retries=retry_strategy,
|
|
59
|
+
pool_connections=50,
|
|
60
|
+
pool_maxsize=50,
|
|
61
|
+
)
|
|
62
|
+
session.mount("https://", adapter)
|
|
63
|
+
session.mount("http://", adapter)
|
|
@@ -114,10 +114,12 @@ class MambuLoan(
|
|
|
114
114
|
except KeyError:
|
|
115
115
|
pass
|
|
116
116
|
|
|
117
|
-
def
|
|
118
|
-
"""
|
|
119
|
-
resp = self._connector.mambu_loanaccount_getSchedule(self.id)
|
|
117
|
+
def _hydrate_schedule(self, resp):
|
|
118
|
+
"""Builds the schedule of MambuInstallments from a Mambu response.
|
|
120
119
|
|
|
120
|
+
Args:
|
|
121
|
+
resp (bytes): a Mambu response holding an installments list.
|
|
122
|
+
"""
|
|
121
123
|
installments = orjson.loads(resp.decode())["installments"]
|
|
122
124
|
|
|
123
125
|
self.schedule = []
|
|
@@ -127,6 +129,38 @@ class MambuLoan(
|
|
|
127
129
|
installment_entity._convertDict2Attrs()
|
|
128
130
|
self.schedule.append(installment_entity)
|
|
129
131
|
|
|
132
|
+
def get_schedule(self, **kwargs):
|
|
133
|
+
"""Retrieves the installments schedule."""
|
|
134
|
+
resp = self._connector.mambu_loanaccount_getSchedule(self.id)
|
|
135
|
+
|
|
136
|
+
self._hydrate_schedule(resp)
|
|
137
|
+
|
|
138
|
+
def update_schedule(self, installments=None, **kwargs):
|
|
139
|
+
"""Request to update the installments schedule of a loan account.
|
|
140
|
+
|
|
141
|
+
Mambu replaces the whole schedule with the installments sent, so the
|
|
142
|
+
complete list must be given, not just the modified ones. Sending a
|
|
143
|
+
subset means deleting the rest, which Mambu rejects for accounts on
|
|
144
|
+
non dynamic term products.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
installments (list of :py:obj:`MambuInstallment`): the schedule to
|
|
148
|
+
send. Defaults to the schedule currently held by this
|
|
149
|
+
account, as retrieved by `get_schedule`.
|
|
150
|
+
"""
|
|
151
|
+
if installments is None:
|
|
152
|
+
installments = self.schedule
|
|
153
|
+
|
|
154
|
+
payload = []
|
|
155
|
+
for installment in installments:
|
|
156
|
+
serialized = copy.deepcopy(installment)
|
|
157
|
+
serialized._serializeFields()
|
|
158
|
+
payload.append(serialized._attrs)
|
|
159
|
+
|
|
160
|
+
resp = self._connector.mambu_loanaccount_updateSchedule(self.id, payload)
|
|
161
|
+
|
|
162
|
+
self._hydrate_schedule(resp)
|
|
163
|
+
|
|
130
164
|
def get_transactions(self, **kwargs):
|
|
131
165
|
"""Retrieves the transactions of the loan"""
|
|
132
166
|
self.transactions = MambuTransaction.get_all(self.id)
|
|
@@ -241,6 +275,28 @@ class MambuLoan(
|
|
|
241
275
|
self._connector.mambu_loanaccount_writeoff(self.id, notes)
|
|
242
276
|
self.refresh()
|
|
243
277
|
|
|
278
|
+
def change_due_dates_settings(
|
|
279
|
+
self, fixedDaysOfMonth, notes=None, valueDate=None, **kwargs
|
|
280
|
+
):
|
|
281
|
+
"""Request to change the due dates settings of a loan account.
|
|
282
|
+
|
|
283
|
+
Only available for loan accounts whose product uses the
|
|
284
|
+
FIXED_DAYS_OF_MONTH repayment methodology. Mambu rejects the request
|
|
285
|
+
otherwise, raising a `MambuError`.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
fixedDaysOfMonth (list of int): the new fixed days of month to be used
|
|
289
|
+
on the loan account.
|
|
290
|
+
notes (str): notes to attach to the change due dates settings
|
|
291
|
+
operation.
|
|
292
|
+
valueDate (str or :py:obj:`datetime`): the date when to change the due
|
|
293
|
+
dates settings.
|
|
294
|
+
"""
|
|
295
|
+
self._connector.mambu_loanaccount_changeDueDatesSettings(
|
|
296
|
+
self.id, fixedDaysOfMonth, notes, valueDate
|
|
297
|
+
)
|
|
298
|
+
self.refresh()
|
|
299
|
+
|
|
244
300
|
def repay(self, amount, notes, valueDate, **kwargs):
|
|
245
301
|
"""Request to repay a loan account.
|
|
246
302
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import copy
|
|
9
|
-
from datetime import datetime
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
10
|
from importlib import import_module
|
|
11
11
|
|
|
12
12
|
from .classes import MambuMapObj
|
|
@@ -289,7 +289,19 @@ class MambuStruct(MambuMapObj):
|
|
|
289
289
|
if isinstance(data, datetime):
|
|
290
290
|
data_asdate = data.isoformat()
|
|
291
291
|
if tzdata: # no tzdata means a date (no time) object
|
|
292
|
-
|
|
292
|
+
if self._as_utc:
|
|
293
|
+
# _convertDict2Attrs moved this datetime to UTC and dropped
|
|
294
|
+
# its tzinfo, so appending the original offset would shift
|
|
295
|
+
# the instant. Convert back to that offset instead.
|
|
296
|
+
offset_str = tzdata[3:] or "+00:00"
|
|
297
|
+
tz_info = datetime.fromisoformat(
|
|
298
|
+
"2000-01-01T00:00:00" + offset_str
|
|
299
|
+
).tzinfo
|
|
300
|
+
data_asdate = (
|
|
301
|
+
data.replace(tzinfo=timezone.utc).astimezone(tz_info).isoformat()
|
|
302
|
+
)
|
|
303
|
+
else:
|
|
304
|
+
data_asdate += tzdata[-6:]
|
|
293
305
|
else:
|
|
294
306
|
data_asdate = data_asdate[:10]
|
|
295
307
|
return data_asdate
|
|
@@ -30,6 +30,7 @@ MambuPy/api/vos.py
|
|
|
30
30
|
MambuPy/api/connector/__init__.py
|
|
31
31
|
MambuPy/api/connector/mambuconnector.py
|
|
32
32
|
MambuPy/api/connector/rest.py
|
|
33
|
+
MambuPy/api/connector/rest_helpers.py
|
|
33
34
|
MambuPy/orm/__init__.py
|
|
34
35
|
MambuPy/orm/schema_activities.py
|
|
35
36
|
MambuPy/orm/schema_addresses.py
|
|
@@ -73,6 +74,7 @@ mambupy/api/vos.py
|
|
|
73
74
|
mambupy/api/connector/__init__.py
|
|
74
75
|
mambupy/api/connector/mambuconnector.py
|
|
75
76
|
mambupy/api/connector/rest.py
|
|
77
|
+
mambupy/api/connector/rest_helpers.py
|
|
76
78
|
mambupy/orm/__init__.py
|
|
77
79
|
mambupy/orm/schema_activities.py
|
|
78
80
|
mambupy/orm/schema_addresses.py
|
|
@@ -286,6 +286,37 @@ class MambuConnectorWriter(ABC):
|
|
|
286
286
|
"""
|
|
287
287
|
raise NotImplementedError
|
|
288
288
|
|
|
289
|
+
@abstractmethod
|
|
290
|
+
def mambu_loanaccount_updateSchedule(self, loanid, installments):
|
|
291
|
+
"""Updates the installments schedule of a loan account
|
|
292
|
+
|
|
293
|
+
Mambu replaces the whole schedule with the given installments, so the
|
|
294
|
+
complete list must be sent, not just the modified ones.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
loanid (str): the id or encoded key of the loan account
|
|
298
|
+
installments (list of dict): the installments of the schedule,
|
|
299
|
+
complying with Mambu's schema
|
|
300
|
+
"""
|
|
301
|
+
raise NotImplementedError
|
|
302
|
+
|
|
303
|
+
@abstractmethod
|
|
304
|
+
def mambu_loanaccount_changeDueDatesSettings(
|
|
305
|
+
self, loanid, fixedDaysOfMonth, notes=None, valueDate=None
|
|
306
|
+
):
|
|
307
|
+
"""Changes the due dates settings of a loan account
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
loanid (str): the id or encoded key of the loan account
|
|
311
|
+
fixedDaysOfMonth (list of int): the new fixed days of month to be used
|
|
312
|
+
on the loan account
|
|
313
|
+
notes (str): notes to associate to the change due dates settings
|
|
314
|
+
operation in Mambu
|
|
315
|
+
valueDate (str or :py:obj:`datetime`): the date when to change the due
|
|
316
|
+
dates settings
|
|
317
|
+
"""
|
|
318
|
+
raise NotImplementedError
|
|
319
|
+
|
|
289
320
|
@abstractmethod
|
|
290
321
|
def mambu_loantransaction_adjust(self, transactionid, notes):
|
|
291
322
|
"""Adjust a loan transaction
|
|
@@ -9,22 +9,18 @@ Currently supports REST.
|
|
|
9
9
|
|
|
10
10
|
import base64
|
|
11
11
|
import copy
|
|
12
|
-
import datetime
|
|
13
|
-
import enum
|
|
14
12
|
import json
|
|
15
13
|
import mimetypes
|
|
16
14
|
import orjson
|
|
17
15
|
import os
|
|
18
16
|
import re
|
|
19
17
|
import uuid
|
|
20
|
-
from decimal import Decimal
|
|
21
18
|
|
|
22
19
|
import requests
|
|
23
20
|
from requests_toolbelt.multipart.encoder import MultipartEncoder
|
|
24
|
-
from requests.adapters import HTTPAdapter
|
|
25
|
-
from requests.packages.urllib3.util.retry import Retry
|
|
26
21
|
|
|
27
22
|
from .mambuconnector import MambuConnector, MambuConnectorReader, MambuConnectorWriter
|
|
23
|
+
from .rest_helpers import _MambuJSONEncoder, _configure_retry_strategy
|
|
28
24
|
from MambuPy.mambuutil import (
|
|
29
25
|
ALLOWED_UPLOAD_MIMETYPES,
|
|
30
26
|
DETAILSLEVEL,
|
|
@@ -47,55 +43,6 @@ from MambuPy.mambuutil import (
|
|
|
47
43
|
logger = setup_logging(__name__)
|
|
48
44
|
|
|
49
45
|
|
|
50
|
-
class _MambuJSONEncoder(json.JSONEncoder):
|
|
51
|
-
"""JSON encoder that handles Python types not natively serializable by json.dumps."""
|
|
52
|
-
|
|
53
|
-
def default(self, obj):
|
|
54
|
-
if isinstance(obj, (datetime.datetime, datetime.date)):
|
|
55
|
-
return obj.isoformat()
|
|
56
|
-
if isinstance(obj, enum.Enum):
|
|
57
|
-
return obj.value
|
|
58
|
-
if isinstance(obj, Decimal):
|
|
59
|
-
return float(obj)
|
|
60
|
-
return super().default(obj)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def _configure_retry_strategy(session, retries=5):
|
|
64
|
-
"""Configure retry strategy for a session.
|
|
65
|
-
|
|
66
|
-
El HTTPAdapter se construye con pool_maxsize=50 para que los flujos
|
|
67
|
-
que despachan multiples requests concurrentes contra Mambu (p.ej.
|
|
68
|
-
fan-out de aprobaciones, cargar_datos paralelo) no queden encolados
|
|
69
|
-
sobre el default de urllib3 de 10 conexiones por host.
|
|
70
|
-
|
|
71
|
-
Args:
|
|
72
|
-
session (requests.Session): The session to configure
|
|
73
|
-
retries (int, optional): Number of retries. Defaults to 5.
|
|
74
|
-
"""
|
|
75
|
-
retry_strategy = Retry(
|
|
76
|
-
total=retries,
|
|
77
|
-
status_forcelist=[429, 500, 502, 503, 504],
|
|
78
|
-
backoff_factor=1,
|
|
79
|
-
allowed_methods=[
|
|
80
|
-
"HEAD",
|
|
81
|
-
"GET",
|
|
82
|
-
"OPTIONS",
|
|
83
|
-
"POST",
|
|
84
|
-
"PUT",
|
|
85
|
-
"DELETE",
|
|
86
|
-
"TRACE",
|
|
87
|
-
"PATCH",
|
|
88
|
-
],
|
|
89
|
-
)
|
|
90
|
-
adapter = HTTPAdapter(
|
|
91
|
-
max_retries=retry_strategy,
|
|
92
|
-
pool_connections=50,
|
|
93
|
-
pool_maxsize=50,
|
|
94
|
-
)
|
|
95
|
-
session.mount("https://", adapter)
|
|
96
|
-
session.mount("http://", adapter)
|
|
97
|
-
|
|
98
|
-
|
|
99
46
|
class SessionSingleton:
|
|
100
47
|
"""Singleton class to manage HTTP sessions for MambuPy.
|
|
101
48
|
|
|
@@ -775,6 +722,50 @@ url %s, params %s, data %s, headers %s",
|
|
|
775
722
|
data = {"notes": notes}
|
|
776
723
|
return self.__request("POST", url, data=data)
|
|
777
724
|
|
|
725
|
+
def mambu_loanaccount_updateSchedule(self, loanid, installments):
|
|
726
|
+
"""Updates the installments schedule of a loan account
|
|
727
|
+
|
|
728
|
+
Mambu replaces the whole schedule with the given installments, so the
|
|
729
|
+
complete list must be sent, not just the modified ones.
|
|
730
|
+
|
|
731
|
+
Args:
|
|
732
|
+
loanid (str): the id or encoded key of the loan account
|
|
733
|
+
installments (list of dict): the installments of the schedule,
|
|
734
|
+
complying with Mambu's schema
|
|
735
|
+
"""
|
|
736
|
+
url = "https://{}/api/{}/{}/schedule".format(self._tenant, "loans", loanid)
|
|
737
|
+
|
|
738
|
+
return self.__request("PUT", url, data=installments)
|
|
739
|
+
|
|
740
|
+
def mambu_loanaccount_changeDueDatesSettings(
|
|
741
|
+
self, loanid, fixedDaysOfMonth, notes=None, valueDate=None
|
|
742
|
+
):
|
|
743
|
+
"""Changes the due dates settings of a loan account
|
|
744
|
+
|
|
745
|
+
Only available for loan accounts whose product uses the
|
|
746
|
+
FIXED_DAYS_OF_MONTH repayment methodology. Mambu rejects the request
|
|
747
|
+
otherwise.
|
|
748
|
+
|
|
749
|
+
Args:
|
|
750
|
+
loanid (str): the id or encoded key of the loan account
|
|
751
|
+
fixedDaysOfMonth (list of int): the new fixed days of month to be used
|
|
752
|
+
on the loan account
|
|
753
|
+
notes (str): notes to associate to the change due dates settings
|
|
754
|
+
operation in Mambu
|
|
755
|
+
valueDate (str or :py:obj:`datetime`): the date when to change the due
|
|
756
|
+
dates settings
|
|
757
|
+
"""
|
|
758
|
+
url = "https://{}/api/{}/{}:changeDueDatesSettings".format(
|
|
759
|
+
self._tenant, "loans", loanid
|
|
760
|
+
)
|
|
761
|
+
data = {"fixedDaysOfMonth": fixedDaysOfMonth}
|
|
762
|
+
if notes is not None:
|
|
763
|
+
data["notes"] = notes
|
|
764
|
+
if valueDate is not None:
|
|
765
|
+
data["valueDate"] = valueDate
|
|
766
|
+
|
|
767
|
+
return self.__request("POST", url, data=data)
|
|
768
|
+
|
|
778
769
|
def mambu_change_state(self, entid, prefix, action, notes):
|
|
779
770
|
"""change state of mambu entity
|
|
780
771
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Helpers for the REST connector.
|
|
2
|
+
|
|
3
|
+
.. autosummary::
|
|
4
|
+
:nosignatures:
|
|
5
|
+
:toctree: _autosummary
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import datetime
|
|
9
|
+
import enum
|
|
10
|
+
import json
|
|
11
|
+
from decimal import Decimal
|
|
12
|
+
|
|
13
|
+
from requests.adapters import HTTPAdapter
|
|
14
|
+
from requests.packages.urllib3.util.retry import Retry
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _MambuJSONEncoder(json.JSONEncoder):
|
|
18
|
+
"""JSON encoder that handles Python types not natively serializable by json.dumps."""
|
|
19
|
+
|
|
20
|
+
def default(self, obj):
|
|
21
|
+
if isinstance(obj, (datetime.datetime, datetime.date)):
|
|
22
|
+
return obj.isoformat()
|
|
23
|
+
if isinstance(obj, enum.Enum):
|
|
24
|
+
return obj.value
|
|
25
|
+
if isinstance(obj, Decimal):
|
|
26
|
+
return float(obj)
|
|
27
|
+
return super().default(obj)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _configure_retry_strategy(session, retries=5):
|
|
31
|
+
"""Configure retry strategy for a session.
|
|
32
|
+
|
|
33
|
+
El HTTPAdapter se construye con pool_maxsize=50 para que los flujos
|
|
34
|
+
que despachan multiples requests concurrentes contra Mambu (p.ej.
|
|
35
|
+
fan-out de aprobaciones, cargar_datos paralelo) no queden encolados
|
|
36
|
+
sobre el default de urllib3 de 10 conexiones por host.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
session (requests.Session): The session to configure
|
|
40
|
+
retries (int, optional): Number of retries. Defaults to 5.
|
|
41
|
+
"""
|
|
42
|
+
retry_strategy = Retry(
|
|
43
|
+
total=retries,
|
|
44
|
+
status_forcelist=[429, 500, 502, 503, 504],
|
|
45
|
+
backoff_factor=1,
|
|
46
|
+
allowed_methods=[
|
|
47
|
+
"HEAD",
|
|
48
|
+
"GET",
|
|
49
|
+
"OPTIONS",
|
|
50
|
+
"POST",
|
|
51
|
+
"PUT",
|
|
52
|
+
"DELETE",
|
|
53
|
+
"TRACE",
|
|
54
|
+
"PATCH",
|
|
55
|
+
],
|
|
56
|
+
)
|
|
57
|
+
adapter = HTTPAdapter(
|
|
58
|
+
max_retries=retry_strategy,
|
|
59
|
+
pool_connections=50,
|
|
60
|
+
pool_maxsize=50,
|
|
61
|
+
)
|
|
62
|
+
session.mount("https://", adapter)
|
|
63
|
+
session.mount("http://", adapter)
|
|
@@ -114,10 +114,12 @@ class MambuLoan(
|
|
|
114
114
|
except KeyError:
|
|
115
115
|
pass
|
|
116
116
|
|
|
117
|
-
def
|
|
118
|
-
"""
|
|
119
|
-
resp = self._connector.mambu_loanaccount_getSchedule(self.id)
|
|
117
|
+
def _hydrate_schedule(self, resp):
|
|
118
|
+
"""Builds the schedule of MambuInstallments from a Mambu response.
|
|
120
119
|
|
|
120
|
+
Args:
|
|
121
|
+
resp (bytes): a Mambu response holding an installments list.
|
|
122
|
+
"""
|
|
121
123
|
installments = orjson.loads(resp.decode())["installments"]
|
|
122
124
|
|
|
123
125
|
self.schedule = []
|
|
@@ -127,6 +129,38 @@ class MambuLoan(
|
|
|
127
129
|
installment_entity._convertDict2Attrs()
|
|
128
130
|
self.schedule.append(installment_entity)
|
|
129
131
|
|
|
132
|
+
def get_schedule(self, **kwargs):
|
|
133
|
+
"""Retrieves the installments schedule."""
|
|
134
|
+
resp = self._connector.mambu_loanaccount_getSchedule(self.id)
|
|
135
|
+
|
|
136
|
+
self._hydrate_schedule(resp)
|
|
137
|
+
|
|
138
|
+
def update_schedule(self, installments=None, **kwargs):
|
|
139
|
+
"""Request to update the installments schedule of a loan account.
|
|
140
|
+
|
|
141
|
+
Mambu replaces the whole schedule with the installments sent, so the
|
|
142
|
+
complete list must be given, not just the modified ones. Sending a
|
|
143
|
+
subset means deleting the rest, which Mambu rejects for accounts on
|
|
144
|
+
non dynamic term products.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
installments (list of :py:obj:`MambuInstallment`): the schedule to
|
|
148
|
+
send. Defaults to the schedule currently held by this
|
|
149
|
+
account, as retrieved by `get_schedule`.
|
|
150
|
+
"""
|
|
151
|
+
if installments is None:
|
|
152
|
+
installments = self.schedule
|
|
153
|
+
|
|
154
|
+
payload = []
|
|
155
|
+
for installment in installments:
|
|
156
|
+
serialized = copy.deepcopy(installment)
|
|
157
|
+
serialized._serializeFields()
|
|
158
|
+
payload.append(serialized._attrs)
|
|
159
|
+
|
|
160
|
+
resp = self._connector.mambu_loanaccount_updateSchedule(self.id, payload)
|
|
161
|
+
|
|
162
|
+
self._hydrate_schedule(resp)
|
|
163
|
+
|
|
130
164
|
def get_transactions(self, **kwargs):
|
|
131
165
|
"""Retrieves the transactions of the loan"""
|
|
132
166
|
self.transactions = MambuTransaction.get_all(self.id)
|
|
@@ -241,6 +275,28 @@ class MambuLoan(
|
|
|
241
275
|
self._connector.mambu_loanaccount_writeoff(self.id, notes)
|
|
242
276
|
self.refresh()
|
|
243
277
|
|
|
278
|
+
def change_due_dates_settings(
|
|
279
|
+
self, fixedDaysOfMonth, notes=None, valueDate=None, **kwargs
|
|
280
|
+
):
|
|
281
|
+
"""Request to change the due dates settings of a loan account.
|
|
282
|
+
|
|
283
|
+
Only available for loan accounts whose product uses the
|
|
284
|
+
FIXED_DAYS_OF_MONTH repayment methodology. Mambu rejects the request
|
|
285
|
+
otherwise, raising a `MambuError`.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
fixedDaysOfMonth (list of int): the new fixed days of month to be used
|
|
289
|
+
on the loan account.
|
|
290
|
+
notes (str): notes to attach to the change due dates settings
|
|
291
|
+
operation.
|
|
292
|
+
valueDate (str or :py:obj:`datetime`): the date when to change the due
|
|
293
|
+
dates settings.
|
|
294
|
+
"""
|
|
295
|
+
self._connector.mambu_loanaccount_changeDueDatesSettings(
|
|
296
|
+
self.id, fixedDaysOfMonth, notes, valueDate
|
|
297
|
+
)
|
|
298
|
+
self.refresh()
|
|
299
|
+
|
|
244
300
|
def repay(self, amount, notes, valueDate, **kwargs):
|
|
245
301
|
"""Request to repay a loan account.
|
|
246
302
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import copy
|
|
9
|
-
from datetime import datetime
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
10
|
from importlib import import_module
|
|
11
11
|
|
|
12
12
|
from .classes import MambuMapObj
|
|
@@ -289,7 +289,19 @@ class MambuStruct(MambuMapObj):
|
|
|
289
289
|
if isinstance(data, datetime):
|
|
290
290
|
data_asdate = data.isoformat()
|
|
291
291
|
if tzdata: # no tzdata means a date (no time) object
|
|
292
|
-
|
|
292
|
+
if self._as_utc:
|
|
293
|
+
# _convertDict2Attrs moved this datetime to UTC and dropped
|
|
294
|
+
# its tzinfo, so appending the original offset would shift
|
|
295
|
+
# the instant. Convert back to that offset instead.
|
|
296
|
+
offset_str = tzdata[3:] or "+00:00"
|
|
297
|
+
tz_info = datetime.fromisoformat(
|
|
298
|
+
"2000-01-01T00:00:00" + offset_str
|
|
299
|
+
).tzinfo
|
|
300
|
+
data_asdate = (
|
|
301
|
+
data.replace(tzinfo=timezone.utc).astimezone(tz_info).isoformat()
|
|
302
|
+
)
|
|
303
|
+
else:
|
|
304
|
+
data_asdate += tzdata[-6:]
|
|
293
305
|
else:
|
|
294
306
|
data_asdate = data_asdate[:10]
|
|
295
307
|
return data_asdate
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|