Source code for MambuPy.rest.mambuloan

# coding: utf-8
"""Mambu Loans objects.

.. autosummary::
   :nosignatures:
   :toctree: _autosummary

MambuLoan holds a loan account.

MambuLoans holds a list of loan accounts.

Uses mambugeturl.getloans as default urlfunc.
You can override this and use mambugeturl.getgrouploansurl for the loans
of a specific group instead (you should send the Group ID instead of the
Loan ID as the entid argument in the constructor in that case).
"""


from ..mambugeturl import getloanscustominformationurl, getloansurl, getpostdocumentsurl
from ..mambuutil import MambuError, strip_tags
from .mambustruct import MambuStruct
from .mamburestutils import MambuStructIterator

mod_urlfunc = getloansurl


# Objeto con una Cuenta desde Mambu
[docs]class MambuLoan(MambuStruct): """A Loan account from Mambu. With the default urlfunc, entid argument must be the ID of the loan account you wish to retrieve. """
[docs] def __init__(self, urlfunc=mod_urlfunc, entid="", *args, **kwargs): """Tasks done here: Just initializes the MambuStruct. """ MambuStruct.__init__( self, urlfunc, entid, custom_field_name="customFieldValues", *args, **kwargs )
[docs] def getDebt(self): """Sums up all the balances of the account and returns them.""" debt = float(self["principalBalance"]) + float(self["interestBalance"]) debt += float(self["feesBalance"]) + float(self["penaltyBalance"]) return debt
[docs] def preprocess(self): """Preprocessing. Each active custom field is given a 'name' key that holds the field name, and for each keyed name, the value of the custom field is assigned. Notes on the group get some html tags removed. """ super(MambuLoan, self).preprocess() try: self["notes"] = strip_tags(self["notes"]) except KeyError: pass
[docs] def setRepayments(self, *args, **kwargs): """Adds the repayments for this loan to a 'repayments' field. Repayments are MambuRepayment objects. Repayments get sorted by due date. Returns the number of requests done to Mambu. .. todo:: since pagination logic was added, is not always true that just 1 request was done. It may be more! But since request counter singleton holds true information about how many requests were done to Mambu, in fact this return value may be obsolete """ def duedate(repayment): """Util function used for sorting repayments according to due Date""" try: return repayment["dueDate"] except KeyError: from datetime import datetime return datetime.now() try: self.mamburepaymentsclass except AttributeError: from .mamburepayment import MambuRepayments self.mamburepaymentsclass = MambuRepayments reps = self.mamburepaymentsclass(entid=self["id"], *args, **kwargs) reps.attrs = sorted(reps.attrs, key=duedate) self["repayments"] = reps return 1
[docs] def setTransactions(self, *args, **kwargs): """Adds the transactions for this loan to a 'transactions' field. Transactions are MambuTransaction objects. Transactions get sorted by transaction id. Returns the number of requests done to Mambu. .. todo:: since pagination logic was added, is not always true that just 1 request was done. It may be more! But since request counter singleton holds true information about how many requests were done to Mambu, in fact this return value may be obsolete """ def transactionid(transaction): """Util function used for sorting transactions according to id""" try: return transaction["transactionId"] except KeyError: return None try: self.mambutransactionsclass except AttributeError: from .mambutransaction import MambuTransactions self.mambutransactionsclass = MambuTransactions trans = self.mambutransactionsclass(entid=self["id"], *args, **kwargs) trans.attrs = sorted(trans.attrs, key=transactionid) self["transactions"] = trans return 1
[docs] def setBranch(self, *args, **kwargs): """Adds the branch for this loan to a 'assignedBranch' field. Also adds an 'assignedBranchName' field with the name of the branch. Branch is a MambuBranch object. Returns the number of requests done to Mambu. """ try: self.mambubranchclass except AttributeError: from .mambubranch import MambuBranch self.mambubranchclass = MambuBranch branch = self.mambubranchclass(entid=self["assignedBranchKey"], *args, **kwargs) self["assignedBranchName"] = branch["name"] self["assignedBranch"] = branch return 1
[docs] def setCentre(self, *args, **kwargs): """Adds the centre for this loan to a 'assignedCentre' field. Also adds an 'assignedCentreName' field with the name of the centre. Centre is a MambuCentre object. Returns the number of requests done to Mambu. """ try: self.mambucentreclass except AttributeError: from .mambucentre import MambuCentre self.mambucentreclass = MambuCentre centre = self.mambucentreclass(entid=self["assignedCentreKey"], *args, **kwargs) self["assignedCentreName"] = centre["name"] self["assignedCentre"] = centre return 1
[docs] def setUser(self, *args, **kwargs): """Adds the user for this loan to a 'user' field. User is a MambuUser object. Returns the number of requests done to Mambu. """ try: self.mambuuserclass except AttributeError: from .mambuuser import MambuUser self.mambuuserclass = MambuUser try: user = self.mambuuserclass(entid=self["assignedUserKey"], *args, **kwargs) except KeyError: err = MambuError("Loan Account %s has no assigned user" % self["id"]) err.noUser = True raise err self["user"] = user return 1
[docs] def setProduct(self, cache=False, *args, **kwargs): """Adds the product for this loan to a 'product' field. Product is a MambuProduct object. cache argument allows to use AllMambuProducts singleton to retrieve the products. See mambuproduct.AllMambuProducts code and pydoc for further information. Returns the number of requests done to Mambu. """ if cache: try: self.allmambuproductsclass except AttributeError: from .mambuproduct import AllMambuProducts self.allmambuproductsclass = AllMambuProducts prods = self.allmambuproductsclass(*args, **kwargs) for prod in prods: if prod["encodedKey"] == self["productTypeKey"]: self["product"] = prod try: # asked for cache, but cache was originally empty prods.noinit except AttributeError: return 1 return 0 try: self.mambuproductclass except AttributeError: from .mambuproduct import MambuProduct self.mambuproductclass = MambuProduct product = self.mambuproductclass(entid=self["productTypeKey"], *args, **kwargs) self["product"] = product return 1
[docs] def _get_roles(self, fullDetails=True, *args, **kwargs): """ Get a list of roles and clients for the loan account. The roles are mentionend in the holder of the loan account. The holder acquires a "roles" property, a list of dictionaries with the following keys: role (str): role name client (obj): MambuClient that has the mentioned role Args: fullDetails (bool): whether instantiate clients with full details or not Returns: int - number of requests done to Mambu """ requests = 0 self["holder"]["roles"] = [] # If holder is group, attach role client data to the group for c in self["holder"]["groupRoles"]: try: self.mambuclientclass except AttributeError: from .mambuclient import MambuClient self.mambuclientclass = MambuClient cli = self.mambuclientclass( entid=c["clientKey"], fullDetails=fullDetails, *args, **kwargs ) self["holder"]["roles"].append( {"role": c["roleName"], "client": cli}) requests += 1 return requests
[docs] def _get_loanclientsdata_from_group_setHolder(self, holder, *args, **kwargs): loanclients = {} loanclientsdata = self.getClientDetails(holder=holder, *args, **kwargs) for data in loanclientsdata: loanclients[data["id"]] = { "client": data["client"], "name": data["name"], "loan": self, "amount": data["amount"], "montoPago": data["amount"] / float(self["repaymentInstallments"]), "porcentaje": data["amount"] / float(self["loanAmount"]), } # Any extra key,val pair on loannames is plainly assigned to the loanclients[cte] dict for k, v in [ (key, val) for (key, val) in data.items() if key not in ["amount", "name"] ]: loanclients[data["id"]][k] = v return loanclients
[docs] def _get_holder_data_by_group(self, getRoles, fullDetails, getClients, *args, **kwargs): requests = 0 self["holderType"] = "Grupo" try: self.mambugroupclass except AttributeError: from .mambugroup import MambuGroup self.mambugroupclass = MambuGroup holder = self.mambugroupclass( entid=self["accountHolderKey"], fullDetails=True, *args, **kwargs ) self["holder"] = holder requests += 1 if getRoles: requests += self._get_roles(fullDetails, *args, **kwargs) if getClients: try: self.mambuclientclass except AttributeError: # pragma: no cover from .mambuclient import MambuClient self.mambuclientclass = MambuClient requests += holder.setClients( fullDetails=fullDetails, mambuclientclass=self.mambuclientclass, *args, **kwargs ) self["clients"] = self._get_loanclientsdata_from_group_setHolder(holder, *args, **kwargs) return requests
[docs] def _get_holder_data_by_client(self, fullDetails, getClients, *args, **kwargs): requests = 0 self["holderType"] = "Cliente" try: self.mambuclientclass except AttributeError: from .mambuclient import MambuClient self.mambuclientclass = MambuClient holder = self.mambuclientclass( entid=self["accountHolderKey"], fullDetails=fullDetails, *args, **kwargs ) self["holder"] = holder requests += 1 if getClients: monto = float(self["loanAmount"]) self["clients"] = { holder["name"]: { "client": holder, "loan": self, "amount": monto, "montoPago": monto / float(self["repaymentInstallments"]), "porcentaje": 1.0, } } return requests
[docs] def setHolder(self, getClients=False, getRoles=False, *args, **kwargs): """Adds the "holder" of the loan to a 'holder' field. Holder may be a MambuClient or a MambuGroup object, depending on the type of loan created at Mambu. getRoles argument tells this method to retrive the clients who have specific roles in the group, in case of a group holder. getClients argument tells this method to retrieve the client members of the group, in case of a group holder. See the definition of the 'clients' field added with this argument below. fullDetails argument works for Client Holder and for the client members of the group. But the Group is always retrieved with full details. When using getRoles argument also adds a 'roles' field to the holder, a dictionary with keys: * 'role' , the rolename * 'client', a MambuClient having that role on the group .. todo:: since the roles field is added to the holder, and it only applies to Groups, perhaps the getRoles functionality should be on MambuGroup code When using getClients argument, also adds a 'clients' field to the loan. By the way it works, when holder is a Group, it calls the setClients method, which adds a 'clients' field to the MambuGroup. The 'clients' field on the loan is a dictionary with the full name of each client receiving from this loan account as a key (see the 'name' field on MambuClient and MambuGroup). Each key holds another dictionary with the following keys: * 'client' holds a MambuClient. So even if a group is the holder of the account, you can access each individual client object here. * 'loan' holds a reference to this loan account (self) * 'amount' defaults to the total loan amount ('loanAmount' field) (see the pydoc for getClientDetails method) * 'montoPago' defaults to the total loan amount divided by the number of repayment installments of the loan account (the 'repaymentInstallments' field), that is, how much to pay for each repayment installment (see the pydoc for getClientDetails method) .. todo:: change the name to an english name * 'porcentaje' defaults to a 100%, the total loan amount divided by itself, that is, hoy much in percentage of the debt belongs to this client (see the pydoc for getClientDetails method) .. todo:: change the name to an english name This three fields are supposed to reflect individual numbers for each client, but Mambu does an awful job indicating this on group loan accounts. So you should indicate how much you assigned to each client in other ways. Using the notes of the loan account perhaps (with a predefined format you parse on the preprocessing of MambuLoan)? with custom fields? Your call that you may define making your own loan account class inheriting from MambuLoan class. See the pydoc for getClientDetails method below, it specifies the way to determine the amount for each client depending on your use of Mambu, and additionally any other fields you wish to add to the 'clients' field on the loan account. Returns the number of requests done to Mambu. .. todo:: what to do on Hybrid loan accounts? """ if "fullDetails" in kwargs: fullDetails = kwargs["fullDetails"] kwargs.pop("fullDetails") else: fullDetails = True if self["accountHolderType"] == "GROUP": requests = self._get_holder_data_by_group(getRoles, fullDetails, getClients, *args, **kwargs) else: # "CLIENT" requests = self._get_holder_data_by_client(fullDetails, getClients, *args, **kwargs) return requests
[docs] def getClientDetails(self, *args, **kwargs): """Gets the loan details for every client holder of the account. As default, assigns the whole loan amount to each client. This works fine for Client holders of the loan account. When Group holders, this is perhaps not ideal, but I cannot tell. If you inherit MambuLoan you should override this method to determine another way to assign particular amounts to each client. You can also use the overriden version of this method to add several other fields with information you wish to associate to each client holder of the loan account. BEWARE: for group loan accounts, this code assumes the holder (the group) currentrly has all the client members. But for accounts where the holder has changed along time, you may stumble upon accounts which assumes certain group members which weren't the members that belonged to the group when it was disbursed. """ loannames = [] holder = kwargs["holder"] for client in holder["clients"]: loannames.append( { "id": client["id"], "name": client["name"], "client": client, "amount": self["loanAmount"], } ) return loannames
[docs] def setActivities(self, *args, **kwargs): """Adds the activities for this loan to a 'activities' field. Activities are MambuActivity objects. Activities get sorted by activity timestamp. Returns the number of requests done to Mambu. """ def activity_date(activity): """Util function used for sorting activities according to timestamp""" try: return activity["activity"]["timestamp"] except KeyError: return None try: self.mambuactivitiesclass except AttributeError: from .mambuactivity import MambuActivities self.mambuactivitiesclass = MambuActivities activities = self.mambuactivitiesclass( loanAccountId=self["encodedKey"], *args, **kwargs ) activities.attrs = sorted(activities.attrs, key=activity_date) self["activities"] = activities return 1
[docs] def update(self, data, *args, **kwargs): """Updates a loan in Mambu Updates customFields of a MambuLoan TODO: update "core fields" of a MambuLoan https://support.mambu.com/docs/loans-api#post-loans https://support.mambu.com/docs/loans-api#patch-loan https://support.mambu.com/docs/loans-api#patch-loan-custom-field-values Parameters -data dictionary with data to update """ cont_requests = 0 data2update = {} # UPDATE customFields if data.get("customInformation"): data2update = {} data2update["customInformation"] = data.get("customInformation") self._MambuStruct__urlfunc = getloanscustominformationurl cont_requests += self.update_patch(data2update, *args, **kwargs) self._MambuStruct__urlfunc = getloansurl cont_requests += super(MambuLoan, self).update(data, *args, **kwargs) return cont_requests
[docs] def update_patch(self, data, *args, **kwargs): """Updates a Mambu loan using method PATCH Args: data (dictionary): dictionary with data to update https://support.mambu.com/docs/loans-api#patch-loan """ return super(MambuLoan, self).update_patch(data, *args, **kwargs)
[docs] def upload_document(self, data, *args, **kwargs): """Updates a loan in Mambu Uploads a document to a MambuLoan https://support.mambu.com/docs/attachments-api#post-attachments Parameters -data dictionary with data to upload Example data = { "document":{ "documentHolderKey" : self.encodedKey, "documentHolderType" : "LOAN_ACCOUNT", "name" : "loan_resume", "type" : "pdf", }, "documentContent" : "['encodedBase64_file']", } """ cont_requests = 0 # upload document self._MambuStruct__urlfunc = getpostdocumentsurl cont_requests += super(MambuLoan, self).upload_document(data, *args, **kwargs) self._MambuStruct__urlfunc = getloansurl return cont_requests
[docs]class MambuLoans(MambuStruct): """A list of Loan accounts from Mambu. With the default urlfunc, entid argument must be empty at instantiation time to retrieve all the loan accounts according to any other filter you send to the urlfunc. There's another possible urlfunc you may use, getgrouploansurl. If you use that, you should send the ID of the group from which you wish to retrieve its loan accounts. itemclass argument allows you to pass some other class as the elements for the list. Why is this useful? at least for now, MambuLoan is the most specialized class on MambuPy. So you may wish to override several behaviours by creating your own MambuLoan son class. Pass that to the itemclass argument here and voila, you get a list of YourMambuLoan class using MambuLoans instead of plain old MambuLoan elements. If you wish to specialize other Mambu objects on MambuPy you may do that. Mind that if you desire that the iterable version of it to have elements of your specialized class, you need to change the logic of the constructor and the convert_dict_to_attrs method in the iterable class to use some sort of itemclass there too. Don't forget to submit the change on a pull request when done ;-) """
[docs] def __init__( self, urlfunc=mod_urlfunc, entid="", itemclass=MambuLoan, *args, **kwargs ): """By default, entid argument is empty. That makes perfect sense: you want several groups, not just one. """ self.itemclass = itemclass MambuStruct.__init__(self, urlfunc, entid, *args, **kwargs)
[docs] def __iter__(self): return MambuStructIterator(self.attrs)
[docs] def convert_dict_to_attrs(self, *args, **kwargs): """The trick for iterable Mambu Objects comes here: You iterate over each element of the responded List from Mambu, and create a Mambu Loan (or your own itemclass) object for each one, initializing them one at a time, and changing the attrs attribute (which just holds a list of plain dictionaries) with a MambuLoan (or your own itemclass) just created. .. todo:: pass a valid (perhaps default) urlfunc, and its corresponding id to entid to each itemclass, telling MambuStruct not to connect() by default. It's desirable to connect at any other further moment to refresh some element in the list. """ for n, l in enumerate(self.attrs): # ok ok, I'm modifying elements of a list while iterating it. BAD PRACTICE! try: params = self.params except AttributeError: params = {} kwargs.update(params) try: self.mambuloanclass except AttributeError: self.mambuloanclass = self.itemclass loan = self.mambuloanclass(urlfunc=None, entid=None, *args, **kwargs) loan.init(l, *args, **kwargs) loan._MambuStruct__urlfunc = getloansurl self.attrs[n] = loan