예제 #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')
    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 _make_row(row):
    loan, sanctioned = row[1], row[3]
    undisbursed = get_undisbursed_principal(loan)
    outstanding = get_outstanding_principal(loan)
    recovered = get_recovered_principal(loan)
    return row + (
        sanctioned - undisbursed,
        recovered,
        outstanding,
    )
 def get_billed_amount(self):
     outstanding_principal = get_outstanding_principal(
         self.loan, self.end_date)
     calculation_slab, rate_of_interest = frappe.get_value(
         'Microfinance Loan',
         self.loan,
         ['calculation_slab', 'rate_of_interest'],
     )
     return calc_interest(outstanding_principal, rate_of_interest,
                          calculation_slab)
 def onload(self):
     self.set_onload(
         'address_text', _make_address_text(self.customer)
     )
     if self.docstatus == 1:
         self.set_onload(
             'chart_data', get_chart_data(self.name)
         )
         self.set_onload(
             'outstanding_principal', get_outstanding_principal(self.name)
         )
    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'))
                )
            )
예제 #7
0
 def get_billed_amount(self):
     (
         loan_type,
         calculation_slab,
         rate_of_interest,
         loan_principal,
     ) = frappe.get_value(
         "Microfinance Loan",
         self.loan,
         ["loan_type", "calculation_slab", "rate_of_interest", "loan_principal"],
     )
     if loan_type == "EMI":
         return calc_interest(loan_principal, rate_of_interest)
     outstanding_principal = get_outstanding_principal(self.loan, self.end_date)
     return calc_interest(outstanding_principal, rate_of_interest, calculation_slab)
예제 #8
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"))))
예제 #9
0
def get_current_interest(loan, posting_date):
    calculation_slab, rate_of_interest, recovery_status = frappe.get_value(
        "Microfinance Loan",
        loan,
        ["calculation_slab", "rate_of_interest", "recovery_status"],
    )
    if recovery_status == "NPA":
        return 0
    prev_billed_amount = compose(
        partial(frappe.get_value,
                "Microfinance Loan Interest",
                fieldname="billed_amount"),
        partial(make_name, loan),
        getdate,
        partial(add_months, months=-1),
    )(posting_date)
    if prev_billed_amount:
        return prev_billed_amount
    outstanding = get_outstanding_principal(loan, posting_date)
    return calc_interest(outstanding, rate_of_interest, calculation_slab)
예제 #10
0
def get_current_interest(loan, posting_date):
    calculation_slab, rate_of_interest, recovery_status = frappe.get_value(
        'Microfinance Loan',
        loan,
        ['calculation_slab', 'rate_of_interest', 'recovery_status'],
    )
    if recovery_status == 'NPA':
        return 0
    prev_billed_amount = compose(
        partial(frappe.get_value,
                'Microfinance Loan Interest',
                fieldname='billed_amount'),
        partial(make_name, loan),
        getdate,
        partial(add_months, months=-1),
    )(posting_date)
    if prev_billed_amount:
        return prev_billed_amount
    outstanding = get_outstanding_principal(loan, posting_date)
    return calc_interest(outstanding, rate_of_interest, calculation_slab)
예제 #11
0
def allocate_interests(loan, posting_date, amount_to_allocate=0, principal=0):
    periods = []
    to_allocate = amount_to_allocate

    existing_unpaid_interests = get_unpaid(loan)
    for period in map(_interest_to_period, existing_unpaid_interests):
        p = _allocate(period, to_allocate)
        periods.append(p)
        to_allocate -= p.get('allocated_amount')

    calculation_slab, loan_date, rate_of_interest = frappe.get_value(
        'Microfinance Loan',
        loan,
        ['calculation_slab', 'posting_date', 'rate_of_interest'],
    )
    outstanding_amount = get_outstanding_principal(loan, posting_date)
    interest_amount = calc_interest(outstanding_amount, rate_of_interest,
                                    calculation_slab)
    adv_interest_amount = calc_interest(outstanding_amount - principal,
                                        rate_of_interest, calculation_slab)
    last = get_last(loan)
    effective_date = frappe.get_value('Microfinance Loan Settings', None,
                                      'effective_date')
    init_date = add_days(last.get('end_date'), 1) if last \
        else max(loan_date, getdate(effective_date))
    gen_per = _generate_periods(init_date)
    while to_allocate > 0:
        per_ = gen_per.next()
        # for advance payments consider outstanding_amount to be minus
        # the current principal to be paid
        amount = adv_interest_amount \
            if _is_advance(per_, posting_date) else interest_amount
        per_.update({
            'billed_amount': amount,
            'outstanding_amount': amount,
        })
        per = _allocate(per_, to_allocate)
        periods.append(per)
        to_allocate -= per.get('allocated_amount')
    return filter(lambda x: x.get('allocated_amount') > 0, periods)
예제 #12
0
def allocate_interests(loan, posting_date, amount_to_allocate=0, principal=0):
    periods = []
    to_allocate = amount_to_allocate

    existing_unpaid_interests = get_unpaid(loan)
    for period in map(_interest_to_period, existing_unpaid_interests):
        p = _allocate(period, to_allocate)
        periods.append(p)
        to_allocate -= p.get("allocated_amount")

    calculation_slab, loan_date, rate_of_interest = frappe.get_value(
        "Microfinance Loan",
        loan,
        ["calculation_slab", "posting_date", "rate_of_interest"],
    )
    outstanding_amount = get_outstanding_principal(loan, posting_date)
    interest_amount = calc_interest(outstanding_amount, rate_of_interest,
                                    calculation_slab)
    adv_interest_amount = calc_interest(outstanding_amount - principal,
                                        rate_of_interest, calculation_slab)
    last = get_last(loan)
    effective_date = frappe.get_value("Microfinance Loan Settings", None,
                                      "effective_date")
    init_date = (add_days(last.get("end_date"), 1) if last else max(
        loan_date, getdate(effective_date)))
    gen_per = _generate_periods(init_date)
    while to_allocate > 0:
        per_ = next(gen_per)
        # for advance payments consider outstanding_amount to be minus
        # the current principal to be paid
        amount = (adv_interest_amount
                  if _is_advance(per_, posting_date) else interest_amount)
        per_.update({"billed_amount": amount, "outstanding_amount": amount})
        per = _allocate(per_, to_allocate)
        periods.append(per)
        to_allocate -= per.get("allocated_amount")
    return filter(lambda x: x.get("allocated_amount") > 0, periods)
def execute(filters={}):
    columns = [
        _("Posting Date") + ":Date:90",
        _("Account") + ":Link/Account:150",
        _("Credit") + ":Currency/currency:90",
        _("Debit") + ":Currency/currency:90",
        _("Amount") + ":Currency/currency:90",
        _("Cummulative") + ":Currency/currency:90",
        _("Remarks") + "::240",
    ]

    company, loan_account = frappe.get_value("Microfinance Loan",
                                             filters.get("loan"),
                                             ["company", "loan_account"])
    accounts_to_exclude = [
        loan_account,
        "Temporary Opening - {}".format(
            frappe.db.get_value("Company", company, "abbr")),
    ]
    conds = [
        "against_voucher_type = 'Microfinance Loan'",
        "against_voucher = '{}'".format(filters.get("loan")),
        "account NOT IN ({})".format(_stringify_accounts(accounts_to_exclude)),
    ]
    opening_entries = frappe.db.sql(
        """
            SELECT
                sum(credit) AS credit,
                sum(debit) AS debit,
                sum(credit - debit) as amount
            FROM `tabGL Entry`
            WHERE {conds} AND posting_date <'{from_date}'
        """.format(conds=join(" AND ")(conds),
                   from_date=filters.get("from_date")),
        as_dict=True,
    )[0]
    results = frappe.db.sql("""
            SELECT
                posting_date,
                account,
                sum(credit) as credit,
                sum(debit) as debit,
                sum(credit - debit) as amount,
                remarks
            FROM `tabGL Entry`
            WHERE {conds}
            AND posting_date BETWEEN '{from_date}' AND '{to_date}'
            GROUP BY posting_date, account, voucher_no, remarks
            ORDER BY posting_date ASC, name ASC
        """.format(
        conds=join(" AND ")(conds),
        from_date=filters.get("from_date"),
        to_date=filters.get("to_date"),
    ))

    opening_credit = opening_entries.get("credit") or 0
    opening_debit = opening_entries.get("debit") or 0
    opening_amount = opening_entries.get("amount") or 0
    total_credit = _col_sum(2)(results)
    total_debit = _col_sum(3)(results)
    total_amount = _col_sum(4)(results)
    opening = (
        None,
        _("Opening"),
        opening_credit,
        opening_debit,
        opening_amount,
        opening_amount,
        None,
    )
    total = (None, _("Total"), total_credit, total_debit, total_amount, None,
             None)
    closing = (
        None,
        _("Closing"),
        opening_credit + total_credit,
        opening_debit + total_debit,
        opening_amount + total_amount,
        get_outstanding_principal(filters.get("loan"), filters.get("to_date")),
        None,
    )
    data = reduce(_accum_reducer, results, [opening]) + [total, closing]

    return columns, data
예제 #14
0
def _result_to_data(row):
    return row[:-1] + (
        get_outstanding_principal(row[1]),
        get_current_interest(row[1], today()),
    )
 def on_save(self):
     self.current_outstanding = get_outstanding_principal(
         self.loan, self.posting_date)
     self.next_outstanding = self.current_outstanding - self.amount
예제 #16
0
 def onload(self):
     if self.docstatus == 1:
         self.set_onload("chart_data", get_chart_data(self.name))
         self.set_onload("outstanding_principal",
                         get_outstanding_principal(self.name))