Example #1
0
 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')
Example #2
0
    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)
Example #4
0
 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)
Example #5
0
 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")
Example #7
0
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'))
                )
            )
Example #9
0
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)
Example #10
0
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)
Example #11
0
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)
Example #12
0
    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"))))