diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 76e66d0906..e27eaabedc 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -74,6 +74,36 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "due_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Due Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -718,7 +748,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-08-03 12:40:09.611951", + "modified": "2017-08-10 18:06:44.904081", "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 7bb9a52ee6..9534f514c1 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -410,7 +410,8 @@ class PaymentEntry(AccountsController): gle = party_gl_dict.copy() gle.update({ "against_voucher_type": d.reference_doctype, - "against_voucher": d.reference_name + "against_voucher": d.reference_name, + "due_date": d.due_date }) allocated_amount_in_company_currency = flt(flt(d.allocated_amount) * flt(d.exchange_rate), @@ -742,18 +743,53 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= pe.allocate_payment_amount = 1 pe.letter_head = doc.get("letter_head") - pe.append("references", { - "reference_doctype": dt, - "reference_name": dn, - "due_date": doc.get("due_date"), - "total_amount": grand_total, - "outstanding_amount": outstanding_amount, - "allocated_amount": outstanding_amount - }) + if doc.get("payment_schedule"): + for d in doc.get("payment_schedule"): + invoice_amount = d.payment_amount * doc.conversion_rate \ + if party_account_currency == doc.company_currency else d.payment_amount + paid_amount = get_paid_amount(dt, dn, party_type, pe.party, party_account, d.due_date) + outstanding_amount = invoice_amount - paid_amount + pe.append("references", { + "reference_doctype": dt, + "reference_name": dn, + "due_date": d.due_date, + "total_amount": invoice_amount, + "outstanding_amount": outstanding_amount, + "allocated_amount": outstanding_amount + }) + else: + pe.append("references", { + "reference_doctype": dt, + "reference_name": dn, + "due_date": doc.get("due_date"), + "total_amount": grand_total, + "outstanding_amount": outstanding_amount, + "allocated_amount": outstanding_amount + }) pe.setup_party_account_field() pe.set_missing_values() if party_account and bank: pe.set_exchange_rate() pe.set_amounts() - return pe \ No newline at end of file + return pe + +def get_paid_amount(dt, dn, party_type, party, account, due_date): + if party_type=="Customer": + dr_or_cr = "credit_in_account_currency - debit_in_account_currency" + else: + dr_or_cr = "debit_in_account_currency - credit_in_account_currency" + + paid_amount = frappe.db.sql(""" + select ifnull(sum({dr_or_cr}), 0) as paid_amount + from `tabGL Entry` + where against_voucher_type = %s + and against_voucher = %s + and party_type = %s + and party = %s + and account = %s + and due_date = %s + and {dr_or_cr} > 0 + """.format(dr_or_cr=dr_or_cr), (dt, dn, party_type, party, account, due_date)) + + return paid_amount[0][0] if paid_amount else 0 \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_schedule/__init__.py b/erpnext/accounts/doctype/payment_schedule/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json new file mode 100644 index 0000000000..45a81b0ab4 --- /dev/null +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -0,0 +1,197 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "", + "beta": 0, + "creation": "2017-08-10 15:38:00.080575", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "payment_term", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Payment Term", + "length": 0, + "no_copy": 0, + "options": "Payment Term", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "description", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Description", + "length": 0, + "no_copy": 0, + "options": "payment_term.description", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "due_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Due Date", + "length": 0, + "no_copy": 0, + "options": "payment_term.due_date", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "invoice_portion", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Invoice Portion", + "length": 0, + "no_copy": 0, + "options": "payment_term.invoice_portion", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "payment_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Payment Amount", + "length": 0, + "no_copy": 0, + "options": "currency", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-08-10 18:09:42.122027", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Payment Schedule", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.py b/erpnext/accounts/doctype/payment_schedule/payment_schedule.py new file mode 100644 index 0000000000..75bacc6406 --- /dev/null +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class PaymentSchedule(Document): + pass diff --git a/erpnext/accounts/doctype/payment_term/__init__.py b/erpnext/accounts/doctype/payment_term/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/payment_term/payment_term.js b/erpnext/accounts/doctype/payment_term/payment_term.js new file mode 100644 index 0000000000..564a2a6d3b --- /dev/null +++ b/erpnext/accounts/doctype/payment_term/payment_term.js @@ -0,0 +1,8 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Payment Term', { + refresh: function(frm) { + + } +}); diff --git a/erpnext/accounts/doctype/payment_term/payment_term.json b/erpnext/accounts/doctype/payment_term/payment_term.json new file mode 100644 index 0000000000..702319ba74 --- /dev/null +++ b/erpnext/accounts/doctype/payment_term/payment_term.json @@ -0,0 +1,344 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:payment_term_name", + "beta": 0, + "creation": "2017-08-10 15:24:54.876365", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 1, + "collapsible": 0, + "columns": 0, + "fieldname": "payment_term_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payment Term Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 1, + "collapsible": 0, + "columns": 0, + "fieldname": "invoice_portion", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Invoice Portion", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 1, + "collapsible": 0, + "columns": 0, + "fieldname": "due_date_based_on", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Due Date Based On", + "length": 0, + "no_copy": 0, + "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 1, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)", + "fieldname": "credit_days", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Credit Days", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'", + "fieldname": "credit_months", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Credit Months", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 1, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Description", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-08-10 16:26:03.581501", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Payment Term", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_term/payment_term.py b/erpnext/accounts/doctype/payment_term/payment_term.py new file mode 100644 index 0000000000..93a7529a11 --- /dev/null +++ b/erpnext/accounts/doctype/payment_term/payment_term.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class PaymentTerm(Document): + pass diff --git a/erpnext/accounts/doctype/payment_term/test_payment_term.js b/erpnext/accounts/doctype/payment_term/test_payment_term.js new file mode 100644 index 0000000000..b26e42aa37 --- /dev/null +++ b/erpnext/accounts/doctype/payment_term/test_payment_term.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Payment Term", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Payment Term + () => frappe.tests.make('Payment Term', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/accounts/doctype/payment_term/test_payment_term.py b/erpnext/accounts/doctype/payment_term/test_payment_term.py new file mode 100644 index 0000000000..c4d5d37594 --- /dev/null +++ b/erpnext/accounts/doctype/payment_term/test_payment_term.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestPaymentTerm(unittest.TestCase): + pass diff --git a/erpnext/accounts/doctype/payment_terms_template/__init__.py b/erpnext/accounts/doctype/payment_terms_template/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js new file mode 100644 index 0000000000..558297fea7 --- /dev/null +++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js @@ -0,0 +1,12 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Payment Terms Template', { + setup: function(frm) { + frm.add_fetch("payment_term", "description", "description"); + frm.add_fetch("payment_term", "invoice_portion", "invoice_portion"); + frm.add_fetch("payment_term", "due_date_based_on", "due_date_based_on"); + frm.add_fetch("payment_term", "credit_days", "credit_days"); + frm.add_fetch("payment_term", "credit_months", "credit_months"); + } +}); diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.json b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.json new file mode 100644 index 0000000000..095965886d --- /dev/null +++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.json @@ -0,0 +1,164 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:template_name", + "beta": 0, + "creation": "2017-08-10 15:34:28.058054", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "template_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Template Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "terms", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payment Terms", + "length": 0, + "no_copy": 0, + "options": "Payment Terms Template Detail", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-08-10 15:46:33.877884", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Payment Terms Template", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py new file mode 100644 index 0000000000..3300a1122c --- /dev/null +++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class PaymentTermsTemplate(Document): + pass diff --git a/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.js b/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.js new file mode 100644 index 0000000000..494a0ed21f --- /dev/null +++ b/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Payment Terms Template", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Payment Terms Template + () => frappe.tests.make('Payment Terms Template', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.py b/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.py new file mode 100644 index 0000000000..33d256c58a --- /dev/null +++ b/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestPaymentTermsTemplate(unittest.TestCase): + pass diff --git a/erpnext/accounts/doctype/payment_terms_template_detail/__init__.py b/erpnext/accounts/doctype/payment_terms_template_detail/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json b/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json new file mode 100644 index 0000000000..88babd5020 --- /dev/null +++ b/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json @@ -0,0 +1,229 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "PTTD.#####", + "beta": 0, + "creation": "2017-08-10 15:34:09.409562", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "payment_term", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Payment Term", + "length": 0, + "no_copy": 0, + "options": "Payment Term", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "description", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Description", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "invoice_portion", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Invoice Portion", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "due_date_based_on", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Due Date Based On", + "length": 0, + "no_copy": 0, + "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)", + "fieldname": "credit_days", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Credit Days", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'", + "fieldname": "credit_months", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Credit Months", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-08-10 16:27:31.401380", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Payment Terms Template Detail", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.py b/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.py new file mode 100644 index 0000000000..1242cc1a08 --- /dev/null +++ b/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class PaymentTermsTemplateDetail(Document): + pass diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 7ab790118a..bf620ab27a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -357,7 +357,27 @@ class PurchaseInvoice(BuyingController): return gl_entries def make_supplier_gl_entry(self, gl_entries): - if self.grand_total: + if self.get("payment_schedule"): + for d in self.get("payment_schedule"): + payment_amount_in_company_currency = flt(d.payment_amount * self.conversion_rate, + d.precision("payment_amount")) + + gl_entries.append( + self.get_gl_dict({ + "account": self.credit_to, + "party_type": "Supplier", + "party": self.supplier, + "due_date": d.due_date, + "against": self.against_expense_account, + "credit": payment_amount_in_company_currency, + "credit_in_account_currency": payment_amount_in_company_currency \ + if self.party_account_currency==self.company_currency else d.payment_amount, + "against_voucher": self.return_against if cint(self.is_return) else self.name, + "against_voucher_type": self.doctype + }, self.party_account_currency) + ) + + elif self.grand_total: # Didnot use base_grand_total to book rounding loss gle grand_total_in_company_currency = flt(self.grand_total * self.conversion_rate, self.precision("grand_total")) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 260c05e3e4..0af17335d8 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -2685,6 +2685,101 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "collapsible_depends_on": "eval:(!doc.is_pos && !doc.is_return)", + "columns": 0, + "fieldname": "payment_schedule_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payment Schedule", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(!doc.is_pos && !doc.is_return)", + "fieldname": "payment_terms_template", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payment Terms Template", + "length": 0, + "no_copy": 0, + "options": "Payment Terms Template", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(!doc.is_pos && !doc.is_return)", + "fieldname": "payment_schedule", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payment Schedule", + "length": 0, + "no_copy": 0, + "options": "Payment Schedule", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -2939,6 +3034,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "is_pos", "fieldname": "base_change_amount", "fieldtype": "Currency", "hidden": 0, @@ -2999,6 +3095,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "is_pos", "fieldname": "change_amount", "fieldtype": "Currency", "hidden": 0, @@ -3030,6 +3127,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "is_pos", "fieldname": "account_for_change_amount", "fieldtype": "Link", "hidden": 0, @@ -4688,7 +4786,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2017-07-07 13:05:37.469682", + "modified": "2017-08-10 18:02:44.286951", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 9dfacbdb5e..4fbfc95d0f 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, erpnext import frappe.defaults -from frappe.utils import cint, flt +from frappe.utils import cint, flt, getdate from frappe import _, msgprint, throw from erpnext.accounts.party import get_party_account, get_due_date from erpnext.controllers.stock_controller import update_gl_entries_after @@ -99,6 +99,8 @@ class SalesInvoice(SellingController): self.set_billing_hours_and_amount() self.update_timesheet_billing_for_project() self.set_status() + self.set_payment_schedule() + self.validate_payment_schedule() def before_save(self): set_account_for_mode_of_payment(self) @@ -530,6 +532,31 @@ class SalesInvoice(SellingController): self.total_billing_amount = total_billing_amount + def set_payment_schedule(self): + if not self.get("payment_schedule"): + if self.due_date: + self.append("payment_schedule", { + "due_date": self.due_date, + "invoice_portion": 100, + "payment_amount": self.grand_total + }) + else: + self.due_date = max([d.due_date for d in self.get("payment_schedule")]) + + def validate_payment_schedule(self): + if self.due_date and getdate(self.due_date) < getdate(self.posting_date): + frappe.throw(_("Due Date cannot be before posting date")) + + total = 0 + for d in self.get("payment_schedule"): + if getdate(d.due_date) < getdate(self.posting_date): + frappe.throw(_("Row {0}: Due Date cannot be before posting date").format(d.idx)) + + total += flt(d.payment_amount) + + if total != self.grand_total: + frappe.throw(_("Total Payment Amount in Payment Schdule must be equal to Grand Total")) + def get_warehouse(self): user_pos_profile = frappe.db.sql("""select name, warehouse from `tabPOS Profile` where ifnull(user,'') = %s and company = %s""", (frappe.session['user'], self.company)) @@ -619,7 +646,27 @@ class SalesInvoice(SellingController): return gl_entries def make_customer_gl_entry(self, gl_entries): - if self.grand_total: + if self.get("payment_schedule"): + for d in self.get("payment_schedule"): + payment_amount_in_company_currency = flt(d.payment_amount * self.conversion_rate, + d.precision("payment_amount")) + + gl_entries.append( + self.get_gl_dict({ + "account": self.debit_to, + "party_type": "Customer", + "party": self.customer, + "due_date": d.due_date, + "against": self.against_income_account, + "debit": payment_amount_in_company_currency, + "debit_in_account_currency": payment_amount_in_company_currency \ + if self.party_account_currency==self.company_currency else d.payment_amount, + "against_voucher": self.return_against if cint(self.is_return) else self.name, + "against_voucher_type": self.doctype + }, self.party_account_currency) + ) + + elif self.grand_total: # Didnot use base_grand_total to book rounding loss gle grand_total_in_company_currency = flt(self.grand_total * self.conversion_rate, self.precision("grand_total")) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.js index 35b255875e..f7a4488cd1 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.js @@ -40,4 +40,3 @@ QUnit.test("test sales Invoice", function(assert) { () => done() ]); }); - diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 806b489da4..bb4cab4817 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe import unittest, copy -from frappe.utils import nowdate, add_days, flt +from frappe.utils import nowdate, add_days, flt, getdate from frappe.model.dynamic_links import get_dynamic_link_map from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice @@ -1271,6 +1271,38 @@ class TestSalesInvoice(unittest.TestCase): }) si.insert() return si + + def test_gl_entry_based_on_payment_schedule(self): + si = create_sales_invoice(do_not_save=True) + si.append("payment_schedule", { + "due_date": add_days(nowdate(), 15), + "payment_amount": 20 + }) + si.append("payment_schedule", { + "due_date": add_days(nowdate(), 45), + "payment_amount": 80 + }) + + si.save() + si.submit() + + gl_entries = frappe.db.sql("""select account, debit, credit, due_date + from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s + order by account asc, debit asc""", si.name, as_dict=1) + self.assertTrue(gl_entries) + + expected_gl_entries = sorted([ + [si.debit_to, 20.0, 0.0, add_days(nowdate(), 15)], + [si.debit_to, 80.0, 0.0, add_days(nowdate(), 45)], + ["Sales - _TC", 0.0, 100.0, None] + ]) + + for i, gle in enumerate(sorted(gl_entries, key=lambda gle: gle.account)): + self.assertEquals(expected_gl_entries[i][0], gle.account) + self.assertEquals(expected_gl_entries[i][1], gle.debit) + self.assertEquals(expected_gl_entries[i][2], gle.credit) + self.assertEquals(getdate(expected_gl_entries[i][3]), getdate(gle.due_date)) + def create_sales_invoice(**args): si = frappe.new_doc("Sales Invoice") diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index ceae61cd86..45d2ef2fc6 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe, erpnext -from frappe.utils import flt, cstr, cint +from frappe.utils import flt, cstr, cint, getdate from frappe import _ from frappe.model.meta import get_field_precision from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget @@ -75,7 +75,8 @@ def check_if_in_list(gle, gl_map): and cstr(e.get('against_voucher'))==cstr(gle.get('against_voucher')) \ and cstr(e.get('against_voucher_type')) == cstr(gle.get('against_voucher_type')) \ and cstr(e.get('cost_center')) == cstr(gle.get('cost_center')) \ - and cstr(e.get('project')) == cstr(gle.get('project')): + and cstr(e.get('project')) == cstr(gle.get('project')) \ + and getdate(e.get('due_date')) == getdate(gle.get('due_date')): return e def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 9906893254..b72a66972f 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -113,7 +113,7 @@ class ReceivablePayableReport(object): row += [self.get_party_name(gle.party_type, gle.party)] # get due date - due_date = voucher_details.get(gle.voucher_no, {}).get("due_date", "") + due_date = gle.due_date or voucher_details.get(gle.voucher_no, {}).get("due_date", "") row += [gle.voucher_type, gle.voucher_no, due_date] @@ -162,8 +162,7 @@ class ReceivablePayableReport(object): def get_entries_till(self, report_date, party_type): # returns a generator - return (e for e in self.get_gl_entries(party_type) - if getdate(e.posting_date) <= report_date) + return (e for e in self.get_gl_entries(party_type) if getdate(e.posting_date) <= report_date) def is_receivable_or_payable(self, gle, dr_or_cr, future_vouchers): return ( @@ -189,7 +188,8 @@ class ReceivablePayableReport(object): reverse_dr_or_cr = "credit" if dr_or_cr=="debit" else "debit" for e in self.get_gl_entries_for(gle.party, gle.party_type, gle.voucher_type, gle.voucher_no): - if getdate(e.posting_date) <= report_date and e.name!=gle.name: + if getdate(e.posting_date) <= report_date and e.name!=gle.name \ + and (not gle.due_date or getdate(e.due_date) == getdate(gle.due_date)): amount = flt(e.get(reverse_dr_or_cr)) - flt(e.get(dr_or_cr)) if e.voucher_no not in return_entries: payment_amount += amount @@ -251,11 +251,11 @@ class ReceivablePayableReport(object): select_fields = "sum(debit) as debit, sum(credit) as credit" self.gl_entries = frappe.db.sql("""select name, posting_date, account, party_type, party, - voucher_type, voucher_no, against_voucher_type, against_voucher, + voucher_type, voucher_no, against_voucher_type, against_voucher, due_date, account_currency, remarks, {0} from `tabGL Entry` where docstatus < 2 and party_type=%s and (party is not null and party != '') {1} - group by voucher_type, voucher_no, against_voucher_type, against_voucher, party + group by voucher_type, voucher_no, against_voucher_type, against_voucher, party, due_date order by posting_date, party""" .format(select_fields, conditions), values, as_dict=True) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 6617802235..38258288a7 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -582,7 +582,7 @@ def get_outstanding_invoices(party_type, party, account, condition=None): invoice_list = frappe.db.sql(""" select - voucher_no, voucher_type, posting_date, ifnull(sum({dr_or_cr}), 0) as invoice_amount, + voucher_no, voucher_type, posting_date, ifnull(sum({dr_or_cr}), 0) as invoice_amount, due_date, ( select ifnull(sum({payment_dr_or_cr}), 0) from `tabGL Entry` payment_gl_entry @@ -591,6 +591,7 @@ def get_outstanding_invoices(party_type, party, account, condition=None): and payment_gl_entry.party_type = invoice_gl_entry.party_type and payment_gl_entry.party = invoice_gl_entry.party and payment_gl_entry.account = invoice_gl_entry.account + and payment_gl_entry.due_date = invoice_gl_entry.due_date and {payment_dr_or_cr} > 0 ) as payment_amount from @@ -602,9 +603,9 @@ def get_outstanding_invoices(party_type, party, account, condition=None): and ((voucher_type = 'Journal Entry' and (against_voucher = '' or against_voucher is null)) or (voucher_type not in ('Journal Entry', 'Payment Entry'))) - group by voucher_type, voucher_no + group by voucher_type, voucher_no, due_date having (invoice_amount - payment_amount) > 0.005 - order by posting_date, name""".format( + order by posting_date, name, due_date""".format( dr_or_cr = dr_or_cr, payment_dr_or_cr = payment_dr_or_cr, condition = condition or "" @@ -615,6 +616,7 @@ def get_outstanding_invoices(party_type, party, account, condition=None): }, as_dict=True) for d in invoice_list: + print d.voucher_no, d.invoice_amount, d.payment_amount outstanding_invoices.append(frappe._dict({ 'voucher_no': d.voucher_no, 'voucher_type': d.voucher_type, @@ -622,8 +624,8 @@ def get_outstanding_invoices(party_type, party, account, condition=None): 'invoice_amount': flt(d.invoice_amount), 'payment_amount': flt(d.payment_amount), 'outstanding_amount': flt(d.invoice_amount - d.payment_amount, precision), - 'due_date': frappe.db.get_value(d.voucher_type, d.voucher_no, - "posting_date" if party_type=="Employee" else "due_date"), + 'due_date': d.due_date or (frappe.db.get_value(d.voucher_type, d.voucher_no, + "posting_date" if party_type=="Employee" else "due_date")), })) outstanding_invoices = sorted(outstanding_invoices, key=lambda k: k['due_date'] or getdate(nowdate())) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b24e047bcc..41f8dc5335 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _, throw -from frappe.utils import today, flt, cint, fmt_money, formatdate, getdate +from frappe.utils import today, flt, cint, fmt_money, formatdate, getdate, add_days, add_months, get_last_day from erpnext.setup.utils import get_exchange_rate from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency from erpnext.utilities.transaction_base import TransactionBase @@ -782,4 +782,43 @@ def update_invoice_status(): where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""") frappe.db.sql(""" update `tabPurchase Invoice` set status = 'Overdue' - where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""") \ No newline at end of file + where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""") + +@frappe.whitelist() +def get_payment_terms(terms_template, posting_date=None, grand_total=None): + if not terms_template: + return + + terms_doc = frappe.get_doc("Payment Terms Template", terms_template) + + schedule = [] + for i, d in enumerate(terms_doc.get("terms")): + term_details = get_payment_term_details(d, posting_date, grand_total) + schedule.append(term_details) + + return schedule + +@frappe.whitelist() +def get_payment_term_details(term, posting_date=None, grand_total=None): + term_details = frappe._dict() + if isinstance(term, unicode): + term = frappe.get_doc("Payment Term", term) + else: + term_details.payment_term = term.payment_term + term_details.description = term.description + term_details.invoice_portion = term.invoice_portion + term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100 + if posting_date: + term_details.due_date = get_due_date(posting_date, term) + return term_details + +def get_due_date(posting_date, term): + due_date = None + if term.due_date_based_on == "Day(s) after invoice date": + due_date = add_days(posting_date, term.credit_days) + elif term.due_date_based_on == "Day(s) after the end of the invoice month": + due_date = add_days(get_last_day(posting_date), term.credit_days) + elif term.due_date_based_on == "Month(s) after the end of the invoice month": + due_date = add_months(get_last_day(posting_date), term.credit_months) + + return due_date \ No newline at end of file diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 3a8ddb5927..f72a9299da 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1100,6 +1100,46 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } } }, + + payment_terms_template: function() { + var me = this; + if(this.frm.doc.payment_terms_template) { + frappe.call({ + method: "erpnext.controllers.accounts_controller.get_payment_terms", + 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 + }, + callback: function(r) { + if(r.message && !r.exc) { + me.frm.set_value("payment_schedule", r.message); + } + } + }) + } + }, + + payment_term: function(doc, cdt, cdn) { + var row = locals[cdt][cdn]; + if(row.payment_term) { + frappe.call({ + method: "erpnext.controllers.accounts_controller.get_payment_term_details", + args: { + term: row.payment_term, + posting_date: this.frm.doc.posting_date || this.frm.doc.transaction_date, + grand_total: this.frm.doc.grand_total + }, + callback: function(r) { + if(r.message && !r.exc) { + for (var d in r.message) { + frappe.model.set_value(cdt, cdn, d, r.message[d]); + } + } + } + }) + } + } }); erpnext.show_serial_batch_selector = function(frm, d) {