mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-25 07:54:46 +00:00
refactor: dunning
This commit is contained in:
@@ -56,19 +56,6 @@ frappe.ui.form.on("Dunning", {
|
|||||||
frm.page.set_inner_btn_group_as_primary(__("Create"));
|
frm.page.set_inner_btn_group_as_primary(__("Create"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.docstatus > 0) {
|
|
||||||
frm.add_custom_button(__("Ledger"), function () {
|
|
||||||
frappe.route_options = {
|
|
||||||
"voucher_no": frm.doc.name,
|
|
||||||
"from_date": frm.doc.posting_date,
|
|
||||||
"to_date": frm.doc.posting_date,
|
|
||||||
"company": frm.doc.company,
|
|
||||||
"show_cancelled_entries": frm.doc.docstatus === 2
|
|
||||||
};
|
|
||||||
frappe.set_route("query-report", "General Ledger");
|
|
||||||
}, __("View"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frm.doc.docstatus === 0) {
|
if (frm.doc.docstatus === 0) {
|
||||||
frm.add_custom_button(__("Fetch Overdue Payments"), function () {
|
frm.add_custom_button(__("Fetch Overdue Payments"), function () {
|
||||||
erpnext.utils.map_current_doc({
|
erpnext.utils.map_current_doc({
|
||||||
@@ -248,22 +235,29 @@ frappe.ui.form.on("Dunning", {
|
|||||||
calculate_interest: function (frm) {
|
calculate_interest: function (frm) {
|
||||||
frm.doc.overdue_payments.forEach((row) => {
|
frm.doc.overdue_payments.forEach((row) => {
|
||||||
const interest_per_day = frm.doc.rate_of_interest / 100 / 365;
|
const interest_per_day = frm.doc.rate_of_interest / 100 / 365;
|
||||||
const interest = flt((interest_per_day * row.outstanding * cint(row.overdue_days)) / 365 || 0, precision("interest"));
|
const interest = flt((interest_per_day * row.overdue_days * row.outstanding), precision("interest"));
|
||||||
frappe.model.set_value(row.doctype, row.name, "interest", interest);
|
frappe.model.set_value(row.doctype, row.name, "interest", interest);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
calculate_totals: function (frm) {
|
calculate_totals: function (frm) {
|
||||||
|
debugger;
|
||||||
const total_interest = frm.doc.overdue_payments
|
const total_interest = frm.doc.overdue_payments
|
||||||
.reduce((prev, cur) => prev + cur.interest, 0);
|
.reduce((prev, cur) => prev + cur.interest, 0);
|
||||||
const total_outstanding = frm.doc.overdue_payments
|
const total_outstanding = frm.doc.overdue_payments
|
||||||
.reduce((prev, cur) => prev + cur.outstanding, 0);
|
.reduce((prev, cur) => prev + cur.outstanding, 0);
|
||||||
const dunning_amount = flt(total_interest + frm.doc.dunning_fee, precision("dunning_amount"));
|
const dunning_amount = total_interest + frm.doc.dunning_fee;
|
||||||
const grand_total = flt(total_outstanding + dunning_amount, precision("grand_total"));
|
const base_dunning_amount = dunning_amount * frm.doc.conversion_rate;
|
||||||
|
const grand_total = total_outstanding + dunning_amount;
|
||||||
|
|
||||||
frm.set_value("total_outstanding", total_outstanding);
|
function setWithPrecison(field, value) {
|
||||||
frm.set_value("total_interest", total_interest);
|
frm.set_value(field, flt(value, precision(field)));
|
||||||
frm.set_value("dunning_amount", dunning_amount);
|
}
|
||||||
frm.set_value("grand_total", grand_total);
|
|
||||||
|
setWithPrecison("total_outstanding", total_outstanding);
|
||||||
|
setWithPrecison("total_interest", total_interest);
|
||||||
|
setWithPrecison("dunning_amount", dunning_amount);
|
||||||
|
setWithPrecison("base_dunning_amount", base_dunning_amount);
|
||||||
|
setWithPrecison("grand_total", grand_total);
|
||||||
},
|
},
|
||||||
make_payment_entry: function (frm) {
|
make_payment_entry: function (frm) {
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
@@ -283,6 +277,7 @@ frappe.ui.form.on("Dunning", {
|
|||||||
|
|
||||||
frappe.ui.form.on("Overdue Payment", {
|
frappe.ui.form.on("Overdue Payment", {
|
||||||
interest: function (frm, cdt, cdn) {
|
interest: function (frm, cdt, cdn) {
|
||||||
|
debugger;
|
||||||
frm.trigger("calculate_totals");
|
frm.trigger("calculate_totals");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -387,7 +387,7 @@
|
|||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-09-23 13:47:23.030561",
|
"modified": "2021-09-30 17:23:23.030561",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Dunning",
|
"name": "Dunning",
|
||||||
|
|||||||
@@ -1,26 +1,45 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
"""
|
||||||
|
# Accounting
|
||||||
|
|
||||||
|
1. Payment of outstanding invoices with dunning amount
|
||||||
|
|
||||||
|
- Debit full amount to bank
|
||||||
|
- Credit invoiced amount to receivables
|
||||||
|
- Credit dunning amount to interest and similar revenue
|
||||||
|
|
||||||
|
-> Resolves dunning automatically
|
||||||
|
"""
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
|
from frappe import _
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
|
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
from erpnext.controllers.accounts_controller import AccountsController
|
||||||
|
|
||||||
|
|
||||||
class Dunning(AccountsController):
|
class Dunning(AccountsController):
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.validate_same_currency()
|
||||||
self.validate_overdue_payments()
|
self.validate_overdue_payments()
|
||||||
self.validate_totals()
|
self.validate_totals()
|
||||||
|
self.set_dunning_level()
|
||||||
|
|
||||||
if not self.income_account:
|
def validate_same_currency(self):
|
||||||
self.income_account = frappe.db.get_value("Company", self.company, "default_income_account")
|
"""
|
||||||
|
Throw an error if invoice currency differs from dunning currency.
|
||||||
|
"""
|
||||||
|
for row in self.overdue_payments:
|
||||||
|
invoice_currency = frappe.get_value("Sales Invoice", row.sales_invoice, "currency")
|
||||||
|
if invoice_currency != self.currency:
|
||||||
|
frappe.throw(_("The currency of invoice {} ({}) is different from the currency of this dunning ({}).").format(row.sales_invoice, invoice_currency, self.currency))
|
||||||
|
|
||||||
def validate_overdue_payments(self):
|
def validate_overdue_payments(self):
|
||||||
daily_interest = self.rate_of_interest / 100 / 365
|
daily_interest = self.rate_of_interest / 100 / 365
|
||||||
@@ -33,51 +52,25 @@ class Dunning(AccountsController):
|
|||||||
self.total_outstanding = sum(row.outstanding for row in self.overdue_payments)
|
self.total_outstanding = sum(row.outstanding for row in self.overdue_payments)
|
||||||
self.total_interest = sum(row.interest for row in self.overdue_payments)
|
self.total_interest = sum(row.interest for row in self.overdue_payments)
|
||||||
self.dunning_amount = self.total_interest + self.dunning_fee
|
self.dunning_amount = self.total_interest + self.dunning_fee
|
||||||
|
self.base_dunning_amount = self.dunning_amount * self.conversion_rate
|
||||||
self.grand_total = self.total_outstanding + self.dunning_amount
|
self.grand_total = self.total_outstanding + self.dunning_amount
|
||||||
|
|
||||||
def on_submit(self):
|
def set_dunning_level(self):
|
||||||
self.make_gl_entries()
|
for row in self.overdue_payments:
|
||||||
|
past_dunnings = frappe.get_all("Overdue Payment",
|
||||||
def on_cancel(self):
|
filters={
|
||||||
if self.dunning_amount:
|
"payment_schedule": row.payment_schedule,
|
||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
|
"parent": ("!=", row.parent),
|
||||||
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
"docstatus": 1
|
||||||
|
}
|
||||||
def make_gl_entries(self):
|
)
|
||||||
if not self.dunning_amount:
|
row.dunning_level = len(past_dunnings) + 1
|
||||||
return
|
|
||||||
|
|
||||||
cost_center = self.cost_center or frappe.get_cached_value("Company", self.company, "cost_center")
|
|
||||||
|
|
||||||
make_gl_entries(
|
|
||||||
[
|
|
||||||
self.get_gl_dict({
|
|
||||||
"account": self.debit_to,
|
|
||||||
"party_type": "Customer",
|
|
||||||
"party": self.customer,
|
|
||||||
"due_date": self.due_date,
|
|
||||||
"against": self.income_account,
|
|
||||||
"debit": self.dunning_amount,
|
|
||||||
"debit_in_account_currency": self.dunning_amount,
|
|
||||||
"against_voucher": self.name,
|
|
||||||
"against_voucher_type": "Dunning",
|
|
||||||
"cost_center": cost_center
|
|
||||||
}),
|
|
||||||
self.get_gl_dict({
|
|
||||||
"account": self.income_account,
|
|
||||||
"against": self.customer,
|
|
||||||
"credit": self.dunning_amount,
|
|
||||||
"cost_center": cost_center,
|
|
||||||
"credit_in_account_currency": self.dunning_amount
|
|
||||||
})
|
|
||||||
],
|
|
||||||
cancel=(self.docstatus == 2),
|
|
||||||
update_outstanding="No",
|
|
||||||
merge_entries=False
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_dunning(doc, state):
|
def resolve_dunning(doc, state):
|
||||||
|
"""
|
||||||
|
Todo: refactor
|
||||||
|
"""
|
||||||
for reference in doc.references:
|
for reference in doc.references:
|
||||||
if reference.reference_doctype == "Sales Invoice" and reference.outstanding_amount <= 0:
|
if reference.reference_doctype == "Sales Invoice" and reference.outstanding_amount <= 0:
|
||||||
dunnings = frappe.get_list("Dunning", filters={
|
dunnings = frappe.get_list("Dunning", filters={
|
||||||
|
|||||||
@@ -1543,23 +1543,19 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
|||||||
pe.append('references', reference)
|
pe.append('references', reference)
|
||||||
else:
|
else:
|
||||||
if dt == "Dunning":
|
if dt == "Dunning":
|
||||||
pe.append("references", {
|
for overdue_payment in doc.overdue_payments:
|
||||||
'reference_doctype': 'Sales Invoice',
|
pe.append("references", {
|
||||||
'reference_name': doc.get('sales_invoice'),
|
"reference_doctype": "Sales Invoice",
|
||||||
"bill_no": doc.get("bill_no"),
|
"reference_name": overdue_payment.sales_invoice,
|
||||||
"due_date": doc.get("due_date"),
|
"payment_term": overdue_payment.payment_term,
|
||||||
'total_amount': doc.get('outstanding_amount'),
|
"due_date": overdue_payment.due_date,
|
||||||
'outstanding_amount': doc.get('outstanding_amount'),
|
"total_amount": overdue_payment.outstanding,
|
||||||
'allocated_amount': doc.get('outstanding_amount')
|
"outstanding_amount": overdue_payment.outstanding,
|
||||||
})
|
"allocated_amount": overdue_payment.outstanding
|
||||||
pe.append("references", {
|
})
|
||||||
'reference_doctype': dt,
|
|
||||||
'reference_name': dn,
|
pe.append("deductions", {
|
||||||
"bill_no": doc.get("bill_no"),
|
"amount": doc.dunning_amount
|
||||||
"due_date": doc.get("due_date"),
|
|
||||||
'total_amount': doc.get('dunning_amount'),
|
|
||||||
'outstanding_amount': doc.get('dunning_amount'),
|
|
||||||
'allocated_amount': doc.get('dunning_amount')
|
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
pe.append("references", {
|
pe.append("references", {
|
||||||
|
|||||||
Reference in New Issue
Block a user