From ef17767132b805b9675605f487012eff26bcd051 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Wed, 30 Jul 2025 19:07:05 +0530 Subject: [PATCH 1/5] fix: dynamic due date when payment terms are fetched from order --- erpnext/controllers/accounts_controller.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 5971af1bb890..114fbac5a661 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2594,6 +2594,7 @@ def fetch_payment_terms_from_order( self.payment_schedule = [] self.payment_terms_template = po_or_so.payment_terms_template + posting_date = self.get("bill_date") or self.get("posting_date") or self.get("transaction_date") for schedule in po_or_so.payment_schedule: payment_schedule = { @@ -2606,6 +2607,16 @@ def fetch_payment_terms_from_order( } if automatically_fetch_payment_terms: + if schedule.payment_term: + # fields not available in payment schedule + payment_term = frappe.db.get_value( + "Payment Term", + schedule.payment_term, + ["due_date_based_on", "credit_days", "credit_months"], + as_dict=True, + ) + payment_schedule["due_date"] = get_due_date(payment_term, posting_date) + payment_schedule["payment_amount"] = flt( grand_total * flt(payment_schedule["invoice_portion"]) / 100, schedule.precision("payment_amount"), From dd78aa4e4ab0279fbb3608594afae7d6670210eb Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 31 Jul 2025 13:09:32 +0530 Subject: [PATCH 2/5] fix(test): use change_settings decorator for settings enable and disable --- .../purchase_invoice/test_purchase_invoice.py | 6 +----- .../doctype/purchase_order/test_purchase_order.py | 14 +++----------- .../doctype/sales_order/test_sales_order.py | 11 +---------- .../doctype/delivery_note/test_delivery_note.py | 6 +----- .../purchase_receipt/test_purchase_receipt.py | 6 +----- 5 files changed, 7 insertions(+), 36 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index ff6f0249660e..280d338d56de 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -2147,19 +2147,16 @@ def test_gl_entries_for_standalone_debit_note(self): rate = flt(sle.stock_value_difference) / flt(sle.actual_qty) self.assertAlmostEqual(rate, 500) + @IntegrationTestCase.change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1}) def test_payment_allocation_for_payment_terms(self): from erpnext.buying.doctype.purchase_order.test_purchase_order import ( create_pr_against_po, create_purchase_order, ) - from erpnext.selling.doctype.sales_order.test_sales_order import ( - automatically_fetch_payment_terms, - ) from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( make_purchase_invoice as make_pi_from_pr, ) - automatically_fetch_payment_terms() frappe.db.set_value( "Payment Terms Template", "_Test Payment Term Template", @@ -2185,7 +2182,6 @@ def test_payment_allocation_for_payment_terms(self): pi = make_pi_from_pr(pr.name) self.assertEqual(pi.payment_schedule[0].payment_amount, 1000) - automatically_fetch_payment_terms(enable=0) frappe.db.set_value( "Payment Terms Template", "_Test Payment Term Template", diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 6a1dd34d83b2..6da863cb0406 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -540,12 +540,8 @@ def test_purchase_order_on_hold(self): self.assertRaises(frappe.ValidationError, pr.submit) self.assertRaises(frappe.ValidationError, pi.submit) + @IntegrationTestCase.change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1}) def test_make_purchase_invoice_with_terms(self): - from erpnext.selling.doctype.sales_order.test_sales_order import ( - automatically_fetch_payment_terms, - ) - - automatically_fetch_payment_terms() po = create_purchase_order(do_not_save=True) self.assertRaises(frappe.ValidationError, make_pi_from_po, po.name) @@ -569,7 +565,6 @@ def test_make_purchase_invoice_with_terms(self): self.assertEqual(getdate(pi.payment_schedule[0].due_date), getdate(po.transaction_date)) self.assertEqual(pi.payment_schedule[1].payment_amount, 2500.0) self.assertEqual(getdate(pi.payment_schedule[1].due_date), add_days(getdate(po.transaction_date), 30)) - automatically_fetch_payment_terms(enable=0) def test_warehouse_company_validation(self): from erpnext.stock.utils import InvalidWarehouseCompany @@ -717,6 +712,7 @@ def test_default_payment_terms(self): ) self.assertEqual(due_date, "2023-03-31") + @IntegrationTestCase.change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 0}) def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self): po = create_purchase_order(do_not_save=1) po.payment_terms_template = "_Test Payment Term Template" @@ -910,18 +906,16 @@ def test_blanket_order_on_po_close_and_open(self): bo.load_from_db() self.assertEqual(bo.items[0].ordered_qty, 5) + @IntegrationTestCase.change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1}) def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( create_payment_terms_template, ) from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.selling.doctype.sales_order.test_sales_order import ( - automatically_fetch_payment_terms, compare_payment_schedules, ) - automatically_fetch_payment_terms() - po = create_purchase_order(qty=10, rate=100, do_not_save=1) create_payment_terms_template() po.payment_terms_template = "Test Receivable Template" @@ -935,8 +929,6 @@ def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): # self.assertEqual(po.payment_terms_template, pi.payment_terms_template) compare_payment_schedules(self, po, pi) - automatically_fetch_payment_terms(enable=0) - def test_internal_transfer_flow(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index cf438b04e5b4..cf908e295a46 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1678,14 +1678,13 @@ def test_so_cancellation_after_work_order_submission(self): so.load_from_db() self.assertRaises(frappe.LinkExistsError, so.cancel) + @IntegrationTestCase.change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1}) def test_payment_terms_are_fetched_when_creating_sales_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( create_payment_terms_template, ) from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice - automatically_fetch_payment_terms() - so = make_sales_order(uom="Nos", do_not_save=1) create_payment_terms_template() so.payment_terms_template = "Test Receivable Template" @@ -1699,8 +1698,6 @@ def test_payment_terms_are_fetched_when_creating_sales_invoice(self): self.assertEqual(so.payment_terms_template, si.payment_terms_template) compare_payment_schedules(self, so, si) - automatically_fetch_payment_terms(enable=0) - def test_zero_amount_sales_order_billing_status(self): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice @@ -2569,12 +2566,6 @@ def test_item_tax_transfer_from_sales_to_purchase(self): self.assertEqual(po.taxes[0].tax_amount, 2) -def automatically_fetch_payment_terms(enable=1): - accounts_settings = frappe.get_doc("Accounts Settings") - accounts_settings.automatically_fetch_payment_terms = enable - accounts_settings.save() - - def compare_payment_schedules(doc, doc1, doc2): for index, schedule in enumerate(doc1.get("payment_schedule")): doc.assertEqual(schedule.payment_term, doc2.payment_schedule[index].payment_term) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index c872bc92997e..4214f55a3232 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -15,7 +15,6 @@ from erpnext.controllers.accounts_controller import InvalidQtyError from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle from erpnext.selling.doctype.sales_order.test_sales_order import ( - automatically_fetch_payment_terms, compare_payment_schedules, create_dn_against_so, make_sales_order, @@ -1316,14 +1315,13 @@ def test_delivery_note_bundle_with_batched_item(self): frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) frappe.db.set_single_value("Accounts Settings", "delete_linked_ledger_entries", 0) + @IntegrationTestCase.change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1}) def test_payment_terms_are_fetched_when_creating_sales_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( create_payment_terms_template, ) from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice - automatically_fetch_payment_terms() - so = make_sales_order(uom="Nos", do_not_save=1) create_payment_terms_template() so.payment_terms_template = "Test Receivable Template" @@ -1343,8 +1341,6 @@ def test_payment_terms_are_fetched_when_creating_sales_invoice(self): self.assertEqual(so.payment_terms_template, si.payment_terms_template) compare_payment_schedules(self, so, si) - automatically_fetch_payment_terms(enable=0) - def test_returned_qty_in_return_dn(self): # SO ---> SI ---> DN # | diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index f5367b880a6b..553fc8fb07c5 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1201,6 +1201,7 @@ def test_purchase_receipt_with_exchange_rate_difference(self): self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount) + @IntegrationTestCase.change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1}) def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( create_payment_terms_template, @@ -1211,12 +1212,9 @@ def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): make_pr_against_po, ) from erpnext.selling.doctype.sales_order.test_sales_order import ( - automatically_fetch_payment_terms, compare_payment_schedules, ) - automatically_fetch_payment_terms() - po = create_purchase_order(qty=10, rate=100, do_not_save=1) create_payment_terms_template() po.payment_terms_template = "Test Receivable Template" @@ -1234,8 +1232,6 @@ def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): # self.assertEqual(po.payment_terms_template, pi.payment_terms_template) compare_payment_schedules(self, po, pi) - automatically_fetch_payment_terms(enable=0) - @IntegrationTestCase.change_settings("Stock Settings", {"allow_negative_stock": 1}) def test_neg_to_positive(self): from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry From 7de7a108d988f105ab41c8f61303139c5656aa12 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 31 Jul 2025 13:31:38 +0530 Subject: [PATCH 3/5] fix(test): compare schedule for due_date dynamically --- erpnext/selling/doctype/sales_order/test_sales_order.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index cf908e295a46..fb6707877459 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -11,7 +11,7 @@ from frappe.utils import add_days, flt, getdate, nowdate, today from erpnext.accounts.test.accounts_mixin import AccountsTestMixin -from erpnext.controllers.accounts_controller import InvalidQtyError, update_child_qty_rate +from erpnext.controllers.accounts_controller import InvalidQtyError, get_due_date, update_child_qty_rate from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import ( make_maintenance_schedule, ) @@ -2568,8 +2568,13 @@ def test_item_tax_transfer_from_sales_to_purchase(self): def compare_payment_schedules(doc, doc1, doc2): for index, schedule in enumerate(doc1.get("payment_schedule")): + term = frappe.get_doc("Payment Term", schedule.payment_term) if schedule.payment_term else None + due_date = schedule.due_date + posting_date = doc1.get("bill_date") or doc1.get("posting_date") or doc1.get("transaction_date") + if term: + due_date = get_due_date(term, posting_date=posting_date) doc.assertEqual(schedule.payment_term, doc2.payment_schedule[index].payment_term) - doc.assertEqual(getdate(schedule.due_date), doc2.payment_schedule[index].due_date) + doc.assertEqual(due_date, doc2.payment_schedule[index].due_date) doc.assertEqual(schedule.invoice_portion, doc2.payment_schedule[index].invoice_portion) doc.assertEqual(schedule.payment_amount, doc2.payment_schedule[index].payment_amount) From 9744403c99ff392cf23b79d3fb4d7ae8c62468ea Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 31 Jul 2025 15:16:18 +0530 Subject: [PATCH 4/5] fix: save conditions for due date at invoice level --- .../payment_schedule/payment_schedule.json | 52 +++++++++++++++++-- .../payment_schedule/payment_schedule.py | 15 ++++++ erpnext/controllers/accounts_controller.py | 24 +++++---- .../doctype/sales_order/test_sales_order.py | 7 ++- 4 files changed, 78 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index b72281b63146..3b0a3ff40643 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -10,14 +10,19 @@ "description", "section_break_4", "due_date", + "invoice_portion", "mode_of_payment", "column_break_5", - "invoice_portion", + "due_date_based_on", + "credit_days", + "credit_months", "section_break_6", - "discount_type", "discount_date", - "column_break_9", "discount", + "discount_type", + "column_break_9", + "discount_validity_based_on", + "discount_validity", "section_break_9", "payment_amount", "outstanding", @@ -172,12 +177,49 @@ "label": "Paid Amount (Company Currency)", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "fetch_from": "payment_term.due_date_based_on", + "fetch_if_empty": 1, + "fieldname": "due_date_based_on", + "fieldtype": "Select", + "label": "Due Date Based On", + "options": "\nDay(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month" + }, + { + "fetch_if_empty": 1, + "fieldname": "credit_days", + "fieldtype": "Int", + "label": "Credit Days", + "non_negative": 1 + }, + { + "fetch_if_empty": 1, + "fieldname": "credit_months", + "fieldtype": "Int", + "label": "Credit Months", + "non_negative": 1 + }, + { + "fetch_from": "payment_term.discount_validity_based_on", + "fetch_if_empty": 1, + "fieldname": "discount_validity_based_on", + "fieldtype": "Select", + "label": "Discount Validity Based On", + "options": "\nDay(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month" + }, + { + "fetch_from": "payment_term.discount_validity", + "fetch_if_empty": 1, + "fieldname": "discount_validity", + "fieldtype": "Int", + "label": "Discount Validity" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-03-11 11:06:51.792982", + "modified": "2025-07-31 05:31:58.406637", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", @@ -189,4 +231,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ 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 index a3d1dbe55645..f506be0eb277 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.py +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.py @@ -17,12 +17,27 @@ class PaymentSchedule(Document): base_outstanding: DF.Currency base_paid_amount: DF.Currency base_payment_amount: DF.Currency + credit_days: DF.Int + credit_months: DF.Int description: DF.SmallText | None discount: DF.Float discount_date: DF.Date | None discount_type: DF.Literal["Percentage", "Amount"] + discount_validity: DF.Int + discount_validity_based_on: DF.Literal[ + "", + "Day(s) after invoice date", + "Day(s) after the end of the invoice month", + "Month(s) after the end of the invoice month", + ] discounted_amount: DF.Currency due_date: DF.Date + due_date_based_on: DF.Literal[ + "", + "Day(s) after invoice date", + "Day(s) after the end of the invoice month", + "Month(s) after the end of the invoice month", + ] invoice_portion: DF.Percent mode_of_payment: DF.Link | None outstanding: DF.Currency diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 114fbac5a661..84ff68313805 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2604,18 +2604,15 @@ def fetch_payment_terms_from_order( "mode_of_payment": schedule.mode_of_payment, "description": schedule.description, "paid_amount": schedule.paid_amount, + "discount_date": schedule.discount_date, } if automatically_fetch_payment_terms: - if schedule.payment_term: - # fields not available in payment schedule - payment_term = frappe.db.get_value( - "Payment Term", - schedule.payment_term, - ["due_date_based_on", "credit_days", "credit_months"], - as_dict=True, - ) - payment_schedule["due_date"] = get_due_date(payment_term, posting_date) + if schedule.due_date_based_on: + payment_schedule["due_date"] = get_due_date(schedule, posting_date) + + if schedule.discount_validity_based_on: + payment_schedule["discount_date"] = get_discount_date(schedule, posting_date) payment_schedule["payment_amount"] = flt( grand_total * flt(payment_schedule["invoice_portion"]) / 100, @@ -3482,6 +3479,11 @@ def get_payment_term_details( term_details.discount = term.discount term_details.outstanding = term_details.payment_amount term_details.mode_of_payment = term.mode_of_payment + term_details.due_date_based_on = term.due_date_based_on + term_details.credit_days = term.credit_days + term_details.credit_months = term.credit_months + term_details.discount_validity_based_on = term.discount_validity_based_on + term_details.discount_validity = term.discount_validity if bill_date: term_details.due_date = get_due_date(term, bill_date) @@ -3496,8 +3498,8 @@ def get_payment_term_details( return term_details -def get_due_date(term, posting_date=None, bill_date=None): - due_date = None +def get_due_date(term, posting_date=None, bill_date=None, default_date=None): + due_date = default_date date = bill_date or posting_date if term.due_date_based_on == "Day(s) after invoice date": due_date = add_days(date, term.credit_days) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index fb6707877459..9e2a50eac52a 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2568,11 +2568,10 @@ def test_item_tax_transfer_from_sales_to_purchase(self): def compare_payment_schedules(doc, doc1, doc2): for index, schedule in enumerate(doc1.get("payment_schedule")): - term = frappe.get_doc("Payment Term", schedule.payment_term) if schedule.payment_term else None - due_date = schedule.due_date posting_date = doc1.get("bill_date") or doc1.get("posting_date") or doc1.get("transaction_date") - if term: - due_date = get_due_date(term, posting_date=posting_date) + due_date = schedule.due_date + if schedule.due_date_based_on: + due_date = get_due_date(schedule, posting_date=posting_date) doc.assertEqual(schedule.payment_term, doc2.payment_schedule[index].payment_term) doc.assertEqual(due_date, doc2.payment_schedule[index].due_date) doc.assertEqual(schedule.invoice_portion, doc2.payment_schedule[index].invoice_portion) From 81dd6d3b677086059ad0f522e2a641a399b4973c Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 31 Jul 2025 17:50:52 +0530 Subject: [PATCH 5/5] fix: make fields read only and on change of date unset the date condition fields --- .../payment_schedule/payment_schedule.json | 21 ++++++++++----- .../payment_terms_template_detail.json | 8 +++--- erpnext/controllers/accounts_controller.py | 6 ++++- erpnext/public/js/controllers/transaction.js | 26 +++++++++++++++---- 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index 3b0a3ff40643..4073a393cce5 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -184,42 +184,51 @@ "fieldname": "due_date_based_on", "fieldtype": "Select", "label": "Due Date Based On", - "options": "\nDay(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month" + "options": "\nDay(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month", + "read_only": 1 }, { + "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)", "fetch_if_empty": 1, "fieldname": "credit_days", "fieldtype": "Int", "label": "Credit Days", - "non_negative": 1 + "non_negative": 1, + "read_only": 1 }, { + "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'", "fetch_if_empty": 1, "fieldname": "credit_months", "fieldtype": "Int", "label": "Credit Months", - "non_negative": 1 + "non_negative": 1, + "read_only": 1 }, { + "depends_on": "discount", "fetch_from": "payment_term.discount_validity_based_on", "fetch_if_empty": 1, "fieldname": "discount_validity_based_on", "fieldtype": "Select", "label": "Discount Validity Based On", - "options": "\nDay(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month" + "options": "\nDay(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month", + "read_only": 1 }, { + "depends_on": "discount_validity_based_on", "fetch_from": "payment_term.discount_validity", "fetch_if_empty": 1, "fieldname": "discount_validity", "fieldtype": "Int", - "label": "Discount Validity" + "label": "Discount Validity", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-07-31 05:31:58.406637", + "modified": "2025-07-31 07:38:37.383470", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", 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 index 8d5e0f910c35..9c933944931f 100644 --- 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 @@ -140,8 +140,7 @@ "fetch_if_empty": 1, "fieldname": "discount_validity", "fieldtype": "Int", - "label": "Discount Validity", - "mandatory_depends_on": "discount" + "label": "Discount Validity" }, { "fieldname": "section_break_4", @@ -151,15 +150,16 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-03-27 13:10:11.829680", + "modified": "2025-07-31 07:42:37.442017", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Terms Template Detail", "owner": "Administrator", "permissions": [], "quick_entry": 1, + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 84ff68313805..d3cbf00a0336 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2604,15 +2604,19 @@ def fetch_payment_terms_from_order( "mode_of_payment": schedule.mode_of_payment, "description": schedule.description, "paid_amount": schedule.paid_amount, - "discount_date": schedule.discount_date, } if automatically_fetch_payment_terms: if schedule.due_date_based_on: payment_schedule["due_date"] = get_due_date(schedule, posting_date) + payment_schedule["due_date_based_on"] = schedule.credit_days + payment_schedule["credit_days"] = schedule.credit_days + payment_schedule["credit_months"] = schedule.credit_months if schedule.discount_validity_based_on: payment_schedule["discount_date"] = get_discount_date(schedule, posting_date) + payment_schedule["discount_validity_based_on"] = schedule.discount_validity_based_on + payment_schedule["discount_validity"] = schedule.discount_validity payment_schedule["payment_amount"] = flt( grand_total * flt(payment_schedule["invoice_portion"]) / 100, diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 61d69e93f7f7..997be3369bac 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1103,12 +1103,22 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } } - due_date(doc, cdt) { + discount_date(doc, cdt,cdn){ + // Remove fields as due_date is auto-managed by payment terms + ["discount_validity","discount_validity_based_on"].forEach(function(field) { + frappe.model.set_value(cdt, cdn, field,"") + }); + + } + + due_date(doc, cdt,cdn) { // due_date is to be changed, payment terms template and/or payment schedule must // be removed as due_date is automatically changed based on payment terms if (doc.doctype !== cdt) { - // triggered by change to the due_date field in payment schedule child table - // do nothing to avoid infinite clearing loop + // Remove fields as due_date is auto-managed by payment terms + ["due_date_based_on", "credit_days", "credit_months"].forEach(function(field) { + frappe.model.set_value(cdt, cdn, field,"") + }); return; } @@ -2638,6 +2648,11 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe payment_term(doc, cdt, cdn) { const me = this; var row = locals[cdt][cdn]; + // empty date condition fields + ["due_date_based_on", "credit_days", "credit_months","discount_validity","discount_validity_based_on"].forEach(function(field) { + row[field] = "" + }); + if(row.payment_term) { frappe.call({ method: "erpnext.controllers.accounts_controller.get_payment_term_details", @@ -2650,8 +2665,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe }, callback: function(r) { if(r.message && !r.exc) { - for (var d in r.message) { - frappe.model.set_value(cdt, cdn, d, r.message[d]); + for (let d in r.message) { + row[d] = r.message[d]; const company_currency = me.get_company_currency(); me.update_payment_schedule_grid_labels(company_currency); } @@ -2659,6 +2674,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } }) } + me.frm.refresh_field("payment_schedule"); } against_blanket_order(doc, cdt, cdn) {