From 148703bfc2fa9248c22d69ef92d14697a91a9301 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 13 Feb 2023 10:27:55 +0530 Subject: [PATCH] feat: Setting to allow Sales Order creation against expired quotation (#33952) * feat: Setting to allow Sales Order creation against expired quotation * chore: linting issues --- erpnext/selling/doctype/quotation/quotation.js | 14 +++++++++----- erpnext/selling/doctype/quotation/quotation.py | 11 +++++++++++ .../selling/doctype/quotation/test_quotation.py | 10 ++++++++++ .../doctype/selling_settings/selling_settings.json | 9 ++++++++- erpnext/startup/boot.py | 6 ++++++ 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js index 6b42e4daea..b348bd3575 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -85,11 +85,15 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext. } if (doc.docstatus == 1 && !["Lost", "Ordered"].includes(doc.status)) { - this.frm.add_custom_button( - __("Sales Order"), - this.frm.cscript["Make Sales Order"], - __("Create") - ); + if (frappe.boot.sysdefaults.allow_sales_order_creation_for_expired_quotation + || (!doc.valid_till) + || frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0) { + this.frm.add_custom_button( + __("Sales Order"), + this.frm.cscript["Make Sales Order"], + __("Create") + ); + } if(doc.status!=="Ordered") { this.frm.add_custom_button(__('Set as Lost'), () => { diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 6836d56647..063813b2dc 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -195,6 +195,17 @@ def get_list_context(context=None): @frappe.whitelist() def make_sales_order(source_name: str, target_doc=None): + if not frappe.db.get_singles_value( + "Selling Settings", "allow_sales_order_creation_for_expired_quotation" + ): + quotation = frappe.db.get_value( + "Quotation", source_name, ["transaction_date", "valid_till"], as_dict=1 + ) + if quotation.valid_till and ( + quotation.valid_till < quotation.transaction_date or quotation.valid_till < getdate(nowdate()) + ): + frappe.throw(_("Validity period of this quotation has ended.")) + return _make_sales_order(source_name, target_doc) diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index 5aaba4fa43..cdf5f5d00c 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -144,11 +144,21 @@ class TestQuotation(FrappeTestCase): def test_so_from_expired_quotation(self): from erpnext.selling.doctype.quotation.quotation import make_sales_order + frappe.db.set_single_value( + "Selling Settings", "allow_sales_order_creation_for_expired_quotation", 0 + ) + quotation = frappe.copy_doc(test_records[0]) quotation.valid_till = add_days(nowdate(), -1) quotation.insert() quotation.submit() + self.assertRaises(frappe.ValidationError, make_sales_order, quotation.name) + + frappe.db.set_single_value( + "Selling Settings", "allow_sales_order_creation_for_expired_quotation", 1 + ) + make_sales_order(quotation.name) def test_shopping_cart_without_website_item(self): diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 2abb169b8a..6ea66a0237 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -27,6 +27,7 @@ "column_break_5", "allow_multiple_items", "allow_against_multiple_purchase_orders", + "allow_sales_order_creation_for_expired_quotation", "hide_tax_id", "enable_discount_accounting" ], @@ -172,6 +173,12 @@ "fieldname": "enable_discount_accounting", "fieldtype": "Check", "label": "Enable Discount Accounting for Selling" + }, + { + "default": "0", + "fieldname": "allow_sales_order_creation_for_expired_quotation", + "fieldtype": "Check", + "label": "Allow Sales Order Creation For Expired Quotation" } ], "icon": "fa fa-cog", @@ -179,7 +186,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2022-05-31 19:39:48.398738", + "modified": "2023-02-04 12:37:53.380857", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py index bb120eaa6b..62936fcfb8 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -25,6 +25,12 @@ def boot_session(bootinfo): frappe.db.get_single_value("CRM Settings", "default_valid_till") ) + bootinfo.sysdefaults.allow_sales_order_creation_for_expired_quotation = cint( + frappe.db.get_single_value( + "Selling Settings", "allow_sales_order_creation_for_expired_quotation" + ) + ) + # if no company, show a dialog box to create a new company bootinfo.customer_count = frappe.db.sql("""SELECT count(*) FROM `tabCustomer`""")[0][0]