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.
Files changed (98) hide show
  1. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/__init__.py +1 -1
  2. {mambupy-2.3.3/mambupy → mambupy-2.4.0/MambuPy}/api/connector/mambuconnector.py +31 -0
  3. {mambupy-2.3.3/mambupy → mambupy-2.4.0/MambuPy}/api/connector/rest.py +45 -54
  4. mambupy-2.4.0/MambuPy/api/connector/rest_helpers.py +63 -0
  5. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambuloan.py +59 -3
  6. {mambupy-2.3.3/mambupy → mambupy-2.4.0/MambuPy}/api/mambustruct.py +14 -2
  7. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy.egg-info/PKG-INFO +1 -1
  8. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy.egg-info/SOURCES.txt +2 -0
  9. {mambupy-2.3.3 → mambupy-2.4.0}/PKG-INFO +1 -1
  10. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/__init__.py +1 -1
  11. {mambupy-2.3.3/MambuPy → mambupy-2.4.0/mambupy}/api/connector/mambuconnector.py +31 -0
  12. {mambupy-2.3.3/MambuPy → mambupy-2.4.0/mambupy}/api/connector/rest.py +45 -54
  13. mambupy-2.4.0/mambupy/api/connector/rest_helpers.py +63 -0
  14. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambuloan.py +59 -3
  15. {mambupy-2.3.3/MambuPy → mambupy-2.4.0/mambupy}/api/mambustruct.py +14 -2
  16. {mambupy-2.3.3 → mambupy-2.4.0}/LICENSE +0 -0
  17. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/__init__.py +0 -0
  18. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/classes.py +0 -0
  19. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/connector/__init__.py +0 -0
  20. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/entities.py +0 -0
  21. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/interfaces.py +0 -0
  22. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambubranch.py +0 -0
  23. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambucentre.py +0 -0
  24. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambuclient.py +0 -0
  25. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambucustomfield.py +0 -0
  26. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambugroup.py +0 -0
  27. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambuproduct.py +0 -0
  28. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mamburole.py +0 -0
  29. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambutask.py +0 -0
  30. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambutransaction.py +0 -0
  31. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/mambuuser.py +0 -0
  32. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/api/vos.py +0 -0
  33. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/mambuconfig.py +0 -0
  34. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/mambugeturl.py +0 -0
  35. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/mambuutil.py +0 -0
  36. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/__init__.py +0 -0
  37. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_activities.py +0 -0
  38. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_addresses.py +0 -0
  39. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_branches.py +0 -0
  40. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_centres.py +0 -0
  41. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_clients.py +0 -0
  42. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_customfields.py +0 -0
  43. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_dummies.py +0 -0
  44. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_groups.py +0 -0
  45. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_loans.py +0 -0
  46. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_mambu.py +0 -0
  47. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_orm.py +0 -0
  48. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_tasks.py +0 -0
  49. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/orm/schema_users.py +0 -0
  50. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/rest/__init__.py +0 -0
  51. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/rest/mambuactivity.py +0 -0
  52. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/utils/__init__.py +0 -0
  53. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/utils/update_user_roles.py +0 -0
  54. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy/utils/userdeactivate.py +0 -0
  55. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy.egg-info/dependency_links.txt +0 -0
  56. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy.egg-info/requires.txt +0 -0
  57. {mambupy-2.3.3 → mambupy-2.4.0}/MambuPy.egg-info/top_level.txt +0 -0
  58. {mambupy-2.3.3 → mambupy-2.4.0}/README.md +0 -0
  59. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/__init__.py +0 -0
  60. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/classes.py +0 -0
  61. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/connector/__init__.py +0 -0
  62. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/entities.py +0 -0
  63. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/interfaces.py +0 -0
  64. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambubranch.py +0 -0
  65. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambucentre.py +0 -0
  66. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambuclient.py +0 -0
  67. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambucustomfield.py +0 -0
  68. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambugroup.py +0 -0
  69. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambuproduct.py +0 -0
  70. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mamburole.py +0 -0
  71. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambutask.py +0 -0
  72. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambutransaction.py +0 -0
  73. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/mambuuser.py +0 -0
  74. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/api/vos.py +0 -0
  75. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/mambuconfig.py +0 -0
  76. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/mambugeturl.py +0 -0
  77. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/mambuutil.py +0 -0
  78. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/__init__.py +0 -0
  79. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_activities.py +0 -0
  80. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_addresses.py +0 -0
  81. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_branches.py +0 -0
  82. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_centres.py +0 -0
  83. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_clients.py +0 -0
  84. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_customfields.py +0 -0
  85. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_dummies.py +0 -0
  86. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_groups.py +0 -0
  87. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_loans.py +0 -0
  88. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_mambu.py +0 -0
  89. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_orm.py +0 -0
  90. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_tasks.py +0 -0
  91. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/orm/schema_users.py +0 -0
  92. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/rest/__init__.py +0 -0
  93. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/rest/mambuactivity.py +0 -0
  94. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/utils/__init__.py +0 -0
  95. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/utils/update_user_roles.py +0 -0
  96. {mambupy-2.3.3 → mambupy-2.4.0}/mambupy/utils/userdeactivate.py +0 -0
  97. {mambupy-2.3.3 → mambupy-2.4.0}/pyproject.toml +0 -0
  98. {mambupy-2.3.3 → mambupy-2.4.0}/setup.cfg +0 -0
@@ -37,7 +37,7 @@ import sys
37
37
  from importlib.abc import MetaPathFinder
38
38
  from importlib.util import spec_from_file_location
39
39
 
40
- __version__ = "2.3.3"
40
+ __version__ = "2.4.0"
41
41
  """The version of this module."""
42
42
 
43
43
 
@@ -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 get_schedule(self, **kwargs):
118
- """Retrieves the installments schedule."""
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
- data_asdate += tzdata[-6:]
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MambuPy
3
- Version: 2.3.3
3
+ Version: 2.4.0
4
4
  Summary: A python library for using Mambu APIs.
5
5
  Author-email: "Javier Novoa C." <jstitch@gmail.com>
6
6
  License: LICENSE
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MambuPy
3
- Version: 2.3.3
3
+ Version: 2.4.0
4
4
  Summary: A python library for using Mambu APIs.
5
5
  Author-email: "Javier Novoa C." <jstitch@gmail.com>
6
6
  License: LICENSE
@@ -37,7 +37,7 @@ import sys
37
37
  from importlib.abc import MetaPathFinder
38
38
  from importlib.util import spec_from_file_location
39
39
 
40
- __version__ = "2.3.3"
40
+ __version__ = "2.4.0"
41
41
  """The version of this module."""
42
42
 
43
43
 
@@ -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 get_schedule(self, **kwargs):
118
- """Retrieves the installments schedule."""
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
- data_asdate += tzdata[-6:]
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