Cleanup and fixes of payment terms feature

This commit is contained in:
Nabin Hait 2017-11-21 19:58:16 +05:30
parent 7fa111de45
commit 0551f7bb00
14 changed files with 131 additions and 196 deletions

View File

@ -505,12 +505,10 @@ def get_outstanding_reference_documents(args):
company_currency = frappe.db.get_value("Company", args.get("company"), "default_currency")
# Get negative outstanding sales /purchase invoices
total_field = "base_grand_total" if party_account_currency == company_currency else "grand_total"
negative_outstanding_invoices = []
if (args.get("party_type") != "Student"):
if args.get("party_type") not in ["Student", "Employee"]:
negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"),
args.get("party"), args.get("party_account"), total_field)
args.get("party"), args.get("party_account"), party_account_currency, company_currency)
# Get positive outstanding sales /purchase invoices/ Fees
outstanding_invoices = get_outstanding_invoices(args.get("party_type"), args.get("party"),
@ -580,28 +578,34 @@ def get_orders_to_be_billed(posting_date, party_type, party, party_account_curre
return order_list
def get_negative_outstanding_invoices(party_type, party, party_account, total_field):
if party_type != "Employee":
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
return frappe.db.sql("""
select
"{voucher_type}" as voucher_type, name as voucher_no,
{total_field} as invoice_amount, outstanding_amount, posting_date,
due_date, conversion_rate as exchange_rate
from
`tab{voucher_type}`
where
{party_type} = %s and {party_account} = %s and docstatus = 1 and outstanding_amount < 0
order by
posting_date, name
""".format(**{
"total_field": total_field,
"voucher_type": voucher_type,
"party_type": scrub(party_type),
"party_account": "debit_to" if party_type == "Customer" else "credit_to"
}), (party, party_account), as_dict=True)
def get_negative_outstanding_invoices(party_type, party, party_account, party_account_currency, company_currency):
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
if party_account_currency == company_currency:
grand_total_field = "base_grand_total"
rounded_total_field = "base_rounded_total"
else:
return []
grand_total_field = "grand_total"
rounded_total_field = "rounded_total"
return frappe.db.sql("""
select
"{voucher_type}" as voucher_type, name as voucher_no,
if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
outstanding_amount, posting_date,
due_date, conversion_rate as exchange_rate
from
`tab{voucher_type}`
where
{party_type} = %s and {party_account} = %s and docstatus = 1 and outstanding_amount < 0
order by
posting_date, name
""".format(**{
"rounded_total_field": rounded_total_field,
"grand_total_field": grand_total_field,
"voucher_type": voucher_type,
"party_type": scrub(party_type),
"party_account": "debit_to" if party_type == "Customer" else "credit_to"
}), (party, party_account), as_dict=True)
@frappe.whitelist()
def get_party_details(company, party_type, party, date):
@ -721,7 +725,10 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
if party_amount:
grand_total = outstanding_amount = party_amount
elif dt in ("Sales Invoice", "Purchase Invoice"):
grand_total = doc.base_grand_total if party_account_currency == doc.company_currency else doc.grand_total
if party_account_currency == doc.company_currency:
grand_total = doc.base_rounded_total or doc.base_grand_total
else:
grand_total = doc.rounded_total or doc.grand_total
outstanding_amount = doc.outstanding_amount
elif dt in ("Expense Claim"):
grand_total = doc.total_sanctioned_amount
@ -730,8 +737,10 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
grand_total = doc.grand_total
outstanding_amount = doc.outstanding_amount
else:
total_field = "base_grand_total" if party_account_currency == doc.company_currency else "grand_total"
grand_total = flt(doc.get(total_field))
if party_account_currency == doc.company_currency:
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
else:
grand_total = flt(doc.get("rounded_total") or doc.grand_total)
outstanding_amount = grand_total - flt(doc.advance_paid)
# bank or cash

View File

@ -93,7 +93,7 @@
"label": "Due Date",
"length": 0,
"no_copy": 0,
"options": "payment_term.due_date",
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@ -129,7 +129,7 @@
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@ -179,7 +179,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-09-26 05:13:54.187475",
"modified": "2017-11-21 19:23:08.490659",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Schedule",

View File

@ -255,6 +255,7 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(pi.outstanding_amount, 1212.30)
pi.disable_rounded_total = 0
pi.get("payment_schedule")[0].payment_amount = 1512.0
pi.save()
self.assertEqual(pi.outstanding_amount, 1212.0)
@ -279,7 +280,7 @@ class TestPurchaseInvoice(unittest.TestCase):
jv.submit()
pi = frappe.copy_doc(test_records[0])
pi.disable_rounded_total = 1
pi.append("advances", {
"reference_type": "Journal Entry",
"reference_name": jv.name,
@ -290,9 +291,10 @@ class TestPurchaseInvoice(unittest.TestCase):
})
pi.insert()
pi.update(
{"payment_schedule": get_payment_terms("_Test Payment Term Template", pi.posting_date, pi.grand_total)}
)
pi.update({
"payment_schedule": get_payment_terms("_Test Payment Term Template",
pi.posting_date, pi.grand_total)
})
pi.save()
pi.submit()
@ -619,7 +621,7 @@ class TestPurchaseInvoice(unittest.TestCase):
"invoice_portion": 40.00
})
pi.append("payment_schedule", {
"due_date": add_days(nowdate(), 45),
"due_date": add_days(nowdate(), 25),
"payment_amount": 150,
"invoice_portion": 60.00
})
@ -634,7 +636,7 @@ class TestPurchaseInvoice(unittest.TestCase):
expected_gl_entries = sorted([
[pi.credit_to, 0.0, 100.0, add_days(nowdate(), 15)],
[pi.credit_to, 0.0, 150.0, add_days(nowdate(), 45)],
[pi.credit_to, 0.0, 150.0, add_days(nowdate(), 25)],
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, None]
])

View File

@ -64,7 +64,7 @@ class TestSalesInvoice(unittest.TestCase):
si.insert()
self.assertTrue(si.payment_schedule)
self.assertEqual(si.payment_schedule[0].due_date, si.due_date)
self.assertEqual(getdate(si.payment_schedule[0].due_date), getdate(si.due_date))
def test_sales_invoice_calculation_base_currency(self):
si = frappe.copy_doc(test_records[2])

View File

@ -320,7 +320,8 @@ def validate_due_date(posting_date, due_date, party_type, party):
msgprint(_("Note: Due / Reference Date exceeds allowed customer credit days by {0} day(s)")
.format(date_diff(due_date, default_due_date)))
else:
frappe.throw(_("Due / Reference Date cannot be after {0}").format(formatdate(default_due_date)))
frappe.throw(_("Due / Reference Date cannot be after {0}")
.format(formatdate(default_due_date)))
@frappe.whitelist()
def set_taxes(party, party_type, posting_date, company, customer_group=None, supplier_type=None,

View File

@ -26,9 +26,12 @@ class AccountsController(TransactionBase):
return self.__company_currency
def onload(self):
self.get("__onload").make_payment_via_journal_entry = frappe.db.get_single_value('Accounts Settings', 'make_payment_via_journal_entry')
self.get("__onload").make_payment_via_journal_entry \
= frappe.db.get_single_value('Accounts Settings', 'make_payment_via_journal_entry')
if self.is_new():
relevant_docs = ("Quotation", "Purchase Order", "Sales Order", "Purchase Invoice", "Purchase Order")
relevant_docs = ("Quotation", "Purchase Order", "Sales Order",
"Purchase Invoice", "Sales Invoice")
if self.doctype in relevant_docs:
self.set_payment_schedule()
@ -59,21 +62,18 @@ class AccountsController(TransactionBase):
self.validate_paid_amount()
def validate_invoice_documents_schedule(self):
if self.get("payment_schedule"):
self.set_due_date()
self.validate_payment_schedule()
else:
self.set_payment_schedule()
self.set_due_date()
self.validate_payment_schedule_dates()
self.set_due_date()
self.validate_invoice_portion()
self.set_payment_schedule()
self.validate_payment_schedule_amount()
self.validate_due_date()
self.validate_advance_entries()
def validate_non_invoice_documents_schedule(self):
if self.get("payment_schedule"):
self.validate_invoice_portion()
self.validate_payment_schedule_amount()
else:
self.set_payment_schedule()
self.validate_invoice_portion()
self.set_payment_schedule()
self.validate_payment_schedule_amount()
def validate_all_documents_schedule(self):
if self.doctype in ("Sales Invoice", "Purchase Invoice") and not self.is_return:
@ -628,22 +628,24 @@ class AccountsController(TransactionBase):
posting_date = self.get("posting_date") or self.get("transaction_date")
date = self.get("due_date")
due_date = date or posting_date
grand_total = self.get("rounded_total") or self.grand_total
if self.get("payment_terms_template") and not self.get("payment_schedule"):
data = get_payment_terms(self.payment_terms_template, posting_date, self.grand_total)
for item in data:
self.append("payment_schedule", item)
elif not self.get("payment_schedule"):
data = dict(due_date=due_date, invoice_portion=100, payment_amount=self.grand_total)
self.append("payment_schedule", data)
if not self.get("payment_schedule"):
if self.get("payment_terms_template"):
data = get_payment_terms(self.payment_terms_template, posting_date, grand_total)
for item in data:
self.append("payment_schedule", item)
else:
data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total)
self.append("payment_schedule", data)
else:
for d in self.get("payment_schedule"):
d.payment_amount = grand_total * flt(d.invoice_portion) / 100
def set_due_date(self):
self.due_date = max([d.due_date for d in self.get("payment_schedule")])
def validate_payment_schedule(self):
self.validate_payment_schedule_dates()
self.validate_invoice_portion()
self.validate_payment_schedule_amount()
due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
if due_dates:
self.due_date = max(due_dates)
def validate_payment_schedule_dates(self):
dates = []
@ -661,24 +663,27 @@ class AccountsController(TransactionBase):
if li:
duplicates = '<br>' + '<br>'.join(li)
frappe.throw(_("Rows with duplicate due dates in other rows were found: {list}").format(list=duplicates))
frappe.throw(_("Rows with duplicate due dates in other rows were found: {list}")
.format(list=duplicates))
def validate_payment_schedule_amount(self):
total = 0
for d in self.get("payment_schedule"):
total += flt(d.payment_amount)
if self.get("payment_schedule"):
total = 0
for d in self.get("payment_schedule"):
total += flt(d.payment_amount)
if total != self.grand_total:
frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand Total"))
grand_total = self.get("rounded_total") or self.grand_total
if total != grand_total:
frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
def validate_invoice_portion(self):
total_portion = 0
for term in self.payment_schedule:
total_portion += flt(term.get('invoice_portion', 0))
if self.get("payment_schedule"):
total_portion = 0
for term in self.payment_schedule:
total_portion += flt(term.get('invoice_portion', 0))
if flt(total_portion, 2) != 100.00:
frappe.msgprint(_('Combined invoice portion must equal 100%'), raise_exception=1, indicator='red')
if flt(total_portion, 2) != 100.00:
frappe.throw(_('Combined invoice portion must equal 100%'), indicator='red')
def is_rounded_total_disabled(self):
if self.meta.get_field("disable_rounded_total"):

View File

@ -460,12 +460,8 @@ execute:frappe.delete_doc_if_exists("DocType", "Program Fee")
erpnext.patches.v9_0.set_pos_profile_name
erpnext.patches.v9_0.remove_non_existing_warehouse_from_stock_settings
execute:frappe.delete_doc_if_exists("DocType", "Program Fee")
erpnext.patches.v8_10.add_due_date_to_gle
erpnext.patches.v8_10.update_gl_due_date_for_pi_and_si
erpnext.patches.v8_10.add_payment_terms_field_to_supplier
erpnext.patches.v8_10.change_default_customer_credit_days
erpnext.patches.v8_10.add_payment_terms_field_to_supplier_type
erpnext.patches.v8_10.change_default_supplier_type_credit_days
erpnext.patches.v9_0.update_employee_loan_details
erpnext.patches.v9_2.delete_healthcare_domain_default_items
erpnext.patches.v9_1.create_issue_opportunity_type

View File

@ -1,7 +0,0 @@
from __future__ import unicode_literals
import frappe
def execute():
if not frappe.db.has_column("GL Entry", "due_date"):
frappe.db.sql("ALTER TABLE `tabGL Entry` ADD COLUMN `due_date` DATE NULL")

View File

@ -1,9 +0,0 @@
from __future__ import unicode_literals
import frappe
def execute():
if not frappe.db.has_column("Customer", "payment_terms"):
frappe.db.sql("ALTER TABLE `tabCustomer` ADD COLUMN `payment_terms` VARCHAR(140) NULL")
if not frappe.db.has_column("Supplier", "payment_terms"):
frappe.db.sql("ALTER TABLE `tabSupplier` ADD COLUMN `payment_terms` VARCHAR(140) NULL")

View File

@ -1,7 +0,0 @@
from __future__ import unicode_literals
import frappe
def execute():
if not frappe.db.has_column("Supplier Type", "payment_terms"):
frappe.db.sql("ALTER TABLE `tabSupplier Type` ADD COLUMN `payment_terms` VARCHAR(140) NULL")

View File

@ -3,67 +3,49 @@ import frappe
def execute():
frappe.reload_doc("selling", "doctype", "customer")
frappe.reload_doc("buying", "doctype", "supplier")
frappe.reload_doc("setup", "doctype", "supplier_type")
frappe.reload_doc("accounts", "doctype", "payment_term")
frappe.reload_doc("accounts", "doctype", "payment_terms_template_detail")
frappe.reload_doc("accounts", "doctype", "payment_terms_template")
payment_terms = []
customers = []
suppliers = []
credit_days = frappe.db.sql(
"SELECT DISTINCT `credit_days`, `credit_days_based_on`, `customer_name` from "
"`tabCustomer` where credit_days_based_on='Fixed Days' or "
"credit_days_based_on='Last Day of the Next Month'")
records = []
for doctype in ("Customer", "Supplier", "Supplier Type"):
credit_days = frappe.db.sql("""
SELECT DISTINCT `credit_days`, `credit_days_based_on`, `name`
from `tab{0}`
where
(credit_days_based_on='Fixed Days' and credit_days is not null)
or credit_days_based_on='Last Day of the Next Month'
""".format(doctype))
credit_records = ((record[0], record[1], record[2]) for record in credit_days)
for days, based_on, customer_name in credit_records:
payment_term = make_payment_term(days, based_on)
template = make_template(payment_term)
payment_terms.append('WHEN `customer_name`="%s" THEN "%s"' % (customer_name, template.template_name))
customers.append(customer_name)
credit_records = ((record[0], record[1], record[2]) for record in credit_days)
for days, based_on, party_name in credit_records:
if based_on == "Fixed Days":
pyt_template_name = 'Default Payment Term - N{0}'.format(days)
else:
pyt_template_name = 'Default Payment Term - EO2M'
begin_query_str = "UPDATE `tabCustomer` SET `payment_terms` = CASE "
value_query_str = " ".join(payment_terms)
cond_query_str = " ELSE `payment_terms` END WHERE "
if not frappe.db.exists("Payment Terms Template", pyt_template_name):
payment_term = make_payment_term(days, based_on)
template = make_template(payment_term)
else:
template = frappe.get_doc("Payment Terms Template", pyt_template_name)
if customers:
frappe.db.sql(
begin_query_str + value_query_str + cond_query_str + '`customer_name` IN %s',
(customers,)
)
payment_terms.append('WHEN `name`="%s" THEN "%s"' % (party_name, template.template_name))
records.append(party_name)
# reset
payment_terms = []
credit_days = frappe.db.sql(
"SELECT DISTINCT `credit_days`, `credit_days_based_on`, `supplier_name` from "
"`tabSupplier` where credit_days_based_on='Fixed Days' or "
"credit_days_based_on='Last Day of the Next Month'")
begin_query_str = "UPDATE `tab{0}` SET `payment_terms` = CASE ".format(doctype)
value_query_str = " ".join(payment_terms)
cond_query_str = " ELSE `payment_terms` END WHERE "
credit_records = ((record[0], record[1], record[2]) for record in credit_days)
for days, based_on, supplier_name in credit_records:
if based_on == "Fixed Days":
pyt_template_name = 'Default Payment Term - N{0}'.format(days)
else:
pyt_template_name = 'Default Payment Term - EO2M'
if not frappe.db.exists("Payment Term Template", pyt_template_name):
payment_term = make_payment_term(days, based_on)
template = make_template(payment_term)
else:
template = frappe.get_doc("Payment Term Template", pyt_template_name)
payment_terms.append('WHEN `supplier_name`="%s" THEN "%s"' % (supplier_name, template.template_name))
suppliers.append(supplier_name)
begin_query_str = "UPDATE `tabSupplier` SET `payment_terms` = CASE "
value_query_str = " ".join(payment_terms)
cond_query_str = " ELSE `payment_terms` END WHERE "
if suppliers:
frappe.db.sql(
begin_query_str + value_query_str + cond_query_str + '`supplier_name` IN %s',
(suppliers,)
)
if records:
frappe.db.sql(
begin_query_str + value_query_str + cond_query_str + '`name` IN %s',
(records,)
)
def make_template(payment_term):

View File

@ -1,39 +0,0 @@
import frappe
from erpnext.patches.v8_10.change_default_customer_credit_days import make_payment_term, make_template
def execute():
payment_terms = []
supplier_types = []
credit_days = frappe.db.sql(
"SELECT DISTINCT `credit_days`, `credit_days_based_on`, `supplier_type` from "
"`tabSupplier Type` where credit_days_based_on='Fixed Days' or "
"credit_days_based_on='Last Day of the Next Month'")
records = ((record[0], record[1], record[2]) for record in credit_days)
for days, based_on, supplier_type in records:
if based_on == "Fixed Days":
pyt_term_name = 'N{0}'.format(days)
else:
pyt_term_name = 'EO2M'
if not frappe.db.exists("Payment Term", pyt_term_name):
payment_term = make_payment_term(days, based_on)
make_template(payment_term)
else:
payment_term = frappe.get_doc("Payment Term", pyt_term_name)
payment_terms.append('WHEN `supplier_type`="%s" THEN "%s"' % (supplier_type, payment_term.payment_term_name))
supplier_types.append(supplier_type)
begin_query_str = "UPDATE `tabSupplier Type` SET `payment_terms` = CASE "
value_query_str = " ".join(payment_terms)
cond_query_str = " ELSE `payment_terms` END WHERE "
if supplier_types:
frappe.db.sql(
begin_query_str + value_query_str + cond_query_str + '`supplier_type` IN %s',
(supplier_types,)
)

View File

@ -6,6 +6,8 @@ import frappe
def execute():
frappe.reload_doc("accounts", "doctype", "gl_entry")
kwargs = get_query_kwargs()
for kwarg in kwargs:

View File

@ -1153,7 +1153,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
args: {
terms_template: this.frm.doc.payment_terms_template,
posting_date: this.frm.doc.posting_date || this.frm.doc.transaction_date,
grand_total: this.frm.doc.grand_total
grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total
},
callback: function(r) {
if(r.message && !r.exc) {
@ -1172,7 +1172,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
args: {
term: row.payment_term,
posting_date: this.frm.doc.posting_date || this.frm.doc.transaction_date,
grand_total: this.frm.doc.grand_total
grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total
},
callback: function(r) {
if(r.message && !r.exc) {