def before_save(self): self.total_amount = \ flt(self.total_interests) + flt(self.principal_amount) self.total_charges = reduce(lambda a, x: a + x.charge_amount, self.charges, 0) self.total_received = self.total_amount + self.total_charges account_dict = get_bank_cash_account( mode_of_payment=self.mode_of_payment or 'Cash', company=self.company, ) self.payment_account = account_dict.get('account') self.periods = [] for period in allocate_interests( self.loan, self.posting_date, amount_to_allocate=self.total_interests, principal=self.principal_amount, ): self.append('periods', period) expected_outstanding = self.principal_amount + compose( sum, partial(map, pick('outstanding_amount')), partial(filter, lambda x: x.ref_interest is not None))( self.periods) if expected_outstanding > get_outstanding_principal(self.loan): frappe.throw('Cannot receive more than the current outstanding')
def get_sum_of(doctype, field): def fn(loan): return frappe.get_all( doctype, filters={"docstatus": 1, "loan": loan}, fields=field ) return compose(sum, partial(map, pick(field)), fn)
def make_interests(self, cancel=0): make_posting_date = compose( partial(min, getdate(self.posting_date)), getdate, partial(add_days, days=1), pick("end_date"), ) return compose( list, partial( map, partial( _create_or_update_interest, update=cancel, submit=self.loan_type == "EMI", ), ), partial( map, compose( update({"loan": self.loan}), lambda x: { "posting_date": make_posting_date(x), "period": x.period_label, "start_date": x.start_date, "end_date": x.end_date, "billed_amount": x.billed_amount, "paid_amount": x.allocated_amount, }, ), ), )(self.periods)
def make_interests(self, cancel=0): make_posting_date = compose( partial(min, getdate(self.posting_date)), getdate, partial(add_days, days=1), pick('end_date'), ) return compose( partial( map, partial(_create_or_update_interest, update=cancel), ), partial( map, compose( update({ 'loan': self.loan, }), lambda x: { 'posting_date': make_posting_date(x), 'period': x.period_label, 'start_date': x.start_date, 'end_date': x.end_date, 'billed_amount': x.billed_amount, 'paid_amount': x.allocated_amount, }), ), )(self.periods)
def before_submit(self): sum_allocated = compose(sum, partial(map, pick('allocated_amount'))) # validation for quirk when total_interests field is cleared and # Submit button is still being rendered instead of being changed to # Save if self.total_interests != sum_allocated(self.periods): frappe.throw('Interests and total allocated do not match. ' 'Please refresh the page or update the interest')
def allocate_amount(self): self.total_amount = (self.paid_amount if self.loan_type == "EMI" else flt(self.total_interests) + flt(self.principal_amount)) self.total_charges = reduce(lambda a, x: a + x.charge_amount, self.get("charges", []), 0) self.total_received = self.total_amount + self.total_charges account_dict = get_bank_cash_account( mode_of_payment=self.mode_of_payment or "Cash", company=self.company) self.payment_account = account_dict.get("account") self.periods = [] if self.loan_type == "EMI": to_allocate = self.paid_amount interests = frappe.get_all( "Microfinance Loan Interest", filters={ "loan": self.loan, "status": ("!=", "Clear") }, fields=[ "period as period_label", "start_date", "end_date", "billed_amount", "principal_amount", "fine_amount", "billed_amount + principal_amount + fine_amount - paid_amount as outstanding_amount", # noqa "name as ref_interest", ], order_by="start_date", ) for interest in interests: allocated_amount = min(to_allocate, interest.get("outstanding_amount")) if allocated_amount == 0: break self.append( "periods", merge(interest, {"allocated_amount": allocated_amount})) to_allocate -= allocated_amount else: for period in allocate_interests( self.loan, self.posting_date, amount_to_allocate=self.total_interests, principal=self.principal_amount, ): self.append("periods", period) expected_outstanding = self.principal_amount + compose( sum, partial(map, pick("outstanding_amount")), partial(filter, lambda x: x.ref_interest is not None), )(self.periods) if expected_outstanding > get_outstanding_principal(self.loan): frappe.throw( "Cannot receive more than the current outstanding")
def update_advance_interests(loan, posting_date): adv_interests = map( pick("name"), frappe.get_all( "Microfinance Loan Interest", filters=[["loan", "=", loan], ["end_date", ">=", posting_date]], order_by="end_date", ), ) for interest in adv_interests: doc = frappe.get_doc("Microfinance Loan Interest", interest) doc.adjust_billed_amount(posting_date)
def validate_allowable_amount(self, is_update=False): effective_date = frappe.get_value( 'Loan Plan', self.loan_plan, 'effective_from' ) if effective_date and \ getdate(effective_date) > getdate(self.posting_date): return None date_of_retirement, net_salary_amount = frappe.get_value( 'Microfinance Loanee', {'customer': self.customer}, ['date_of_retirement', 'net_salary_amount'] ) if not date_of_retirement or not net_salary_amount: return None allowed = calculate_principal( income=net_salary_amount, loan_plan=self.loan_plan, end_date=date_of_retirement, execution_date=self.posting_date, ) loan_principal = flt(self.loan_principal) recovery_amount = flt(self.recovery_amount) if loan_principal > allowed.get('principal'): frappe.throw( "Requested principal cannot exceed {}".format( _fmt_money(allowed.get('principal')) ) ) tentative_outstanding = reduce( lambda a, x: a + get_outstanding_principal(x), map(pick('name'), _existing_loans_by(self.customer)), loan_principal ) if is_update: before = self.get_doc_before_save() tentative_outstanding -= before.loan_principal if tentative_outstanding > allowed.get('principal'): frappe.throw( "Customer already has existing loans. " "Total principal would exceed allowed {}".format( _fmt_money(allowed.get('principal')), ) ) if recovery_amount < tentative_outstanding / allowed.get('duration'): frappe.throw( "Recovery Amount cannot be less than {}".format( _fmt_money(loan_principal / allowed.get('duration')) ) )
def update_advance_interests(loan, posting_date): adv_interests = map( pick('name'), frappe.get_all( 'Microfinance Loan Interest', filters=[ ['loan', '=', loan], ['end_date', '>=', posting_date], ], order_by='end_date', )) for interest in adv_interests: doc = frappe.get_doc('Microfinance Loan Interest', interest) doc.adjust_billed_amount(posting_date)
def generate_late_fines(posting_date): """ Returns a list of interests that were posted in the previous period from the posting_date. Processes all interests in the previous period what have outstanding interest amounts. """ get_interests = compose( partial(map, pick("name")), _get_interests, partial(add_months, months=-2), get_last_day, ) for interest in get_interests(posting_date): _set_fine(interest)
def generate_interest(posting_date): """ Returns a list of new interests posted on posting_date. Processes all active loans which have an interest for the previous period as this one. """ not_exists_filter = compose(_interest_does_not_exists, partial(add_months, months=-1)) get_loans = compose( partial(filter, not_exists_filter(posting_date)), partial(map, pick("name")), _get_active_loans_after, ) for loan in get_loans(posting_date): _create_interest_on(loan, posting_date)
def validate_allowable_amount(self, is_update=False): loan_type = self.loan_type or frappe.db.get_value( "Loan Plan", self.loan_plan, "loan_type") if loan_type == "EMI": return effective_date = frappe.get_value("Loan Plan", self.loan_plan, "effective_from") if effective_date and getdate(effective_date) > getdate( self.posting_date): return None date_of_retirement, net_salary_amount = frappe.get_value( "Microfinance Loanee", {"customer": self.customer}, ["date_of_retirement", "net_salary_amount"], ) if not date_of_retirement or not net_salary_amount: return None allowed = calculate_principal( income=net_salary_amount, loan_plan=self.loan_plan, end_date=date_of_retirement, execution_date=self.posting_date, ) loan_principal = flt(self.loan_principal) recovery_amount = flt(self.recovery_amount) if loan_principal > allowed.get("principal"): frappe.throw("Requested principal cannot exceed {}".format( _fmt_money(allowed.get("principal")))) tentative_outstanding = reduce( lambda a, x: a + get_outstanding_principal(x), map(pick("name"), _existing_loans_by(self.customer)), loan_principal, ) if is_update: before = self.get_doc_before_save() tentative_outstanding -= before.loan_principal if tentative_outstanding > allowed.get("principal"): frappe.throw("Customer already has existing loans. " "Total principal would exceed allowed {}".format( _fmt_money(allowed.get("principal")))) if recovery_amount < tentative_outstanding / allowed.get("duration"): frappe.throw("Recovery Amount cannot be less than {}".format( _fmt_money(loan_principal / allowed.get("duration"))))