Merge pull request #26458 from rohitwaghchaure/fixed-multi-currency-issue-pre

fix: multi-currency issue
diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index 4ddc458..1766c2c 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -60,10 +60,23 @@
 				erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name);
 			}, __('Create'));
 
+			frm.add_custom_button(__('Get Supplier Group Details'), function () {
+				frm.trigger("get_supplier_group_details");
+			}, __('Actions'));
+
 			// indicators
 			erpnext.utils.set_party_dashboard_indicators(frm);
 		}
 	},
+	get_supplier_group_details: function(frm) {
+		frappe.call({
+			method: "get_supplier_group_details",
+			doc: frm.doc,
+			callback: function() {
+				frm.refresh();
+			}
+		});
+	},
 
 	is_internal_supplier: function(frm) {
 		if (frm.doc.is_internal_supplier == 1) {
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index edeb135..fd16b23 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -51,6 +51,23 @@
 		validate_party_accounts(self)
 		self.validate_internal_supplier()
 
+	@frappe.whitelist()
+	def get_supplier_group_details(self):
+		doc = frappe.get_doc('Supplier Group', self.supplier_group)
+		self.payment_terms = ""
+		self.accounts = []
+
+		if doc.accounts:
+			for account in doc.accounts:
+				child = self.append('accounts')
+				child.company = account.company
+				child.account = account.account
+
+		if doc.payment_terms:
+			self.payment_terms = doc.payment_terms
+
+		self.save()
+
 	def validate_internal_supplier(self):
 		internal_supplier = frappe.db.get_value("Supplier",
 			{"is_internal_supplier": 1, "represents_company": self.represents_company, "name": ("!=", self.name)}, "name")
@@ -86,4 +103,4 @@
 						create_contact(supplier, 'Supplier',
 							doc.name, args.get('supplier_email_' + str(i)))
 				except frappe.NameError:
-					pass
\ No newline at end of file
+					pass
diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py
index f9c8d35..8980466 100644
--- a/erpnext/buying/doctype/supplier/test_supplier.py
+++ b/erpnext/buying/doctype/supplier/test_supplier.py
@@ -13,6 +13,30 @@
 
 
 class TestSupplier(unittest.TestCase):
+    def test_get_supplier_group_details(self):
+        doc = frappe.new_doc("Supplier Group")
+        doc.supplier_group_name = "_Testing Supplier Group"
+        doc.payment_terms = "_Test Payment Term Template 3"
+        doc.accounts = []
+        test_account_details = {
+            "company": "_Test Company",
+            "account": "Creditors - _TC",
+        }
+        doc.append("accounts", test_account_details)
+        doc.save()
+        s_doc = frappe.new_doc("Supplier")
+        s_doc.supplier_name = "Testing Supplier"
+        s_doc.supplier_group = "_Testing Supplier Group"
+        s_doc.payment_terms = ""
+        s_doc.accounts = []
+        s_doc.insert()
+        s_doc.get_supplier_group_details()
+        self.assertEqual(s_doc.payment_terms, "_Test Payment Term Template 3")
+        self.assertEqual(s_doc.accounts[0].company, "_Test Company")
+        self.assertEqual(s_doc.accounts[0].account, "Creditors - _TC")
+        s_doc.delete()
+        doc.delete()
+
     def test_supplier_default_payment_terms(self):
         # Payment Term based on Days after invoice date
         frappe.db.set_value(
@@ -136,4 +160,4 @@
         return doc
 
     except frappe.DuplicateEntryError:
-        return frappe.get_doc("Supplier", args.supplier_name)
\ No newline at end of file
+        return frappe.get_doc("Supplier", args.supplier_name)
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index 825b170..2849466 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -130,6 +130,10 @@
 				erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name);
 			}, __('Create'));
 
+			frm.add_custom_button(__('Get Customer Group Details'), function () {
+				frm.trigger("get_customer_group_details");
+			}, __('Actions'));
+
 			// indicator
 			erpnext.utils.set_party_dashboard_indicators(frm);
 
@@ -145,4 +149,15 @@
 		if(frm.doc.lead_name) frappe.model.clear_doc("Lead", frm.doc.lead_name);
 
 	},
-});
\ No newline at end of file
+	get_customer_group_details: function(frm) {
+		frappe.call({
+			method: "get_customer_group_details",
+			doc: frm.doc,
+			callback: function() {
+				frm.refresh();
+			}
+		});
+
+	}
+});
+
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 818888c..3b62081 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -78,6 +78,29 @@
 			if sum(member.allocated_percentage or 0 for member in self.sales_team) != 100:
 				frappe.throw(_("Total contribution percentage should be equal to 100"))
 
+	@frappe.whitelist()
+	def get_customer_group_details(self):
+		doc = frappe.get_doc('Customer Group', self.customer_group)
+		self.accounts = self.credit_limits = []
+		self.payment_terms = self.default_price_list = ""
+
+		tables = [["accounts", "account"], ["credit_limits", "credit_limit"]]
+		fields = ["payment_terms", "default_price_list"]
+
+		for row in tables:
+			table, field = row[0], row[1]
+			if not doc.get(table): continue
+
+			for entry in doc.get(table):
+				child = self.append(table)
+				child.update({"company": entry.company, field: entry.get(field)})
+
+		for field in fields:
+			if not doc.get(field): continue
+			self.update({field: doc.get(field)})
+
+		self.save()
+
 	def check_customer_group_change(self):
 		frappe.flags.customer_group_changed = False
 
diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py
index 7761aa7..b1a5b52 100644
--- a/erpnext/selling/doctype/customer/test_customer.py
+++ b/erpnext/selling/doctype/customer/test_customer.py
@@ -27,6 +27,42 @@
 	def tearDown(self):
 		set_credit_limit('_Test Customer', '_Test Company', 0)
 
+	def test_get_customer_group_details(self):
+		doc = frappe.new_doc("Customer Group")
+		doc.customer_group_name = "_Testing Customer Group"
+		doc.payment_terms = "_Test Payment Term Template 3"
+		doc.accounts = []
+		doc.default_price_list = "Standard Buying"
+		doc.credit_limits = []
+		test_account_details = {
+			"company": "_Test Company",
+			"account": "Creditors - _TC",
+		}
+		test_credit_limits = {
+			"company": "_Test Company",
+			"credit_limit": 350000
+		}
+		doc.append("accounts", test_account_details)
+		doc.append("credit_limits", test_credit_limits)
+		doc.insert()
+
+		c_doc = frappe.new_doc("Customer")
+		c_doc.customer_name = "Testing Customer"
+		c_doc.customer_group = "_Testing Customer Group"
+		c_doc.payment_terms = c_doc.default_price_list = ""
+		c_doc.accounts = c_doc.credit_limits= []
+		c_doc.insert()
+		c_doc.get_customer_group_details()
+		self.assertEqual(c_doc.payment_terms, "_Test Payment Term Template 3")
+
+		self.assertEqual(c_doc.accounts[0].company, "_Test Company")
+		self.assertEqual(c_doc.accounts[0].account, "Creditors - _TC")
+
+		self.assertEqual(c_doc.credit_limits[0].company, "_Test Company")
+		self.assertEqual(c_doc.credit_limits[0].credit_limit, 350000)
+		c_doc.delete()
+		doc.delete()
+
 	def test_party_details(self):
 		from erpnext.accounts.party import get_party_details
 
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 915e6a4..36a7d20 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -395,7 +395,7 @@
 
 @frappe.whitelist()
 def enqueue_replace_abbr(company, old, new):
-	kwargs = dict(company=company, old=old, new=new)
+	kwargs = dict(queue="long", company=company, old=old, new=new)
 	frappe.enqueue('erpnext.setup.doctype.company.company.replace_abbr', **kwargs)
 
 
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 8f27ef4..90b81dd 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -529,7 +529,7 @@
 		scrap_items_cost = sum([flt(d.basic_amount) for d in self.get("items") if d.is_scrap_item])
 
 		# Get raw materials cost from BOM if multiple material consumption entries
-		if frappe.db.get_single_value("Manufacturing Settings", "material_consumption"):
+		if frappe.db.get_single_value("Manufacturing Settings", "material_consumption", cache=True):
 			bom_items = self.get_bom_raw_materials(finished_item_qty)
 			outgoing_items_cost = sum([flt(row.qty)*flt(row.rate) for row in bom_items.values()])
 
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index 0febcb6..cb939e6 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -178,3 +178,4 @@
 
 	frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"])
 	frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"])
+	frappe.db.add_index("Stock Ledger Entry", ["voucher_detail_no"])
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 4e9c768..c15d1ed 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -6,13 +6,14 @@
 import erpnext
 import copy
 from frappe import _
-from frappe.utils import cint, flt, cstr, now, get_link_to_form
+from frappe.utils import cint, flt, cstr, now, get_link_to_form, getdate
 from frappe.model.meta import get_field_precision
 from erpnext.stock.utils import get_valuation_method, get_incoming_outgoing_rate_for_cancel
 from erpnext.stock.utils import get_bin
 import json
 from six import iteritems
 
+
 # future reposting
 class NegativeStockError(frappe.ValidationError): pass
 class SerialNoExistsInFutureTransaction(frappe.ValidationError):
@@ -130,7 +131,13 @@
 	if not args and voucher_type and voucher_no:
 		args = get_args_for_voucher(voucher_type, voucher_no)
 
-	distinct_item_warehouses = [(d.item_code, d.warehouse) for d in args]
+	distinct_item_warehouses = {}
+	for i, d in enumerate(args):
+		distinct_item_warehouses.setdefault((d.item_code, d.warehouse), frappe._dict({
+			"reposting_status": False,
+			"sle": d,
+			"args_idx": i
+		}))
 
 	i = 0
 	while i < len(args):
@@ -139,13 +146,21 @@
 			"warehouse": args[i].warehouse,
 			"posting_date": args[i].posting_date,
 			"posting_time": args[i].posting_time,
-			"creation": args[i].get("creation")
+			"creation": args[i].get("creation"),
+			"distinct_item_warehouses": distinct_item_warehouses
 		}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
 
-		for item_wh, new_sle in iteritems(obj.new_items):
-			if item_wh not in distinct_item_warehouses:
-				args.append(new_sle)
+		distinct_item_warehouses[(args[i].item_code, args[i].warehouse)].reposting_status = True
 
+		if obj.new_items_found:
+			for item_wh, data in iteritems(distinct_item_warehouses):
+				if ('args_idx' not in data and not data.reposting_status) or (data.sle_changed and data.reposting_status):
+					data.args_idx = len(args)
+					args.append(data.sle)
+				elif data.sle_changed and not data.reposting_status:
+					args[data.args_idx] = data.sle
+				
+				data.sle_changed = False
 		i += 1
 
 def get_args_for_voucher(voucher_type, voucher_no):
@@ -186,11 +201,12 @@
 		self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company")
 		self.get_precision()
 		self.valuation_method = get_valuation_method(self.item_code)
-		self.new_items = {}
+
+		self.new_items_found = False
+		self.distinct_item_warehouses = args.get("distinct_item_warehouses", frappe._dict())
 
 		self.data = frappe._dict()
 		self.initialize_previous_data(self.args)
-
 		self.build()
 
 	def get_precision(self):
@@ -296,11 +312,29 @@
 		elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse == self.args.warehouse:
 			return entries_to_fix
 		elif dependant_sle.item_code != self.item_code:
-			if (dependant_sle.item_code, dependant_sle.warehouse) not in self.new_items:
-				self.new_items[(dependant_sle.item_code, dependant_sle.warehouse)] = dependant_sle
+			self.update_distinct_item_warehouses(dependant_sle)
 			return entries_to_fix
 		elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data:
 			return entries_to_fix
+		else:
+			return self.append_future_sle_for_dependant(dependant_sle, entries_to_fix)
+
+	def update_distinct_item_warehouses(self, dependant_sle):
+		key = (dependant_sle.item_code, dependant_sle.warehouse)
+		val = frappe._dict({
+			"sle": dependant_sle
+		})
+		if key not in self.distinct_item_warehouses:
+			self.distinct_item_warehouses[key] = val
+			self.new_items_found = True
+		else:
+			existing_sle_posting_date = self.distinct_item_warehouses[key].get("sle", {}).get("posting_date")
+			if getdate(dependant_sle.posting_date) < getdate(existing_sle_posting_date):
+				val.sle_changed = True
+				self.distinct_item_warehouses[key] = val
+				self.new_items_found = True
+
+	def append_future_sle_for_dependant(self, dependant_sle, entries_to_fix):
 		self.initialize_previous_data(dependant_sle)
 
 		args = self.data[dependant_sle.warehouse].previous_sle \
@@ -393,6 +427,7 @@
 		rate = 0
 		# Material Transfer, Repack, Manufacturing
 		if sle.voucher_type == "Stock Entry":
+			self.recalculate_amounts_in_stock_entry(sle.voucher_no)
 			rate = frappe.db.get_value("Stock Entry Detail", sle.voucher_detail_no, "valuation_rate")
 		# Sales and Purchase Return
 		elif sle.voucher_type in ("Purchase Receipt", "Purchase Invoice", "Delivery Note", "Sales Invoice"):
@@ -442,7 +477,11 @@
 		frappe.db.set_value("Stock Entry Detail", sle.voucher_detail_no, "basic_rate", outgoing_rate)
 
 		# Update outgoing item's rate, recalculate FG Item's rate and total incoming/outgoing amount
-		stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no, for_update=True)
+		if not sle.dependant_sle_voucher_detail_no:
+			self.recalculate_amounts_in_stock_entry(sle.voucher_no)
+
+	def recalculate_amounts_in_stock_entry(self, voucher_no):
+		stock_entry = frappe.get_doc("Stock Entry", voucher_no, for_update=True)
 		stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False)
 		stock_entry.db_update()
 		for d in stock_entry.items: