diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 15badf105f8..25e62e20f3e 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -216,7 +216,7 @@
"description": "Payment Terms from orders will be fetched into the invoices as is",
"fieldname": "automatically_fetch_payment_terms",
"fieldtype": "Check",
- "label": "Automatically Fetch Payment Terms from Order"
+ "label": "Automatically Fetch Payment Terms from Order/Quotation"
},
{
"description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ",
@@ -307,7 +307,7 @@
},
{
"default": "0",
- "description": "Learn about Common Party",
+ "description": "Learn about Common Party",
"fieldname": "enable_common_party_accounting",
"fieldtype": "Check",
"label": "Enable Common Party Accounting"
@@ -671,7 +671,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2025-12-26 19:46:55.093717",
+ "modified": "2026-03-06 14:49:11.467716",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@@ -701,4 +701,4 @@
"sort_order": "ASC",
"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 008402eeb53..ec2b5caf9d2 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -2502,13 +2502,14 @@ class AccountsController(TransactionBase):
grand_total = self.get("rounded_total") or self.grand_total
automatically_fetch_payment_terms = 0
- if self.doctype in ("Sales Invoice", "Purchase Invoice"):
- base_grand_total = base_grand_total - flt(self.base_write_off_amount)
- grand_total = grand_total - flt(self.write_off_amount)
+ if self.doctype in ("Sales Invoice", "Purchase Invoice", "Sales Order"):
po_or_so, doctype, fieldname = self.get_order_details()
automatically_fetch_payment_terms = cint(
frappe.db.get_single_value("Accounts Settings", "automatically_fetch_payment_terms")
)
+ if self.doctype != "Sales Order":
+ base_grand_total = base_grand_total - flt(self.base_write_off_amount)
+ grand_total = grand_total - flt(self.write_off_amount)
if self.get("total_advance"):
if party_account_currency == self.company_currency:
@@ -2524,7 +2525,7 @@ class AccountsController(TransactionBase):
if not self.get("payment_schedule"):
if (
- self.doctype in ["Sales Invoice", "Purchase Invoice"]
+ self.doctype in ["Sales Invoice", "Purchase Invoice", "Sales Order"]
and automatically_fetch_payment_terms
and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype)
):
@@ -2579,17 +2580,23 @@ class AccountsController(TransactionBase):
self.ignore_default_payment_terms_template = 1
def get_order_details(self):
+ if not self.get("items"):
+ return None, None, None
if self.doctype == "Sales Invoice":
- po_or_so = self.get("items") and self.get("items")[0].get("sales_order")
- po_or_so_doctype = "Sales Order"
- po_or_so_doctype_name = "sales_order"
-
+ prev_doc = self.get("items")[0].get("sales_order")
+ prev_doctype = "Sales Order"
+ prev_doctype_name = "sales_order"
+ elif self.doctype == "Purchase Invoice":
+ prev_doc = self.get("items")[0].get("purchase_order")
+ prev_doctype = "Purchase Order"
+ prev_doctype_name = "purchase_order"
+ elif self.doctype == "Sales Order":
+ prev_doc = self.get("items")[0].get("prevdoc_docname")
+ prev_doctype = "Quotation"
+ prev_doctype_name = "prevdoc_docname"
else:
- po_or_so = self.get("items") and self.get("items")[0].get("purchase_order")
- po_or_so_doctype = "Purchase Order"
- po_or_so_doctype_name = "purchase_order"
-
- return po_or_so, po_or_so_doctype, po_or_so_doctype_name
+ return None, None, None
+ return prev_doc, prev_doctype, prev_doctype_name
def linked_order_has_payment_terms(self, po_or_so, fieldname, doctype):
if po_or_so and self.all_items_have_same_po_or_so(po_or_so, fieldname):
@@ -2685,7 +2692,9 @@ class AccountsController(TransactionBase):
for d in self.get("payment_schedule"):
d.validate_from_to_dates("discount_date", "due_date")
- if self.doctype == "Sales Order" and getdate(d.due_date) < getdate(self.transaction_date):
+ if self.doctype in ["Sales Order", "Quotation"] and getdate(d.due_date) < getdate(
+ self.transaction_date
+ ):
frappe.throw(
_("Row {0}: Due Date in the Payment Terms table cannot be before Posting Date").format(
d.idx
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index ef727eec8d8..b0b1281aeff 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1052,6 +1052,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
if (this.frm.doc.transaction_date) {
this.frm.transaction_date = this.frm.doc.transaction_date;
frappe.ui.form.trigger(this.frm.doc.doctype, "currency");
+ this.recalculate_terms();
}
}
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 7a31854d259..b4e433ac805 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -7,7 +7,7 @@ import json
import frappe
from frappe import _
from frappe.model.mapper import get_mapped_doc
-from frappe.utils import flt, getdate, nowdate
+from frappe.utils import cint, flt, getdate, nowdate
from erpnext.controllers.selling_controller import SellingController
@@ -442,6 +442,10 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
child_filter = d.name in filtered_items if filtered_items else True
return child_filter
+ automatically_fetch_payment_terms = cint(
+ frappe.get_single_value("Accounts Settings", "automatically_fetch_payment_terms")
+ )
+
doclist = get_mapped_doc(
"Quotation",
source_name,
@@ -449,6 +453,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
"Quotation": {
"doctype": "Sales Order",
"validation": {"docstatus": ["=", 1]},
+ "field_no_map": ["payment_terms_template"],
},
"Quotation Item": {
"doctype": "Sales Order Item",
@@ -458,13 +463,15 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
},
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True},
"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
- "Payment Schedule": {"doctype": "Payment Schedule", "add_if_empty": True},
},
target_doc,
set_missing_values,
ignore_permissions=ignore_permissions,
)
+ if automatically_fetch_payment_terms:
+ doclist.set_payment_schedule()
+
return doclist
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index 2d1da049653..4d4d485c71a 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -175,6 +175,10 @@ class TestQuotation(FrappeTestCase):
self.assertTrue(quotation.payment_schedule)
+ @change_settings(
+ "Accounts Settings",
+ {"automatically_fetch_payment_terms": 1},
+ )
def test_make_sales_order_terms_copied(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order
@@ -317,7 +321,11 @@ class TestQuotation(FrappeTestCase):
@change_settings(
"Accounts Settings",
- {"add_taxes_from_item_tax_template": 0, "add_taxes_from_taxes_and_charges_template": 0},
+ {
+ "add_taxes_from_item_tax_template": 0,
+ "add_taxes_from_taxes_and_charges_template": 0,
+ "automatically_fetch_payment_terms": 1,
+ },
)
def test_make_sales_order_with_terms(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order
@@ -355,10 +363,13 @@ class TestQuotation(FrappeTestCase):
sales_order.save()
self.assertEqual(sales_order.payment_schedule[0].payment_amount, 8906.00)
- self.assertEqual(sales_order.payment_schedule[0].due_date, getdate(quotation.transaction_date))
+ self.assertEqual(
+ getdate(sales_order.payment_schedule[0].due_date), getdate(quotation.transaction_date)
+ )
self.assertEqual(sales_order.payment_schedule[1].payment_amount, 8906.00)
self.assertEqual(
- sales_order.payment_schedule[1].due_date, getdate(add_days(quotation.transaction_date, 30))
+ getdate(sales_order.payment_schedule[1].due_date),
+ getdate(add_days(quotation.transaction_date, 30)),
)
def test_valid_till_before_transaction_date(self):
@@ -1058,6 +1069,56 @@ class TestQuotation(FrappeTestCase):
quotation.reload()
self.assertEqual(quotation.status, "Open")
+ @change_settings(
+ "Accounts Settings",
+ {"automatically_fetch_payment_terms": 1},
+ )
+ def test_make_sales_order_with_payment_terms(self):
+ from erpnext.selling.doctype.quotation.quotation import make_sales_order
+
+ template = frappe.get_doc(
+ {
+ "doctype": "Payment Terms Template",
+ "template_name": "_Test Payment Terms Template for Quotation",
+ "terms": [
+ {
+ "doctype": "Payment Terms Template Detail",
+ "invoice_portion": 50.00,
+ "credit_days_based_on": "Day(s) after invoice date",
+ "credit_days": 0,
+ },
+ {
+ "doctype": "Payment Terms Template Detail",
+ "invoice_portion": 50.00,
+ "credit_days_based_on": "Day(s) after invoice date",
+ "credit_days": 10,
+ },
+ ],
+ }
+ ).save()
+
+ quotation = make_quotation(qty=10, rate=1000, do_not_submit=1)
+ quotation.transaction_date = add_days(nowdate(), -2)
+ quotation.valid_till = add_days(nowdate(), 10)
+ quotation.update({"payment_terms_template": template.name, "payment_schedule": []})
+ quotation.save()
+ quotation.submit()
+
+ self.assertEqual(quotation.payment_schedule[0].payment_amount, 5000)
+ self.assertEqual(quotation.payment_schedule[1].payment_amount, 5000)
+ self.assertEqual(quotation.payment_schedule[0].due_date, quotation.transaction_date)
+ self.assertEqual(quotation.payment_schedule[1].due_date, add_days(quotation.transaction_date, 10))
+
+ sales_order = make_sales_order(quotation.name)
+ sales_order.transaction_date = nowdate()
+ sales_order.delivery_date = nowdate()
+ sales_order.save()
+
+ self.assertEqual(sales_order.payment_schedule[0].due_date, sales_order.transaction_date)
+ self.assertEqual(sales_order.payment_schedule[1].due_date, add_days(sales_order.transaction_date, 10))
+ self.assertEqual(sales_order.payment_schedule[0].payment_amount, 5000)
+ self.assertEqual(sales_order.payment_schedule[1].payment_amount, 5000)
+
test_records = frappe.get_test_records("Quotation")
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index 4bbdb20d311..e649b8e9383 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -160,6 +160,7 @@
"language",
"additional_info_section",
"is_internal_customer",
+ "ignore_default_payment_terms_template",
"represents_company",
"column_break_152",
"source",
@@ -1484,9 +1485,9 @@
},
{
"default": "0",
- "depends_on": "eval:doc.order_type == 'Maintenance';",
"fieldname": "skip_delivery_note",
"fieldtype": "Check",
+ "hidden": 1,
"hide_days": 1,
"hide_seconds": 1,
"label": "Skip Delivery Note",
@@ -1665,13 +1666,21 @@
"fieldtype": "Data",
"is_virtual": 1,
"label": "Last Scanned Warehouse"
+ },
+ {
+ "default": "0",
+ "fieldname": "ignore_default_payment_terms_template",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Ignore Default Payment Terms Template",
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2026-02-06 11:06:16.092658",
+ "modified": "2026-03-06 15:33:49.059029",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
@@ -1750,4 +1759,4 @@
"title_field": "customer_name",
"track_changes": 1,
"track_seen": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 7ceba32232f..dbd7f406432 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -66,7 +66,6 @@ class SalesOrder(SellingController):
additional_discount_percentage: DF.Float
address_display: DF.SmallText | None
advance_paid: DF.Currency
- advance_payment_status: DF.Literal["Not Requested", "Requested", "Partially Paid", "Fully Paid"]
amended_from: DF.Link | None
amount_eligible_for_commission: DF.Currency
apply_discount_on: DF.Literal["", "Grand Total", "Net Total"]
@@ -111,6 +110,7 @@ class SalesOrder(SellingController):
grand_total: DF.Currency
group_same_items: DF.Check
has_unit_price_items: DF.Check
+ ignore_default_payment_terms_template: DF.Check
ignore_pricing_rule: DF.Check
in_words: DF.Data | None
incoterm: DF.Link | None
@@ -158,7 +158,6 @@ class SalesOrder(SellingController):
"",
"Draft",
"On Hold",
- "To Pay",
"To Deliver and Bill",
"To Bill",
"To Deliver",