Merge pull request #25070 from marination/update-items-bin

fix: Update Bin via Update Item on Purchase/Sales Order
diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..399b176
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,32 @@
+[flake8]
+ignore =
+    E121,
+    E126,
+    E127,
+    E128,
+    E203,
+    E225,
+    E226,
+    E231,
+    E241,
+    E251,
+    E261,
+    E265,
+    E302,
+    E303,
+    E305,
+    E402,
+    E501,
+    E741,
+    W291,
+    W292,
+    W293,
+    W391,
+    W503,
+    W504,
+    F403,
+    B007,
+    B950,
+    W191,
+
+max-line-length = 200
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
index d08a854..3377164 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
@@ -357,7 +357,6 @@
    "reqd": 1
   },
   {
-   "depends_on": "eval: doc.selling == 1",
    "fieldname": "margin",
    "fieldtype": "Section Break",
    "label": "Margin"
@@ -565,7 +564,7 @@
  "icon": "fa fa-gift",
  "idx": 1,
  "links": [],
- "modified": "2020-12-04 00:36:24.698219",
+ "modified": "2021-03-01 23:18:38.717613",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Pricing Rule",
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 07e75ac..96ad0fd 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -28,10 +28,16 @@
   "stock_qty",
   "sec_break1",
   "price_list_rate",
-  "discount_percentage",
-  "discount_amount",
   "col_break3",
   "base_price_list_rate",
+  "section_break_26",
+  "margin_type",
+  "margin_rate_or_amount",
+  "rate_with_margin",
+  "column_break_30",
+  "discount_percentage",
+  "discount_amount",
+  "base_rate_with_margin",
   "sec_break2",
   "rate",
   "amount",
@@ -789,6 +795,7 @@
    "fieldname": "stock_uom_rate",
    "fieldtype": "Currency",
    "label": "Rate of Stock UOM",
+   "no_copy": 1,
    "options": "currency",
    "read_only": 1
   },
@@ -799,12 +806,54 @@
    "no_copy": 1,
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "section_break_26",
+   "fieldtype": "Section Break",
+   "label": "Discount and Margin"
+  },
+  {
+   "depends_on": "price_list_rate",
+   "fieldname": "margin_type",
+   "fieldtype": "Select",
+   "label": "Margin Type",
+   "options": "\nPercentage\nAmount",
+   "print_hide": 1
+  },
+  {
+   "depends_on": "eval:doc.margin_type && doc.price_list_rate",
+   "fieldname": "margin_rate_or_amount",
+   "fieldtype": "Float",
+   "label": "Margin Rate or Amount",
+   "print_hide": 1
+  },
+  {
+   "depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount",
+   "fieldname": "rate_with_margin",
+   "fieldtype": "Currency",
+   "label": "Rate With Margin",
+   "options": "currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_30",
+   "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount",
+   "fieldname": "base_rate_with_margin",
+   "fieldtype": "Currency",
+   "label": "Rate With Margin (Company Currency)",
+   "options": "Company:company:default_currency",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-01-30 21:43:21.488258",
+ "modified": "2021-02-23 00:59:52.614805",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 720a917..d382386 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -1952,13 +1952,12 @@
  "is_submittable": 1,
  "links": [
   {
-   "custom": 1,
    "group": "Reference",
    "link_doctype": "POS Invoice",
    "link_fieldname": "consolidated_invoice"
   }
  ],
- "modified": "2021-02-01 15:42:26.261540",
+ "modified": "2021-03-31 15:42:26.261540",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index b403c7b..8e6952a 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -818,6 +818,7 @@
    "fieldname": "stock_uom_rate",
    "fieldtype": "Currency",
    "label": "Rate of Stock UOM",
+   "no_copy": 1,
    "options": "currency",
    "read_only": 1
   }
@@ -825,7 +826,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-01-30 21:42:37.796771",
+ "modified": "2021-02-23 01:05:22.123527",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice Item",
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index 75b2954..5baf693 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -27,11 +27,17 @@
   "stock_qty",
   "sec_break1",
   "price_list_rate",
+  "last_purchase_rate",
+  "col_break3",
+  "base_price_list_rate",
+  "discount_and_margin_section",
+  "margin_type",
+  "margin_rate_or_amount",
+  "rate_with_margin",
+  "column_break_28",
   "discount_percentage",
   "discount_amount",
-  "col_break3",
-  "last_purchase_rate",
-  "base_price_list_rate",
+  "base_rate_with_margin",
   "sec_break2",
   "rate",
   "amount",
@@ -733,15 +739,59 @@
    "fieldname": "stock_uom_rate",
    "fieldtype": "Currency",
    "label": "Rate of Stock UOM",
+   "no_copy": 1,
    "options": "currency",
    "read_only": 1
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "discount_and_margin_section",
+   "fieldtype": "Section Break",
+   "label": "Discount and Margin"
+  },
+  {
+   "depends_on": "price_list_rate",
+   "fieldname": "margin_type",
+   "fieldtype": "Select",
+   "label": "Margin Type",
+   "options": "\nPercentage\nAmount",
+   "print_hide": 1
+  },
+  {
+   "depends_on": "eval:doc.margin_type && doc.price_list_rate",
+   "fieldname": "margin_rate_or_amount",
+   "fieldtype": "Float",
+   "label": "Margin Rate or Amount",
+   "print_hide": 1
+  },
+  {
+   "depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount",
+   "fieldname": "rate_with_margin",
+   "fieldtype": "Currency",
+   "label": "Rate With Margin",
+   "options": "currency",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_28",
+   "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount",
+   "fieldname": "base_rate_with_margin",
+   "fieldtype": "Currency",
+   "label": "Rate With Margin (Company Currency)",
+   "options": "Company:company:default_currency",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-01-30 21:44:41.816974",
+ "modified": "2021-02-23 01:00:27.132705",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Order Item",
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 11ac703..f352bae 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -495,7 +495,7 @@
 			"voucher_no": self.name,
 			"company": self.company
 		})
-		if check_if_future_sle_exists(args):
+		if future_sle_exists(args):
 			create_repost_item_valuation_entry(args)
 		elif not is_reposting_pending():
 			check_if_stock_and_account_balance_synced(self.posting_date,
@@ -506,37 +506,42 @@
 		{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
 
 
-def check_if_future_sle_exists(args):
-	sl_entries = frappe.db.get_all("Stock Ledger Entry",
+def future_sle_exists(args):
+	sl_entries = frappe.get_all("Stock Ledger Entry",
 		filters={"voucher_type": args.voucher_type, "voucher_no": args.voucher_no},
 		fields=["item_code", "warehouse"],
 		order_by="creation asc")
 
-	distinct_item_warehouses = list(set([(d.item_code, d.warehouse) for d in sl_entries]))
+	if not sl_entries:
+		return
 
-	sle_exists = False
-	for item_code, warehouse in distinct_item_warehouses:
-		args.update({
-			"item_code": item_code,
-			"warehouse": warehouse
-		})
-		if get_sle(args):
-			sle_exists = True
-			break
-	return sle_exists
+	warehouse_items_map = {}
+	for entry in sl_entries:
+		if entry.warehouse not in warehouse_items_map:
+			warehouse_items_map[entry.warehouse] = set()
 
-def get_sle(args):
+		warehouse_items_map[entry.warehouse].add(entry.item_code)
+
+	or_conditions = []
+	for warehouse, items in warehouse_items_map.items():
+		or_conditions.append(
+			"warehouse = '{}' and item_code in ({})".format(
+				warehouse,
+				", ".join(frappe.db.escape(item) for item in items)
+			)
+		)
+
 	return frappe.db.sql("""
 		select name
 		from `tabStock Ledger Entry`
 		where
-			item_code=%(item_code)s
-			and warehouse=%(warehouse)s
-			and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
+			({})
+			and timestamp(posting_date, posting_time)
+				>= timestamp(%(posting_date)s, %(posting_time)s)
 			and voucher_no != %(voucher_no)s
 			and is_cancelled = 0
 		limit 1
-	""", args)
+		""".format(" or ".join(or_conditions)), args)
 
 def create_repost_item_valuation_entry(args):
 	args = frappe._dict(args)
@@ -554,4 +559,4 @@
 	repost_entry.allow_zero_rate = args.allow_zero_rate
 	repost_entry.flags.ignore_links = True
 	repost_entry.save()
-	repost_entry.submit()
\ No newline at end of file
+	repost_entry.submit()
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 10271cb..aab5770 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -109,7 +109,7 @@
 					elif item.discount_amount and item.pricing_rules:
 						item.rate =  item.price_list_rate - item.discount_amount
 
-				if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', 'POS Invoice Item']:
+				if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', 'POS Invoice Item', 'Purchase Invoice Item', 'Purchase Order Item', 'Purchase Receipt Item']:
 					item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
 					if flt(item.rate_with_margin) > 0:
 						item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index c963866..67b12fb 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -141,29 +141,6 @@
 		this.apply_price_list();
 	},
 
-	price_list_rate: function(doc, cdt, cdn) {
-		var item = frappe.get_doc(cdt, cdn);
-
-		frappe.model.round_floats_in(item, ["price_list_rate", "discount_percentage"]);
-
-		let item_rate = item.price_list_rate;
-		if (doc.doctype == "Purchase Order" && item.blanket_order_rate) {
-			item_rate = item.blanket_order_rate;
-		}
-
-		if (item.discount_percentage) {
-			item.discount_amount = flt(item_rate) * flt(item.discount_percentage) / 100;
-		}
-
-		if (item.discount_amount) {
-			item.rate = flt((item.price_list_rate) - (item.discount_amount), precision('rate', item));
-		} else {
-			item.rate = item_rate;
-		}
-
-		this.calculate_taxes_and_totals();
-	},
-
 	discount_percentage: function(doc, cdt, cdn) {
 		var item = frappe.get_doc(cdt, cdn);
 		item.discount_amount = 0.0;
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 9351f6d..310f3d3 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -649,6 +649,40 @@
 		}
 	},
 
+	price_list_rate: function(doc, cdt, cdn) {
+		var item = frappe.get_doc(cdt, cdn);
+		frappe.model.round_floats_in(item, ["price_list_rate", "discount_percentage"]);
+
+		// check if child doctype is Sales Order Item/Qutation Item and calculate the rate
+		if (in_list(["Quotation Item", "Sales Order Item", "Delivery Note Item", "Sales Invoice Item", "POS Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Purchase Receipt Item"]), cdt)
+			this.apply_pricing_rule_on_item(item);
+		else
+			item.rate = flt(item.price_list_rate * (1 - item.discount_percentage / 100.0),
+				precision("rate", item));
+
+		this.calculate_taxes_and_totals();
+	},
+
+	margin_rate_or_amount: function(doc, cdt, cdn) {
+		// calculated the revised total margin and rate on margin rate changes
+		let item = frappe.get_doc(cdt, cdn);
+		this.apply_pricing_rule_on_item(item);
+		this.calculate_taxes_and_totals();
+		cur_frm.refresh_fields();
+	},
+
+	margin_type: function(doc, cdt, cdn) {
+		// calculate the revised total margin and rate on margin type changes
+		let item = frappe.get_doc(cdt, cdn);
+		if (!item.margin_type) {
+			frappe.model.set_value(cdt, cdn, "margin_rate_or_amount", 0);
+		} else {
+			this.apply_pricing_rule_on_item(item, doc, cdt, cdn);
+			this.calculate_taxes_and_totals();
+			cur_frm.refresh_fields();
+		}
+	},
+
 	get_incoming_rate: function(item, posting_date, posting_time, voucher_type, company) {
 
 		let item_args = {
@@ -1030,7 +1064,7 @@
 	},
 
 	set_margin_amount_based_on_currency: function(exchange_rate) {
-		if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]), this.frm.doc.doctype) {
+		if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "Purchase Invoice", "Purchase Order", "Purchase Receipt"]), this.frm.doc.doctype) {
 			var me = this;
 			$.each(this.frm.doc.items || [], function(i, d) {
 				if(d.margin_type == "Amount") {
@@ -1139,6 +1173,11 @@
 				this.calculate_net_weight();
 			}
 
+			// for handling customization not to fetch price list rate
+			if (frappe.flags.dont_fetch_price_list_rate) {
+				return;
+			}
+
 			if (!dont_fetch_price_list_rate &&
 				frappe.meta.has_field(doc.doctype, "price_list_currency")) {
 				this.apply_price_list(item, true);
@@ -1280,10 +1319,10 @@
 	change_grid_labels: function(company_currency) {
 		var me = this;
 
-		this.frm.set_currency_labels(["base_rate", "base_net_rate", "base_price_list_rate", "base_amount", "base_net_amount"],
+		this.frm.set_currency_labels(["base_rate", "base_net_rate", "base_price_list_rate", "base_amount", "base_net_amount", "base_rate_with_margin"],
 			company_currency, "items");
 
-		this.frm.set_currency_labels(["rate", "net_rate", "price_list_rate", "amount", "net_amount", "stock_uom_rate"],
+		this.frm.set_currency_labels(["rate", "net_rate", "price_list_rate", "amount", "net_amount", "stock_uom_rate", "rate_with_margin"],
 			this.frm.doc.currency, "items");
 
 		if(this.frm.fields_dict["operations"]) {
@@ -1321,7 +1360,7 @@
 
 		// toggle columns
 		var item_grid = this.frm.fields_dict["items"].grid;
-		$.each(["base_rate", "base_price_list_rate", "base_amount"], function(i, fname) {
+		$.each(["base_rate", "base_price_list_rate", "base_amount", "base_rate_with_margin"], function(i, fname) {
 			if(frappe.meta.get_docfield(item_grid.doctype, fname))
 				item_grid.set_column_disp(fname, me.frm.doc.currency != company_currency);
 		});
@@ -1468,7 +1507,7 @@
 				});
 
 				// if doctype is Quotation Item / Sales Order Iten then add Margin Type and rate in item_list
-				if (in_list(["Quotation Item", "Sales Order Item", "Delivery Note Item", "Sales Invoice Item"]), d.doctype){
+				if (in_list(["Quotation Item", "Sales Order Item", "Delivery Note Item", "Sales Invoice Item",  "Purchase Invoice Item", "Purchase Order Item", "Purchase Receipt Item"]), d.doctype) {
 					item_list[0]["margin_type"] = d.margin_type;
 					item_list[0]["margin_rate_or_amount"] = d.margin_rate_or_amount;
 				}
diff --git a/erpnext/regional/italy/sales_invoice.js b/erpnext/regional/italy/sales_invoice.js
index 586a529..b54ac53 100644
--- a/erpnext/regional/italy/sales_invoice.js
+++ b/erpnext/regional/italy/sales_invoice.js
@@ -11,15 +11,10 @@
 						callback: function(r) {
 							frm.reload_doc();
 							if(r.message) {
-								var w = window.open(
-									frappe.urllib.get_full_url(
-										"/api/method/erpnext.regional.italy.utils.download_e_invoice_file?"
-										+ "file_name=" + r.message
-									)
-								)
-								if (!w) {
-									frappe.msgprint(__("Please enable pop-ups")); return;
-								}
+								open_url_post(frappe.request.url, {
+									cmd: 'frappe.core.doctype.file.file.download_file',
+									file_url: r.message
+								});
 							}
 						}
 					});
diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py
index 95b92e7..a1f5bb9 100644
--- a/erpnext/regional/italy/setup.py
+++ b/erpnext/regional/italy/setup.py
@@ -128,11 +128,8 @@
 				fetch_from="company.vat_collectability"),
 			dict(fieldname='sb_e_invoicing_reference', label='E-Invoicing',
 				fieldtype='Section Break', insert_after='against_income_account', print_hide=1),
-			dict(fieldname='company_tax_id', label='Company Tax ID',
-				fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1,
-				fetch_from="company.tax_id"),
 			dict(fieldname='company_fiscal_code', label='Company Fiscal Code',
-				fieldtype='Data', insert_after='company_tax_id', print_hide=1, read_only=1,
+				fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1,
 				fetch_from="company.fiscal_code"),
 			dict(fieldname='company_fiscal_regime', label='Company Fiscal Regime',
 				fieldtype='Data', insert_after='company_fiscal_code', print_hide=1, read_only=1,
@@ -217,4 +214,4 @@
 	update_permission_property(doctype, 'Accounts Manager', 0, 'delete', 1)
 	add_permission(doctype, 'Accounts Manager', 1)
 	update_permission_property(doctype, 'Accounts Manager', 1, 'write', 1)
-	update_permission_property(doctype, 'Accounts Manager', 1, 'create', 1)
\ No newline at end of file
+	update_permission_property(doctype, 'Accounts Manager', 1, 'create', 1)
diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py
index 6842fb2..08573cd 100644
--- a/erpnext/regional/italy/utils.py
+++ b/erpnext/regional/italy/utils.py
@@ -1,6 +1,8 @@
 from __future__ import unicode_literals
 
-import frappe, json, os
+import io
+import json
+import frappe
 from frappe.utils import flt, cstr
 from erpnext.controllers.taxes_and_totals import get_itemised_tax
 from frappe import _
@@ -28,20 +30,22 @@
 
 @frappe.whitelist()
 def export_invoices(filters=None):
-	saved_xmls = []
+	frappe.has_permission('Sales Invoice', throw=True)
 
-	invoices = frappe.get_all("Sales Invoice", filters=get_conditions(filters), fields=["*"])
+	invoices = frappe.get_all(
+		"Sales Invoice",
+		filters=get_conditions(filters),
+		fields=["name", "company_tax_id"]
+	)
 
-	for invoice in invoices:
-		attachments = get_e_invoice_attachments(invoice)
-		saved_xmls += [attachment.file_name for attachment in attachments]
+	attachments = get_e_invoice_attachments(invoices)
 
-	zip_filename = "{0}-einvoices.zip".format(frappe.utils.get_datetime().strftime("%Y%m%d_%H%M%S"))
+	zip_filename = "{0}-einvoices.zip".format(
+		frappe.utils.get_datetime().strftime("%Y%m%d_%H%M%S"))
 
-	download_zip(saved_xmls, zip_filename)
+	download_zip(attachments, zip_filename)
 
 
-@frappe.whitelist()
 def prepare_invoice(invoice, progressive_number):
 	#set company information
 	company = frappe.get_doc("Company", invoice.company)
@@ -98,7 +102,7 @@
 def get_conditions(filters):
 	filters = json.loads(filters)
 
-	conditions = {"docstatus": 1}
+	conditions = {"docstatus": 1, "company_tax_id": ("!=", "")}
 
 	if filters.get("company"): conditions["company"] = filters["company"]
 	if filters.get("customer"): conditions["customer"] =  filters["customer"]
@@ -111,23 +115,22 @@
 
 	return conditions
 
-#TODO: Use function from frappe once PR #6853 is merged.
+
 def download_zip(files, output_filename):
-	from zipfile import ZipFile
+	import zipfile
 
-	input_files = [frappe.get_site_path('private', 'files', filename) for filename in files]
-	output_path = frappe.get_site_path('private', 'files', output_filename)
+	zip_stream = io.BytesIO()
+	with zipfile.ZipFile(zip_stream, 'w', zipfile.ZIP_DEFLATED) as zip_file:
+		for file in files:
+			file_path = frappe.utils.get_files_path(
+				file.file_name, is_private=file.is_private)
 
-	with ZipFile(output_path, 'w') as output_zip:
-		for input_file in input_files:
-			output_zip.write(input_file, arcname=os.path.basename(input_file))
-
-	with open(output_path, 'rb') as fileobj:
-		filedata = fileobj.read()
+			zip_file.write(file_path, arcname=file.file_name)
 
 	frappe.local.response.filename = output_filename
-	frappe.local.response.filecontent = filedata
+	frappe.local.response.filecontent = zip_stream.getvalue()
 	frappe.local.response.type = "download"
+	zip_stream.close()
 
 def get_invoice_summary(items, taxes):
 	summary_data = frappe._dict()
@@ -307,23 +310,12 @@
 @frappe.whitelist()
 def generate_single_invoice(docname):
 	doc = frappe.get_doc("Sales Invoice", docname)
-
+	frappe.has_permission("Sales Invoice", doc=doc, throw=True)
 
 	e_invoice = prepare_and_attach_invoice(doc, True)
+	return e_invoice.file_url
 
-	return e_invoice.file_name
-
-@frappe.whitelist()
-def download_e_invoice_file(file_name):
-	content = None
-	with open(frappe.get_site_path('private', 'files', file_name), "r") as f:
-		content = f.read()
-
-	frappe.local.response.filename = file_name
-	frappe.local.response.filecontent = content
-	frappe.local.response.type = "download"
-
-#Delete e-invoice attachment on cancel.
+# Delete e-invoice attachment on cancel.
 def sales_invoice_on_cancel(doc, method):
 	if get_company_country(doc.company) not in ['Italy',
 		'Italia', 'Italian Republic', 'Repubblica Italiana']:
@@ -335,16 +327,38 @@
 def get_company_country(company):
 	return frappe.get_cached_value('Company', company, 'country')
 
-def get_e_invoice_attachments(invoice):
-	if not invoice.company_tax_id:
-		return []
+def get_e_invoice_attachments(invoices):
+	if not isinstance(invoices, list):
+		if not invoices.company_tax_id:
+			return
+
+		invoices = [invoices]
+
+	tax_id_map = {
+		invoice.name: (
+			invoice.company_tax_id
+			if invoice.company_tax_id.startswith("IT")
+			else "IT" + invoice.company_tax_id
+		) for invoice in invoices
+	}
+
+	attachments = frappe.get_all(
+		"File",
+		fields=("name", "file_name", "attached_to_name", "is_private"),
+		filters= {
+			"attached_to_name": ('in', tax_id_map),
+			"attached_to_doctype": 'Sales Invoice'
+		}
+	)
 
 	out = []
-	attachments = get_attachments(invoice.doctype, invoice.name)
-	company_tax_id = invoice.company_tax_id if invoice.company_tax_id.startswith("IT") else "IT" + invoice.company_tax_id
-
 	for attachment in attachments:
-		if attachment.file_name and attachment.file_name.startswith(company_tax_id) and attachment.file_name.endswith(".xml"):
+		if (
+			attachment.file_name
+			and attachment.file_name.endswith(".xml")
+			and attachment.file_name.startswith(
+				tax_id_map.get(attachment.attached_to_name))
+		):
 			out.append(attachment)
 
 	return out
diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json
index a6785f7..8b53902 100644
--- a/erpnext/selling/doctype/quotation_item/quotation_item.json
+++ b/erpnext/selling/doctype/quotation_item/quotation_item.json
@@ -641,6 +641,7 @@
    "fieldname": "stock_uom_rate",
    "fieldtype": "Currency",
    "label": "Rate of Stock UOM",
+   "no_copy": 1,
    "options": "currency",
    "read_only": 1
   }
@@ -648,7 +649,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-01-30 21:39:40.174551",
+ "modified": "2021-02-23 01:13:54.670763",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Quotation Item",
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index fd9ddd8..bad4d97 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -778,6 +778,7 @@
 
 @frappe.whitelist()
 def make_purchase_order_for_default_supplier(source_name, selected_items=None, target_doc=None):
+	"""Creates Purchase Order for each Supplier. Returns a list of doc objects."""
 	if not selected_items: return
 
 	if isinstance(selected_items, string_types):
@@ -820,15 +821,16 @@
 		target.stock_qty = (flt(source.stock_qty) - flt(source.ordered_qty))
 		target.project = source_parent.project
 
-	suppliers = [item.get('supplier') for item in selected_items if item.get('supplier') and item.get('supplier')]
-	suppliers = list(set(suppliers))
+	suppliers = [item.get('supplier') for item in selected_items if item.get('supplier')]
+	suppliers = list(dict.fromkeys(suppliers)) # remove duplicates while preserving order
 
-	items_to_map = [item.get('item_code') for item in selected_items if item.get('item_code') and item.get('item_code')]
+	items_to_map = [item.get('item_code') for item in selected_items if item.get('item_code')]
 	items_to_map = list(set(items_to_map))
 
 	if not suppliers:
 		frappe.throw(_("Please set a Supplier against the Items to be considered in the Purchase Order."))
 
+	purchase_orders = []
 	for supplier in suppliers:
 		doc = get_mapped_doc("Sales Order", source_name, {
 			"Sales Order": {
@@ -872,7 +874,9 @@
 
 		doc.insert()
 		frappe.db.commit()
-		return doc
+		purchase_orders.append(doc)
+
+	return purchase_orders
 
 @frappe.whitelist()
 def make_purchase_order(source_name, selected_items=None, target_doc=None):
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 7752b7b..ab5f089 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -769,7 +769,7 @@
 		so = make_sales_order(item_list=so_items, do_not_submit=True)
 		so.submit()
 
-		po = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])
+		po = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])[0]
 		po.submit()
 
 		dn = create_dn_against_so(so.name, delivered_qty=2)
@@ -851,7 +851,7 @@
 		so.submit()
 
 		# create po for only one item
-		po1 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])
+		po1 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])[0]
 		po1.submit()
 
 		self.assertEqual(so.customer, po1.customer)
@@ -861,7 +861,7 @@
 		self.assertEqual(len(po1.items), 1)
 
 		# create po for remaining item
-		po2 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[1]])
+		po2 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[1]])[0]
 		po2.submit()
 
 		# teardown
@@ -872,6 +872,45 @@
 		so.load_from_db()
 		so.cancel()
 
+	def test_drop_shipping_full_for_default_suppliers(self):
+		"""Test if multiple POs are generated in one go against different default suppliers."""
+		from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order_for_default_supplier
+
+		if not frappe.db.exists("Item", "_Test Item for Drop Shipping 1"):
+			make_item("_Test Item for Drop Shipping 1", {"is_stock_item": 1, "delivered_by_supplier": 1})
+
+		if not frappe.db.exists("Item", "_Test Item for Drop Shipping 2"):
+			make_item("_Test Item for Drop Shipping 2", {"is_stock_item": 1, "delivered_by_supplier": 1})
+
+		so_items = [
+			{
+				"item_code": "_Test Item for Drop Shipping 1",
+				"warehouse": "",
+				"qty": 2,
+				"rate": 400,
+				"delivered_by_supplier": 1,
+				"supplier": '_Test Supplier'
+			},
+			{
+				"item_code": "_Test Item for Drop Shipping 2",
+				"warehouse": "",
+				"qty": 2,
+				"rate": 400,
+				"delivered_by_supplier": 1,
+				"supplier": '_Test Supplier 1'
+			}
+		]
+
+		# create so and po
+		so = make_sales_order(item_list=so_items, do_not_submit=True)
+		so.submit()
+
+		purchase_orders = make_purchase_order_for_default_supplier(so.name, selected_items=so_items)
+
+		self.assertEqual(len(purchase_orders), 2)
+		self.assertEqual(purchase_orders[0].supplier, '_Test Supplier')
+		self.assertEqual(purchase_orders[1].supplier, '_Test Supplier 1')
+
 	def test_reserved_qty_for_closing_so(self):
 		bin = frappe.get_all("Bin", filters={"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
 			fields=["reserved_qty"])
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index 37e47a9..1e5590e 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -786,6 +786,7 @@
    "fieldname": "stock_uom_rate",
    "fieldtype": "Currency",
    "label": "Rate of Stock UOM",
+   "no_copy": 1,
    "options": "currency",
    "read_only": 1
   }
@@ -793,7 +794,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-01-30 21:35:07.617320",
+ "modified": "2021-02-23 01:15:05.803091",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Sales Order Item",
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
index 39f54fa..a5a739c 100644
--- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
+++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
@@ -64,10 +64,7 @@
 				{fieldname: 'print', fieldtype: 'Data', label: 'Print Preview'}
 			],
 			primary_action: () => {
-				const frm = this.events.get_frm();
-				frm.doc = this.doc;
-				frm.print_preview.lang_code = frm.doc.language;
-				frm.print_preview.printit(true);
+				this.print_receipt();
 			},
 			primary_action_label: __('Print'),
 		});
@@ -200,13 +197,21 @@
 		});
 
 		this.$summary_container.on('click', '.print-btn', () => {
-			const frm = this.events.get_frm();
-			frm.doc = this.doc;
-			frm.print_preview.lang_code = frm.doc.language;
-			frm.print_preview.printit(true);
+			this.print_receipt();
 		});
 	}
 
+	print_receipt() {
+		const frm = this.events.get_frm();
+		frappe.utils.print(
+			frm.doctype,
+			frm.docname,
+			frm.pos_print_format,
+			frm.doc.letter_head,
+			frm.doc.language || frappe.boot.lang
+		);
+	}
+
 	attach_shortcuts() {
 		const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl';
 		this.$summary_container.find('.print-btn').attr("title", `${ctrl_label}+P`);
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index ce08464..0428573 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -127,20 +127,6 @@
 		this.set_dynamic_labels();
 	},
 
-	price_list_rate: function(doc, cdt, cdn) {
-		var item = frappe.get_doc(cdt, cdn);
-		frappe.model.round_floats_in(item, ["price_list_rate", "discount_percentage"]);
-
-		// check if child doctype is Sales Order Item/Qutation Item and calculate the rate
-		if(in_list(["Quotation Item", "Sales Order Item", "Delivery Note Item", "Sales Invoice Item", "POS Invoice Item"]), cdt)
-			this.apply_pricing_rule_on_item(item);
-		else
-			item.rate = flt(item.price_list_rate * (1 - item.discount_percentage / 100.0),
-				precision("rate", item));
-
-		this.calculate_taxes_and_totals();
-	},
-
 	discount_percentage: function(doc, cdt, cdn) {
 		var item = frappe.get_doc(cdt, cdn);
 		item.discount_amount = 0.0;
@@ -353,26 +339,6 @@
 		refresh_field('product_bundle_help');
 	},
 
-	margin_rate_or_amount: function(doc, cdt, cdn) {
-		// calculated the revised total margin and rate on margin rate changes
-		var item = locals[cdt][cdn];
-		this.apply_pricing_rule_on_item(item)
-		this.calculate_taxes_and_totals();
-		cur_frm.refresh_fields();
-	},
-
-	margin_type: function(doc, cdt, cdn){
-		// calculate the revised total margin and rate on margin type changes
-		var item = locals[cdt][cdn];
-		if(!item.margin_type) {
-			frappe.model.set_value(cdt, cdn, "margin_rate_or_amount", 0);
-		} else {
-			this.apply_pricing_rule_on_item(item, doc,cdt, cdn)
-			this.calculate_taxes_and_totals();
-			cur_frm.refresh_fields();
-		}
-	},
-
 	company_address: function() {
 		var me = this;
 		if(this.frm.doc.company_address) {
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 3544390..d326a04 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -101,7 +101,7 @@
 			for f in fieldname:
 				toggle_print_hide(self.meta if key == "parent" else item_meta, f)
 
-		super(DeliveryNote, self).before_print()
+		super(DeliveryNote, self).before_print(settings)
 
 	def set_actual_qty(self):
 		for d in self.get('items'):
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index 1799624..b05090a 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -750,6 +750,7 @@
    "fieldname": "stock_uom_rate",
    "fieldtype": "Currency",
    "label": "Rate of Stock UOM",
+   "no_copy": 1,
    "options": "currency",
    "read_only": 1
   }
@@ -758,7 +759,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-01-30 21:42:03.767968",
+ "modified": "2021-02-23 01:04:08.588104",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 5539123..2079cf8 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -717,6 +717,18 @@
 				.on('focus', function(e) {
 					$(e.target).val('').trigger('input');
 				})
+				.on("awesomplete-open", () => {
+					let modal = field.$input.parents('.modal-dialog')[0];
+					if (modal) {
+						$(modal).removeClass("modal-dialog-scrollable");
+					}
+				})
+				.on("awesomplete-close", () => {
+					let modal = field.$input.parents('.modal-dialog')[0];
+					if (modal) {
+						$(modal).addClass("modal-dialog-scrollable");
+					}
+				});
 		});
 	},
 
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 8974ad9..efe3642 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -37,10 +37,16 @@
   "returned_qty",
   "rate_and_amount",
   "price_list_rate",
-  "discount_percentage",
-  "discount_amount",
   "col_break3",
   "base_price_list_rate",
+  "discount_and_margin_section",
+  "margin_type",
+  "margin_rate_or_amount",
+  "rate_with_margin",
+  "column_break_37",
+  "discount_percentage",
+  "discount_amount",
+  "base_rate_with_margin",
   "sec_break1",
   "rate",
   "amount",
@@ -880,6 +886,7 @@
    "fieldname": "stock_uom_rate",
    "fieldtype": "Currency",
    "label": "Rate of Stock UOM",
+   "no_copy": 1,
    "options": "currency",
    "read_only": 1
   },
@@ -890,12 +897,55 @@
    "no_copy": 1,
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "discount_and_margin_section",
+   "fieldtype": "Section Break",
+   "label": "Discount and Margin"
+  },
+  {
+   "depends_on": "price_list_rate",
+   "fieldname": "margin_type",
+   "fieldtype": "Select",
+   "label": "Margin Type",
+   "options": "\nPercentage\nAmount",
+   "print_hide": 1
+  },
+  {
+   "depends_on": "eval:doc.margin_type && doc.price_list_rate",
+   "fieldname": "margin_rate_or_amount",
+   "fieldtype": "Float",
+   "label": "Margin Rate or Amount",
+   "print_hide": 1
+  },
+  {
+   "depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount",
+   "fieldname": "rate_with_margin",
+   "fieldtype": "Currency",
+   "label": "Rate With Margin",
+   "options": "currency",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_37",
+   "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount",
+   "fieldname": "base_rate_with_margin",
+   "fieldtype": "Currency",
+   "label": "Rate With Margin (Company Currency)",
+   "options": "Company:company:default_currency",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-01-30 21:44:06.918515",
+ "modified": "2021-02-23 00:59:14.360847",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 4979234..af3c4e5 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -848,7 +848,6 @@
 		}
 		erpnext.hide_company();
 		erpnext.utils.add_item(this.frm);
-		this.frm.trigger('add_to_transit');
 	},
 
 	scan_barcode: function() {
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index f54b3c1..121c51c 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -207,11 +207,11 @@
 
 
 	def build(self):
-		from erpnext.controllers.stock_controller import check_if_future_sle_exists
+		from erpnext.controllers.stock_controller import future_sle_exists
 
 		if self.args.get("sle_id"):
 			self.process_sle_against_current_timestamp()
-			if not check_if_future_sle_exists(self.args):
+			if not future_sle_exists(self.args):
 				self.update_bin()
 		else:
 			entries_to_fix = self.get_future_entries_to_fix()
@@ -856,4 +856,4 @@
 			and qty_after_transaction < 0
 		order by timestamp(posting_date, posting_time) asc
 		limit 1
-	""", args, as_dict=1)
\ No newline at end of file
+	""", args, as_dict=1)
diff --git a/erpnext/templates/includes/issue_row.html b/erpnext/templates/includes/issue_row.html
index d909c5f..a04f558 100644
--- a/erpnext/templates/includes/issue_row.html
+++ b/erpnext/templates/includes/issue_row.html
@@ -1,6 +1,6 @@
 <div class="web-list-item transaction-list-item">
 	<a href="/issues?name={{ doc.name }}" class="no-underline">
-		<div class="row py-4 border-bottom">
+		<div class="row py-4">
 			<div class="col-3 d-flex align-items-center">
 				{% set indicator = 'red' if doc.status == 'Open' else 'gray' %}
 				{% set indicator = 'green' if doc.status == 'Closed' else indicator %}
diff --git a/sider.yml b/sider.yml
new file mode 100644
index 0000000..2ca6e8d
--- /dev/null
+++ b/sider.yml
@@ -0,0 +1,3 @@
+linter:
+  flake8:
+    config: .flake8
\ No newline at end of file