Merge pull request #24408 from rohitwaghchaure/custom-user-type-feat

feat: Employee Self Service
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index 248cb9a..630a1dc 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -13,6 +13,8 @@
   "po_required",
   "pr_required",
   "maintain_same_rate",
+  "maintain_same_rate_action",
+  "role_to_override_stop_action",
   "allow_multiple_items",
   "subcontract",
   "backflush_raw_materials_of_subcontract_based_on",
@@ -89,6 +91,23 @@
   {
    "fieldname": "column_break_11",
    "fieldtype": "Column Break"
+  },
+  {
+   "default": "Stop",
+   "depends_on": "maintain_same_rate",
+   "description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.",
+   "fieldname": "maintain_same_rate_action",
+   "fieldtype": "Select",
+   "label": "Action If Same Rate is Not Maintained",
+   "mandatory_depends_on": "maintain_same_rate",
+   "options": "Stop\nWarn"
+  },
+  {
+   "depends_on": "eval:doc.maintain_same_rate_action == 'Stop'",
+   "fieldname": "role_to_override_stop_action",
+   "fieldtype": "Link",
+   "label": "Role Allowed to Override Stop Action",
+   "options": "Role"
   }
  ],
  "icon": "fa fa-cog",
@@ -96,7 +115,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-03-02 17:34:04.190677",
+ "modified": "2021-04-04 20:01:44.087066",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Buying Settings",
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 81f0ad3..c0c1315 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -325,7 +325,7 @@
 			and status not in ("Stopped", "Closed") %(fcond)s
 			and (
 				(`tabDelivery Note`.is_return = 0 and `tabDelivery Note`.per_billed < 100)
-				or `tabDelivery Note`.grand_total = 0
+				or (`tabDelivery Note`.grand_total = 0 and `tabDelivery Note`.per_billed < 100)
 				or (
 					`tabDelivery Note`.is_return = 1
 					and return_against in (select name from `tabDelivery Note` where per_billed < 100)
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 1ae7310..9fae494 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -291,10 +291,13 @@
 				# set precision in the last item iteration
 				if n == len(self.doc.get("items")) - 1:
 					self.round_off_totals(tax)
+					self._set_in_company_currency(tax,
+						["tax_amount", "tax_amount_after_discount_amount"])
+
+					self.round_off_base_values(tax)
 					self.set_cumulative_total(i, tax)
 
-					self._set_in_company_currency(tax,
-						["total", "tax_amount", "tax_amount_after_discount_amount"])
+					self._set_in_company_currency(tax, ["total"])
 
 					# adjust Discount Amount loss in last tax iteration
 					if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
@@ -341,20 +344,11 @@
 		elif tax.charge_type == "On Item Quantity":
 			current_tax_amount = tax_rate * item.qty
 
-		current_tax_amount = self.get_final_current_tax_amount(tax, current_tax_amount)
-
 		if not self.doc.get("is_consolidated"):
 			self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
 
 		return current_tax_amount
 
-	def get_final_current_tax_amount(self, tax, current_tax_amount):
-		# Some countries need individual tax components to be rounded
-		# Handeled via regional doctypess
-		if tax.account_head in frappe.flags.round_off_applicable_accounts:
-			current_tax_amount = round(current_tax_amount, 0)
-		return current_tax_amount
-
 	def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
 		# store tax breakup for each item
 		key = item.item_code or item.item_name
@@ -365,10 +359,20 @@
 		tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)]
 
 	def round_off_totals(self, tax):
+		if tax.account_head in frappe.flags.round_off_applicable_accounts:
+			tax.tax_amount = round(tax.tax_amount, 0)
+			tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0)
+
 		tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
 		tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
 			tax.precision("tax_amount"))
 
+	def round_off_base_values(self, tax):
+		# Round off to nearest integer based on regional settings
+		if tax.account_head in frappe.flags.round_off_applicable_accounts:
+			tax.base_tax_amount = round(tax.base_tax_amount, 0)
+			tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
+
 	def manipulate_grand_total_for_inclusive_tax(self):
 		# if fully inclusive taxes and diff
 		if self.doc.get("taxes") and any([cint(t.included_in_print_rate) for t in self.doc.get("taxes")]):
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 3a3ee38..2e133be 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -323,12 +323,15 @@
 				// set precision in the last item iteration
 				if (n == me.frm.doc["items"].length - 1) {
 					me.round_off_totals(tax);
+					me.set_in_company_currency(tax,
+						["tax_amount", "tax_amount_after_discount_amount"]);
+
+					me.round_off_base_values(tax);
 
 					// in tax.total, accumulate grand total for each item
 					me.set_cumulative_total(i, tax);
 
-					me.set_in_company_currency(tax,
-						["total", "tax_amount", "tax_amount_after_discount_amount"]);
+					me.set_in_company_currency(tax, ["total"]);
 
 					// adjust Discount Amount loss in last tax iteration
 					if ((i == me.frm.doc["taxes"].length - 1) && me.discount_amount_applied
@@ -393,20 +396,11 @@
 			current_tax_amount = tax_rate * item.qty;
 		}
 
-		current_tax_amount = this.get_final_tax_amount(tax, current_tax_amount);
 		this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount);
 
 		return current_tax_amount;
 	},
 
-	get_final_tax_amount: function(tax, current_tax_amount) {
-		if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) {
-			current_tax_amount = Math.round(current_tax_amount);
-		}
-
-		return current_tax_amount;
-	},
-
 	set_item_wise_tax: function(item, tax, tax_rate, current_tax_amount) {
 		// store tax breakup for each item
 		let tax_detail = tax.item_wise_tax_detail;
@@ -420,10 +414,22 @@
 	},
 
 	round_off_totals: function(tax) {
+		if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) {
+			tax.tax_amount= Math.round(tax.tax_amount);
+			tax.tax_amount_after_discount_amount = Math.round(tax.tax_amount_after_discount_amount);
+		}
+
 		tax.tax_amount = flt(tax.tax_amount, precision("tax_amount", tax));
 		tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount, precision("tax_amount", tax));
 	},
 
+	round_off_base_values: function(tax) {
+		if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) {
+			tax.base_tax_amount= Math.round(tax.base_tax_amount);
+			tax.base_tax_amount_after_discount_amount = Math.round(tax.base_tax_amount_after_discount_amount);
+		}
+	},
+
 	manipulate_grand_total_for_inclusive_tax: function() {
 		var me = this;
 		// if fully inclusive taxes and diff
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json
index 2104c01..f01934b 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.json
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.json
@@ -18,6 +18,8 @@
   "dn_required",
   "sales_update_frequency",
   "maintain_same_sales_rate",
+  "maintain_same_rate_action",
+  "role_to_override_stop_action",
   "editable_price_list_rate",
   "allow_multiple_items",
   "allow_against_multiple_purchase_orders",
@@ -133,6 +135,23 @@
    "fieldname": "hide_tax_id",
    "fieldtype": "Check",
    "label": "Hide Customer's Tax ID from Sales Transactions"
+  },
+  {
+   "default": "Stop",
+   "depends_on": "maintain_same_sales_rate",
+   "description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.",
+   "fieldname": "maintain_same_rate_action",
+   "fieldtype": "Select",
+   "label": "Action If Same Rate is Not Maintained",
+   "mandatory_depends_on": "maintain_same_sales_rate",
+   "options": "Stop\nWarn"
+  },
+  {
+   "depends_on": "eval: doc.maintain_same_rate_action == 'Stop'",
+   "fieldname": "role_to_override_stop_action",
+   "fieldtype": "Link",
+   "label": "Role Allowed to Override Stop Action",
+   "options": "Role"
   }
  ],
  "icon": "fa fa-cog",
@@ -140,7 +159,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-03-02 17:35:53.603607",
+ "modified": "2021-04-04 20:18:12.814624",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Selling Settings",
diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index b575855..f99da58 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -120,11 +120,11 @@
 		buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]
 
 		if self.doctype in buying_doctypes:
-			to_disable = "Maintain same rate throughout Purchase cycle"
-			settings_page = "Buying Settings"
+			action = frappe.db.get_single_value("Buying Settings", "maintain_same_rate_action")
+			settings_doc = "Buying Settings"
 		else:
-			to_disable = "Maintain same rate throughout Sales cycle"
-			settings_page = "Selling Settings"
+			action = frappe.db.get_single_value("Selling Settings", "maintain_same_rate_action")
+			settings_doc = "Selling Settings"
 
 		for ref_dt, ref_dn_field, ref_link_field in ref_details:
 			for d in self.get("items"):
@@ -132,11 +132,16 @@
 					ref_rate = frappe.db.get_value(ref_dt + " Item", d.get(ref_link_field), "rate")
 
 					if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= .01:
-						frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ")
-							.format(d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate))
-						frappe.throw(_("To allow different rates, disable the {0} checkbox in {1}.")
-							.format(frappe.bold(_(to_disable)),
-							get_link_to_form(settings_page, settings_page, frappe.bold(settings_page))))
+						if action == "Stop":
+							role_allowed_to_override = frappe.db.get_single_value(settings_doc, 'role_to_override_stop_action')
+
+							if role_allowed_to_override not in frappe.get_roles():
+								frappe.throw(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
+									d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate))
+						else:
+							frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
+								d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate), title=_("Warning"), indicator="orange")
+
 
 	def get_link_filters(self, for_doctype):
 		if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):