Merge pull request #23965 from vorasmit/remarks-fix-develop

fix: add remarks to sales invoice
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..24f122a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+# Root editor config file
+root = true
+
+# Common settings
+[*]
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+charset = utf-8
+
+# python, js indentation settings
+[{*.py,*.js}]
+indent_style = tab
+indent_size = 4
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..26bb7ab
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+  - name: Community Forum
+    url: https://discuss.erpnext.com/
+    about: For general QnA, discussions and community help.
diff --git a/.github/helper/documentation.py b/.github/helper/documentation.py
index b603ed5..9cc4663 100644
--- a/.github/helper/documentation.py
+++ b/.github/helper/documentation.py
@@ -21,8 +21,8 @@
 			if word.startswith('http') and uri_validator(word):
 				parsed_url = urlparse(word)
 				if parsed_url.netloc == "github.com":
-					_, org, repo, _type, ref = parsed_url.path.split('/')
-					if org == "frappe" and repo in docs_repos:
+					parts = parsed_url.path.split('/')
+					if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
 						return True
 
 
diff --git a/.travis/site_config.json b/.travis/site_config.json
index dae8009..572bbd0 100644
--- a/.travis/site_config.json
+++ b/.travis/site_config.json
@@ -9,5 +9,6 @@
  "root_login": "root",
  "root_password": "travis",
  "host_name": "http://test_site:8000",
- "install_apps": ["erpnext"]
+ "install_apps": ["erpnext"],
+ "throttle_user_limit": 100
 }
\ No newline at end of file
diff --git a/README.md b/README.md
index 0f6a521..15782a2 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
         <p>ERP made simple</p>
     </p>
 
-[![Build Status](https://travis-ci.com/frappe/erpnext.svg)](https://travis-ci.com/frappe/erpnext)
+[![Build Status](https://api.travis-ci.com/frappe/erpnext.svg?branch=develop)](https://travis-ci.com/frappe/erpnext)
 [![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
 [![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop)
 
diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
index 39bf4b0..85f54f9 100644
--- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
+++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
@@ -6,9 +6,8 @@
 from frappe import _
 from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate, get_link_to_form
 from erpnext.accounts.report.general_ledger.general_ledger import execute
-from frappe.utils.dashboard import cache_source, get_from_date_from_timespan
-from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending
-
+from frappe.utils.dashboard import cache_source
+from frappe.utils.dateutils import get_from_date_from_timespan, get_period_ending
 from frappe.utils.nestedset import get_descendants_of
 
 @frappe.whitelist()
diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json
index b2a3f83..a18dbff 100644
--- a/erpnext/accounts/desk_page/accounting/accounting.json
+++ b/erpnext/accounts/desk_page/accounting/accounting.json
@@ -43,7 +43,7 @@
   {
    "hidden": 0,
    "label": "Bank Statement",
-   "links": "[\n    {\n        \"label\": \"Bank\",\n        \"name\": \"Bank\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Bank Account\",\n        \"name\": \"Bank Account\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Bank Clearance\",\n        \"name\": \"Bank Clearance\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Bank Reconciliation\",\n        \"name\": \"bank-reconciliation\",\n        \"type\": \"page\"\n    },\n    {\n        \"dependencies\": [\n            \"GL Entry\"\n        ],\n        \"doctype\": \"GL Entry\",\n        \"is_query_report\": true,\n        \"label\": \"Bank Reconciliation Statement\",\n        \"name\": \"Bank Reconciliation Statement\",\n        \"type\": \"report\"\n    },\n    {\n        \"label\": \"Bank Statement Transaction Entry\",\n        \"name\": \"Bank Statement Transaction Entry\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Bank Statement Settings\",\n        \"name\": \"Bank Statement Settings\",\n        \"type\": \"doctype\"\n    }\n]"
+   "links": "[\n    {\n        \"label\": \"Bank\",\n        \"name\": \"Bank\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Bank Account\",\n        \"name\": \"Bank Account\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Bank Clearance\",\n        \"name\": \"Bank Clearance\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Bank Reconciliation\",\n        \"name\": \"bank-reconciliation\",\n        \"type\": \"page\"\n    },\n    {\n        \"dependencies\": [\n            \"GL Entry\"\n        ],\n        \"doctype\": \"GL Entry\",\n        \"is_query_report\": true,\n        \"label\": \"Bank Reconciliation Statement\",\n        \"name\": \"Bank Reconciliation Statement\",\n        \"type\": \"report\"\n    }\n]"
   },
   {
    "hidden": 0,
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json
index 3fc109b..849df18 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json
@@ -910,98 +910,8 @@
             },
             "is_group": 1
         },
-        "Passiva": {
+        "Passiva - Verbindlichkeiten": {
             "root_type": "Liability",
-            "A - Eigenkapital": {
-                "account_type": "Equity",
-                "is_group": 1,
-                "I - Gezeichnetes Kapital": {
-                    "account_type": "Equity",
-					"is_group": 1,
-					"Gezeichnetes Kapital": {
-						"account_type": "Equity",
-						"account_number": "2900"
-					},
-					"Ausstehende Einlagen auf das gezeichnete Kapital": {
-						"account_number": "2910",
-						"is_group": 1
-					}
-                },
-                "II - Kapitalr\u00fccklage": {
-                    "account_type": "Equity",
-					"is_group": 1,
-					"Kapitalr\u00fccklage": {
-						"account_number": "2920"
-					}
-                },
-                "III - Gewinnr\u00fccklagen": {
-                    "account_type": "Equity",
-                    "1 - gesetzliche R\u00fccklage": {
-                        "account_type": "Equity",
-						"is_group": 1,
-						"Gesetzliche R\u00fccklage": {
-							"account_number": "2930"
-						}
-                    },
-                    "2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
-                        "account_type": "Equity",
-                        "is_group": 1
-                    },
-                    "3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
-                        "account_type": "Equity",
-						"is_group": 1,
-						"Satzungsm\u00e4\u00dfige R\u00fccklagen": {
-							"account_number": "2950"
-						}
-                    },
-                    "4 - andere Gewinnr\u00fccklagen": {
-                        "account_type": "Equity",
-                        "is_group": 1,
-                        "Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
-                            "is_group": 1,
-                            "Gewinnr\u00fccklagen (BilMoG)": {
-                                "account_number": "2963"
-                            },
-                            "Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
-                                "account_number": "2964"
-                            },
-                            "Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
-                                "account_number": "2965"
-                            },
-                            "Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
-                                "account_number": "2966"
-                            }
-                        },
-                        "Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
-                            "account_number": "2967"
-                        },
-                        "Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
-                            "account_number": "2968"
-                        },
-                        "Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
-                            "account_number": "2969"
-                        }
-                    },
-                    "is_group": 1
-                },
-                "IV - Gewinnvortrag/Verlustvortrag": {
-                    "account_type": "Equity",
-                    "is_group": 1,
-					"Gewinnvortrag vor Verwendung": {
-						"account_number": "2970"
-					},
-					"Verlustvortrag vor Verwendung": {
-						"account_number": "2978"
-					}
-                },
-                "V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
-                    "account_type": "Equity",
-                    "is_group": 1
-                },
-                "Einlagen stiller Gesellschafter": {
-                    "account_number": "9295"
-                }
-            },
             "B - R\u00fcckstellungen": {
                 "is_group": 1,
                 "1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": {
@@ -1618,6 +1528,143 @@
             },
             "is_group": 1
         },
+        "Passiva - Eigenkapital": {
+            "root_type": "Equity",
+            "A - Eigenkapital": {
+                "account_type": "Equity",
+                "is_group": 1,
+                "I - Gezeichnetes Kapital": {
+                    "account_type": "Equity",
+                    "is_group": 1,
+                    "Gezeichnetes Kapital": {
+                        "account_number": "2900",
+                        "account_type": "Equity"
+                    },
+                    "Gesch\u00e4ftsguthaben der verbleibenden Mitglieder": {
+                        "account_number": "2901"
+                    },
+                    "Gesch\u00e4ftsguthaben der ausscheidenden Mitglieder": {
+                        "account_number": "2902"
+                    },
+                    "Gesch\u00e4ftsguthaben aus gek\u00fcndigten Gesch\u00e4ftsanteilen": {
+                        "account_number": "2903"
+                    },
+                    "R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
+                        "account_number": "2906"
+                    },
+                    "Gegenkonto R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
+                        "account_number": "2907"
+                    },
+                    "Kapitalerh\u00f6hung aus Gesellschaftsmitteln": {
+                        "account_number": "2908"
+                    },
+                    "Ausstehende Einlagen auf das gezeichnete Kapital, nicht eingefordert": {
+                        "account_number": "2910"
+                    }
+                },
+                "II - Kapitalr\u00fccklage": {
+                    "account_type": "Equity",
+                    "is_group": 1,
+                    "Kapitalr\u00fccklage": {
+                        "account_number": "2920"
+                    },
+                    "Kapitalr\u00fccklage durch Ausgabe von Anteilen \u00fcber Nennbetrag": {
+                        "account_number": "2925"
+                    },
+                    "Kapitalr\u00fccklage durch Ausgabe von Schuldverschreibungen": {
+                        "account_number": "2926"
+                    },
+                    "Kapitalr\u00fccklage durch Zuzahlungen gegen Gew\u00e4hrung eines Vorzugs": {
+                        "account_number": "2927"
+                    },
+                    "Kapitalr\u00fccklage durch Zuzahlungen in das Eigenkapital": {
+                        "account_number": "2928"
+                    },
+                    "Nachschusskapital (Gegenkonto 1299)": {
+                        "account_number": "2929"
+                    }
+                },
+                "III - Gewinnr\u00fccklagen": {
+                    "account_type": "Equity",
+                    "1 - gesetzliche R\u00fccklage": {
+                        "account_type": "Equity",
+                        "is_group": 1,
+                        "Gesetzliche R\u00fccklage": {
+                            "account_number": "2930"
+                        }
+                    },
+                    "2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
+                        "account_type": "Equity",
+                        "is_group": 1,
+                        "R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
+                            "account_number": "2935"
+                        }
+                    },
+                    "3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
+                        "account_type": "Equity",
+                        "is_group": 1,
+                        "Satzungsm\u00e4\u00dfige R\u00fccklagen": {
+                            "account_number": "2950"
+                        }
+                    },
+                    "4 - andere Gewinnr\u00fccklagen": {
+                        "account_type": "Equity",
+                        "is_group": 1,
+                        "Andere Gewinnr\u00fccklagen": {
+                            "account_number": "2960"
+                        },
+                        "Andere Gewinnr\u00fccklagen aus dem Erwerb eigener Anteile": {
+                            "account_number": "2961"
+                        },
+                        "Eigenkapitalanteil von Wertaufholungen": {
+                            "account_number": "2962"
+                        },
+                        "Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
+                            "is_group": 1,
+                            "Gewinnr\u00fccklagen (BilMoG)": {
+                                "account_number": "2963"
+                            },
+                            "Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
+                                "account_number": "2964"
+                            },
+                            "Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
+                                "account_number": "2965"
+                            },
+                            "Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
+                                "account_number": "2966"
+                            }
+                        },
+                        "Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
+                            "account_number": "2967"
+                        },
+                        "Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
+                            "account_number": "2968"
+                        },
+                        "Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
+                            "account_number": "2969"
+                        }
+                    },
+                    "is_group": 1
+                },
+                "IV - Gewinnvortrag/Verlustvortrag": {
+                    "account_type": "Equity",
+                    "is_group": 1,
+                    "Gewinnvortrag vor Verwendung": {
+                        "account_number": "2970"
+                    },
+                    "Verlustvortrag vor Verwendung": {
+                        "account_number": "2978"
+                    }
+                },
+                "V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
+                    "account_type": "Equity",
+                    "is_group": 1
+                },
+                "Einlagen stiller Gesellschafter": {
+                    "account_number": "9295"
+                }
+            }
+        },
         "1 - Umsatzerl\u00f6se": {
             "root_type": "Income",
             "is_group": 1,
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py
index 6c83e3b..acb11e5 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py
@@ -245,6 +245,9 @@
                     "account_number": "2200"
                 },
                 _("Duties and Taxes"): {
+                    _("TDS Payable"): {
+                        "account_number": "2310"
+                    },
                     "account_type": "Tax",
                     "is_group": 1,
                     "account_number": "2300"
diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py
index 0605d89..113bea0 100644
--- a/erpnext/accounts/doctype/account/test_account.py
+++ b/erpnext/accounts/doctype/account/test_account.py
@@ -172,7 +172,7 @@
 			frappe.delete_doc("Account", doc)
 
 
-def _make_test_records(verbose):
+def _make_test_records(verbose=None):
 	from frappe.test_runner import make_test_objects
 
 	accounts = [
diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
index 2754633..e9fc5f0 100644
--- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
@@ -9,11 +9,13 @@
 from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
 from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
 from erpnext.accounts.page.bank_reconciliation.bank_reconciliation import reconcile, get_linked_payments
+from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
 
 test_dependencies = ["Item", "Cost Center"]
 
 class TestBankTransaction(unittest.TestCase):
 	def setUp(self):
+		make_pos_profile()
 		add_transactions()
 		add_payments()
 
@@ -27,6 +29,9 @@
 		frappe.db.sql("""delete from `tabPayment Entry Reference`""")
 		frappe.db.sql("""delete from `tabPayment Entry`""")
 
+		# Delete POS Profile
+		frappe.db.sql("delete from `tabPOS Profile`")
+
 		frappe.flags.test_bank_transactions_created = False
 		frappe.flags.test_payments_created = False
 
diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py
index 0f115f9..cd88b11 100644
--- a/erpnext/accounts/doctype/budget/test_budget.py
+++ b/erpnext/accounts/doctype/budget/test_budget.py
@@ -159,10 +159,10 @@
 
 		budget = make_budget(budget_against="Cost Center")
 		month = now_datetime().month
-		if month > 10:
-			month = 10
+		if month > 9:
+			month = 9
 
-		for i in range(month):
+		for i in range(month+1):
 			jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
 				"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
 
@@ -181,10 +181,10 @@
 
 		budget = make_budget(budget_against="Project")
 		month = now_datetime().month
-		if month > 10:
-			month = 10
+		if month > 9:
+			month = 9
 
-		for i in range(month):
+		for i in range(month + 1):
 			jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
 				"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")
 
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js
index 2235298..f795dfa 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js
@@ -94,8 +94,7 @@
 				callback: function(r) {
 					if(r.message===false) {
 						frm.set_value("company", "");
-						frappe.throw(__(`Transactions against the company already exist!
-							Chart Of accounts can be imported for company with no transactions`));
+						frappe.throw(__("Transactions against the Company already exist! Chart of Accounts can only be imported for a Company with no transactions."));
 					} else {
 						frm.trigger("refresh");
 					}
diff --git a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py
index 340b9dd..622bd33 100644
--- a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py
+++ b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py
@@ -28,22 +28,22 @@
 		"item_group": "_Test Item Group",
 		"item_name": "_Test Tesla Car",
 		"apply_warehouse_wise_reorder_level": 0,
-		"warehouse":"Stores - TCP1",
+		"warehouse":"Stores - _TC",
 		"gst_hsn_code": "999800",
 		"valuation_rate": 5000,
 		"standard_rate":5000,
 		"item_defaults": [{
-		"company": "_Test Company with perpetual inventory",
-		"default_warehouse": "Stores - TCP1",
+		"company": "_Test Company",
+		"default_warehouse": "Stores - _TC",
 		"default_price_list":"_Test Price List",
-		"expense_account": "Cost of Goods Sold - TCP1",
-		"buying_cost_center": "Main - TCP1",
-		"selling_cost_center": "Main - TCP1",
-		"income_account": "Sales - TCP1"
+		"expense_account": "Cost of Goods Sold - _TC",
+		"buying_cost_center": "Main - _TC",
+		"selling_cost_center": "Main - _TC",
+		"income_account": "Sales - _TC"
 		}],
 		"show_in_website": 1,
 		"route":"-test-tesla-car",
-		"website_warehouse": "Stores - TCP1"
+		"website_warehouse": "Stores - _TC"
 		})
 		item.insert()
 	# create test item price
@@ -65,12 +65,12 @@
 		"items": [{
 			"item_code": "_Test Tesla Car"
 		}],
-		"warehouse":"Stores - TCP1",
+		"warehouse":"Stores - _TC",
 		"coupon_code_based":1,
 		"selling": 1,
 		"rate_or_discount": "Discount Percentage",
 		"discount_percentage": 30,
-		"company": "_Test Company with perpetual inventory",
+		"company": "_Test Company",
 		"currency":"INR",
 		"for_price_list":"_Test Price List"
 		})
@@ -85,7 +85,7 @@
 		})
 		sales_partner.insert()
 	# create test item coupon code
-	if not frappe.db.exists("Coupon Code","SAVE30"):
+	if not frappe.db.exists("Coupon Code", "SAVE30"):
 		coupon_code = frappe.get_doc({
 		"doctype": "Coupon Code",
 		"coupon_name":"SAVE30",
@@ -102,35 +102,27 @@
 		test_create_test_data()
 
 	def tearDown(self):
-		frappe.set_user("Administrator")
+		frappe.set_user("Administrator")		
 
-	def test_1_check_coupon_code_used_before_so(self):
-		coupon_code = frappe.get_doc("Coupon Code", frappe.db.get_value("Coupon Code", {"coupon_name":"SAVE30"}))
-		# reset used coupon code count
-		coupon_code.used=0
-		coupon_code.save()
-		# check no coupon code is used before sales order is made
-		self.assertEqual(coupon_code.get("used"),0)
+	def test_sales_order_with_coupon_code(self):
+		frappe.db.set_value("Coupon Code", "SAVE30", "used", 0)
 
-	def test_2_sales_order_with_coupon_code(self):
-		so = make_sales_order(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1',
-			customer="_Test Customer", selling_price_list="_Test Price List", item_code="_Test Tesla Car", rate=5000,qty=1,
+		so = make_sales_order(company='_Test Company', warehouse='Stores - _TC',
+			customer="_Test Customer", selling_price_list="_Test Price List",
+			item_code="_Test Tesla Car", rate=5000, qty=1,
 			do_not_submit=True)
 
-		so = frappe.get_doc('Sales Order', so.name)
-		# check item price before coupon code is applied
 		self.assertEqual(so.items[0].rate, 5000)
+
 		so.coupon_code='SAVE30'
 		so.sales_partner='_Test Coupon Partner'
 		so.save()
+
 		# check item price after coupon code is applied
 		self.assertEqual(so.items[0].rate, 3500)
-		so.submit()
 
-	def test_3_check_coupon_code_used_after_so(self):
-		doc = frappe.get_doc("Coupon Code", frappe.db.get_value("Coupon Code", {"coupon_name":"SAVE30"}))
-		# check no coupon code is used before sales order is made
-		self.assertEqual(doc.get("used"),1)
+		so.submit()
+		self.assertEqual(frappe.db.get_value("Coupon Code", "SAVE30", "used"), 1)
 
 
 
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index def9ed6..c441274 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -30,20 +30,22 @@
 		self.pl_must_have_cost_center()
 		self.validate_cost_center()
 
-		self.check_pl_account()
-		self.validate_party()
-		self.validate_currency()
+		if not self.flags.from_repost:
+			self.check_pl_account()
+			self.validate_party()
+			self.validate_currency()
 
-	def on_update_with_args(self, adv_adj, update_outstanding = 'Yes'):
-		self.validate_account_details(adv_adj)
-		self.validate_dimensions_for_pl_and_bs()
+	def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False):
+		if not from_repost:
+			self.validate_account_details(adv_adj)
+			self.validate_dimensions_for_pl_and_bs()
 
 		validate_frozen_account(self.account, adv_adj)
 		validate_balance_type(self.account, adv_adj)
 
 		# Update outstanding amt on against voucher
 		if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \
-			and self.against_voucher and update_outstanding == 'Yes':
+			and self.against_voucher and update_outstanding == 'Yes' and not from_repost:
 				update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
 					self.against_voucher)
 
@@ -106,8 +108,8 @@
 			from tabAccount where name=%s""", self.account, as_dict=1)[0]
 
 		if ret.is_group==1:
-			frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in
-				transactions''').format(self.voucher_type, self.voucher_no, self.account))
+			frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions''')
+				.format(self.voucher_type, self.voucher_no, self.account))
 
 		if ret.docstatus==2:
 			frappe.throw(_("{0} {1}: Account {2} is inactive")
@@ -136,8 +138,8 @@
 				.format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
 
 		if self.cost_center and _check_is_group():
-			frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot
-				be used in transactions""").format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
+			frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""")
+				.format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
 
 	def validate_party(self):
 		validate_party_frozen_disabled(self.party_type, self.party)
diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
index 8083b21..af8940c 100644
--- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
+++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
@@ -137,11 +137,12 @@
 			"cost_center": erpnext.get_default_cost_center(self.company)
 		})
 
-		je.append("accounts", {
-			"account": self.bank_charges_account,
-			"debit_in_account_currency": flt(self.bank_charges),
-			"cost_center": erpnext.get_default_cost_center(self.company)
-		})
+		if self.bank_charges:
+			je.append("accounts", {
+				"account": self.bank_charges_account,
+				"debit_in_account_currency": flt(self.bank_charges),
+				"cost_center": erpnext.get_default_cost_center(self.company)
+			})
 
 		je.append("accounts", {
 			"account": self.short_term_loan,
diff --git a/erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py
index 3d74d9a..919dd0c 100644
--- a/erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py
+++ b/erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py
@@ -80,6 +80,7 @@
 			short_term_loan=self.short_term_loan,
 			bank_charges_account=self.bank_charges_account,
 			bank_account=self.bank_account,
+			bank_charges=100
 			)
 
 		je = inv_disc.create_disbursement_entry()
@@ -289,6 +290,7 @@
 	inv_disc.bank_account=args.bank_account
 	inv_disc.loan_start_date = args.start or nowdate()
 	inv_disc.loan_period = args.period or 30
+	inv_disc.bank_charges = flt(args.bank_charges)
 
 	for d in invoices:
 		inv_disc.append("invoices", {
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json
index 4573c50..b7bbb74 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.json
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json
@@ -1,5 +1,6 @@
 {
  "actions": [],
+ "allow_auto_repeat": 1,
  "allow_import": 1,
  "autoname": "naming_series:",
  "creation": "2013-03-25 10:53:52",
@@ -503,7 +504,7 @@
  "idx": 176,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-02 18:15:46.955697",
+ "modified": "2020-10-30 13:56:01.121995",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Journal Entry",
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index d839478..cb90f80 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -6,14 +6,18 @@
 from frappe.utils import cstr, flt, fmt_money, formatdate, getdate, nowdate, cint, get_link_to_form
 from frappe import msgprint, _, scrub
 from erpnext.controllers.accounts_controller import AccountsController
-from erpnext.accounts.utils import get_balance_on, get_account_currency
+from erpnext.accounts.utils import get_balance_on, get_stock_accounts, get_stock_and_account_balance, \
+	get_account_currency, check_if_stock_and_account_balance_synced
 from erpnext.accounts.party import get_party_account
 from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
-from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting
+from erpnext.accounts.doctype.invoice_discounting.invoice_discounting \
+	import get_party_account_based_on_invoice_discounting
 from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
 
 from six import string_types, iteritems
 
+class StockAccountInvalidTransaction(frappe.ValidationError): pass
+
 class JournalEntry(AccountsController):
 	def __init__(self, *args, **kwargs):
 		super(JournalEntry, self).__init__(*args, **kwargs)
@@ -34,6 +38,7 @@
 		self.validate_entries_for_advance()
 		self.validate_multi_currency()
 		self.set_amounts_in_company_currency()
+		self.validate_debit_credit_amount()
 		self.validate_total_debit_and_credit()
 		self.validate_against_jv()
 		self.validate_reference_doc()
@@ -45,6 +50,7 @@
 		self.validate_empty_accounts_table()
 		self.set_account_and_party_balance()
 		self.validate_inter_company_accounts()
+		self.validate_stock_accounts()
 		if not self.title:
 			self.title = self.get_title()
 
@@ -56,6 +62,8 @@
 		self.update_expense_claim()
 		self.update_inter_company_jv()
 		self.update_invoice_discounting()
+		check_if_stock_and_account_balance_synced(self.posting_date,
+			self.company, self.doctype, self.name)
 
 	def on_cancel(self):
 		from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
@@ -94,6 +102,16 @@
 			if account_currency == previous_account_currency:
 				if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit:
 					frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry"))
+	
+	def validate_stock_accounts(self):
+		stock_accounts = get_stock_accounts(self.company, self.doctype, self.name)
+		for account in stock_accounts:
+			account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
+				self.posting_date, self.company)
+
+			if account_bal == stock_bal:
+				frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
+					.format(account), StockAccountInvalidTransaction)
 
 	def update_inter_company_jv(self):
 		if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
@@ -339,8 +357,7 @@
 						currency=account_currency)
 
 				if flt(voucher_total) < (flt(order.advance_paid) + total):
-					frappe.throw(_("Advance paid against {0} {1} cannot be greater \
-						than Grand Total {2}").format(reference_type, reference_name, formatted_voucher_total))
+					frappe.throw(_("Advance paid against {0} {1} cannot be greater than Grand Total {2}").format(reference_type, reference_name, formatted_voucher_total))
 
 	def validate_invoices(self):
 		"""Validate totals and docstatus for invoices"""
@@ -369,6 +386,11 @@
 			if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited)))
 			if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited)))
 
+	def validate_debit_credit_amount(self):
+		for d in self.get('accounts'):
+			if not flt(d.debit) and not flt(d.credit):
+				frappe.throw(_("Row {0}: Both Debit and Credit values cannot be zero").format(d.idx))
+
 	def validate_total_debit_and_credit(self):
 		self.set_total_debit_credit()
 		if self.difference:
diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
index 53c0758..b56f8e5 100644
--- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
@@ -6,7 +6,7 @@
 from frappe.utils import flt, nowdate
 from erpnext.accounts.doctype.account.test_account import get_inventory_account
 from erpnext.exceptions import InvalidAccountCurrency
-from erpnext.accounts.general_ledger import StockAccountInvalidTransaction
+from erpnext.accounts.doctype.journal_entry.journal_entry import StockAccountInvalidTransaction
 
 class TestJournalEntry(unittest.TestCase):
 	def test_journal_entry_with_against_jv(self):
@@ -75,54 +75,46 @@
 
 		elif test_voucher.doctype in ["Sales Order", "Purchase Order"]:
 			# if test_voucher is a Sales Order/Purchase Order, test error on cancellation of test_voucher
+			frappe.db.set_value("Accounts Settings", "Accounts Settings",
+				"unlink_advance_payment_on_cancelation_of_order", 0)
 			submitted_voucher = frappe.get_doc(test_voucher.doctype, test_voucher.name)
 			self.assertRaises(frappe.LinkExistsError, submitted_voucher.cancel)
 
 	def test_jv_against_stock_account(self):
-		from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
-		set_perpetual_inventory()
+		company = "_Test Company with perpetual inventory"
+		stock_account = get_inventory_account(company)
 
-		jv = frappe.copy_doc({
-			"cheque_date": nowdate(),
-			"cheque_no": "33",
-			"company": "_Test Company with perpetual inventory",
-			"doctype": "Journal Entry",
-			"accounts": [
-			{
-				"account": "Debtors - TCP1",
-				"party_type": "Customer",
-				"party": "_Test Customer",
-				"credit_in_account_currency": 400.0,
-				"debit_in_account_currency": 0.0,
-				"doctype": "Journal Entry Account",
-				"parentfield": "accounts",
-				"cost_center": "Main - TCP1"
-			},
-			{
-				"account": "_Test Bank - TCP1",
-				"credit_in_account_currency": 0.0,
-				"debit_in_account_currency": 400.0,
-				"doctype": "Journal Entry Account",
-				"parentfield": "accounts",
-				"cost_center": "Main - TCP1"
-			}
-			],
-			"naming_series": "_T-Journal Entry-",
-			"posting_date": nowdate(),
-			"user_remark": "test",
-			"voucher_type": "Bank Entry"
-			})
+		from erpnext.accounts.utils import get_stock_and_account_balance
+		account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(stock_account, nowdate(), company)
+		diff = flt(account_bal) - flt(stock_bal)
 
-		jv.get("accounts")[0].update({
-			"account": get_inventory_account('_Test Company with perpetual inventory'),
-			"company": "_Test Company with perpetual inventory",
-			"party_type": None,
-			"party": None
+		if not diff:
+			diff = 100
+
+		jv = frappe.new_doc("Journal Entry")
+		jv.company = company
+		jv.posting_date = nowdate()
+		jv.append("accounts", {
+			"account": stock_account,
+			"cost_center": "Main - TCP1",
+			"debit_in_account_currency": 0 if diff > 0 else abs(diff),
+			"credit_in_account_currency": diff if diff > 0 else 0
 		})
+		
+		jv.append("accounts", {
+			"account": "Stock Adjustment - TCP1",
+			"cost_center": "Main - TCP1",
+			"debit_in_account_currency": diff if diff > 0 else 0,
+			"credit_in_account_currency": 0 if diff > 0 else abs(diff)
+		})
+		jv.insert()
 
-		self.assertRaises(StockAccountInvalidTransaction, jv.submit)
-		jv.cancel()
-		set_perpetual_inventory(0)
+		if account_bal == stock_bal:
+			self.assertRaises(StockAccountInvalidTransaction, jv.submit)
+			frappe.db.rollback()
+		else:
+			jv.submit()
+			jv.cancel()
 
 	def test_multi_currency(self):
 		jv = make_journal_entry("_Test Bank USD - _TC",
diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
index 5278d8b..3199488 100644
--- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
+++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
@@ -8,12 +8,10 @@
 from frappe.utils import today, cint, flt, getdate
 from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points
 from erpnext.accounts.party import get_dashboard_info
-from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
 
 class TestLoyaltyProgram(unittest.TestCase):
 	@classmethod
 	def setUpClass(self):
-		set_perpetual_inventory(0)
 		# create relevant item, customer, loyalty program, etc
 		create_records()
 
diff --git a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.js b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.js
index d3040c8..7a06d35 100644
--- a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.js
+++ b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.js
@@ -1,13 +1,17 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
-cur_frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
-	var d = locals[cdt][cdn];
-	return{
-		filters: [
-			['Account', 'account_type', 'in', 'Bank, Cash, Receivable'],
-			['Account', 'is_group', '=', 0],
-			['Account', 'company', '=', d.company]
-		]
-	}
-});
+frappe.ui.form.on('Mode of Payment', {
+	setup: function(frm) {
+		frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
+			let d = locals[cdt][cdn];
+			return {
+				filters: [
+					['Account', 'account_type', 'in', 'Bank, Cash, Receivable'],
+					['Account', 'is_group', '=', 0],
+					['Account', 'company', '=', d.company]
+				]
+			};
+		});
+	},
+});
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
index d51856a..ee2092a 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
@@ -155,7 +155,8 @@
 			"posting_date": row.posting_date,
 			frappe.scrub(row.party_type): row.party,
 			"is_pos": 0,
-			"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice"
+			"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
+			"update_stock": 0
 		})
 
 		accounting_dimension = get_accounting_dimensions()
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
index 54229f5..bdfe532 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
@@ -7,17 +7,24 @@
 import unittest
 
 test_dependencies = ["Customer", "Supplier"]
+from frappe.custom.doctype.property_setter.property_setter import make_property_setter
 from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account
 
 class TestOpeningInvoiceCreationTool(unittest.TestCase):
-	def make_invoices(self, invoice_type="Sales"):
+	def setUp(self):
+		if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
+			make_company()
+
+	def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None):
 		doc = frappe.get_single("Opening Invoice Creation Tool")
-		args = get_opening_invoice_creation_dict(invoice_type=invoice_type)
+		args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
+			party_1=party_1, party_2=party_2)
 		doc.update(args)
 		return doc.make_invoices()
 
 	def test_opening_sales_invoice_creation(self):
-		invoices = self.make_invoices()
+		property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check")
+		invoices = self.make_invoices(company="_Test Opening Invoice Company")
 
 		self.assertEqual(len(invoices), 2)
 		expected_value = {
@@ -27,6 +34,13 @@
 		}
 		self.check_expected_values(invoices, expected_value)
 
+		si = frappe.get_doc("Sales Invoice", invoices[0])
+
+		# Check if update stock is not enabled
+		self.assertEqual(si.update_stock, 0)
+
+		property_setter.delete()
+
 	def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
 		doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
 
@@ -36,7 +50,7 @@
 				self.assertEqual(si.get(field, ""), expected_value[invoice_idx][field_idx])
 
 	def test_opening_purchase_invoice_creation(self):
-		invoices = self.make_invoices(invoice_type="Purchase")
+		invoices = self.make_invoices(invoice_type="Purchase", company="_Test Opening Invoice Company")
 
 		self.assertEqual(len(invoices), 2)
 		expected_value = {
@@ -46,6 +60,32 @@
 		}
 		self.check_expected_values(invoices, expected_value, "Purchase")
 
+	def test_opening_sales_invoice_creation_with_missing_debit_account(self):
+		company = "_Test Opening Invoice Company"
+		party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
+
+		old_default_receivable_account = frappe.db.get_value("Company", company, "default_receivable_account")
+		frappe.db.set_value("Company", company, "default_receivable_account", "")
+
+		if not frappe.db.exists("Cost Center", "_Test Opening Invoice Company - _TOIC"):
+			cc = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "_Test Opening Invoice Company",
+				"is_group": 1, "company": "_Test Opening Invoice Company"})
+			cc.insert(ignore_mandatory=True)
+			cc2 = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "Main", "is_group": 0,
+				"company": "_Test Opening Invoice Company", "parent_cost_center": cc.name})
+			cc2.insert()
+
+		frappe.db.set_value("Company", company, "cost_center", "Main - _TOIC")
+
+		self.make_invoices(company="_Test Opening Invoice Company", party_1=party_1, party_2=party_2)
+
+		# Check if missing debit account error raised
+		error_log = frappe.db.exists("Error Log", {"error": ["like", "%erpnext.controllers.accounts_controller.AccountMissingError%"]})
+		self.assertTrue(error_log)
+
+		# teardown
+		frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account)
+
 def get_opening_invoice_creation_dict(**args):
 	party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
 	company = args.get("company", "_Test Company")
@@ -57,7 +97,7 @@
 			{
 				"qty": 1.0,
 				"outstanding_amount": 300,
-				"party": "_Test {0}".format(party),
+				"party": args.get("party_1") or "_Test {0}".format(party),
 				"item_name": "Opening Item",
 				"due_date": "2016-09-10",
 				"posting_date": "2016-09-05",
@@ -66,7 +106,7 @@
 			{
 				"qty": 2.0,
 				"outstanding_amount": 250,
-				"party": "_Test {0} 1".format(party),
+				"party": args.get("party_2") or "_Test {0} 1".format(party),
 				"item_name": "Opening Item",
 				"due_date": "2016-09-10",
 				"posting_date": "2016-09-05",
@@ -76,4 +116,31 @@
 	})
 
 	invoice_dict.update(args)
-	return invoice_dict
\ No newline at end of file
+	return invoice_dict
+
+def make_company():
+	if frappe.db.exists("Company", "_Test Opening Invoice Company"):
+		return frappe.get_doc("Company", "_Test Opening Invoice Company")
+
+	company = frappe.new_doc("Company")
+	company.company_name = "_Test Opening Invoice Company"
+	company.abbr = "_TOIC"
+	company.default_currency = "INR"
+	company.country = "India"
+	company.insert()
+	return company
+
+def make_customer(customer=None):
+	customer_name = customer or "Opening Customer"
+	customer = frappe.get_doc({
+		"doctype": "Customer",
+		"customer_name": customer_name,
+		"customer_group": "All Customer Groups",
+		"customer_type": "Company",
+		"territory": "All Territories"
+	})
+	if not frappe.db.exists("Customer", customer_name):
+		customer.insert(ignore_permissions=True)
+		return customer.name
+	else:
+		return frappe.db.exists("Customer", customer_name)
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 72149a6..2e1f201 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -1,5 +1,6 @@
 {
  "actions": [],
+ "allow_auto_repeat": 1,
  "allow_import": 1,
  "autoname": "naming_series:",
  "creation": "2016-06-01 14:38:51.012597",
@@ -587,7 +588,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-09-02 13:39:43.383705",
+ "modified": "2020-10-30 13:56:20.007336",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 11ab020..31a4c8a 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -202,17 +202,32 @@
 		# if account_type not in account_types:
 		# 	frappe.throw(_("Account Type for {0} must be {1}").format(account, comma_or(account_types)))
 
-	def set_exchange_rate(self):
+	def set_exchange_rate(self, ref_doc=None):
+		self.set_source_exchange_rate(ref_doc)
+		self.set_target_exchange_rate(ref_doc)
+
+	def set_source_exchange_rate(self, ref_doc=None):
 		if self.paid_from and not self.source_exchange_rate:
 			if self.paid_from_account_currency == self.company_currency:
 				self.source_exchange_rate = 1
 			else:
-				self.source_exchange_rate = get_exchange_rate(self.paid_from_account_currency,
-					self.company_currency, self.posting_date)
+				if ref_doc:
+					if self.paid_from_account_currency == ref_doc.currency:
+						self.source_exchange_rate = ref_doc.get("exchange_rate")
 
+			if not self.source_exchange_rate:
+					self.source_exchange_rate = get_exchange_rate(self.paid_from_account_currency,
+						self.company_currency, self.posting_date)
+
+	def set_target_exchange_rate(self, ref_doc=None):
 		if self.paid_to and not self.target_exchange_rate:
-			self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency,
-				self.company_currency, self.posting_date)
+			if ref_doc:
+				if self.paid_to_account_currency == ref_doc.currency:
+					self.target_exchange_rate = ref_doc.get("exchange_rate")
+
+			if not self.target_exchange_rate:
+				self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency,
+					self.company_currency, self.posting_date)
 
 	def validate_mandatory(self):
 		for field in ("paid_amount", "received_amount", "source_exchange_rate", "target_exchange_rate"):
@@ -282,9 +297,10 @@
 					no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
 
 		for k, v in no_oustanding_refs.items():
-			frappe.msgprint(_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.<br><br>\
-					If this is undesirable please cancel the corresponding Payment Entry.")
-				.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount")),
+			frappe.msgprint(
+				_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
+					.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount"))
+				+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
 				title=_("Warning"), indicator="orange")
 
 
@@ -909,22 +925,24 @@
 			exchange_rate = 1
 			outstanding_amount = get_outstanding_on_journal_entry(reference_name)
 	elif reference_doctype != "Journal Entry":
-		if party_account_currency == company_currency:
-			if ref_doc.doctype == "Expense Claim":
+		if ref_doc.doctype == "Expense Claim":
 				total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
-			elif ref_doc.doctype == "Employee Advance":
-				total_amount = ref_doc.advance_amount
-			else:
+		elif ref_doc.doctype == "Employee Advance":
+			total_amount = ref_doc.advance_amount
+			exchange_rate = ref_doc.get("exchange_rate")
+			if party_account_currency != ref_doc.currency:
+				total_amount = flt(total_amount) * flt(exchange_rate)
+		if not total_amount:
+			if party_account_currency == company_currency:
 				total_amount = ref_doc.base_grand_total
-			exchange_rate = 1
-		else:
-			total_amount = ref_doc.grand_total
-
+				exchange_rate = 1
+			else:
+				total_amount = ref_doc.grand_total
+		if not exchange_rate:
 			# Get the exchange rate from the original ref doc
-			# or get it based on the posting date of the ref doc
+			# or get it based on the posting date of the ref doc.
 			exchange_rate = ref_doc.get("conversion_rate") or \
 				get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
-
 		if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
 			outstanding_amount = ref_doc.get("outstanding_amount")
 			bill_no = ref_doc.get("bill_no")
@@ -932,11 +950,15 @@
 			outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) + flt(ref_doc.get("total_taxes_and_charges"))\
 				- flt(ref_doc.get("total_amount_reimbursed")) - flt(ref_doc.get("total_advance_amount"))
 		elif reference_doctype == "Employee Advance":
-			outstanding_amount = ref_doc.advance_amount - flt(ref_doc.paid_amount)
+			outstanding_amount = (flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount))
+			if party_account_currency != ref_doc.currency:
+				outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
+				if party_account_currency == company_currency:
+					exchange_rate = 1
 		else:
 			outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
 	else:
-		# Get the exchange rate based on the posting date of the ref doc
+		# Get the exchange rate based on the posting date of the ref doc.
 		exchange_rate = get_exchange_rate(party_account_currency,
 			company_currency, ref_doc.posting_date)
 
@@ -948,102 +970,104 @@
 		"bill_no": bill_no
 	})
 
+def get_amounts_based_on_reference_doctype(reference_doctype, ref_doc, party_account_currency, company_currency, reference_name):
+	total_amount, outstanding_amount, exchange_rate = None
+	if reference_doctype == "Fees":
+		total_amount = ref_doc.get("grand_total")
+		exchange_rate = 1
+		outstanding_amount = ref_doc.get("outstanding_amount")
+	elif reference_doctype == "Dunning":
+		total_amount = ref_doc.get("dunning_amount")
+		exchange_rate = 1
+		outstanding_amount = ref_doc.get("dunning_amount")
+	elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
+		total_amount = ref_doc.get("total_amount")
+		if ref_doc.multi_currency:
+			exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
+		else:
+			exchange_rate = 1
+			outstanding_amount = get_outstanding_on_journal_entry(reference_name)
+
+	return total_amount, outstanding_amount, exchange_rate
+
+def get_amounts_based_on_ref_doc(reference_doctype, ref_doc, party_account_currency, company_currency):
+	total_amount, outstanding_amount, exchange_rate = None
+	if ref_doc.doctype == "Expense Claim":
+			total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
+	elif ref_doc.doctype == "Employee Advance":
+		total_amount, exchange_rate = get_total_amount_exchange_rate_for_employee_advance(party_account_currency, ref_doc)
+		
+	if not total_amount:
+		total_amount, exchange_rate = get_total_amount_exchange_rate_base_on_currency(
+			party_account_currency, company_currency, ref_doc)
+
+	if not exchange_rate:
+		# Get the exchange rate from the original ref doc
+		# or get it based on the posting date of the ref doc
+		exchange_rate = ref_doc.get("conversion_rate") or \
+			get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
+
+	outstanding_amount, exchange_rate, bill_no = get_bill_no_and_update_amounts(
+		reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency)
+
+	return total_amount, outstanding_amount, exchange_rate, bill_no
+
+def get_total_amount_exchange_rate_for_employee_advance(party_account_currency, ref_doc):
+	total_amount = ref_doc.advance_amount
+	exchange_rate = ref_doc.get("exchange_rate")
+	if party_account_currency != ref_doc.currency:
+		total_amount = flt(total_amount) * flt(exchange_rate)
+
+	return total_amount, exchange_rate
+
+def get_total_amount_exchange_rate_base_on_currency(party_account_currency, company_currency, ref_doc):
+	exchange_rate = None
+	if party_account_currency == company_currency:
+		total_amount = ref_doc.base_grand_total
+		exchange_rate = 1
+	else:
+		total_amount = ref_doc.grand_total
+
+	return total_amount, exchange_rate
+
+def get_bill_no_and_update_amounts(reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency):
+	outstanding_amount, bill_no = None
+	if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
+		outstanding_amount = ref_doc.get("outstanding_amount")
+		bill_no = ref_doc.get("bill_no")
+	elif reference_doctype == "Expense Claim":
+		outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) + flt(ref_doc.get("total_taxes_and_charges"))\
+			- flt(ref_doc.get("total_amount_reimbursed")) - flt(ref_doc.get("total_advance_amount"))
+	elif reference_doctype == "Employee Advance":
+		outstanding_amount = (flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount))
+		if party_account_currency != ref_doc.currency:
+			outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
+			if party_account_currency == company_currency:
+				exchange_rate = 1
+	else:
+		outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
+
+	return outstanding_amount, exchange_rate, bill_no
+
 
 @frappe.whitelist()
 def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
+	reference_doc = None
 	doc = frappe.get_doc(dt, dn)
 	if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
 		frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
 
-	if dt in ("Sales Invoice", "Sales Order", "Dunning"):
-		party_type = "Customer"
-	elif dt in ("Purchase Invoice", "Purchase Order"):
-		party_type = "Supplier"
-	elif dt in ("Expense Claim", "Employee Advance"):
-		party_type = "Employee"
-	elif dt in ("Fees"):
-		party_type = "Student"
-
-	# party account
-	if dt == "Sales Invoice":
-		party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
-	elif dt == "Purchase Invoice":
-		party_account = doc.credit_to
-	elif dt == "Fees":
-		party_account = doc.receivable_account
-	elif dt == "Employee Advance":
-		party_account = doc.advance_account
-	elif dt == "Expense Claim":
-		party_account = doc.payable_account
-	else:
-		party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
-
-	if dt not in ("Sales Invoice", "Purchase Invoice"):
-		party_account_currency = get_account_currency(party_account)
-	else:
-		party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
-
-	# payment type
-	if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
-		or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
-			payment_type = "Receive"
-	else:
-		payment_type = "Pay"
-
-	# amounts
-	grand_total = outstanding_amount = 0
-	if party_amount:
-		grand_total = outstanding_amount = party_amount
-	elif dt in ("Sales Invoice", "Purchase Invoice"):
-		if party_account_currency == doc.company_currency:
-			grand_total = doc.base_rounded_total or doc.base_grand_total
-		else:
-			grand_total = doc.rounded_total or doc.grand_total
-		outstanding_amount = doc.outstanding_amount
-	elif dt in ("Expense Claim"):
-		grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges
-		outstanding_amount = doc.grand_total \
-			- doc.total_amount_reimbursed
-	elif dt == "Employee Advance":
-		grand_total = doc.advance_amount
-		outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)
-	elif dt == "Fees":
-		grand_total = doc.grand_total
-		outstanding_amount = doc.outstanding_amount
-	elif dt == "Dunning":
-		grand_total = doc.grand_total
-		outstanding_amount = doc.grand_total
-	else:
-		if party_account_currency == doc.company_currency:
-			grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
-		else:
-			grand_total = flt(doc.get("rounded_total") or doc.grand_total)
-		outstanding_amount = grand_total - flt(doc.advance_paid)
+	party_type = set_party_type(dt)
+	party_account = set_party_account(dt, dn, doc, party_type)
+	party_account_currency = set_party_account_currency(dt, party_account, doc)
+	payment_type = set_payment_type(dt, doc)
+	grand_total, outstanding_amount = set_grand_total_and_outstanding_amount(party_amount, dt, party_account_currency, doc)
 
 	# bank or cash
-	bank = get_default_bank_cash_account(doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"),
-		account=bank_account)
+	bank = get_bank_cash_account(doc, bank_account)
 
-	if not bank:
-		bank = get_default_bank_cash_account(doc.company, "Cash", mode_of_payment=doc.get("mode_of_payment"),
-			account=bank_account)
-
-	paid_amount = received_amount = 0
-	if party_account_currency == bank.account_currency:
-		paid_amount = received_amount = abs(outstanding_amount)
-	elif payment_type == "Receive":
-		paid_amount = abs(outstanding_amount)
-		if bank_amount:
-			received_amount = bank_amount
-		else:
-			received_amount = paid_amount * doc.get('conversion_rate', 1)
-	else:
-		received_amount = abs(outstanding_amount)
-		if bank_amount:
-			paid_amount = bank_amount
-		else:
-			# if party account currency and bank currency is different then populate paid amount as well
-			paid_amount = received_amount * doc.get('conversion_rate', 1)
+	paid_amount, received_amount = set_paid_amount_and_received_amount(
+		dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc)
 
 	pe = frappe.new_doc("Payment Entry")
 	pe.payment_type = payment_type
@@ -1115,10 +1139,120 @@
 	pe.setup_party_account_field()
 	pe.set_missing_values()
 	if party_account and bank:
-		pe.set_exchange_rate()
+		if dt == "Employee Advance":
+			reference_doc = doc
+		pe.set_exchange_rate(ref_doc=reference_doc)
 		pe.set_amounts()
 	return pe
 
+def get_bank_cash_account(doc, bank_account):
+	bank = get_default_bank_cash_account(doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"),
+		account=bank_account)
+
+	if not bank:
+		bank = get_default_bank_cash_account(doc.company, "Cash", mode_of_payment=doc.get("mode_of_payment"),
+			account=bank_account)
+
+	return bank
+
+def set_party_type(dt):
+	if dt in ("Sales Invoice", "Sales Order", "Dunning"):
+		party_type = "Customer"
+	elif dt in ("Purchase Invoice", "Purchase Order"):
+		party_type = "Supplier"
+	elif dt in ("Expense Claim", "Employee Advance"):
+		party_type = "Employee"
+	elif dt in ("Fees"):
+		party_type = "Student"
+	return party_type
+
+def set_party_account(dt, dn, doc, party_type):
+	if dt == "Sales Invoice":
+		party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
+	elif dt == "Purchase Invoice":
+		party_account = doc.credit_to
+	elif dt == "Fees":
+		party_account = doc.receivable_account
+	elif dt == "Employee Advance":
+		party_account = doc.advance_account
+	elif dt == "Expense Claim":
+		party_account = doc.payable_account
+	else:
+		party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
+	return party_account
+
+def set_party_account_currency(dt, party_account, doc):
+	if dt not in ("Sales Invoice", "Purchase Invoice"):
+		party_account_currency = get_account_currency(party_account)
+	else:
+		party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
+	return party_account_currency
+
+def set_payment_type(dt, doc):
+	if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
+		or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
+			payment_type = "Receive"
+	else:
+		payment_type = "Pay"
+	return payment_type
+
+def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_currency, doc):
+	grand_total = outstanding_amount = 0
+	if party_amount:
+		grand_total = outstanding_amount = party_amount
+	elif dt in ("Sales Invoice", "Purchase Invoice"):
+		if party_account_currency == doc.company_currency:
+			grand_total = doc.base_rounded_total or doc.base_grand_total
+		else:
+			grand_total = doc.rounded_total or doc.grand_total
+		outstanding_amount = doc.outstanding_amount
+	elif dt in ("Expense Claim"):
+		grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges
+		outstanding_amount = doc.grand_total \
+			- doc.total_amount_reimbursed
+	elif dt == "Employee Advance":
+		grand_total = flt(doc.advance_amount)
+		outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)
+		if party_account_currency != doc.currency:
+			grand_total = flt(doc.advance_amount) * flt(doc.exchange_rate)
+			outstanding_amount = (flt(doc.advance_amount) - flt(doc.paid_amount)) * flt(doc.exchange_rate)
+	elif dt == "Fees":
+		grand_total = doc.grand_total
+		outstanding_amount = doc.outstanding_amount
+	elif dt == "Dunning":
+		grand_total = doc.grand_total
+		outstanding_amount = doc.grand_total
+	else:
+		if party_account_currency == doc.company_currency:
+			grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
+		else:
+			grand_total = flt(doc.get("rounded_total") or doc.grand_total)
+		outstanding_amount = grand_total - flt(doc.advance_paid)
+	return grand_total, outstanding_amount
+
+def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc):
+	paid_amount = received_amount = 0
+	if party_account_currency == bank.account_currency:
+		paid_amount = received_amount = abs(outstanding_amount)
+	elif payment_type == "Receive":
+		paid_amount = abs(outstanding_amount)
+		if bank_amount:
+			received_amount = bank_amount
+		else:
+			received_amount = paid_amount * doc.get('conversion_rate', 1)
+			if dt == "Employee Advance":
+				received_amount = paid_amount * doc.get('exchange_rate', 1)
+	else:
+		received_amount = abs(outstanding_amount)
+		if bank_amount:
+			paid_amount = bank_amount
+		else:
+			# if party account currency and bank currency is different then populate paid amount as well
+			paid_amount = received_amount * doc.get('conversion_rate', 1)
+			if dt == "Employee Advance":
+				paid_amount = received_amount * doc.get('exchange_rate', 1)
+	return paid_amount, received_amount
+
 def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
 	references = []
 	for payment_term in payment_schedule:
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
index 1cff3c6..5bc57b4 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -1,5 +1,6 @@
 {
  "actions": [],
+ "allow_auto_repeat": 1,
  "allow_import": 1,
  "autoname": "naming_series:",
  "creation": "2020-01-24 15:29:29.933693",
@@ -1580,7 +1581,7 @@
  "icon": "fa fa-file-text",
  "is_submittable": 1,
  "links": [],
- "modified": "2020-09-28 16:51:24.641755",
+ "modified": "2020-10-30 13:56:51.056083",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "POS Invoice",
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index d486ff6..ac98dcc 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -267,6 +267,8 @@
 		from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
 		if not self.pos_profile:
 			pos_profile = get_pos_profile(self.company) or {}
+			if not pos_profile:
+				frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
 			self.pos_profile = pos_profile.get('name')
 
 		profile = {}
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.js b/erpnext/accounts/doctype/pos_profile/pos_profile.js
index 558e21c..7f4f755 100755
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.js
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.js
@@ -35,6 +35,15 @@
 			};
 		});
 
+		frm.set_query("taxes_and_charges", function() {
+			return {
+				filters: [
+					['Sales Taxes and Charges Template', 'company', '=', frm.doc.company],
+					['Sales Taxes and Charges Template', 'docstatus', '!=', 2]
+				]
+			};
+		});
+
 		frm.set_query('company_address', function(doc) {
 			if(!doc.company) {
 				frappe.throw(__('Please set Company'));
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json
index 570111a..d856ae3 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.json
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json
@@ -14,7 +14,6 @@
   "column_break_9",
   "update_stock",
   "ignore_pricing_rule",
-  "hide_unavailable_items",
   "warehouse",
   "campaign",
   "company_address",
@@ -23,6 +22,9 @@
   "section_break_11",
   "payments",
   "section_break_14",
+  "hide_images",
+  "hide_unavailable_items",
+  "auto_add_item_to_cart",
   "item_groups",
   "column_break_16",
   "customer_groups",
@@ -124,7 +126,8 @@
   },
   {
    "fieldname": "section_break_14",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "label": "Configuration"
   },
   {
    "description": "Only show Items from these Item Groups",
@@ -314,13 +317,25 @@
    "fieldname": "hide_unavailable_items",
    "fieldtype": "Check",
    "label": "Hide Unavailable Items"
+  },
+  {
+   "default": "0",
+   "fieldname": "hide_images",
+   "fieldtype": "Check",
+   "label": "Hide Images"
+  },
+  {
+   "default": "0",
+   "fieldname": "auto_add_item_to_cart",
+   "fieldtype": "Check",
+   "label": "Automatically Add Filtered Item To Cart"
   }
  ],
  "icon": "icon-cog",
  "idx": 1,
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2020-10-29 13:18:38.795925",
+ "modified": "2020-12-10 13:59:28.877572",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "POS Profile",
diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
index edf8659..62dc1fc 100644
--- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
+++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
@@ -70,6 +70,7 @@
 		""".format(cond=cond), tuple([company] + args_list), as_dict=1)
 
 def make_pos_profile(**args):
+	frappe.db.sql("delete from `tabPOS Payment Method`")
 	frappe.db.sql("delete from `tabPOS Profile`")
 
 	args = frappe._dict(args)
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.js b/erpnext/accounts/doctype/pricing_rule/pricing_rule.js
index c92b58b..d79ad5f 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.js
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.js
@@ -42,56 +42,56 @@
 				<tr><td>
 					<h4>
 						<i class="fa fa-hand-right"></i>
-						${__('Notes')}
+						{{__('Notes')}}
 					</h4>
 					<ul>
 						<li>
-							${__("Pricing Rule is made to overwrite Price List / define discount percentage, based on some criteria.")}
+							{{__("Pricing Rule is made to overwrite Price List / define discount percentage, based on some criteria.")}}
 						</li>
 						<li>
-							${__("If selected Pricing Rule is made for 'Rate', it will overwrite Price List. Pricing Rule rate is the final rate, so no further discount should be applied. Hence, in transactions like Sales Order, Purchase Order etc, it will be fetched in 'Rate' field, rather than 'Price List Rate' field.")}
+							{{__("If selected Pricing Rule is made for 'Rate', it will overwrite Price List. Pricing Rule rate is the final rate, so no further discount should be applied. Hence, in transactions like Sales Order, Purchase Order etc, it will be fetched in 'Rate' field, rather than 'Price List Rate' field.")}}
 						</li>
 						<li>
-							${__('Discount Percentage can be applied either against a Price List or for all Price List.')}
+							{{__('Discount Percentage can be applied either against a Price List or for all Price List.')}}
 						</li>
 						<li>
-							${__('To not apply Pricing Rule in a particular transaction, all applicable Pricing Rules should be disabled.')}
+							{{__('To not apply Pricing Rule in a particular transaction, all applicable Pricing Rules should be disabled.')}}
 						</li>
 					</ul>
 				</td></tr>
 				<tr><td>
 					<h4><i class="fa fa-question-sign"></i>
-						${__('How Pricing Rule is applied?')}
+						{{__('How Pricing Rule is applied?')}}
 					</h4>
 					<ol>
 						<li>
-							${__("Pricing Rule is first selected based on 'Apply On' field, which can be Item, Item Group or Brand.")}
+							{{__("Pricing Rule is first selected based on 'Apply On' field, which can be Item, Item Group or Brand.")}}
 						</li>
 						<li>
-							${__("Then Pricing Rules are filtered out based on Customer, Customer Group, Territory, Supplier, Supplier Type, Campaign, Sales Partner etc.")}
+							{{__("Then Pricing Rules are filtered out based on Customer, Customer Group, Territory, Supplier, Supplier Type, Campaign, Sales Partner etc.")}}
 						</li>
 						<li>
-							${__('Pricing Rules are further filtered based on quantity.')}
+							{{__('Pricing Rules are further filtered based on quantity.')}}
 						</li>
 						<li>
-							${__('If two or more Pricing Rules are found based on the above conditions, Priority is applied. Priority is a number between 0 to 20 while default value is zero (blank). Higher number means it will take precedence if there are multiple Pricing Rules with same conditions.')}
+							{{__('If two or more Pricing Rules are found based on the above conditions, Priority is applied. Priority is a number between 0 to 20 while default value is zero (blank). Higher number means it will take precedence if there are multiple Pricing Rules with same conditions.')}}
 						</li>
 						<li>
-							${__('Even if there are multiple Pricing Rules with highest priority, then following internal priorities are applied:')}
+							{{__('Even if there are multiple Pricing Rules with highest priority, then following internal priorities are applied:')}}
 							<ul>
 								<li>
-									${__('Item Code > Item Group > Brand')}
+									{{__('Item Code > Item Group > Brand')}}
 								</li>
 								<li>
-									${__('Customer > Customer Group > Territory')}
+									{{__('Customer > Customer Group > Territory')}}
 								</li>
 								<li>
-									${__('Supplier > Supplier Type')}
+									{{__('Supplier > Supplier Type')}}
 								</li>
 							</ul>
 						</li>
 						<li>
-							${__('If multiple Pricing Rules continue to prevail, users are asked to set Priority manually to resolve conflict.')}
+							{{__('If multiple Pricing Rules continue to prevail, users are asked to set Priority manually to resolve conflict.')}}
 						</li>
 					</ol>
 				</td></tr>
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
index cc8ed4b..d08a854 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
@@ -406,6 +406,7 @@
    "fieldtype": "Column Break"
   },
   {
+   "default": "0",
    "depends_on": "eval:doc.rate_or_discount==\"Rate\"",
    "fieldname": "rate",
    "fieldtype": "Currency",
@@ -469,6 +470,7 @@
    "options": "UOM"
   },
   {
+   "description": "If rate is zero them item will be treated as \"Free Item\"",
    "fieldname": "free_item_rate",
    "fieldtype": "Currency",
    "label": "Rate"
@@ -563,7 +565,7 @@
  "icon": "fa fa-gift",
  "idx": 1,
  "links": [],
- "modified": "2020-10-28 16:53:14.416172",
+ "modified": "2020-12-04 00:36:24.698219",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Pricing Rule",
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index 55a5b0e..0565264 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -345,9 +345,13 @@
 	if ((pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == args.currency)
 			or (pricing_rule.margin_type == 'Percentage')):
 		item_details.margin_type = pricing_rule.margin_type
-		item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
 		item_details.has_margin = True
 
+		if pricing_rule.apply_multiple_pricing_rules and item_details.margin_rate_or_amount is not None:
+			item_details.margin_rate_or_amount += pricing_rule.margin_rate_or_amount
+		else:
+			item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
+
 	if pricing_rule.rate_or_discount == 'Rate':
 		pricing_rule_rate = 0.0
 		if pricing_rule.currency == args.currency:
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index ec0a485..af8d21d 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -521,6 +521,22 @@
 		frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
 		item.delete()
 
+	def test_pricing_rule_for_transaction(self):
+		make_item("Water Flask 1")
+		frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
+		make_pricing_rule(selling=1, min_qty=5, price_or_product_discount="Product",
+			apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10)
+
+		si = create_sales_invoice(qty=5, do_not_submit=True)
+		self.assertEquals(len(si.items), 2)
+		self.assertEquals(si.items[1].rate, 10)
+
+		si1 = create_sales_invoice(qty=2, do_not_submit=True)
+		self.assertEquals(len(si1.items), 1)
+
+		for doc in [si, si1]:
+			doc.delete()
+
 def make_pricing_rule(**args):
 	args = frappe._dict(args)
 
@@ -539,20 +555,23 @@
 		"rate_or_discount": args.rate_or_discount or "Discount Percentage",
 		"discount_percentage": args.discount_percentage or 0.0,
 		"rate": args.rate or 0.0,
-		"margin_type": args.margin_type,
 		"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
 		"condition": args.condition or '',
 		"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
 	})
 
-	if args.get("priority"):
-		doc.priority = args.get("priority")
+	for field in ["free_item", "free_qty", "free_item_rate", "priority",
+		"margin_type", "price_or_product_discount"]:
+		if args.get(field):
+			doc.set(field, args.get(field))
 
 	apply_on = doc.apply_on.replace(' ', '_').lower()
 	child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
-	doc.append(child_table.get(doc.apply_on), {
-		apply_on: args.get(apply_on) or "_Test Item"
-	})
+
+	if doc.apply_on != "Transaction":
+		doc.append(child_table.get(doc.apply_on), {
+			apply_on: args.get(apply_on) or "_Test Item"
+		})
 
 	doc.insert(ignore_permissions=True)
 	if args.get(apply_on) and apply_on != "item_code":
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index b003328..fb1fbe4 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -164,7 +164,15 @@
 			frappe.throw(_("Invalid {0}").format(args.get(field)))
 
 		parent_groups = frappe.db.sql_list("""select name from `tab%s`
-			where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
+			where lft>=%s and rgt<=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
+
+		if parenttype in ["Customer Group", "Item Group", "Territory"]:
+			parent_field = "parent_{0}".format(frappe.scrub(parenttype))
+			root_name = frappe.db.get_list(parenttype,
+				{"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1)
+
+			if root_name and root_name[0][0]:
+				parent_groups.append(root_name[0][0])
 
 		if parent_groups:
 			if allow_blank: parent_groups.append('')
@@ -457,6 +465,9 @@
 		pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
 			doc.total, pricing_rules)
 
+		if not pricing_rules:
+			remove_free_item(doc)
+
 		for d in pricing_rules:
 			if d.price_or_product_discount == 'Price':
 				if d.apply_discount_on:
@@ -480,6 +491,12 @@
 				get_product_discount_rule(d, item_details, doc=doc)
 				apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
 				doc.set_missing_values()
+				doc.calculate_taxes_and_totals()
+
+def remove_free_item(doc):
+	for d in doc.items:
+		if d.is_free_item:
+			doc.remove(d)
 
 def get_applied_pricing_rules(pricing_rules):
 	if pricing_rules:
@@ -492,7 +509,7 @@
 
 def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
 	free_item = pricing_rule.free_item
-	if pricing_rule.same_item:
+	if pricing_rule.same_item and pricing_rule.get("apply_on") != 'Transaction':
 		free_item = item_details.item_code or args.item_code
 
 	if not free_item:
diff --git a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
index 31356c6..e08a0e5 100644
--- a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
+++ b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
@@ -21,7 +21,7 @@
 		item.no_of_months = 12
 		item.save()
 
-		si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True)
+		si = create_sales_invoice(item=item.name, update_stock=0, posting_date="2019-01-10", do_not_submit=True)
 		si.items[0].enable_deferred_revenue = 1
 		si.items[0].service_start_date = "2019-01-10"
 		si.items[0].service_end_date = "2019-03-15"
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 1d41d0f..7830cfd 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -15,6 +15,16 @@
 				return (doc.qty<=doc.received_qty) ? "green" : "orange";
 			});
 		}
+
+		this.frm.set_query("unrealized_profit_loss_account", function() {
+			return {
+				filters: {
+					company: doc.company,
+					is_group: 0,
+					root_type: "Liability",
+				}
+			};
+		});
 	},
 	onload: function() {
 		this._super();
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 8925b87..c64ffd8 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -126,6 +126,7 @@
   "write_off_cost_center",
   "advances_section",
   "allocate_advances_automatically",
+  "adjust_advance_taxes",
   "get_advances",
   "advances",
   "payment_schedule_section",
@@ -151,9 +152,11 @@
   "is_opening",
   "against_expense_account",
   "column_break_63",
+  "unrealized_profit_loss_account",
   "status",
   "inter_company_invoice_reference",
   "is_internal_supplier",
+  "represents_company",
   "remarks",
   "subscription_section",
   "from_date",
@@ -1222,7 +1225,7 @@
    "fieldtype": "Select",
    "in_standard_filter": 1,
    "label": "Status",
-   "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled",
+   "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer",
    "print_hide": 1
   },
   {
@@ -1329,12 +1332,37 @@
    "fieldtype": "Link",
    "label": "Project",
    "options": "Project"
+  },
+  {
+   "default": "0",
+   "description": "Taxes paid while advance payment will be adjusted against this invoice",
+   "fieldname": "adjust_advance_taxes",
+   "fieldtype": "Check",
+   "label": "Adjust Advance Taxes"
+  },
+  {
+   "depends_on": "eval:doc.is_internal_supplier",
+   "description": "Unrealized Profit / Loss account for intra-company transfers",
+   "fieldname": "unrealized_profit_loss_account",
+   "fieldtype": "Link",
+   "label": "Unrealized Profit / Loss Account",
+   "options": "Account"
+  },
+  {
+   "depends_on": "eval:doc.is_internal_supplier",
+   "description": "Company which internal supplier represents",
+   "fetch_from": "supplier.represents_company",
+   "fieldname": "represents_company",
+   "fieldtype": "Link",
+   "label": "Represents Company",
+   "options": "Company"
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 204,
  "is_submittable": 1,
- "modified": "2020-09-21 12:22:09.164068",
+ "links": [],
+ "modified": "2020-12-11 12:46:12.796378",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice",
@@ -1396,4 +1424,4 @@
  "timeline_field": "supplier",
  "title_field": "title",
  "track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 91c4dfb..b52678e 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -147,6 +147,11 @@
 			throw(_("Conversion rate cannot be 0 or 1"))
 
 	def validate_credit_to_acc(self):
+		if not self.credit_to:
+			self.credit_to = get_party_account("Supplier", self.supplier, self.company)
+			if not self.credit_to:
+				self.raise_missing_debit_credit_account_error("Supplier", self.supplier)
+
 		account = frappe.db.get_value("Account", self.credit_to,
 			["account_type", "report_type", "account_currency"], as_dict=True)
 
@@ -201,8 +206,8 @@
 				["Purchase Receipt", "purchase_receipt", "pr_detail"]
 			])
 
-	def validate_warehouse(self):
-		if self.update_stock:
+	def validate_warehouse(self, for_validate=True):
+		if self.update_stock and for_validate:
 			for d in self.get('items'):
 				if not d.warehouse:
 					frappe.throw(_("Warehouse required at Row No {0}, please set default warehouse for the item {1} for the company {2}").
@@ -228,7 +233,7 @@
 
 		if self.update_stock:
 			self.validate_item_code()
-			self.validate_warehouse()
+			self.validate_warehouse(for_validate)
 			if auto_accounting_for_stock:
 				warehouse_account = get_warehouse_account_map(self.company)
 
@@ -405,10 +410,13 @@
 		# this sequence because outstanding may get -negative
 		self.make_gl_entries()
 
+		if self.update_stock == 1:
+			self.repost_future_sle_and_gle()
+
 		self.update_project()
 		update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
 
-	def make_gl_entries(self, gl_entries=None):
+	def make_gl_entries(self, gl_entries=None, from_repost=False):
 		if not gl_entries:
 			gl_entries = self.get_gl_entries()
 
@@ -416,7 +424,7 @@
 			update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
 
 			if self.docstatus == 1:
-				make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False)
+				make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost)
 			elif self.docstatus == 2:
 				make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
 
@@ -431,9 +439,11 @@
 		self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
 		if self.auto_accounting_for_stock:
 			self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed")
+			self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
 		else:
 			self.stock_received_but_not_billed = None
-		self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
+			self.expenses_included_in_valuation = None
+		
 		self.negative_expense_to_be_booked = 0.0
 		gl_entries = []
 
@@ -444,15 +454,15 @@
 			self.get_asset_gl_entry(gl_entries)
 
 		self.make_tax_gl_entries(gl_entries)
+		self.make_internal_transfer_gl_entries(gl_entries)
 
 		gl_entries = make_regional_gl_entries(gl_entries, self)
-
+		
 		gl_entries = merge_similar_entries(gl_entries)
 
 		self.make_payment_gl_entries(gl_entries)
 		self.make_write_off_gl_entry(gl_entries)
 		self.make_gle_for_rounding_adjustment(gl_entries)
-
 		return gl_entries
 
 	def check_asset_cwip_enabled(self):
@@ -469,31 +479,30 @@
 		# because rounded_total had value even before introcution of posting GLE based on rounded total
 		grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
 
-		if grand_total:
-			# Didnot use base_grand_total to book rounding loss gle
-			grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
-				self.precision("grand_total"))
-			gl_entries.append(
-				self.get_gl_dict({
-					"account": self.credit_to,
-					"party_type": "Supplier",
-					"party": self.supplier,
-					"due_date": self.due_date,
-					"against": self.against_expense_account,
-					"credit": grand_total_in_company_currency,
-					"credit_in_account_currency": grand_total_in_company_currency \
-						if self.party_account_currency==self.company_currency else grand_total,
-					"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
-					"against_voucher_type": self.doctype,
-					"project": self.project,
-					"cost_center": self.cost_center
-				}, self.party_account_currency, item=self)
-			)
+		if grand_total and not self.is_internal_transfer():
+				# Didnot use base_grand_total to book rounding loss gle
+				grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
+					self.precision("grand_total"))
+				gl_entries.append(
+					self.get_gl_dict({
+						"account": self.credit_to,
+						"party_type": "Supplier",
+						"party": self.supplier,
+						"due_date": self.due_date,
+						"against": self.against_expense_account,
+						"credit": grand_total_in_company_currency,
+						"credit_in_account_currency": grand_total_in_company_currency \
+							if self.party_account_currency==self.company_currency else grand_total,
+						"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
+						"against_voucher_type": self.doctype,
+						"project": self.project,
+						"cost_center": self.cost_center
+					}, self.party_account_currency, item=self)
+				)
 
 	def make_item_gl_entries(self, gl_entries):
 		# item gl entries
 		stock_items = self.get_stock_items()
-		expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
 		if self.update_stock and self.auto_accounting_for_stock:
 			warehouse_account = get_warehouse_account_map(self.company)
 
@@ -521,7 +530,6 @@
 						item, voucher_wise_stock_value, account_currency)
 
 					if item.from_warehouse:
-
 						gl_entries.append(self.get_gl_dict({
 							"account":  warehouse_account[item.warehouse]['account'],
 							"against": warehouse_account[item.from_warehouse]["account"],
@@ -541,16 +549,18 @@
 							"debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
 						}, warehouse_account[item.from_warehouse]["account_currency"], item=item))
 
-						gl_entries.append(
-							self.get_gl_dict({
-								"account": item.expense_account,
-								"against": self.supplier,
-								"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
-								"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
-								"cost_center": item.cost_center,
-								"project": item.project
-							}, account_currency, item=item)
-						)
+						# Do not book expense for transfer within same company transfer
+						if not self.is_internal_transfer():
+							gl_entries.append(
+								self.get_gl_dict({
+									"account": item.expense_account,
+									"against": self.supplier,
+									"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
+									"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+									"cost_center": item.cost_center,
+									"project": item.project
+								}, account_currency, item=item)
+							)
 
 					else:
 						gl_entries.append(
@@ -827,7 +837,8 @@
 					}, account_currency, item=tax)
 				)
 			# accumulate valuation tax
-			if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount):
+			if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount) \
+				and not self.is_internal_transfer():
 				if self.auto_accounting_for_stock and not tax.cost_center:
 					frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category)))
 				valuation_tax.setdefault(tax.name, 0)
@@ -871,8 +882,19 @@
 							"against": self.supplier,
 							"credit": valuation_tax[tax.name],
 							"remarks": self.remarks or "Accounting Entry for Stock"
-						}, item=tax)
-					)
+						}, item=tax))
+
+	def make_internal_transfer_gl_entries(self, gl_entries):
+		if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
+			account_currency = get_account_currency(self.unrealized_profit_loss_account)
+			gl_entries.append(
+				self.get_gl_dict({
+					"account": self.unrealized_profit_loss_account,
+					"against": self.supplier,
+					"credit": flt(self.total_taxes_and_charges),
+					"credit_in_account_currency": flt(self.base_total_taxes_and_charges),
+					"cost_center": self.cost_center
+				}, account_currency, item=self))
 
 	def make_payment_gl_entries(self, gl_entries):
 		# Make Cash GL Entries
@@ -977,11 +999,15 @@
 			self.delete_auto_created_batches()
 
 		self.make_gl_entries_on_cancel()
+		
+		if self.update_stock == 1:
+			self.repost_future_sle_and_gle()
+		
 		self.update_project()
 		frappe.db.set(self, 'status', 'Cancelled')
 
 		unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
-		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
+		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
 
 	def update_project(self):
 		project_list = []
@@ -1032,7 +1058,9 @@
 				updated_pr += update_billed_amount_based_on_po(d.po_detail, update_modified)
 
 		for pr in set(updated_pr):
-			frappe.get_doc("Purchase Receipt", pr).update_billing_percentage(update_modified=update_modified)
+			from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage
+			pr_doc = frappe.get_doc("Purchase Receipt", pr)
+			update_billing_percentage(pr_doc, update_modified=update_modified)
 
 	def on_recurring(self, reference_doc, auto_repeat_doc):
 		self.due_date = None
@@ -1088,7 +1116,9 @@
 			if self.docstatus == 2:
 				status = "Cancelled"
 			elif self.docstatus == 1:
-				if outstanding_amount > 0 and due_date < nowdate:
+				if self.is_internal_transfer():
+					self.status = 'Internal Transfer'
+				elif outstanding_amount > 0 and due_date < nowdate:
 					self.status = "Overdue"
 				elif outstanding_amount > 0 and due_date >= nowdate:
 					self.status = "Unpaid"
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js
index 86c2e40..8da7d6f 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js
@@ -4,23 +4,25 @@
 // render
 frappe.listview_settings['Purchase Invoice'] = {
 	add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
-		"currency", "is_return", "release_date", "on_hold"],
+		"currency", "is_return", "release_date", "on_hold", "represents_company", "is_internal_supplier"],
 	get_indicator: function(doc) {
-		if( (flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 &&  doc.status == 'Debit Note Issued') {
+		if ((flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 &&  doc.status == 'Debit Note Issued') {
 			return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"];
-		} else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
+		} else if (flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
 			if(cint(doc.on_hold) && !doc.release_date) {
 				return [__("On Hold"), "darkgrey"];
-			} else if(cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
+			} else if (cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
 				return [__("Temporarily on Hold"), "darkgrey"];
-			} else if(frappe.datetime.get_diff(doc.due_date) < 0) {
+			} else if (frappe.datetime.get_diff(doc.due_date) < 0) {
 				return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];
 			} else {
 				return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"];
 			}
-		} else if(cint(doc.is_return)) {
+		} else if (cint(doc.is_return)) {
 			return [__("Return"), "darkgrey", "is_return,=,Yes"];
-		} else if(flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
+		} else if (doc.company == doc.represents_company && doc.is_internal_supplier) {
+			return [__("Internal Transfer"), "darkgrey", "outstanding_amount,=,0"];
+		} else if (flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
 			return [__("Paid"), "green", "outstanding_amount,=,0"];
 		}
 	}
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index f2499d2..c0506ba 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -9,8 +9,7 @@
 from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
 from frappe.utils import cint, flt, today, nowdate, add_days, getdate
 import frappe.defaults
-from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \
-	test_records as pr_test_records, make_purchase_receipt, get_taxes
+from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt, get_taxes
 from erpnext.controllers.accounts_controller import get_payment_terms
 from erpnext.exceptions import InvalidCurrency
 from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction
@@ -33,13 +32,10 @@
 
 	def test_gl_entries_without_perpetual_inventory(self):
 		frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
-		wrapper = frappe.copy_doc(test_records[0])
-		set_perpetual_inventory(0, wrapper.company)
-		self.assertTrue(not cint(erpnext.is_perpetual_inventory_enabled(wrapper.company)))
-		wrapper.insert()
-		wrapper.submit()
-		wrapper.load_from_db()
-		dl = wrapper
+		pi = frappe.copy_doc(test_records[0])
+		self.assertTrue(not cint(erpnext.is_perpetual_inventory_enabled(pi.company)))
+		pi.insert()
+		pi.submit()
 
 		expected_gl_entries = {
 			"_Test Payable - _TC": [0, 1512.0],
@@ -54,12 +50,16 @@
 			"Round Off - _TC": [0, 0.3]
 		}
 		gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
-			where voucher_type = 'Purchase Invoice' and voucher_no = %s""", dl.name, as_dict=1)
+			where voucher_type = 'Purchase Invoice' and voucher_no = %s""", pi.name, as_dict=1)
 		for d in gl_entries:
 			self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account))
 
 	def test_gl_entries_with_perpetual_inventory(self):
-		pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10)
+		pi = make_purchase_invoice(company="_Test Company with perpetual inventory",
+			warehouse= "Stores - TCP1", cost_center = "Main - TCP1",
+			expense_account ="_Test Account Cost for Goods Sold - TCP1",
+			get_taxes_and_charges=True, qty=10)
+
 		self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1)
 
 		self.check_gle_for_pi(pi.name)
@@ -198,8 +198,6 @@
 
 		pr = make_purchase_receipt(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", get_taxes_and_charges=True,)
 
-		self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1)
-
 		pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10,do_not_save= "True")
 
 		for d in pi.items:
@@ -247,17 +245,11 @@
 
 		self.assertRaises(frappe.CannotChangeConstantError, pi.save)
 
-	def test_gl_entries_with_aia_for_non_stock_items(self):
-		pi = frappe.copy_doc(test_records[1])
-		set_perpetual_inventory(1, pi.company)
-		self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1)
-		pi.get("items")[0].item_code = "_Test Non Stock Item"
-		pi.get("items")[0].expense_account = "_Test Account Cost for Goods Sold - _TC"
-		pi.get("taxes").pop(0)
-		pi.get("taxes").pop(1)
-		pi.insert()
-		pi.submit()
-		pi.load_from_db()
+	def test_gl_entries_for_non_stock_items_with_perpetual_inventory(self):
+		pi = make_purchase_invoice(item_code = "_Test Non Stock Item",
+			company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
+			cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
+
 		self.assertTrue(pi.status, "Unpaid")
 
 		gl_entries = frappe.db.sql("""select account, debit, credit
@@ -265,17 +257,15 @@
 			order by account asc""", pi.name, as_dict=1)
 		self.assertTrue(gl_entries)
 
-		expected_values = sorted([
-			["_Test Payable - _TC", 0, 620],
-			["_Test Account Cost for Goods Sold - _TC", 500.0, 0],
-			["_Test Account VAT - _TC", 120.0, 0],
-		])
+		expected_values = [
+			["_Test Account Cost for Goods Sold - TCP1", 250.0, 0],
+			["Creditors - TCP1", 0, 250]
+		]
 
 		for i, gle in enumerate(gl_entries):
 			self.assertEqual(expected_values[i][0], gle.account)
 			self.assertEqual(expected_values[i][1], gle.debit)
 			self.assertEqual(expected_values[i][2], gle.credit)
-		set_perpetual_inventory(0, pi.company)
 
 	def test_purchase_invoice_calculation(self):
 		pi = frappe.copy_doc(test_records[0])
@@ -457,12 +447,13 @@
 		pi.cancel()
 		self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), existing_purchase_cost)
 
-	def test_return_purchase_invoice(self):
-		set_perpetual_inventory()
+	def test_return_purchase_invoice_with_perpetual_inventory(self):
+		pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
+			cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
 
-		pi = make_purchase_invoice()
-
-		return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2)
+		return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2, 
+			company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
+			cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
 
 
 		# check gl entries for return
@@ -473,19 +464,15 @@
 		self.assertTrue(gl_entries)
 
 		expected_values = {
-			"Creditors - _TC": [100.0, 0.0],
-			"Stock Received But Not Billed - _TC": [0.0, 100.0],
+			"Creditors - TCP1": [100.0, 0.0],
+			"Stock Received But Not Billed - TCP1": [0.0, 100.0],
 		}
 
 		for gle in gl_entries:
 			self.assertEqual(expected_values[gle.account][0], gle.debit)
 			self.assertEqual(expected_values[gle.account][1], gle.credit)
 
-		set_perpetual_inventory(0)
-
 	def test_multi_currency_gle(self):
-		set_perpetual_inventory(0)
-
 		pi = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC",
 			currency="USD", conversion_rate=50)
 
@@ -640,10 +627,9 @@
 		self.assertEqual(len(pi.get("supplied_items")), 2)
 
 		rm_supp_cost = sum([d.amount for d in pi.get("supplied_items")])
-		self.assertEqual(pi.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2))
+		self.assertEqual(flt(pi.get("items")[0].rm_supp_cost, 2), flt(rm_supp_cost, 2))
 
 	def test_rejected_serial_no(self):
-		set_perpetual_inventory(0)
 		pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1,
 			rejected_qty=1, rate=500, update_stock=1,
 			rejected_warehouse = "_Test Rejected Warehouse - _TC")
diff --git a/erpnext/accounts/doctype/salary_component_account/salary_component_account.json b/erpnext/accounts/doctype/salary_component_account/salary_component_account.json
index 23dc6c4..f1ed8ef 100644
--- a/erpnext/accounts/doctype/salary_component_account/salary_component_account.json
+++ b/erpnext/accounts/doctype/salary_component_account/salary_component_account.json
@@ -1,92 +1,38 @@
 {
- "allow_copy": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2016-07-27 17:24:24.956896", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
+ "actions": [],
+ "creation": "2016-07-27 17:24:24.956896",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "company",
+  "account"
+ ],
  "fields": [
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "company", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Company", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Company", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Company",
+   "options": "Company"
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "description": "Default Bank / Cash account will be automatically updated in Salary Journal Entry when this mode is selected.", 
-   "fieldname": "default_account", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Default Account", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Account", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
+   "description": "Default Bank / Cash account will be automatically updated in Salary Journal Entry when this mode is selected.",
+   "fieldname": "account",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Account",
+   "options": "Account"
   }
- ], 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
-
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "modified": "2016-09-02 07:49:06.567389", 
- "modified_by": "Administrator", 
- "module": "Accounts", 
- "name": "Salary Component Account", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-10-18 17:57:57.110257",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Salary Component Account",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC"
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india.js b/erpnext/accounts/doctype/sales_invoice/regional/india.js
index 6336db1..f54bce8 100644
--- a/erpnext/accounts/doctype/sales_invoice/regional/india.js
+++ b/erpnext/accounts/doctype/sales_invoice/regional/india.js
@@ -1,6 +1,8 @@
 {% include "erpnext/regional/india/taxes.js" %}
+{% include "erpnext/regional/india/e_invoice/einvoice.js" %}
 
 erpnext.setup_auto_gst_taxation('Sales Invoice');
+erpnext.setup_einvoice_actions('Sales Invoice')
 
 frappe.ui.form.on("Sales Invoice", {
 	setup: function(frm) {
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 502e65e..5efc32e 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -580,6 +580,16 @@
 			};
 		});
 
+		frm.set_query("unrealized_profit_loss_account", function() {
+			return {
+				filters: {
+					company: frm.doc.company,
+					is_group: 0,
+					root_type: "Liability",
+				}
+			};
+		});
+
 		frm.custom_make_buttons = {
 			'Delivery Note': 'Delivery',
 			'Sales Invoice': 'Sales Return',
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index ae40153..6799fb9 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -157,6 +157,7 @@
   "more_information",
   "inter_company_invoice_reference",
   "is_internal_customer",
+  "represents_company",
   "customer_group",
   "campaign",
   "is_discounted",
@@ -170,6 +171,7 @@
   "c_form_applicable",
   "c_form_no",
   "column_break8",
+  "unrealized_profit_loss_account",
   "remarks",
   "sales_team_section_break",
   "sales_partner",
@@ -1654,7 +1656,7 @@
    "in_standard_filter": 1,
    "label": "Status",
    "no_copy": 1,
-   "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled",
+   "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer",
    "print_hide": 1,
    "read_only": 1
   },
@@ -1949,13 +1951,31 @@
    "fieldtype": "Data",
    "label": "Company Tax ID",
    "read_only": 1
+  },
+  {
+   "depends_on": "eval:doc.is_internal_customer",
+   "description": "Unrealized Profit / Loss account for intra-company transfers",
+   "fieldname": "unrealized_profit_loss_account",
+   "fieldtype": "Link",
+   "label": "Unrealized Profit / Loss Account",
+   "options": "Account"
+  },
+  {
+   "depends_on": "eval:doc.is_internal_customer",
+   "description": "Company which internal customer represents",
+   "fetch_from": "customer.represents_company",
+   "fieldname": "represents_company",
+   "fieldtype": "Link",
+   "label": "Represents Company",
+   "options": "Company",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 181,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-09 15:59:57.544736",
+ "modified": "2020-12-11 12:48:31.769958",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 0530aa2..50eb400 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -179,6 +179,9 @@
 
 		# this sequence because outstanding may get -ve
 		self.make_gl_entries()
+		
+		if self.update_stock == 1:
+			self.repost_future_sle_and_gle()
 
 		if not self.is_return:
 			self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
@@ -229,9 +232,9 @@
 			frappe.throw(_("At least one mode of payment is required for POS invoice."))
 
 	def before_cancel(self):
+		super(SalesInvoice, self).before_cancel()
 		self.update_time_sheet(None)
 
-
 	def on_cancel(self):
 		super(SalesInvoice, self).on_cancel()
 
@@ -258,6 +261,10 @@
 			self.update_stock_ledger()
 
 		self.make_gl_entries_on_cancel()
+		
+		if self.update_stock == 1:
+			self.repost_future_sle_and_gle()
+		
 		frappe.db.set(self, 'status', 'Cancelled')
 
 		if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction":
@@ -279,7 +286,7 @@
 		if "Healthcare" in active_domains:
 			manage_invoice_submit_cancel(self, "on_cancel")
 
-		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
+		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
 
 	def update_status_updater_args(self):
 		if cint(self.update_stock):
@@ -405,6 +412,8 @@
 		from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
 		if not self.pos_profile:
 			pos_profile = get_pos_profile(self.company) or {}
+			if not pos_profile:
+				frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
 			self.pos_profile = pos_profile.get('name')
 
 		pos = {}
@@ -472,6 +481,11 @@
 		return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0]
 
 	def validate_debit_to_acc(self):
+		if not self.debit_to:
+			self.debit_to = get_party_account("Customer", self.customer, self.company)
+			if not self.debit_to:
+				self.raise_missing_debit_credit_account_error("Customer", self.customer)
+
 		account = frappe.get_cached_value("Account", self.debit_to,
 			["account_type", "report_type", "account_currency"], as_dict=True)
 
@@ -720,22 +734,20 @@
 			if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1:
 				throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
 
-	def make_gl_entries(self, gl_entries=None):
-		from erpnext.accounts.general_ledger import make_reverse_gl_entries
+	def make_gl_entries(self, gl_entries=None, from_repost=False):
+		from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
 
 		auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
 		if not gl_entries:
 			gl_entries = self.get_gl_entries()
 
 		if gl_entries:
-			from erpnext.accounts.general_ledger import make_gl_entries
-
 			# if POS and amount is written off, updating outstanding amt after posting all gl entries
 			update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account or
 				cint(self.redeem_loyalty_points)) else "Yes"
 
 			if self.docstatus == 1:
-				make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False)
+				make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost)
 			elif self.docstatus == 2:
 				make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
 
@@ -756,6 +768,7 @@
 		self.make_customer_gl_entry(gl_entries)
 
 		self.make_tax_gl_entries(gl_entries)
+		self.make_internal_transfer_gl_entries(gl_entries)
 
 		self.make_item_gl_entries(gl_entries)
 
@@ -775,7 +788,7 @@
 		# Checked both rounding_adjustment and rounded_total
 		# because rounded_total had value even before introcution of posting GLE based on rounded total
 		grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
-		if grand_total:
+		if grand_total and not self.is_internal_transfer():
 			# Didnot use base_grand_total to book rounding loss gle
 			grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
 				self.precision("grand_total"))
@@ -814,6 +827,18 @@
 					}, account_currency, item=tax)
 				)
 
+	def make_internal_transfer_gl_entries(self, gl_entries):
+		if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
+			account_currency = get_account_currency(self.unrealized_profit_loss_account)
+			gl_entries.append(
+				self.get_gl_dict({
+					"account": self.unrealized_profit_loss_account,
+					"against": self.customer,
+					"debit": flt(self.total_taxes_and_charges),
+					"debit_in_account_currency": flt(self.base_total_taxes_and_charges),
+					"cost_center": self.cost_center
+				}, account_currency, item=self))
+
 	def make_item_gl_entries(self, gl_entries):
 		# income account gl entries
 		for item in self.get("items"):
@@ -836,22 +861,24 @@
 					asset.db_set("disposal_date", self.posting_date)
 					asset.set_status("Sold" if self.docstatus==1 else None)
 				else:
-					income_account = (item.income_account
-						if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
+					# Do not book income for transfer within same company
+					if not self.is_internal_transfer():
+						income_account = (item.income_account
+							if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
 
-					account_currency = get_account_currency(income_account)
-					gl_entries.append(
-						self.get_gl_dict({
-							"account": income_account,
-							"against": self.customer,
-							"credit": flt(item.base_net_amount, item.precision("base_net_amount")),
-							"credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))
-								if account_currency==self.company_currency
-								else flt(item.net_amount, item.precision("net_amount"))),
-							"cost_center": item.cost_center,
-							"project": item.project or self.project
-						}, account_currency, item=item)
-					)
+						account_currency = get_account_currency(income_account)
+						gl_entries.append(
+							self.get_gl_dict({
+								"account": income_account,
+								"against": self.customer,
+								"credit": flt(item.base_net_amount, item.precision("base_net_amount")),
+								"credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))
+									if account_currency==self.company_currency
+									else flt(item.net_amount, item.precision("net_amount"))),
+								"cost_center": item.cost_center,
+								"project": item.project or self.project
+							}, account_currency, item=item)
+						)
 
 		# expense account gl entries
 		if cint(self.update_stock) and \
@@ -1263,7 +1290,9 @@
 			if self.docstatus == 2:
 				status = "Cancelled"
 			elif self.docstatus == 1:
-				if outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed':
+				if self.is_internal_transfer():
+					self.status = 'Internal Transfer'
+				elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed':
 					self.status = "Overdue and Discounted"
 				elif outstanding_amount > 0 and due_date < nowdate:
 					self.status = "Overdue"
@@ -1528,9 +1557,13 @@
 	if doctype in ["Sales Invoice", "Sales Order"]:
 		source_doc = frappe.get_doc(doctype, source_name)
 		target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
+		source_document_warehouse_field = 'target_warehouse'
+		target_document_warehouse_field = 'from_warehouse'
 	else:
 		source_doc = frappe.get_doc(doctype, source_name)
 		target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order"
+		source_document_warehouse_field = 'from_warehouse'
+		target_document_warehouse_field = 'target_warehouse'
 
 	validate_inter_company_transaction(source_doc, doctype)
 	details = get_inter_company_details(source_doc, doctype)
@@ -1557,6 +1590,26 @@
 			if currency:
 				target_doc.currency = currency
 
+	item_field_map = {
+		"doctype": target_doctype + " Item",
+		"field_no_map": [
+			"income_account",
+			"expense_account",
+			"cost_center",
+			"warehouse"
+		]
+	}
+
+	if source_doc.get('update_stock'):
+		item_field_map.update({
+			'field_map': {
+				source_document_warehouse_field: target_document_warehouse_field,
+				'batch_no': 'batch_no',
+				'serial_no': 'serial_no'
+			}
+		})
+
+
 	doclist = get_mapped_doc(doctype, source_name,	{
 		doctype: {
 			"doctype": target_doctype,
@@ -1565,15 +1618,7 @@
 				"taxes_and_charges"
 			]
 		},
-		doctype +" Item": {
-			"doctype": target_doctype + " Item",
-			"field_no_map": [
-				"income_account",
-				"expense_account",
-				"cost_center",
-				"warehouse"
-			]
-		}
+		doctype +" Item": item_field_map
 
 	}, target_doc, set_missing_values)
 
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js
index 05d49df..41140d1 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js
@@ -14,8 +14,8 @@
 			"Credit Note Issued": "darkgrey",
 			"Unpaid and Discounted": "orange",
 			"Overdue and Discounted": "red",
-			"Overdue": "red"
-
+			"Overdue": "red",
+			"Internal Transfer": "darkgrey"
 		};
 		return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
 	},
diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json
index 11ebe6a..ee6419d 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_records.json
+++ b/erpnext/accounts/doctype/sales_invoice/test_records.json
@@ -17,7 +17,8 @@
     "description": "138-CMS Shoe",
     "doctype": "Sales Invoice Item",
     "income_account": "Sales - _TC",
-  	"expense_account": "_Test Account Cost for Goods Sold - _TC",
+    "expense_account": "_Test Account Cost for Goods Sold - _TC",
+    "item_code": "138-CMS Shoe",
     "item_name": "138-CMS Shoe",
     "parentfield": "items",
     "qty": 1.0,
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 9660c95..eb223ee 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -10,7 +10,6 @@
 from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
 from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
 from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
-from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
 from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
 from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
 from frappe.model.naming import make_autoname
@@ -659,7 +658,6 @@
 
 	def test_sales_invoice_gl_entry_without_perpetual_inventory(self):
 		si = frappe.copy_doc(test_records[1])
-		set_perpetual_inventory(0, si.company)
 		si.insert()
 		si.submit()
 
@@ -690,7 +688,8 @@
 		self.assertTrue(gle)
 
 	def test_pos_gl_entry_with_perpetual_inventory(self):
-		make_pos_profile()
+		make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1", 
+			expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
 
 		pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
 
@@ -746,7 +745,8 @@
 		self.assertEqual(pos_return.get('payments')[0].amount, -1000)
 
 	def test_pos_change_amount(self):
-		make_pos_profile()
+		make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1", 
+			expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
 
 		pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
 			item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
@@ -813,7 +813,6 @@
 		frappe.db.sql("delete from `tabPOS Profile`")
 
 	def test_pos_si_without_payment(self):
-		set_perpetual_inventory()
 		make_pos_profile()
 
 		pos = copy.deepcopy(test_records[1])
@@ -827,9 +826,8 @@
 		self.assertRaises(frappe.ValidationError, si.submit)
 
 	def test_sales_invoice_gl_entry_with_perpetual_inventory_no_item_code(self):
-		set_perpetual_inventory()
-
-		si = frappe.get_doc(test_records[1])
+		si = create_sales_invoice(company="_Test Company with perpetual inventory", debit_to = "Debtors - TCP1",
+			income_account="Sales - TCP1", cost_center = "Main - TCP1", do_not_save=True)
 		si.get("items")[0].item_code = None
 		si.insert()
 		si.submit()
@@ -840,24 +838,16 @@
 		self.assertTrue(gl_entries)
 
 		expected_values = dict((d[0], d) for d in [
-			[si.debit_to, 630.0, 0.0],
-			[test_records[1]["items"][0]["income_account"], 0.0, 500.0],
-			[test_records[1]["taxes"][0]["account_head"], 0.0, 80.0],
-			[test_records[1]["taxes"][1]["account_head"], 0.0, 50.0],
+			["Debtors - TCP1", 100.0, 0.0],
+			["Sales - TCP1", 0.0, 100.0]
 		])
 		for i, gle in enumerate(gl_entries):
 			self.assertEqual(expected_values[gle.account][0], gle.account)
 			self.assertEqual(expected_values[gle.account][1], gle.debit)
 			self.assertEqual(expected_values[gle.account][2], gle.credit)
 
-		set_perpetual_inventory(0)
-
 	def test_sales_invoice_gl_entry_with_perpetual_inventory_non_stock_item(self):
-		set_perpetual_inventory()
-		si = frappe.get_doc(test_records[1])
-		si.get("items")[0].item_code = "_Test Non Stock Item"
-		si.insert()
-		si.submit()
+		si = create_sales_invoice(item="_Test Non Stock Item")
 
 		gl_entries = frappe.db.sql("""select account, debit, credit
 			from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
@@ -865,17 +855,14 @@
 		self.assertTrue(gl_entries)
 
 		expected_values = dict((d[0], d) for d in [
-			[si.debit_to, 630.0, 0.0],
-			[test_records[1]["items"][0]["income_account"], 0.0, 500.0],
-			[test_records[1]["taxes"][0]["account_head"], 0.0, 80.0],
-			[test_records[1]["taxes"][1]["account_head"], 0.0, 50.0],
+			[si.debit_to, 100.0, 0.0],
+			[test_records[1]["items"][0]["income_account"], 0.0, 100.0]
 		])
 		for i, gle in enumerate(gl_entries):
 			self.assertEqual(expected_values[gle.account][0], gle.account)
 			self.assertEqual(expected_values[gle.account][1], gle.debit)
 			self.assertEqual(expected_values[gle.account][2], gle.credit)
 
-		set_perpetual_inventory(0)
 
 	def _insert_purchase_receipt(self):
 		from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import test_records \
@@ -1104,7 +1091,6 @@
 		self.assertEqual(si.grand_total, 859.43)
 
 	def test_multi_currency_gle(self):
-		set_perpetual_inventory(0)
 		si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
 			currency="USD", conversion_rate=50)
 
@@ -1571,7 +1557,7 @@
 
 		for gle in gl_entries:
 			self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
-	
+
 	def test_sales_invoice_with_project_link(self):
 		from erpnext.projects.doctype.project.test_project import make_project
 
@@ -1605,9 +1591,9 @@
 			debit_in_account_currency, credit_in_account_currency
 			from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
 			order by account asc""", sales_invoice.name, as_dict=1)
-		
+
 		self.assertTrue(gl_entries)
-		
+
 		for gle in gl_entries:
 			self.assertEqual(expected_values[gle.account]["project"], gle.project)
 
@@ -1774,99 +1760,72 @@
 		si.submit()
 
 		target_doc = make_inter_company_transaction("Sales Invoice", si.name)
+		target_doc.items[0].update({
+			"expense_account": "Cost of Goods Sold - _TC1",
+			"cost_center": "Main - _TC1",
+			"warehouse": "Stores - _TC1"
+		})
 		target_doc.submit()
 
 		self.assertEqual(target_doc.company, "_Test Company 1")
 		self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
 
+	# def test_internal_transfer_gl_entry(self):
+	# 	## Create internal transfer account
+	# 	account = create_account(account_name="Unrealized Profit",
+	# 		parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
+
+	# 	frappe.db.set_value('Company', '_Test Company with perpetual inventory',
+	# 		'unrealized_profit_loss_account', account)
+
+	# 	customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
+	# 		"_Test Company with perpetual inventory")
+
+	# 	create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
+	# 		"_Test Company with perpetual inventory")
+
+	# 	si = create_sales_invoice(
+	# 		company = "_Test Company with perpetual inventory",
+	# 		customer = customer,
+	# 		debit_to = "Debtors - TCP1",
+	# 		warehouse = "Stores - TCP1",
+	# 		income_account = "Sales - TCP1",
+	# 		expense_account = "Cost of Goods Sold - TCP1",
+	# 		cost_center = "Main - TCP1",
+	# 		currency = "INR",
+	# 		do_not_save = 1
+	# 	)
+
+	# 	si.selling_price_list = "_Test Price List Rest of the World"
+	# 	si.update_stock = 1
+	# 	si.items[0].target_warehouse = 'Work In Progress - TCP1'
+	# 	add_taxes(si)
+	# 	si.save()
+	# 	si.submit()
+
+	# 	target_doc = make_inter_company_transaction("Sales Invoice", si.name)
+	# 	target_doc.company = '_Test Company with perpetual inventory'
+	# 	target_doc.items[0].warehouse = 'Finished Goods - TCP1'
+	# 	add_taxes(target_doc)
+	# 	target_doc.save()
+	# 	target_doc.submit()
+
+	# 	si_gl_entries = [
+	# 		["_Test Account Excise Duty - TCP1", 0.0, 12.0, nowdate()],
+	# 		["Unrealized Profit - TCP1", 12.0, 0.0, nowdate()]
+	# 	]
+
+	# 	check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
+
+	# 	pi_gl_entries = [
+	# 		["_Test Account Excise Duty - TCP1", 12.0 , 0.0, nowdate()],
+	# 		["Unrealized Profit - TCP1", 0.0, 12.0, nowdate()]
+	# 	]
+
+	# 	check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
+
 	def test_eway_bill_json(self):
-		if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
-			address = frappe.get_doc({
-				"address_line1": "_Test Address Line 1",
-				"address_title": "_Test Address for Eway bill",
-				"address_type": "Billing",
-				"city": "_Test City",
-				"state": "Test State",
-				"country": "India",
-				"doctype": "Address",
-				"is_primary_address": 1,
-				"phone": "+91 0000000000",
-				"gstin": "27AAECE4835E1ZR",
-				"gst_state": "Maharashtra",
-				"gst_state_number": "27",
-				"pincode": "401108"
-			}).insert()
-
-			address.append("links", {
-				"link_doctype": "Company",
-				"link_name": "_Test Company"
-			})
-
-			address.save()
-
-		if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'):
-			address = frappe.get_doc({
-				"address_line1": "_Test Address Line 1",
-				"address_title": "_Test Customer-Address for Eway bill",
-				"address_type": "Shipping",
-				"city": "_Test City",
-				"state": "Test State",
-				"country": "India",
-				"doctype": "Address",
-				"is_primary_address": 1,
-				"phone": "+91 0000000000",
-				"gst_state": "Maharashtra",
-				"gst_state_number": "27",
-				"pincode": "410038"
-			}).insert()
-
-			address.append("links", {
-				"link_doctype": "Customer",
-				"link_name": "_Test Customer"
-			})
-
-			address.save()
-
-		gst_settings = frappe.get_doc("GST Settings")
-
-		gst_account = frappe.get_all(
-			"GST Account",
-			fields=["cgst_account", "sgst_account", "igst_account"],
-			filters = {"company": "_Test Company"})
-
-		if not gst_account:
-			gst_settings.append("gst_accounts", {
-				"company": "_Test Company",
-				"cgst_account": "CGST - _TC",
-				"sgst_account": "SGST - _TC",
-				"igst_account": "IGST - _TC",
-			})
-
-		gst_settings.save()
-
-		si = create_sales_invoice(do_not_save =1, rate = '60000')
-
-		si.distance = 2000
-		si.company_address = "_Test Address for Eway bill-Billing"
-		si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
-		si.vehicle_no = "KA12KA1234"
-		si.gst_category = "Registered Regular"
-
-		si.append("taxes", {
-			"charge_type": "On Net Total",
-			"account_head": "CGST - _TC",
-			"cost_center": "Main - _TC",
-			"description": "CGST @ 9.0",
-			"rate": 9
-		})
-
-		si.append("taxes", {
-			"charge_type": "On Net Total",
-			"account_head": "SGST - _TC",
-			"cost_center": "Main - _TC",
-			"description": "SGST @ 9.0",
-			"rate": 9
-		})
+		si = make_sales_invoice_for_ewaybill()
 
 		si.submit()
 
@@ -1882,6 +1841,214 @@
 		self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
 		self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
 		self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
+	
+	def test_einvoice_submission_without_irn(self):
+		# init
+		frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1)
+		country = frappe.flags.country
+		frappe.flags.country = 'India'
+
+		si = make_sales_invoice_for_ewaybill()
+		self.assertRaises(frappe.ValidationError, si.submit)
+
+		si.irn = 'test_irn'
+		si.submit()
+
+		# reset
+		frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0)
+		frappe.flags.country = country
+	
+	def test_einvoice_json(self):
+		from erpnext.regional.india.e_invoice.utils import make_einvoice
+
+		customer_gstin = '27AACCM7806M1Z3'
+		customer_gstin_dtls = {
+			'LegalName': '_Test Customer', 'TradeName': '_Test Customer', 'AddrLoc': '_Test City',
+			'StateCode': '27', 'AddrPncd': '410038', 'AddrBno': '_Test Bldg',
+			'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
+		}
+		company_gstin = '27AAECE4835E1ZR'
+		company_gstin_dtls = {
+			'LegalName': '_Test Company', 'TradeName': '_Test Company', 'AddrLoc': '_Test City',
+			'StateCode': '27', 'AddrPncd': '401108', 'AddrBno': '_Test Bldg',
+			'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
+		}
+		# set cache gstin details to avoid fetching details which will require connection to GSP servers
+		frappe.local.gstin_cache = {}
+		frappe.local.gstin_cache[customer_gstin] = customer_gstin_dtls
+		frappe.local.gstin_cache[company_gstin] = company_gstin_dtls
+
+		si = make_sales_invoice_for_ewaybill()
+		si.naming_series = 'INV-2020-.#####'
+		si.items = []
+		si.append("items", {
+			"item_code": "_Test Item",
+			"uom": "Nos",
+			"warehouse": "_Test Warehouse - _TC",
+			"qty": 2000,
+			"rate": 12,
+			"income_account": "Sales - _TC",
+			"expense_account": "Cost of Goods Sold - _TC",
+			"cost_center": "_Test Cost Center - _TC",
+		})
+		si.append("items", {
+			"item_code": "_Test Item 2",
+			"uom": "Nos",
+			"warehouse": "_Test Warehouse - _TC",
+			"qty": 420,
+			"rate": 15,
+			"income_account": "Sales - _TC",
+			"expense_account": "Cost of Goods Sold - _TC",
+			"cost_center": "_Test Cost Center - _TC",
+		})
+		si.discount_amount = 100
+		si.save()
+
+		einvoice = make_einvoice(si)
+
+		total_item_ass_value = 0
+		total_item_cgst_value = 0
+		total_item_sgst_value = 0
+		total_item_igst_value = 0
+		total_item_value = 0
+
+		for item in einvoice['ItemList']:
+			total_item_ass_value += item['AssAmt']
+			total_item_cgst_value += item['CgstAmt']
+			total_item_sgst_value += item['SgstAmt']
+			total_item_igst_value += item['IgstAmt']
+			total_item_value += item['TotItemVal']
+
+			self.assertTrue(item['AssAmt'], item['TotAmt'] - item['Discount'])
+			self.assertTrue(item['TotItemVal'], item['AssAmt'] + item['CgstAmt'] + item['SgstAmt'] + item['IgstAmt'])
+
+		value_details = einvoice['ValDtls']
+
+		self.assertEqual(einvoice['Version'], '1.1')
+		self.assertEqual(value_details['AssVal'], total_item_ass_value)
+		self.assertEqual(value_details['CgstVal'], total_item_cgst_value)
+		self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
+		self.assertEqual(value_details['IgstVal'], total_item_igst_value)
+
+		self.assertEqual(
+			value_details['TotInvVal'],
+			value_details['AssVal'] + value_details['CgstVal']
+			+ value_details['SgstVal'] + value_details['IgstVal']
+			+ value_details['OthChrg'] - value_details['Discount']
+		)
+
+		self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
+		self.assertTrue(einvoice['EwbDtls'])
+
+def make_test_address_for_ewaybill():
+	if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
+		address = frappe.get_doc({
+			"address_line1": "_Test Address Line 1",
+			"address_title": "_Test Address for Eway bill",
+			"address_type": "Billing",
+			"city": "_Test City",
+			"state": "Test State",
+			"country": "India",
+			"doctype": "Address",
+			"is_primary_address": 1,
+			"phone": "+910000000000",
+			"gstin": "27AAECE4835E1ZR",
+			"gst_state": "Maharashtra",
+			"gst_state_number": "27",
+			"pincode": "401108"
+		}).insert()
+
+		address.append("links", {
+			"link_doctype": "Company",
+			"link_name": "_Test Company"
+		})
+
+		address.save()
+
+	if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'):
+		address = frappe.get_doc({
+			"address_line1": "_Test Address Line 1",
+			"address_title": "_Test Customer-Address for Eway bill",
+			"address_type": "Shipping",
+			"city": "_Test City",
+			"state": "Test State",
+			"country": "India",
+			"doctype": "Address",
+			"is_primary_address": 1,
+			"phone": "+910000000000",
+			"gstin": "27AACCM7806M1Z3",
+			"gst_state": "Maharashtra",
+			"gst_state_number": "27",
+			"pincode": "410038"
+		}).insert()
+
+		address.append("links", {
+			"link_doctype": "Customer",
+			"link_name": "_Test Customer"
+		})
+
+		address.save()
+
+def make_test_transporter_for_ewaybill():
+	if not frappe.db.exists('Supplier', '_Test Transporter'):
+		frappe.get_doc({
+			"doctype": "Supplier",
+			"supplier_name": "_Test Transporter",
+			"country": "India",
+			"supplier_group": "_Test Supplier Group",
+			"supplier_type": "Company",
+			"is_transporter": 1
+		}).insert()
+
+def make_sales_invoice_for_ewaybill():
+	make_test_address_for_ewaybill()
+	make_test_transporter_for_ewaybill()
+
+	gst_settings = frappe.get_doc("GST Settings")
+
+	gst_account = frappe.get_all(
+		"GST Account",
+		fields=["cgst_account", "sgst_account", "igst_account"],
+		filters = {"company": "_Test Company"}
+	)
+
+	if not gst_account:
+		gst_settings.append("gst_accounts", {
+			"company": "_Test Company",
+			"cgst_account": "CGST - _TC",
+			"sgst_account": "SGST - _TC",
+			"igst_account": "IGST - _TC",
+		})
+
+	gst_settings.save()
+
+	si = create_sales_invoice(do_not_save=1, rate='60000')
+
+	si.distance = 2000
+	si.company_address = "_Test Address for Eway bill-Billing"
+	si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
+	si.vehicle_no = "KA12KA1234"
+	si.gst_category = "Registered Regular"
+	si.mode_of_transport = 'Road'
+	si.transporter = '_Test Transporter'
+
+	si.append("taxes", {
+		"charge_type": "On Net Total",
+		"account_head": "CGST - _TC",
+		"cost_center": "Main - _TC",
+		"description": "CGST @ 9.0",
+		"rate": 9
+	})
+
+	si.append("taxes", {
+		"charge_type": "On Net Total",
+		"account_head": "SGST - _TC",
+		"cost_center": "Main - _TC",
+		"description": "SGST @ 9.0",
+		"rate": 9
+	})
+
+	return si
 
 def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
 	gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
@@ -1935,14 +2102,19 @@
 
 	si.append("items", {
 		"item_code": args.item or args.item_code or "_Test Item",
+		"item_name": args.item_name or "_Test Item",
+		"description": args.description or "_Test Item",
 		"gst_hsn_code": "999800",
 		"warehouse": args.warehouse or "_Test Warehouse - _TC",
 		"qty": args.qty or 1,
+		"uom": args.uom or "Nos",
+		"stock_uom": args.uom or "Nos",
 		"rate": args.rate if args.get("rate") is not None else 100,
 		"income_account": args.income_account or "Sales - _TC",
 		"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
 		"cost_center": args.cost_center or "_Test Cost Center - _TC",
-		"serial_no": args.serial_no
+		"serial_no": args.serial_no,
+		"conversion_factor": 1
 	})
 
 	if not args.do_not_save:
@@ -2037,4 +2209,57 @@
 	"parentfield": "taxes",
 	"rate": 2,
 	"row_id": 1
-   }]
\ No newline at end of file
+	}]
+
+def create_internal_customer(customer_name, represents_company, allowed_to_interact_with):
+	if not frappe.db.exists("Customer", customer_name):
+		customer = frappe.get_doc({
+			"customer_group": "_Test Customer Group",
+			"customer_name": customer_name,
+			"customer_type": "Individual",
+			"doctype": "Customer",
+			"territory": "_Test Territory",
+			"is_internal_customer": 1,
+			"represents_company": represents_company
+		})
+
+		customer.append("companies", {
+			"company": allowed_to_interact_with
+		})
+
+		customer.insert()
+		customer_name = customer.name
+	else:
+		customer_name = frappe.db.get_value("Customer", customer_name)
+
+	return customer_name
+
+def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
+	if not frappe.db.exists("Supplier", supplier_name):
+		supplier = frappe.get_doc({
+			"supplier_group": "_Test Supplier Group",
+			"supplier_name": supplier_name,
+			"doctype": "Supplier",
+			"is_internal_supplier": 1,
+			"represents_company": represents_company
+		})
+
+		supplier.append("companies", {
+			"company": allowed_to_interact_with
+		})
+
+		supplier.insert()
+		supplier_name = supplier.name
+	else:
+		supplier_name = frappe.db.exists("Supplier", supplier_name)
+
+	return supplier_name
+
+def add_taxes(doc):
+	doc.append('taxes', {
+		'account_head': '_Test Account Excise Duty - TCP1',
+		"charge_type": "On Net Total",
+		"cost_center": "Main - TCP1",
+		"description": "Excise Duty",
+		"rate": 12
+	})
\ No newline at end of file
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 fb3dd6a..3695075 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "autoname": "hash",
  "creation": "2013-06-04 11:02:19",
  "doctype": "DocType",
@@ -51,6 +52,7 @@
   "column_break_24",
   "base_net_rate",
   "base_net_amount",
+  "incoming_rate",
   "drop_ship",
   "delivered_by_supplier",
   "accounting",
@@ -792,20 +794,28 @@
    "options": "Project"
   },
   {
-    "depends_on": "eval:parent.update_stock == 1",
-    "fieldname": "sales_invoice_item",
-    "fieldtype": "Data",
-    "ignore_user_permissions": 1,
-    "label": "Sales Invoice Item",
-    "no_copy": 1,
-    "print_hide": 1,
-    "read_only": 1
-   }
+   "depends_on": "eval:parent.update_stock == 1",
+   "fieldname": "sales_invoice_item",
+   "fieldtype": "Data",
+   "ignore_user_permissions": 1,
+   "label": "Sales Invoice Item",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "incoming_rate",
+   "fieldtype": "Currency",
+   "label": "Incoming Rate",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-08-20 11:24:41.749986",
+ "modified": "2020-09-23 19:59:04.879322",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice Item",
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index 8b5e68b..32ad4cb 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -140,9 +140,9 @@
 		else:
 			tds_amount = _get_tds(net_total, tax_details.rate)
 	else:
-		supplier_credit_amount = frappe.get_all('Purchase Invoice Item',
-			fields = ['sum(net_amount)'],
-			filters = {'parent': ('in', vouchers), 'docstatus': 1}, as_list=1)
+		supplier_credit_amount = frappe.get_all('Purchase Invoice',
+			fields = ['sum(net_total)'],
+			filters = {'name': ('in', vouchers), 'docstatus': 1, "apply_tds": 1}, as_list=1)
 
 		supplier_credit_amount = (supplier_credit_amount[0][0]
 			if supplier_credit_amount and supplier_credit_amount[0][0] else 0)
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
index b146899..ef77674 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
@@ -7,6 +7,7 @@
 import unittest
 from frappe.utils import today
 from erpnext.accounts.utils import get_fiscal_year
+from erpnext.buying.doctype.supplier.test_supplier import create_supplier
 
 test_dependencies = ["Supplier Group"]
 
@@ -101,6 +102,32 @@
 		for d in invoices:
 			d.cancel()
 
+	def test_single_threshold_tds_with_previous_vouchers_and_no_tds(self):
+		invoices = []
+		doc = create_supplier(supplier_name = "Test TDS Supplier ABC",
+			tax_withholding_category="Single Threshold TDS")
+		supplier = doc.name
+
+		pi = create_purchase_invoice(supplier=supplier)
+		pi.submit()
+		invoices.append(pi)
+
+		# TDS not applied
+		pi = create_purchase_invoice(supplier=supplier, do_not_apply_tds=True)
+		pi.submit()
+		invoices.append(pi)
+
+		pi = create_purchase_invoice(supplier=supplier)
+		pi.submit()
+		invoices.append(pi)
+
+		self.assertEqual(pi.taxes_and_charges_deducted, 2000)
+		self.assertEqual(pi.grand_total, 8000)
+
+		# delete invoices to avoid clashing
+		for d in invoices:
+			d.cancel()
+
 def create_purchase_invoice(**args):
 	# return sales invoice doc object
 	item = frappe.get_doc('Item', {'item_name': 'TDS Item'})
@@ -109,7 +136,7 @@
 	pi = frappe.get_doc({
 		"doctype": "Purchase Invoice",
 		"posting_date": today(),
-		"apply_tds": 1,
+		"apply_tds": 0 if args.do_not_apply_tds else 1,
 		"supplier": args.supplier,
 		"company": '_Test Company',
 		"taxes_and_charges": "",
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 9a091bf..287c79f 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -5,23 +5,19 @@
 import frappe, erpnext
 from frappe.utils import flt, cstr, cint, comma_and, today, getdate, formatdate, now
 from frappe import _
-from erpnext.accounts.utils import get_stock_and_account_balance
 from frappe.model.meta import get_field_precision
 from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
 
-
 class ClosedAccountingPeriod(frappe.ValidationError): pass
-class StockAccountInvalidTransaction(frappe.ValidationError): pass
-class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
 
-def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes'):
+def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False):
 	if gl_map:
 		if not cancel:
 			validate_accounting_period(gl_map)
 			gl_map = process_gl_map(gl_map, merge_entries)
 			if gl_map and len(gl_map) > 1:
-				save_entries(gl_map, adv_adj, update_outstanding)
+				save_entries(gl_map, adv_adj, update_outstanding, from_repost)
 			else:
 				frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."))
 		else:
@@ -119,8 +115,9 @@
 		if same_head:
 			return e
 
-def save_entries(gl_map, adv_adj, update_outstanding):
-	validate_cwip_accounts(gl_map)
+def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
+	if not from_repost:
+		validate_cwip_accounts(gl_map)
 
 	round_off_debit_credit(gl_map)
 
@@ -128,76 +125,19 @@
 		check_freezing_date(gl_map[0]["posting_date"], adv_adj)
 
 	for entry in gl_map:
-		make_entry(entry, adv_adj, update_outstanding)
+		make_entry(entry, adv_adj, update_outstanding, from_repost)
 
-		# check against budget
-		validate_expense_against_budget(entry)
-
-	validate_account_for_perpetual_inventory(gl_map)
-
-
-def make_entry(args, adv_adj, update_outstanding):
+def make_entry(args, adv_adj, update_outstanding, from_repost=False):
 	gle = frappe.new_doc("GL Entry")
 	gle.update(args)
 	gle.flags.ignore_permissions = 1
+	gle.flags.from_repost = from_repost
 	gle.insert()
-	gle.run_method("on_update_with_args", adv_adj, update_outstanding)
+	gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost)
 	gle.submit()
 
-	# check against budget
-	validate_expense_against_budget(args)
-
-def validate_account_for_perpetual_inventory(gl_map):
-	if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)):
-		account_list = [gl_entries.account for gl_entries in gl_map]
-
-		aii_accounts = [d.name for d in frappe.get_all("Account",
-			filters={'account_type': 'Stock', 'is_group': 0, 'company': gl_map[0].company})]
-
-		for account in account_list:
-			if account not in aii_accounts:
-				continue
-
-			# Always use current date to get stock and account balance as there can future entries for
-			# other items
-			account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
-				getdate(), gl_map[0].company)
-
-			if gl_map[0].voucher_type=="Journal Entry":
-				# In case of Journal Entry, there are no corresponding SL entries,
-				# hence deducting currency amount
-				account_bal -= flt(gl_map[0].debit) - flt(gl_map[0].credit)
-				if account_bal == stock_bal:
-					frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
-						.format(account), StockAccountInvalidTransaction)
-
-			elif abs(account_bal - stock_bal) > 0.1:
-				precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
-					currency=frappe.get_cached_value('Company',  gl_map[0].company,  "default_currency"))
-
-				diff = flt(stock_bal - account_bal, precision)
-				error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format(
-					stock_bal, account_bal, frappe.bold(account))
-				error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff))
-				stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account")
-
-				db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency')
-				db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency')
-
-				journal_entry_args = {
-				'accounts':[
-					{'account': account, db_or_cr_warehouse_account : abs(diff)},
-					{'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }]
-				}
-
-				frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
-					raise_exception=StockValueAndAccountBalanceOutOfSync,
-					title=_('Values Out Of Sync'),
-					primary_action={
-						'label': _('Make Journal Entry'),
-						'client_action': 'erpnext.route_to_adjustment_jv',
-						'args': journal_entry_args
-					})
+	if not from_repost:
+		validate_expense_against_budget(args)
 
 def validate_cwip_accounts(gl_map):
 	cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
index 9703527..6ae81d7 100644
--- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
+++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
@@ -156,7 +156,7 @@
 
 	setup_transactions_dom() {
 		const me = this;
-		me.parent.$main_section.append(`<div class="transactions-table"></div>`)
+		me.parent.$main_section.append('<div class="transactions-table"></div>');
 	}
 
 	create_datatable() {
@@ -167,9 +167,7 @@
 			})
 		}
 		catch(err) {
-			let msg = __(`Your file could not be processed by ERPNext.
-						<br>It should be a standard CSV or XLSX file.
-						<br>The headers should be in the first row.`)
+			let msg = __("Your file could not be processed. It should be a standard CSV or XLSX file with headers in the first row.");
 			frappe.throw(msg)
 		}
 
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/accounts/print_format/gst_e_invoice/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/accounts/print_format/gst_e_invoice/__init__.py
diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
new file mode 100644
index 0000000..9827e00
--- /dev/null
+++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
@@ -0,0 +1,162 @@
+{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%}
+{%- set einvoice = json.loads(doc.signed_einvoice) -%}
+
+<div class="page-break">
+	<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
+		{% if letter_head and not no_letterhead %}
+			<div class="letter-head">{{ letter_head }}</div>
+		{% endif %}
+		<div class="print-heading">
+			<h2>E Invoice<br><small>{{ doc.name }}</small></h2>
+		</div>
+	</div>
+	{% if print_settings.repeat_header_footer %}
+	<div id="footer-html" class="visible-pdf">
+		{% if not no_letterhead and footer %}
+		<div class="letter-head-footer">
+			{{ footer }}
+		</div>
+		{% endif %}
+		<p class="text-center small page-number visible-pdf">
+			{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
+		</p>
+	</div>
+	{% endif %}
+	<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
+		<h5 class="font-bold" style="margin-left: 15px; margin-top: 0px;">1. Transaction Details</h5>
+		<div class="col-xs-8 column-break">
+			<div class="row data-field">
+				<div class="col-xs-4"><label>IRN</label></div>
+				<div class="col-xs-8 value">{{ einvoice.Irn }}</div>
+			</div>
+			<div class="row data-field">
+				<div class="col-xs-4"><label>Ack. No</label></div>
+				<div class="col-xs-8 value">{{ einvoice.AckNo }}</div>
+			</div>
+			<div class="row data-field">
+				<div class="col-xs-4"><label>Ack. Date</label></div>
+				<div class="col-xs-8 value">{{ frappe.utils.format_datetime(einvoice.AckDt, "dd/MM/yyyy hh:mm:ss") }}</div>
+			</div>
+			<div class="row data-field">
+				<div class="col-xs-4"><label>Category</label></div>
+				<div class="col-xs-8 value">{{ einvoice.TranDtls.SupTyp }}</div>
+			</div>
+			<div class="row data-field">
+				<div class="col-xs-4"><label>Document Type</label></div>
+				<div class="col-xs-8 value">{{ einvoice.DocDtls.Typ }}</div>
+			</div>
+			<div class="row data-field">
+				<div class="col-xs-4"><label>Document No</label></div>
+				<div class="col-xs-8 value">{{ einvoice.DocDtls.No }}</div>
+			</div>
+		</div>
+		<div class="col-xs-4 column-break">
+			<img src="{{ doc.qrcode_image }}" width="175px" style="float: right;">
+		</div>
+	</div>
+	<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
+		<h5 class="font-bold" style="margin-left: 15px; margin-bottom: 0px;">2. Party Details</h5>
+		{%- set seller = einvoice.SellerDtls -%}
+		<div class="col-xs-6 column-break">
+			<h5 style="margin-bottom: 5px;">Seller</h5>
+			<p>{{ seller.Gstin }}</p>
+			<p>{{ seller.LglNm }}</p>
+			<p>{{ seller.Addr1 }}</p>
+			{%- if seller.Addr2 -%} <p>{{ seller.Addr2 }}</p> {% endif %}
+			<p>{{ seller.Loc }}</p>
+			<p>{{ frappe.db.get_value("Address", doc.company_address, "gst_state") }} - {{ seller.Pin }}</p>
+
+			{%- if einvoice.ShipDtls -%}
+				{%- set shipping = einvoice.ShipDtls -%}
+				<h5 style="margin-bottom: 5px;">Shipping</h5>
+				<p>{{ shipping.Gstin }}</p>
+				<p>{{ shipping.LglNm }}</p>
+				<p>{{ shipping.Addr1 }}</p>
+				{%- if shipping.Addr2 -%} <p>{{ shipping.Addr2 }}</p> {% endif %}
+				<p>{{ shipping.Loc }}</p>
+				<p>{{ frappe.db.get_value("Address", doc.shipping_address_name, "gst_state") }} - {{ shipping.Pin }}</p>
+			{% endif %}
+		</div>
+		{%- set buyer = einvoice.BuyerDtls -%}
+		<div class="col-xs-6 column-break">
+			<h5 style="margin-bottom: 5px;">Buyer</h5>
+			<p>{{ buyer.Gstin }}</p>
+			<p>{{ buyer.LglNm }}</p>
+			<p>{{ buyer.Addr1 }}</p>
+			{%- if buyer.Addr2 -%} <p>{{ buyer.Addr2 }}</p> {% endif %}
+			<p>{{ buyer.Loc }}</p>
+			<p>{{ frappe.db.get_value("Address", doc.customer_address, "gst_state") }} - {{ buyer.Pin }}</p>
+		</div>
+	</div>
+	<div style="overflow-x: auto;">
+		<h5 class="font-bold" style="margin-bottom: 0px;">3. Item Details</h5>
+		<table class="table table-bordered">
+			<thead>
+				<tr>
+					<th class="text-left" style="width: 3%;">Sr. No.</th>
+					<th class="text-left">Item</th>
+					<th class="text-left" style="width: 10%;">HSN Code</th>
+					<th class="text-left" style="width: 5%;">Qty</th>
+					<th class="text-left" style="width: 5%;">UOM</th>
+					<th class="text-left">Rate</th>
+					<th class="text-left" style="width: 5%;">Discount</th>
+					<th class="text-left">Taxable Amount</th>
+					<th class="text-left" style="width: 7%;">Tax Rate</th>
+					<th class="text-left" style="width: 5%;">Other Charges</th>
+					<th class="text-left">Total</th>
+				</tr>
+			</thead>
+			<tbody>
+				{% for item in einvoice.ItemList %}
+					<tr>
+						<td class="text-left" style="width: 3%;">{{ item.SlNo }}</td>
+						<td class="text-left">{{ item.PrdDesc }}</td>
+						<td class="text-left" style="width: 10%;">{{ item.HsnCd }}</td>
+						<td class="text-right" style="width: 5%;">{{ item.Qty }}</td>
+						<td class="text-left" style="width: 5%;">{{ item.Unit }}</td>
+						<td class="text-right">{{ frappe.utils.fmt_money(item.UnitPrice, None, "INR") }}</td>
+						<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(item.Discount, None, "INR") }}</td>
+						<td class="text-right">{{ frappe.utils.fmt_money(item.AssAmt, None, "INR") }}</td>
+						<td class="text-right" style="width: 7%;">{{ item.GstRt + item.CesRt }} %</td>
+						<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
+						<td class="text-right">{{ frappe.utils.fmt_money(item.TotItemVal, None, "INR") }}</td>
+					</tr>
+				{% endfor %}
+			</tbody>
+		</table>
+	</div>
+	<div style="overflow-x: auto;">
+		<h5 class="font-bold" style="margin-bottom: 0px;">4. Value Details</h5>
+		<table class="table table-bordered">
+			<thead>
+				<tr>
+					<th class="text-left">Taxable Amount</th>
+					<th class="text-left">CGST</th>
+					<th class="text-left"">SGST</th>
+					<th class="text-left">IGST</th>
+					<th class="text-left">CESS</th>
+					<th class="text-left" style="width: 10%;">State CESS</th>
+					<th class="text-left">Discount</th>
+					<th class="text-left" style="width: 10%;">Other Charges</th>
+					<th class="text-left" style="width: 10%;">Round Off</th>
+					<th class="text-left">Total Value</th>
+				</tr>
+			</thead>
+			<tbody>
+				{%- set value_details = einvoice.ValDtls -%}
+				<tr>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.AssVal, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.CgstVal, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.SgstVal, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.IgstVal, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
+				</tr>
+			</tbody>
+		</table>
+	</div>
+</div>
\ No newline at end of file
diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json
new file mode 100644
index 0000000..1001199
--- /dev/null
+++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json
@@ -0,0 +1,24 @@
+{
+ "align_labels_right": 1,
+ "creation": "2020-10-10 18:01:21.032914",
+ "custom_format": 0,
+ "default_print_language": "en-US",
+ "disabled": 1,
+ "doc_type": "Sales Invoice",
+ "docstatus": 0,
+ "doctype": "Print Format",
+ "font": "Default",
+ "html": "",
+ "idx": 0,
+ "line_breaks": 1,
+ "modified": "2020-10-23 19:54:40.634936",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "GST E-Invoice",
+ "owner": "Administrator",
+ "print_format_builder": 0,
+ "print_format_type": "Jinja",
+ "raw_printing": 0,
+ "show_section_headings": 1,
+ "standard": "Yes"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html
index bb0d0a1..79a6aab 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html
@@ -42,11 +42,13 @@
 
 	{% if(filters.show_future_payments) { %}
 		{% var balance_row = data.slice(-1).pop();
-			var range1 = report.columns[11].label;
-			var range2 = report.columns[12].label;
-			var range3 = report.columns[13].label;
-			var range4 = report.columns[14].label;
-			var range5 = report.columns[15].label;
+			var start = filters.based_on_payment_terms ? 13 : 11;
+			var range1 = report.columns[start].label;
+			var range2 = report.columns[start+1].label;
+			var range3 = report.columns[start+2].label;
+			var range4 = report.columns[start+3].label;
+			var range5 = report.columns[start+4].label;
+			var range6 = report.columns[start+5].label;
 		%}
 		{% if(balance_row) { %}
 		<table class="table table-bordered table-condensed">
@@ -70,20 +72,34 @@
 					<th>{%= __(range3) %}</th>
 					<th>{%= __(range4) %}</th>
 					<th>{%= __(range5) %}</th>
+					<th>{%= __(range6) %}</th>
 					<th>{%= __("Total") %}</th>
 				</tr>
 			</thead>
 			<tbody>
 				<tr>
 					<td>{%= __("Total Outstanding") %}</td>
-					<td class="text-right">{%= format_number(balance_row["range1"], null, 2) %}</td>
-					<td class="text-right">{%= format_currency(balance_row["range2"]) %}</td>
-					<td class="text-right">{%= format_currency(balance_row["range3"]) %}</td>
-					<td class="text-right">{%= format_currency(balance_row["range4"]) %}</td>
-					<td class="text-right">{%= format_currency(balance_row["range5"]) %}</td>
+					<td class="text-right">
+						{%= format_number(balance_row["age"], null, 2) %}
+					</td>
+					<td class="text-right">
+						{%= format_currency(balance_row["range1"], data[data.length-1]["currency"]) %}
+					</td>
+					<td class="text-right">
+						{%= format_currency(balance_row["range2"], data[data.length-1]["currency"]) %}
+					</td>
+					<td class="text-right">
+						{%= format_currency(balance_row["range3"], data[data.length-1]["currency"]) %}
+					</td>
+					<td class="text-right">
+						{%= format_currency(balance_row["range4"], data[data.length-1]["currency"]) %}
+					</td>
+					<td class="text-right">
+						{%= format_currency(balance_row["range5"], data[data.length-1]["currency"]) %}
+					</td>
 					<td class="text-right">
 						{%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %}
-						</td>
+					</td>
 				</tr>
 					<td>{%= __("Future Payments") %}</td>
 					<td></td>
@@ -91,6 +107,7 @@
 					<td></td>
 					<td></td>
 					<td></td>
+					<td></td>
 					<td class="text-right">
 						{%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
 					</td>
@@ -101,6 +118,7 @@
 					<th></th>
 					<th></th>
 					<th></th>
+					<th></th>
 					<th class="text-right">
 						{%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th>
 				</tr>
@@ -218,15 +236,15 @@
 						<td></td>
 						<td style="text-align: right"><b>{%= __("Total") %}</b></td>
 						<td style="text-align: right">
-							{%= format_currency(data[i]["invoiced"], data[0]["currency"] ) %}</td>
+							{%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %}</td>
 
 						{% if(!filters.show_future_payments) { %}
 							<td style="text-align: right">
-								{%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td>
-							<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %} </td>
+								{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
+							<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} </td>
 						{% } %}
 						<td style="text-align: right">
-							{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td>
+							{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
 
 						{% if(filters.show_future_payments) { %}
 							{% if(report.report_name === "Accounts Receivable") { %}
@@ -234,8 +252,8 @@
 									{%= data[i]["po_no"] %}</td>
 							{% } %}
 							<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
-							<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[0]["currency"]) %}</td>
-							<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[0]["currency"]) %}</td>
+							<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
+							<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
 						{% } %}
 					{% } %}
 				{% } else { %}
@@ -256,10 +274,10 @@
 						{% } else { %}
 							<td><b>{%= __("Total") %}</b></td>
 						{% } %}
-						<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[0]["currency"]) %}</td>
-						<td style="text-align: right">{%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td>
-						<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %}</td>
-						<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td>
+						<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
+						<td style="text-align: right">{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
+						<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
+						<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
 					{% } %}
 				{% } %}
 				</tr>
diff --git a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
index 16bef56..2162a02 100644
--- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
+++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
@@ -47,21 +47,22 @@
 
 	for d in gl_entries:
 		asset_data = assets_details.get(d.against_voucher)
-		if not asset_data.get("accumulated_depreciation_amount"):
-			asset_data.accumulated_depreciation_amount = d.debit
-		else:
-			asset_data.accumulated_depreciation_amount += d.debit
+		if asset_data:
+			if not asset_data.get("accumulated_depreciation_amount"):
+				asset_data.accumulated_depreciation_amount = d.debit
+			else:
+				asset_data.accumulated_depreciation_amount += d.debit
 
-		row = frappe._dict(asset_data)
-		row.update({
-			"depreciation_amount": d.debit,
-			"depreciation_date": d.posting_date,
-			"amount_after_depreciation": (flt(row.gross_purchase_amount) -
-				flt(row.accumulated_depreciation_amount)),
-			"depreciation_entry": d.voucher_no
-		})
+			row = frappe._dict(asset_data)
+			row.update({
+				"depreciation_amount": d.debit,
+				"depreciation_date": d.posting_date,
+				"amount_after_depreciation": (flt(row.gross_purchase_amount) -
+					flt(row.accumulated_depreciation_amount)),
+				"depreciation_entry": d.voucher_no
+			})
 
-		data.append(row)
+			data.append(row)
 
 	return data
 
diff --git a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
index 3ffb3ac..515fd99 100644
--- a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
+++ b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
@@ -14,11 +14,93 @@
 
 def get_column():
 	return [
-		_("Delivery Note") + ":Link/Delivery Note:120", _("Status") + "::120", _("Date") + ":Date:100",
-		_("Suplier") + ":Link/Customer:120", _("Customer Name") + "::120",
-		_("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
-		_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Pending Amount") + ":Currency:100",
-		_("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
+		{
+			"label": _("Delivery Note"),
+			"fieldname": "name",
+			"fieldtype": "Link",
+			"options": "Delivery Note",
+			"width": 160
+		},
+		{
+			"label": _("Date"),
+			"fieldname": "date",
+			"fieldtype": "Date",
+			"width": 100
+		},
+		{
+			"label": _("Customer"),
+			"fieldname": "customer",
+			"fieldtype": "Link",
+			"options": "Customer",
+			"width": 120
+		},
+		{
+			"label": _("Customer Name"),
+			"fieldname": "customer_name",
+			"fieldtype": "Data",
+			"width": 120
+		},
+		{
+			"label": _("Item Code"),
+			"fieldname": "item_code",
+			"fieldtype": "Link",
+			"options": "Item",
+			"width": 120
+		},
+		{
+			"label": _("Amount"),
+			"fieldname": "amount",
+			"fieldtype": "Currency",
+			"width": 100,
+			"options": "Company:company:default_currency"
+		},
+		{
+			"label": _("Billed Amount"),
+			"fieldname": "billed_amount",
+			"fieldtype": "Currency",
+			"width": 100,
+			"options": "Company:company:default_currency"
+		},
+		{
+			"label": _("Returned Amount"),
+			"fieldname": "returned_amount",
+			"fieldtype": "Currency",
+			"width": 120,
+			"options": "Company:company:default_currency"
+		},
+		{
+			"label": _("Pending Amount"),
+			"fieldname": "pending_amount",
+			"fieldtype": "Currency",
+			"width": 120,
+			"options": "Company:company:default_currency"
+		},
+		{
+			"label": _("Item Name"),
+			"fieldname": "item_name",
+			"fieldtype": "Data",
+			"width": 120
+		},
+		{
+			"label": _("Description"),
+			"fieldname": "description",
+			"fieldtype": "Data",
+			"width": 120
+		},
+		{
+			"label": _("Project"),
+			"fieldname": "project",
+			"fieldtype": "Link",
+			"options": "Project",
+			"width": 120
+		},
+		{
+			"label": _("Company"),
+			"fieldname": "company",
+			"fieldtype": "Link",
+			"options": "Company",
+			"width": 120
+		}
 	]
 
 def get_args():
diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
index 3445df7..a36e7f8 100644
--- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
+++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
@@ -8,6 +8,7 @@
 from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (get_tax_accounts,
 	get_grand_total, add_total_row, get_display_value, get_group_by_and_display_fields, add_sub_total_row,
 	get_group_by_conditions)
+from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details
 
 def execute(filters=None):
 	return _execute(filters)
@@ -22,7 +23,7 @@
 	aii_account_map = get_aii_accounts()
 	if item_list:
 		itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency,
-			doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges")
+			doctype='Purchase Invoice', tax_doctype='Purchase Taxes and Charges')
 
 	po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
 
@@ -34,10 +35,14 @@
 	if filters.get('group_by'):
 		grand_total = get_grand_total(filters, 'Purchase Invoice')
 
+	item_details = get_item_details()
+
 	for d in item_list:
 		if not d.stock_qty:
 			continue
 
+		item_record = item_details.get(d.item_code)
+
 		purchase_receipt = None
 		if d.purchase_receipt:
 			purchase_receipt = d.purchase_receipt
@@ -48,8 +53,8 @@
 
 		row = {
 			'item_code': d.item_code,
-			'item_name': d.item_name,
-			'item_group': d.item_group,
+			'item_name': item_record.item_name,
+			'item_group': item_record.item_group,
 			'description': d.description,
 			'invoice': d.parent,
 			'posting_date': d.posting_date,
@@ -81,10 +86,10 @@
 		for tax in tax_columns:
 			item_tax = itemised_tax.get(d.name, {}).get(tax, {})
 			row.update({
-				frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0),
-				frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0),
+				frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
+				frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
 			})
-			total_tax += flt(item_tax.get("tax_amount"))
+			total_tax += flt(item_tax.get('tax_amount'))
 
 		row.update({
 			'total_tax': total_tax,
@@ -309,8 +314,8 @@
 		select
 			`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
 			`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
-			`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice Item`.`item_code`,
-			`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`, `tabPurchase Invoice Item`.description,
+			`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
+			`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
 			`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
 			`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
 			`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
index a05dcd7..f54ceb0 100644
--- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
+++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
@@ -8,6 +8,7 @@
 from frappe.model.meta import get_field_precision
 from frappe.utils.xlsxutils import handle_html
 from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
+from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details, get_customer_details
 
 def execute(filters=None):
 	return _execute(filters)
@@ -16,7 +17,7 @@
 	if not filters: filters = {}
 	columns = get_columns(additional_table_columns, filters)
 
-	company_currency = frappe.get_cached_value('Company',  filters.get("company"),  "default_currency")
+	company_currency = frappe.get_cached_value('Company',  filters.get('company'),  'default_currency')
 
 	item_list = get_items(filters, additional_query_columns)
 	if item_list:
@@ -33,7 +34,13 @@
 	if filters.get('group_by'):
 		grand_total = get_grand_total(filters, 'Sales Invoice')
 
+	customer_details = get_customer_details()
+	item_details = get_item_details()
+
 	for d in item_list:
+		customer_record = customer_details.get(d.customer)
+		item_record = item_details.get(d.item_code)
+
 		delivery_note = None
 		if d.delivery_note:
 			delivery_note = d.delivery_note
@@ -45,14 +52,14 @@
 
 		row = {
 			'item_code': d.item_code,
-			'item_name': d.item_name,
-			'item_group': d.item_group,
+			'item_name': item_record.item_name,
+			'item_group': item_record.item_group,
 			'description': d.description,
 			'invoice': d.parent,
 			'posting_date': d.posting_date,
 			'customer': d.customer,
-			'customer_name': d.customer_name,
-			'customer_group': d.customer_group,
+			'customer_name': customer_record.customer_name,
+			'customer_group': customer_record.customer_group,
 		}
 
 		if additional_query_columns:
@@ -90,10 +97,10 @@
 		for tax in tax_columns:
 			item_tax = itemised_tax.get(d.name, {}).get(tax, {})
 			row.update({
-				frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0),
-				frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0),
+				frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
+				frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
 			})
-			total_tax += flt(item_tax.get("tax_amount"))
+			total_tax += flt(item_tax.get('tax_amount'))
 
 		row.update({
 			'total_tax': total_tax,
@@ -226,7 +233,7 @@
 	if filters.get('group_by') != 'Territory':
 		columns.extend([
 			{
-				'label': _("Territory"),
+				'label': _('Territory'),
 				'fieldname': 'territory',
 				'fieldtype': 'Link',
 				'options': 'Territory',
@@ -374,13 +381,12 @@
 			`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
 			`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
 			`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
-			`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.item_name,
-			`tabSales Invoice Item`.item_group, `tabSales Invoice Item`.description, `tabSales Invoice Item`.sales_order,
-			`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.income_account,
-			`tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.stock_qty,
-			`tabSales Invoice Item`.stock_uom, `tabSales Invoice Item`.base_net_rate,
-			`tabSales Invoice Item`.base_net_amount, `tabSales Invoice`.customer_name,
-			`tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
+			`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
+			`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
+			`tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
+			`tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
+			`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
+			`tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
 			`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0}
 		from `tabSales Invoice`, `tabSales Invoice Item`
 		where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
@@ -417,14 +423,14 @@
 	return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'")
 
 def get_tax_accounts(item_list, columns, company_currency,
-		doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"):
+		doctype='Sales Invoice', tax_doctype='Sales Taxes and Charges'):
 	import json
 	item_row_map = {}
 	tax_columns = []
 	invoice_item_row = {}
 	itemised_tax = {}
 
-	tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field("tax_amount"),
+	tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field('tax_amount'),
 		currency=company_currency) or 2
 
 	for d in item_list:
@@ -469,8 +475,8 @@
 						tax_rate = tax_data
 						tax_amount = 0
 
-					if charge_type == "Actual" and not tax_rate:
-						tax_rate = "NA"
+					if charge_type == 'Actual' and not tax_rate:
+						tax_rate = 'NA'
 
 					item_net_amount = sum([flt(d.base_net_amount)
 						for d in item_row_map.get(parent, {}).get(item_code, [])])
@@ -484,17 +490,17 @@
 								if (doctype == 'Purchase Invoice' and name in deducted_tax) else tax_value)
 
 							itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
-								"tax_rate": tax_rate,
-								"tax_amount": tax_value
+								'tax_rate': tax_rate,
+								'tax_amount': tax_value
 							})
 
 			except ValueError:
 				continue
-		elif charge_type == "Actual" and tax_amount:
+		elif charge_type == 'Actual' and tax_amount:
 			for d in invoice_item_row.get(parent, []):
 				itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
-					"tax_rate": "NA",
-					"tax_amount": flt((tax_amount * d.base_net_amount) / d.base_net_total,
+					'tax_rate': 'NA',
+					'tax_amount': flt((tax_amount * d.base_net_amount) / d.base_net_total,
 						tax_amount_precision)
 				})
 
@@ -563,7 +569,7 @@
 		})
 
 		total_row_map.setdefault('total_row', {
-			subtotal_display_field: "Total",
+			subtotal_display_field: 'Total',
 			'stock_qty': 0.0,
 			'amount': 0.0,
 			'bold': 1,
diff --git a/erpnext/accounts/report/non_billed_report.py b/erpnext/accounts/report/non_billed_report.py
index a9e25bc..2e18ce1 100644
--- a/erpnext/accounts/report/non_billed_report.py
+++ b/erpnext/accounts/report/non_billed_report.py
@@ -17,18 +17,26 @@
 
 	return frappe.db.sql("""
 		Select
-			`{parent_tab}`.name, `{parent_tab}`.status, `{parent_tab}`.{date_field}, `{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
-			{project_field}, `{child_tab}`.item_code, `{child_tab}`.base_amount,
+			`{parent_tab}`.name, `{parent_tab}`.{date_field},
+			`{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
+			`{child_tab}`.item_code,
+			`{child_tab}`.base_amount,
 			(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)),
-			(`{child_tab}`.base_amount - (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1))),
-			`{child_tab}`.item_name, `{child_tab}`.description, `{parent_tab}`.company
+			(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0)),
+			(`{child_tab}`.base_amount -
+			(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)) -
+			(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))),
+			`{child_tab}`.item_name, `{child_tab}`.description,
+			{project_field}, `{parent_tab}`.company
 		from
 			`{parent_tab}`, `{child_tab}`
 		where
 			`{parent_tab}`.name = `{child_tab}`.parent and `{parent_tab}`.docstatus = 1
 			and `{parent_tab}`.status not in ('Closed', 'Completed')
-			and `{child_tab}`.amount > 0 and round(`{child_tab}`.billed_amt *
-			ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) < `{child_tab}`.base_amount
+			and `{child_tab}`.amount > 0
+			and (`{child_tab}`.base_amount -
+			round(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) -
+			(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))) > 0
 		order by
 			`{parent_tab}`.{order} {order_by}
 		""".format(parent_tab = 'tab' + doctype, child_tab = 'tab' + child_tab, precision= precision, party = party,
diff --git a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
index 5e8d773..e9e9c9c 100644
--- a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
+++ b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
@@ -14,11 +14,93 @@
 
 def get_column():
 	return [
-		_("Purchase Receipt") + ":Link/Purchase Receipt:120", _("Status") + "::120", _("Date") + ":Date:100",
-		_("Supplier") + ":Link/Supplier:120", _("Supplier Name") + "::120",
-		_("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
-		_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Amount to Bill") + ":Currency:100",
-		_("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
+		{
+			"label": _("Purchase Receipt"),
+			"fieldname": "name",
+			"fieldtype": "Link",
+			"options": "Purchase Receipt",
+			"width": 160
+		},
+		{
+			"label": _("Date"),
+			"fieldname": "date",
+			"fieldtype": "Date",
+			"width": 100
+		},
+		{
+			"label": _("Supplier"),
+			"fieldname": "supplier",
+			"fieldtype": "Link",
+			"options": "Supplier",
+			"width": 120
+		},
+		{
+			"label": _("Supplier Name"),
+			"fieldname": "supplier_name",
+			"fieldtype": "Data",
+			"width": 120
+		},
+		{
+			"label": _("Item Code"),
+			"fieldname": "item_code",
+			"fieldtype": "Link",
+			"options": "Item",
+			"width": 120
+		},
+		{
+			"label": _("Amount"),
+			"fieldname": "amount",
+			"fieldtype": "Currency",
+			"width": 100,
+			"options": "Company:company:default_currency"
+		},
+		{
+			"label": _("Billed Amount"),
+			"fieldname": "billed_amount",
+			"fieldtype": "Currency",
+			"width": 100,
+			"options": "Company:company:default_currency"
+		},
+		{
+			"label": _("Returned Amount"),
+			"fieldname": "returned_amount",
+			"fieldtype": "Currency",
+			"width": 120,
+			"options": "Company:company:default_currency"
+		},
+		{
+			"label": _("Pending Amount"),
+			"fieldname": "pending_amount",
+			"fieldtype": "Currency",
+			"width": 120,
+			"options": "Company:company:default_currency"
+		},
+		{
+			"label": _("Item Name"),
+			"fieldname": "item_name",
+			"fieldtype": "Data",
+			"width": 120
+		},
+		{
+			"label": _("Description"),
+			"fieldname": "description",
+			"fieldtype": "Data",
+			"width": 120
+		},
+		{
+			"label": _("Project"),
+			"fieldname": "project",
+			"fieldtype": "Link",
+			"options": "Project",
+			"width": 120
+		},
+		{
+			"label": _("Company"),
+			"fieldname": "company",
+			"fieldtype": "Link",
+			"options": "Company",
+			"width": 120
+		}
 	]
 
 def get_args():
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 53677cd..67c7fd2 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -12,11 +12,12 @@
 from six import iteritems
 # imported to enable erpnext.accounts.utils.get_account_currency
 from erpnext.accounts.doctype.account.account import get_account_currency
+from frappe.model.meta import get_field_precision
 
 from erpnext.stock.utils import get_stock_value_on
 from erpnext.stock import get_warehouse_account_map
 
-
+class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
 class FiscalYearError(frappe.ValidationError): pass
 
 @frappe.whitelist()
@@ -78,7 +79,10 @@
 			else:
 				return ((fy.name, fy.year_start_date, fy.year_end_date),)
 
-	error_msg = _("""{0} {1} not in any active Fiscal Year.""").format(label, formatdate(transaction_date))
+	error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(label, formatdate(transaction_date))
+	if company:
+		error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company))
+	
 	if verbose==1: frappe.msgprint(error_msg)
 	raise FiscalYearError(error_msg)
 
@@ -582,24 +586,6 @@
 				(dr_or_cr, dr_or_cr, '%s', '%s', '%s', dr_or_cr),
 				(d.diff, d.voucher_type, d.voucher_no))
 
-def get_stock_and_account_balance(account=None, posting_date=None, company=None):
-	if not posting_date: posting_date = nowdate()
-
-	warehouse_account = get_warehouse_account_map(company)
-
-	account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True)
-
-	related_warehouses = [wh for wh, wh_details in warehouse_account.items()
-		if wh_details.account == account and not wh_details.is_group]
-
-	total_stock_value = 0.0
-	for warehouse in related_warehouses:
-		value = get_stock_value_on(warehouse, posting_date)
-		total_stock_value += value
-
-	precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
-	return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses
-
 def get_currency_precision():
 	precision = cint(frappe.db.get_default("currency_precision"))
 	if not precision:
@@ -900,12 +886,6 @@
 
 	return accounts
 
-def get_stock_accounts(company):
-	return frappe.get_all("Account", filters = {
-		"account_type": "Stock",
-		"company": company
-	})
-
 def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
 		warehouse_account=None, company=None):
 	def _delete_gl_entries(voucher_type, voucher_no):
@@ -925,7 +905,7 @@
 		if expected_gle:
 			if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle):
 				_delete_gl_entries(voucher_type, voucher_no)
-				voucher_obj.make_gl_entries(gl_entries=expected_gle, repost_future_gle=False, from_repost=True)
+				voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True)
 		else:
 			_delete_gl_entries(voucher_type, voucher_no)
 
@@ -944,7 +924,10 @@
 
 	for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
 		from `tabStock Ledger Entry` sle
-		where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition}
+		where
+			timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s)
+			and is_cancelled = 0
+			{condition}
 		order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition),
 		tuple([posting_date, posting_time] + values), as_dict=True):
 			future_stock_vouchers.append([d.voucher_type, d.voucher_no])
@@ -961,3 +944,106 @@
 				gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
 
 	return gl_entries
+
+def compare_existing_and_expected_gle(existing_gle, expected_gle):
+	matched = True
+	for entry in expected_gle:
+		account_existed = False
+		for e in existing_gle:
+			if entry.account == e.account:
+				account_existed = True
+			if entry.account == e.account and entry.against_account == e.against_account \
+					and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center) \
+					and (entry.debit != e.debit or entry.credit != e.credit):
+				matched = False
+				break
+		if not account_existed:
+			matched = False
+			break
+	return matched
+
+def check_if_stock_and_account_balance_synced(posting_date, company, voucher_type=None, voucher_no=None):
+	if not cint(erpnext.is_perpetual_inventory_enabled(company)):
+		return
+
+	accounts = get_stock_accounts(company, voucher_type, voucher_no)
+	stock_adjustment_account = frappe.db.get_value("Company", company, "stock_adjustment_account")
+
+	for account in accounts:
+		account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
+			posting_date, company)
+
+		if abs(account_bal - stock_bal) > 0.1:
+			precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
+				currency=frappe.get_cached_value('Company',  company,  "default_currency"))
+
+			diff = flt(stock_bal - account_bal, precision)
+
+			error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses as on {3}.").format(
+				stock_bal, account_bal, frappe.bold(account), posting_date)
+			error_resolution = _("Please create an adjustment Journal Entry for amount {0} on {1}")\
+				.format(frappe.bold(diff), frappe.bold(posting_date))			
+
+			frappe.msgprint(
+				msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
+				raise_exception=StockValueAndAccountBalanceOutOfSync,
+				title=_('Values Out Of Sync'),
+				primary_action={
+					'label': _('Make Journal Entry'),
+					'client_action': 'erpnext.route_to_adjustment_jv',
+					'args': get_journal_entry(account, stock_adjustment_account, diff)
+				})
+
+def get_stock_accounts(company, voucher_type=None, voucher_no=None):
+	stock_accounts = [d.name for d in frappe.db.get_all("Account", {
+		"account_type": "Stock",
+		"company": company,
+		"is_group": 0
+	})]
+	if voucher_type and voucher_no:
+		if voucher_type == "Journal Entry":
+			stock_accounts = [d.account for d in frappe.db.get_all("Journal Entry Account", {
+				"parent": voucher_no,
+				"account": ["in", stock_accounts]
+			}, "account")]
+
+		else:
+			stock_accounts = [d.account for d in frappe.db.get_all("GL Entry", {
+				"voucher_type": voucher_type,
+				"voucher_no": voucher_no,
+				"account": ["in", stock_accounts]
+			}, "account")]
+
+	return stock_accounts
+
+def get_stock_and_account_balance(account=None, posting_date=None, company=None):
+	if not posting_date: posting_date = nowdate()
+
+	warehouse_account = get_warehouse_account_map(company)
+
+	account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True)
+
+	related_warehouses = [wh for wh, wh_details in warehouse_account.items()
+		if wh_details.account == account and not wh_details.is_group]
+
+	total_stock_value = 0.0
+	for warehouse in related_warehouses:
+		value = get_stock_value_on(warehouse, posting_date)
+		total_stock_value += value
+
+	precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
+	return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses
+
+def get_journal_entry(account, stock_adjustment_account, amount):
+	db_or_cr_warehouse_account =('credit_in_account_currency' if amount < 0 else 'debit_in_account_currency')
+	db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if amount < 0 else 'credit_in_account_currency')
+
+	return {
+		'accounts':[{
+			'account': account,
+			db_or_cr_warehouse_account: abs(amount)
+		}, {
+			'account': stock_adjustment_account,
+			db_or_cr_stock_adjustment_account : abs(amount)
+		}]
+	}
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index 7ad164a..b2318a2 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -373,8 +373,8 @@
 			doctype_field = frappe.scrub(doctype)
 			frm.set_value(doctype_field, '');
 			frappe.msgprint({
-				title: __(`Invalid ${doctype}`),
-				message: __(`The selected ${doctype} doesn't contains selected Asset Item.`),
+				title: __('Invalid {0}', [__(doctype)]),
+				message: __('The selected {0} does not contain the selected Asset Item.', [__(doctype)]),
 				indicator: 'red'
 			});
 		}
@@ -436,7 +436,7 @@
 	depreciation_start_date: function(frm, cdt, cdn) {
 		const book = locals[cdt][cdn];
 		if (frm.doc.available_for_use_date && book.depreciation_start_date == frm.doc.available_for_use_date) {
-			frappe.msgprint(__(`Depreciation Posting Date should not be equal to Available for Use Date.`));
+			frappe.msgprint(__("Depreciation Posting Date should not be equal to Available for Use Date."));
 			book.depreciation_start_date = "";
 			frm.refresh_field("finance_books");
 		}
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
index fd702c7..1430827 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
@@ -13,19 +13,16 @@
 class AssetValueAdjustment(Document):
 	def validate(self):
 		self.validate_date()
-		self.set_difference_amount()
 		self.set_current_asset_value()
+		self.set_difference_amount()
 
 	def on_submit(self):
 		self.make_depreciation_entry()
 		self.reschedule_depreciations(self.new_asset_value)
 
 	def on_cancel(self):
-		if self.journal_entry:
-			frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry))
-
 		self.reschedule_depreciations(self.current_asset_value)
-	
+
 	def validate_date(self):
 		asset_purchase_date = frappe.db.get_value('Asset', self.asset, 'purchase_date')
 		if getdate(self.date) < getdate(asset_purchase_date):
@@ -53,6 +50,7 @@
 		je.posting_date = self.date
 		je.company = self.company
 		je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount)
+		je.finance_book = self.finance_book
 
 		credit_entry = {
 			"account": accumulated_depreciation_account,
@@ -78,7 +76,7 @@
 				debit_entry.update({
 					dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension')
 				})
-		
+
 		je.append("accounts", credit_entry)
 		je.append("accounts", debit_entry)
 
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
index af08a2a..d1457b9 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
@@ -75,24 +75,23 @@
 	for asset in assets_record:
 		asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \
 			- flt(depreciation_amount_map.get(asset.name))
-		if asset_value:
-			row = {
-				"asset_id": asset.asset_id,
-				"asset_name": asset.asset_name,
-				"status": asset.status,
-				"department": asset.department,
-				"cost_center": asset.cost_center,
-				"vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
-				"gross_purchase_amount": asset.gross_purchase_amount,
-				"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
-				"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
-				"available_for_use_date": asset.available_for_use_date,
-				"location": asset.location,
-				"asset_category": asset.asset_category,
-				"purchase_date": asset.purchase_date,
-				"asset_value": asset_value
-			}
-			data.append(row)
+		row = {
+			"asset_id": asset.asset_id,
+			"asset_name": asset.asset_name,
+			"status": asset.status,
+			"department": asset.department,
+			"cost_center": asset.cost_center,
+			"vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
+			"gross_purchase_amount": asset.gross_purchase_amount,
+			"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
+			"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
+			"available_for_use_date": asset.available_for_use_date,
+			"location": asset.location,
+			"asset_category": asset.asset_category,
+			"purchase_date": asset.purchase_date,
+			"asset_value": asset_value
+		}
+		data.append(row)
 
 	return data
 
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 4b865a9..75da71c 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -1,5 +1,6 @@
 {
  "actions": [],
+ "allow_auto_repeat": 1,
  "allow_import": 1,
  "autoname": "naming_series:",
  "creation": "2013-05-21 16:16:39",
@@ -167,6 +168,7 @@
    "bold": 1,
    "fieldname": "supplier",
    "fieldtype": "Link",
+   "in_global_search": 1,
    "in_standard_filter": 1,
    "label": "Supplier",
    "oldfieldname": "supplier",
@@ -1105,7 +1107,7 @@
  "idx": 105,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-30 11:39:37.388249",
+ "modified": "2020-12-03 16:46:44.229351",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Order",
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 10db240..c691e9f 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -732,7 +732,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-10-30 11:59:47.670951",
+ "modified": "2020-12-07 11:59:47.670951",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Order Item",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index c427242..e537771 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -290,11 +290,17 @@
 				dialog.show();
 			}, __("Get Items From"));
 
+			// Link Material Requests
+			this.frm.add_custom_button(__('Link to Material Requests'),
+				function() {
+					erpnext.buying.link_to_mrs(me.frm);
+				}, __("Tools"));
+
 			// Get Suppliers
 			this.frm.add_custom_button(__('Get Suppliers'),
 				function() {
 					me.get_suppliers_button(me.frm);
-				});
+				}, __("Tools"));
 		}
 	},
 
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
index 3af6cf8..4ce4100 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
@@ -18,7 +18,6 @@
   "suppliers",
   "items_section",
   "items",
-  "link_to_mrs",
   "supplier_response_section",
   "salutation",
   "subject",
@@ -118,13 +117,6 @@
    "reqd": 1
   },
   {
-   "depends_on": "eval:doc.docstatus===0 && (doc.items && doc.items.length)",
-   "fieldname": "link_to_mrs",
-   "fieldtype": "Button",
-   "label": "Link to Material Requests"
-  },
-  {
-   "depends_on": "eval:!doc.__islocal",
    "fieldname": "supplier_response_section",
    "fieldtype": "Section Break",
    "label": "Email Details"
@@ -260,7 +252,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-04 22:04:29.017134",
+ "modified": "2020-11-05 22:04:29.017134",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Request for Quotation",
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index df143ee..0ee9d18 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -49,6 +49,12 @@
 				msgprint(_("Series is mandatory"), raise_exception=1)
 
 		validate_party_accounts(self)
+		self.validate_internal_supplier()
+
+	def validate_internal_supplier(self):
+		if self.is_internal_supplier and frappe.db.get_value("Supplier", {"represents_company": self.represents_company}, "name"):
+			frappe.throw(_("Internal Supplier for company {0} already exists").format(
+				frappe.bold(self.represents_company)))
 
 	def on_trash(self):
 		delete_contact_and_address('Supplier', self.name)
diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py
index a377ec9..f9c8d35 100644
--- a/erpnext/buying/doctype/supplier/test_supplier.py
+++ b/erpnext/buying/doctype/supplier/test_supplier.py
@@ -120,3 +120,20 @@
 
         # Rollback
         address.delete()
+
+def create_supplier(**args):
+    args = frappe._dict(args)
+
+    try:
+        doc = frappe.get_doc({
+            "doctype": "Supplier",
+            "supplier_name": args.supplier_name,
+            "supplier_group": args.supplier_group or "Services",
+            "supplier_type": args.supplier_type or "Company",
+            "tax_withholding_category": args.tax_withholding_category
+        }).insert()
+
+        return doc
+
+    except frappe.DuplicateEntryError:
+        return frappe.get_doc("Supplier", args.supplier_name)
\ No newline at end of file
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
index a7cab50..a3b2085 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
@@ -50,6 +50,12 @@
 					})
 				}, __("Get Items From"));
 
+			// Link Material Requests
+			this.frm.add_custom_button(__('Link to Material Requests'),
+				function() {
+					erpnext.buying.link_to_mrs(me.frm);
+				}, __("Tools"));
+
 			this.frm.add_custom_button(__("Request for Quotation"),
 			function() {
 				if (!me.frm.doc.supplier) {
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
index 9a092ca..40fbe2c 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
@@ -1,5 +1,6 @@
 {
  "actions": [],
+ "allow_auto_repeat": 1,
  "allow_import": 1,
  "autoname": "naming_series:",
  "creation": "2013-05-21 16:16:45",
@@ -34,7 +35,6 @@
   "ignore_pricing_rule",
   "items_section",
   "items",
-  "link_to_mrs",
   "pricing_rule_details",
   "pricing_rules",
   "section_break_22",
@@ -322,12 +322,6 @@
    "reqd": 1
   },
   {
-   "depends_on": "eval:doc.docstatus===0 && (doc.items && doc.items.length)",
-   "fieldname": "link_to_mrs",
-   "fieldtype": "Button",
-   "label": "Link to material requests"
-  },
-  {
    "fieldname": "pricing_rule_details",
    "fieldtype": "Section Break",
    "label": "Pricing Rules"
@@ -805,9 +799,10 @@
  ],
  "icon": "fa fa-shopping-cart",
  "idx": 29,
+ "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-01 20:56:17.932007",
+ "modified": "2020-12-03 15:18:29.073368",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Supplier Quotation",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 6108a61..0f1aa23 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -23,6 +23,8 @@
 from erpnext.stock.get_item_details import get_item_warehouse, _get_item_tax_template, get_item_tax_map
 from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
 
+class AccountMissingError(frappe.ValidationError): pass
+
 force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
 
 class AccountsController(TransactionBase):
@@ -105,9 +107,17 @@
 			else:
 				self.validate_deferred_start_and_end_date()
 
+			self.set_inter_company_account()
+
 		validate_regional(self)
+		
+		validate_einvoice_fields(self)
+
 		if self.doctype != 'Material Request':
 			apply_pricing_rule_on_transaction(self)
+	
+	def before_cancel(self):
+		validate_einvoice_fields(self)
 
 	def validate_deferred_start_and_end_date(self):
 		for d in self.items:
@@ -735,6 +745,21 @@
 
 		return self._abbr
 
+	def raise_missing_debit_credit_account_error(self, party_type, party):
+		"""Raise an error if debit to/credit to account does not exist."""
+		db_or_cr = frappe.bold("Debit To") if self.doctype == "Sales Invoice" else frappe.bold("Credit To")
+		rec_or_pay = "Receivable" if self.doctype == "Sales Invoice" else "Payable"
+
+		link_to_party = frappe.utils.get_link_to_form(party_type, party)
+		link_to_company = frappe.utils.get_link_to_form("Company", self.company)
+
+		message = _("{0} Account not found against Customer {1}.").format(db_or_cr, frappe.bold(party) or '')
+		message += "<br>" + _("Please set one of the following:") + "<br>"
+		message += "<br><ul><li>" + _("'Account' in the Accounting section of Customer {0}").format(link_to_party) + "</li>"
+		message += "<li>" + _("'Default {0} Account' in Company {1}").format(rec_or_pay, link_to_company) + "</li></ul>"
+
+		frappe.throw(message, title=_("Account Missing"), exc=AccountMissingError)
+
 	def validate_party(self):
 		party_type, party = self.get_party()
 		validate_party_frozen_disabled(party_type, party)
@@ -915,6 +940,38 @@
 		else:
 			return frappe.db.get_single_value("Global Defaults", "disable_rounded_total")
 
+	def set_inter_company_account(self):
+		"""
+			Set intercompany account for inter warehouse transactions
+			This account will be used in case billing company and internal customer's
+			representation company is same
+		"""
+
+		if self.is_internal_transfer() and not self.unrealized_profit_loss_account:
+			unrealized_profit_loss_account = frappe.db.get_value('Company', self.company, 'unrealized_profit_loss_account')
+
+			if not unrealized_profit_loss_account:
+				msg = _("Please select Unrealized Profit / Loss account or add default Unrealized Profit / Loss account account for company {0}").format(
+						frappe.bold(self.company))
+				frappe.throw(msg)
+
+			self.unrealized_profit_loss_account = unrealized_profit_loss_account
+
+	def is_internal_transfer(self):
+		"""
+			It will an internal transfer if its an internal customer and representation
+			company is same as billing company
+		"""
+		if self.doctype == 'Sales Invoice':
+			internal_party_field = 'is_internal_customer'
+		else:
+			internal_party_field = 'is_internal_supplier'
+
+		if self.get(internal_party_field) and (self.represents_company == self.company):
+			return True
+
+		return False
+
 @frappe.whitelist()
 def get_tax_rate(account_head):
 	return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
@@ -1467,3 +1524,7 @@
 @erpnext.allow_regional
 def validate_regional(doc):
 	pass
+
+@erpnext.allow_regional
+def validate_einvoice_fields(doc):
+	pass
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 9ee83e3..6edc020 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -16,6 +16,8 @@
 
 from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
 from erpnext.controllers.stock_controller import StockController
+from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
+from erpnext.stock.utils import get_incoming_rate
 
 class BuyingController(StockController):
 	def __setup__(self):
@@ -42,6 +44,7 @@
 		self.validate_items()
 		self.set_qty_as_per_stock_uom()
 		self.validate_stock_or_nonstock_items()
+		self.update_tax_category_for_internal_transfer()
 		self.validate_warehouse()
 		self.validate_from_warehouse()
 		self.set_supplier_address()
@@ -62,7 +65,7 @@
 			self.set_landed_cost_voucher_amount()
 
 		if self.doctype in ("Purchase Receipt", "Purchase Invoice"):
-			self.update_valuation_rate("items")
+			self.update_valuation_rate()
 
 	def set_missing_values(self, for_validate=False):
 		super(BuyingController, self).set_missing_values(for_validate)
@@ -94,13 +97,23 @@
 
 	def validate_stock_or_nonstock_items(self):
 		if self.meta.get_field("taxes") and not self.get_stock_items() and not self.get_asset_items():
-			tax_for_valuation = [d for d in self.get("taxes")
+			msg = _('Tax Category has been changed to "Total" because all the Items are non-stock items')
+			self.update_tax_category(msg)
+
+	def update_tax_category_for_internal_transfer(self):
+		if self.doctype == 'Purchase Invoice' and self.is_internal_transfer():
+			msg = _('Tax Category has been changed to "Total" as its an internal purchase.')
+			self.update_tax_category(msg)
+
+	def update_tax_category(self, msg):
+		tax_for_valuation = [d for d in self.get("taxes")
 				if d.category in ["Valuation", "Valuation and Total"]]
 
-			if tax_for_valuation:
-				for d in tax_for_valuation:
-					d.category = 'Total'
-				msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items'))
+		if tax_for_valuation:
+			for d in tax_for_valuation:
+				d.category = 'Total'
+
+			msgprint(msg)
 
 	def validate_asset_return(self):
 		if self.doctype not in ['Purchase Receipt', 'Purchase Invoice'] or not self.is_return:
@@ -166,7 +179,7 @@
 			self.in_words = money_in_words(amount, self.currency)
 
 	# update valuation rate
-	def update_valuation_rate(self, parentfield):
+	def update_valuation_rate(self, reset_outgoing_rate=True):
 		"""
 			item_tax_amount is the total tax amount applied on that item
 			stored for valuation
@@ -177,7 +190,7 @@
 
 		stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0
 		last_item_idx = 1
-		for d in self.get(parentfield):
+		for d in self.get("items"):
 			if d.item_code and d.item_code in stock_and_asset_items:
 				stock_and_asset_items_qty += flt(d.qty)
 				stock_and_asset_items_amount += flt(d.base_net_amount)
@@ -187,7 +200,7 @@
 			if d.category in ["Valuation", "Valuation and Total"]])
 
 		valuation_amount_adjustment = total_valuation_amount
-		for i, item in enumerate(self.get(parentfield)):
+		for i, item in enumerate(self.get("items")):
 			if item.item_code and item.qty and item.item_code in stock_and_asset_items:
 				item_proportion = flt(item.base_net_amount) / stock_and_asset_items_amount if stock_and_asset_items_amount \
 					else flt(item.qty) / stock_and_asset_items_qty
@@ -205,16 +218,34 @@
 					item.conversion_factor = get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
 
 				qty_in_stock_uom = flt(item.qty * item.conversion_factor)
-				rm_supp_cost = flt(item.rm_supp_cost) if self.doctype in ["Purchase Receipt", "Purchase Invoice"] else 0.0
-
-				landed_cost_voucher_amount = flt(item.landed_cost_voucher_amount) \
-					if self.doctype in ["Purchase Receipt", "Purchase Invoice"] else 0.0
-
-				item.valuation_rate = ((item.base_net_amount + item.item_tax_amount + rm_supp_cost
-					 + landed_cost_voucher_amount) / qty_in_stock_uom)
+				item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
+				item.valuation_rate = ((item.base_net_amount + item.item_tax_amount + item.rm_supp_cost
+					 + flt(item.landed_cost_voucher_amount)) / qty_in_stock_uom)
 			else:
 				item.valuation_rate = 0.0
 
+	def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
+		supplied_items_cost = 0.0
+		for d in self.get("supplied_items"):
+			if d.reference_name == item_row_id:
+				if reset_outgoing_rate and frappe.db.get_value('Item', d.rm_item_code, 'is_stock_item'):
+					rate = get_incoming_rate({
+						"item_code": d.rm_item_code,
+						"warehouse": self.supplier_warehouse,
+						"posting_date": self.posting_date,
+						"posting_time": self.posting_time,
+						"qty": -1 * d.consumed_qty,
+						"serial_no": d.serial_no
+					})
+
+					if rate > 0:
+						d.rate = rate
+
+				d.amount = flt(flt(d.consumed_qty) * flt(d.rate), d.precision("amount"))
+				supplied_items_cost += flt(d.amount)
+		
+		return supplied_items_cost
+
 	def validate_for_subcontracting(self):
 		if not self.is_subcontracted and self.sub_contracted_items:
 			frappe.throw(_("Please enter 'Is Subcontracted' as Yes or No"))
@@ -341,35 +372,17 @@
 				else:
 					self.append_raw_material_to_be_backflushed(item, raw_material, qty)
 
-	def append_raw_material_to_be_backflushed(self, fg_item_doc, raw_material_data, qty):
+	def append_raw_material_to_be_backflushed(self, fg_item_row, raw_material_data, qty):
 		rm = self.append('supplied_items', {})
 		rm.update(raw_material_data)
 
 		if not rm.main_item_code:
-			rm.main_item_code = fg_item_doc.item_code
+			rm.main_item_code = fg_item_row.item_code
 
-		rm.reference_name = fg_item_doc.name
+		rm.reference_name = fg_item_row.name
 		rm.required_qty = qty
 		rm.consumed_qty = qty
 
-		if not raw_material_data.get('non_stock_item'):
-			from erpnext.stock.utils import get_incoming_rate
-			rm.rate = get_incoming_rate({
-				"item_code": raw_material_data.rm_item_code,
-				"warehouse": self.supplier_warehouse,
-				"posting_date": self.posting_date,
-				"posting_time": self.posting_time,
-				"qty": -1 * qty,
-				"serial_no": rm.serial_no
-			})
-
-			if not rm.rate:
-				rm.rate = get_valuation_rate(raw_material_data.rm_item_code, self.supplier_warehouse,
-					self.doctype, self.name, currency=self.company_currency, company=self.company)
-
-		rm.amount = qty * flt(rm.rate)
-		fg_item_doc.rm_supp_cost += rm.amount
-
 	def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table):
 		exploded_item = 1
 		if hasattr(item, 'include_exploded_items'):
@@ -378,7 +391,7 @@
 		bom_items = get_items_from_bom(item.item_code, item.bom, exploded_item)
 
 		used_alternative_items = []
-		if self.doctype == 'Purchase Receipt' and item.purchase_order:
+		if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and item.purchase_order:
 			used_alternative_items = get_used_alternative_items(purchase_order = item.purchase_order)
 
 		raw_materials_cost = 0
@@ -395,7 +408,7 @@
 					reserve_warehouse = None
 
 			conversion_factor = item.conversion_factor
-			if (self.doctype == 'Purchase Receipt' and item.purchase_order and
+			if (self.doctype in ["Purchase Receipt", "Purchase Invoice"] and item.purchase_order and
 				bom_item.item_code in used_alternative_items):
 				alternative_item_data = used_alternative_items.get(bom_item.item_code)
 				bom_item.item_code = alternative_item_data.item_code
@@ -423,9 +436,7 @@
 			rm.rm_item_code = bom_item.item_code
 			rm.stock_uom = bom_item.stock_uom
 			rm.required_qty = required_qty
-			if self.doctype == "Purchase Order" and not rm.reserve_warehouse:
-				rm.reserve_warehouse = reserve_warehouse
-
+			rm.rate = bom_item.rate
 			rm.conversion_factor = conversion_factor
 
 			if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
@@ -433,29 +444,8 @@
 				rm.description = bom_item.description
 				if item.batch_no and frappe.db.get_value("Item", rm.rm_item_code, "has_batch_no") and not rm.batch_no:
 					rm.batch_no = item.batch_no
-
-			# get raw materials rate
-			if self.doctype == "Purchase Receipt":
-				from erpnext.stock.utils import get_incoming_rate
-				rm.rate = get_incoming_rate({
-					"item_code": bom_item.item_code,
-					"warehouse": self.supplier_warehouse,
-					"posting_date": self.posting_date,
-					"posting_time": self.posting_time,
-					"qty": -1 * required_qty,
-					"serial_no": rm.serial_no
-				})
-				if not rm.rate:
-					rm.rate = get_valuation_rate(bom_item.item_code, self.supplier_warehouse,
-						self.doctype, self.name, currency=self.company_currency, company = self.company)
-			else:
-				rm.rate = bom_item.rate
-
-			rm.amount = required_qty * flt(rm.rate)
-			raw_materials_cost += flt(rm.amount)
-
-		if self.doctype in ("Purchase Receipt", "Purchase Invoice"):
-			item.rm_supp_cost = raw_materials_cost
+			elif not rm.reserve_warehouse:
+				rm.reserve_warehouse = reserve_warehouse
 
 	def cleanup_raw_materials_supplied(self, parent_items, raw_material_table):
 		"""Remove all those child items which are no longer present in main item table"""
@@ -497,6 +487,10 @@
 					frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx))
 				d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
 
+				if self.doctype=="Purchase Receipt" and d.meta.get_field("received_stock_qty"):
+					# Set Received Qty in Stock UOM
+					d.received_stock_qty = flt(d.received_qty) * flt(d.conversion_factor, d.precision("conversion_factor"))
+
 	def validate_purchase_return(self):
 		for d in self.get("items"):
 			if self.is_return and flt(d.rejected_qty) != 0:
@@ -564,7 +558,8 @@
 						or (cint(self.is_return) and self.docstatus==2)):
 						from_warehouse_sle = self.get_sl_entries(d, {
 							"actual_qty": -1 * pr_qty,
-							"warehouse": d.from_warehouse
+							"warehouse": d.from_warehouse,
+							"dependant_sle_voucher_detail_no": d.name
 						})
 
 						sl_entries.append(from_warehouse_sle)
@@ -574,28 +569,20 @@
 						"serial_no": cstr(d.serial_no).strip()
 					})
 					if self.is_return:
-						filters = {
-							"voucher_type": self.doctype,
-							"voucher_no": self.return_against,
-							"item_code": d.item_code
-						}
-
-						if (self.doctype == "Purchase Invoice" and self.update_stock
-							and d.get("purchase_invoice_item")):
-							filters["voucher_detail_no"] = d.purchase_invoice_item
-						elif self.doctype == "Purchase Receipt" and d.get("purchase_receipt_item"):
-							filters["voucher_detail_no"] = d.purchase_receipt_item
-
-						original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", filters, "incoming_rate")
+						outgoing_rate = get_rate_for_return(self.doctype, self.name, d.item_code, self.return_against, item_row=d)
 
 						sle.update({
-							"outgoing_rate": original_incoming_rate
+							"outgoing_rate": outgoing_rate,
+							"recalculate_rate": 1
 						})
+						if d.from_warehouse:
+							sle.dependant_sle_voucher_detail_no = d.name
 					else:
 						val_rate_db_precision = 6 if cint(self.precision("valuation_rate", d)) <= 6 else 9
 						incoming_rate = flt(d.valuation_rate, val_rate_db_precision)
 						sle.update({
-							"incoming_rate": incoming_rate
+							"incoming_rate": incoming_rate,
+							"recalculate_rate": 1 if (self.is_subcontracted and d.bom) or d.from_warehouse else 0
 						})
 					sl_entries.append(sle)
 
@@ -603,7 +590,8 @@
 						or (cint(self.is_return) and self.docstatus==1)):
 						from_warehouse_sle = self.get_sl_entries(d, {
 							"actual_qty": -1 * pr_qty,
-							"warehouse": d.from_warehouse
+							"warehouse": d.from_warehouse,
+							"recalculate_rate": 1
 						})
 
 						sl_entries.append(from_warehouse_sle)
@@ -651,6 +639,7 @@
 					"item_code": d.rm_item_code,
 					"warehouse": self.supplier_warehouse,
 					"actual_qty": -1*flt(d.consumed_qty),
+					"dependant_sle_voucher_detail_no": d.reference_name
 				}))
 
 	def on_submit(self):
@@ -842,6 +831,7 @@
 		else:
 			validate_item_type(self, "is_purchase_item", "purchase")
 
+
 def get_items_from_bom(item_code, bom, exploded_item=1):
 	doctype = "BOM Item" if not exploded_item else "BOM Explosion Item"
 
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index afc5f81..7979226 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -203,10 +203,42 @@
 
 	return items
 
+def get_returned_qty_map_for_row(row_name, doctype):
+	if doctype == "POS Invoice": return {}
+
+	child_doctype = doctype + " Item"
+	reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype)
+
+	fields = [
+		"sum(abs(`tab{0}`.qty)) as qty".format(child_doctype),
+		"sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype)
+	]
+
+	if doctype in ("Purchase Receipt", "Purchase Invoice"):
+		fields += [
+			"sum(abs(`tab{0}`.rejected_qty)) as rejected_qty".format(child_doctype),
+			"sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype)
+		]
+
+		if doctype == "Purchase Receipt":
+			fields += ["sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)]
+
+	data = frappe.db.get_list(doctype,
+		fields = fields,
+		filters = [
+			[doctype, "docstatus", "=", 1],
+			[doctype, "is_return", "=", 1],
+			[child_doctype, reference_field, "=", row_name]
+	])
+
+	return data[0]
+
 def make_return_doc(doctype, source_name, target_doc=None):
 	from frappe.model.mapper import get_mapped_doc
+	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 	company = frappe.db.get_value("Delivery Note", source_name, "company")
 	default_warehouse_for_sales_return = frappe.db.get_value("Company", company, "default_warehouse_for_sales_return")
+
 	def set_missing_values(source, target):
 		doc = frappe.get_doc(target)
 		doc.is_return = 1
@@ -261,22 +293,35 @@
 		doc.run_method("calculate_taxes_and_totals")
 
 	def update_item(source_doc, target_doc, source_parent):
-		target_doc.qty = -1* source_doc.qty
+		target_doc.qty = -1 * source_doc.qty
+
+		if source_doc.serial_no:
+			returned_serial_nos = get_returned_serial_nos(source_doc, source_parent)
+			serial_nos = list(set(get_serial_nos(source_doc.serial_no)) - set(returned_serial_nos))
+			if serial_nos:
+				target_doc.serial_no = '\n'.join(serial_nos)
+
 		if doctype == "Purchase Receipt":
-			target_doc.received_qty = -1* source_doc.received_qty
-			target_doc.rejected_qty = -1* source_doc.rejected_qty
-			target_doc.qty = -1* source_doc.qty
-			target_doc.stock_qty = -1 * source_doc.stock_qty
+			returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
+			target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
+			target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
+			target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
+
+			target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
+			target_doc.received_stock_qty = -1 * flt(source_doc.received_stock_qty - (returned_qty_map.get('received_stock_qty') or 0))
+
 			target_doc.purchase_order = source_doc.purchase_order
 			target_doc.purchase_order_item = source_doc.purchase_order_item
 			target_doc.rejected_warehouse = source_doc.rejected_warehouse
 			target_doc.purchase_receipt_item = source_doc.name
 
 		elif doctype == "Purchase Invoice":
-			target_doc.received_qty = -1* source_doc.received_qty
-			target_doc.rejected_qty = -1* source_doc.rejected_qty
-			target_doc.qty = -1* source_doc.qty
-			target_doc.stock_qty = -1 * source_doc.stock_qty
+			returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
+			target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
+			target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
+			target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
+
+			target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
 			target_doc.purchase_order = source_doc.purchase_order
 			target_doc.purchase_receipt = source_doc.purchase_receipt
 			target_doc.rejected_warehouse = source_doc.rejected_warehouse
@@ -285,6 +330,10 @@
 			target_doc.purchase_invoice_item = source_doc.name
 
 		elif doctype == "Delivery Note":
+			returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
+			target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
+			target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
+
 			target_doc.against_sales_order = source_doc.against_sales_order
 			target_doc.against_sales_invoice = source_doc.against_sales_invoice
 			target_doc.so_detail = source_doc.so_detail
@@ -294,6 +343,10 @@
 			if default_warehouse_for_sales_return:
 				target_doc.warehouse = default_warehouse_for_sales_return
 		elif doctype == "Sales Invoice" or doctype == "POS Invoice":
+			returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
+			target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
+			target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
+
 			target_doc.sales_order = source_doc.sales_order
 			target_doc.delivery_note = source_doc.delivery_note
 			target_doc.so_detail = source_doc.so_detail
@@ -329,3 +382,63 @@
 	}, target_doc, set_missing_values)
 
 	return doclist
+
+def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None, item_row=None, voucher_detail_no=None):
+	if not return_against:
+		return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against")
+
+	return_against_item_field = get_return_against_item_fields(voucher_type)
+
+	filters = get_filters(voucher_type, voucher_no, voucher_detail_no,
+		return_against, item_code, return_against_item_field, item_row)
+
+	if voucher_type in ("Purchase Receipt", "Purchase Invoice"):
+		select_field = "incoming_rate"
+	else:
+		select_field = "abs(stock_value_difference / actual_qty)"
+
+	return flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
+
+def get_return_against_item_fields(voucher_type):
+	return_against_item_fields = {
+		"Purchase Receipt": "purchase_receipt_item",
+		"Purchase Invoice": "purchase_invoice_item",
+		"Delivery Note": "dn_detail",
+		"Sales Invoice": "sales_invoice_item"
+	}
+	return return_against_item_fields[voucher_type]
+
+def get_filters(voucher_type, voucher_no, voucher_detail_no, return_against, item_code, return_against_item_field, item_row):
+	filters = {
+		"voucher_type": voucher_type,
+		"voucher_no": return_against,
+		"item_code": item_code
+	}
+
+	if item_row:
+		reference_voucher_detail_no = item_row.get(return_against_item_field)
+	else:
+		reference_voucher_detail_no = frappe.db.get_value(voucher_type + " Item", voucher_detail_no, return_against_item_field)
+
+	if reference_voucher_detail_no:
+		filters["voucher_detail_no"] = reference_voucher_detail_no
+
+	return filters
+
+def get_returned_serial_nos(child_doc, parent_doc):
+	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+	return_ref_field = frappe.scrub(child_doc.doctype)
+	if child_doc.doctype == "Delivery Note Item":
+		return_ref_field = "dn_detail"
+
+	serial_nos = []
+
+	fields = ["`{0}`.`serial_no`".format("tab" + child_doc.doctype)]
+
+	filters = [[parent_doc.doctype, "return_against", "=", parent_doc.name], [parent_doc.doctype, "is_return", "=", 1],
+		[child_doc.doctype, return_ref_field, "=", child_doc.name], [parent_doc.doctype, "docstatus", "=", 1]]
+
+	for row in frappe.get_all(parent_doc.doctype, fields = fields, filters=filters):
+		serial_nos.extend(get_serial_nos(row.serial_no))
+
+	return serial_nos
\ No newline at end of file
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 7504746..85cfb95 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -13,6 +13,7 @@
 from erpnext.controllers.accounts_controller import get_taxes_and_charges
 
 from erpnext.controllers.stock_controller import StockController
+from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
 
 class SellingController(StockController):
 	def __setup__(self):
@@ -42,12 +43,13 @@
 		self.validate_max_discount()
 		self.validate_selling_price()
 		self.set_qty_as_per_stock_uom()
-		self.set_po_nos()
+		self.set_po_nos(for_validate=True)
 		self.set_gross_profit()
 		set_default_income_account_for_item(self)
 		self.set_customer_address()
 		self.validate_for_duplicate_items()
 		self.validate_target_warehouse()
+		self.set_incoming_rate()
 
 	def set_missing_values(self, for_validate=False):
 
@@ -230,7 +232,8 @@
 							'voucher_type': self.doctype,
 							'allow_zero_valuation': d.allow_zero_valuation_rate,
 							'sales_invoice_item': d.get("sales_invoice_item"),
-							'delivery_note_item': d.get("dn_detail")
+							'dn_detail': d.get("dn_detail"),
+							'incoming_rate': p.incoming_rate
 						}))
 			else:
 				il.append(frappe._dict({
@@ -248,7 +251,8 @@
 					'voucher_type': self.doctype,
 					'allow_zero_valuation': d.allow_zero_valuation_rate,
 					'sales_invoice_item': d.get("sales_invoice_item"),
-					'delivery_note_item': d.get("dn_detail")
+					'dn_detail': d.get("dn_detail"),
+					'incoming_rate': d.incoming_rate
 				}))
 		return il
 
@@ -307,83 +311,111 @@
 
 				sales_order.update_reserved_qty(so_item_rows)
 
+	def set_incoming_rate(self):
+		if self.doctype not in ("Delivery Note", "Sales Invoice"):
+			return
+
+		items = self.get("items") + (self.get("packed_items") or [])
+		for d in items:
+			if not cint(self.get("is_return")):
+				# Get incoming rate based on original item cost based on valuation method
+				d.incoming_rate = get_incoming_rate({
+					"item_code": d.item_code,
+					"warehouse": d.warehouse,
+					"posting_date": self.posting_date,
+					"posting_time": self.posting_time,
+					"qty": -1*flt(d.qty),
+					"serial_no": d.serial_no,
+					"company": self.company,
+					"voucher_type": self.doctype,
+					"voucher_no": self.name,
+					"allow_zero_valuation": d.get("allow_zero_valuation")
+				}, raise_error_if_no_rate=False)
+			elif self.get("return_against"):
+				# Get incoming rate of return entry from reference document
+				# based on original item cost as per valuation method
+				d.incoming_rate = get_rate_for_return(self.doctype, self.name, d.item_code, self.return_against, item_row=d)
+
 	def update_stock_ledger(self):
 		self.update_reserved_qty()
 
 		sl_entries = []
+		# Loop over items and packed items table
 		for d in self.get_item_list():
 			if frappe.get_cached_value("Item", d.item_code, "is_stock_item") == 1 and flt(d.qty):
 				if flt(d.conversion_factor)==0.0:
 					d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0
-				return_rate = 0
-				if cint(self.is_return) and self.return_against and self.docstatus==1:
-					against_document_no = (d.get("sales_invoice_item")
-						if self.doctype == "Sales Invoice" else d.get("delivery_note_item"))
 
-					return_rate = self.get_incoming_rate_for_return(d.item_code,
-						self.return_against, against_document_no)
-
-				# On cancellation or if return entry submission, make stock ledger entry for
+				# On cancellation or return entry submission, make stock ledger entry for
 				# target warehouse first, to update serial no values properly
 
 				if d.warehouse and ((not cint(self.is_return) and self.docstatus==1)
 					or (cint(self.is_return) and self.docstatus==2)):
-						sl_entries.append(self.get_sl_entries(d, {
-							"actual_qty": -1*flt(d.qty),
-							"incoming_rate": return_rate
-						}))
+						sl_entries.append(self.get_sle_for_source_warehouse(d))
 
 				if d.target_warehouse:
-					target_warehouse_sle = self.get_sl_entries(d, {
-						"actual_qty": flt(d.qty),
-						"warehouse": d.target_warehouse
-					})
-
-					if self.docstatus == 1:
-						if not cint(self.is_return):
-							args = frappe._dict({
-								"item_code": d.item_code,
-								"warehouse": d.warehouse,
-								"posting_date": self.posting_date,
-								"posting_time": self.posting_time,
-								"qty": -1*flt(d.qty),
-								"serial_no": d.serial_no,
-								"company": d.company,
-								"voucher_type": d.voucher_type,
-								"voucher_no": d.name,
-								"allow_zero_valuation": d.allow_zero_valuation
-							})
-							target_warehouse_sle.update({
-								"incoming_rate": get_incoming_rate(args)
-							})
-						else:
-							target_warehouse_sle.update({
-								"outgoing_rate": return_rate
-							})
-					sl_entries.append(target_warehouse_sle)
+					sl_entries.append(self.get_sle_for_target_warehouse(d))
 
 				if d.warehouse and ((not cint(self.is_return) and self.docstatus==2)
 					or (cint(self.is_return) and self.docstatus==1)):
-						sl_entries.append(self.get_sl_entries(d, {
-							"actual_qty": -1*flt(d.qty),
-							"incoming_rate": return_rate
-						}))
+						sl_entries.append(self.get_sle_for_source_warehouse(d))
+
 		self.make_sl_entries(sl_entries)
 
-	def set_po_nos(self):
+	def get_sle_for_source_warehouse(self, item_row):
+		sle = self.get_sl_entries(item_row, {
+			"actual_qty": -1*flt(item_row.qty),
+			"incoming_rate": item_row.incoming_rate,
+			"recalculate_rate": cint(self.is_return)
+		})
+		if item_row.target_warehouse and not cint(self.is_return):
+			sle.dependant_sle_voucher_detail_no = item_row.name
+
+		return sle
+
+	def get_sle_for_target_warehouse(self, item_row):
+		sle = self.get_sl_entries(item_row, {
+			"actual_qty": flt(item_row.qty),
+			"warehouse": item_row.target_warehouse
+		})
+
+		if self.docstatus == 1:
+			if not cint(self.is_return):
+				sle.update({
+					"incoming_rate": item_row.incoming_rate,
+					"recalculate_rate": 1
+				})
+			else:
+				sle.update({
+					"outgoing_rate": item_row.incoming_rate
+				})
+				if item_row.warehouse:
+					sle.dependant_sle_voucher_detail_no = item_row.name
+			
+		return sle
+
+	def set_po_nos(self, for_validate=False):
 		if self.doctype == 'Sales Invoice' and hasattr(self, "items"):
+			if for_validate and self.po_no:
+				return
 			self.set_pos_for_sales_invoice()
 		if self.doctype == 'Delivery Note' and hasattr(self, "items"):
+			if for_validate and self.po_no:
+				return
 			self.set_pos_for_delivery_note()
 
 	def set_pos_for_sales_invoice(self):
 		po_nos = []
+		if self.po_no:
+			po_nos.append(self.po_no)
 		self.get_po_nos('Sales Order', 'sales_order', po_nos)
 		self.get_po_nos('Delivery Note', 'delivery_note', po_nos)
 		self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
 
 	def set_pos_for_delivery_note(self):
 		po_nos = []
+		if self.po_no:
+			po_nos.append(self.po_no)
 		self.get_po_nos('Sales Order', 'against_sales_order', po_nos)
 		self.get_po_nos('Sales Invoice', 'against_sales_invoice', po_nos)
 		self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
@@ -416,26 +448,26 @@
 			return
 
 		for d in self.get('items'):
-			if self.doctype == "Sales Invoice":
-				e = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or '']
-				f = [d.item_code, d.description, d.sales_order or d.delivery_note]
+			if self.doctype in ["POS Invoice","Sales Invoice"]:
+				stock_items = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or '']
+				non_stock_items = [d.item_code, d.description, d.sales_order or d.delivery_note]
 			elif self.doctype == "Delivery Note":
-				e = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or '']
-				f = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice]
+				stock_items = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or '']
+				non_stock_items = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice]
 			elif self.doctype in ["Sales Order", "Quotation"]:
-				e = [d.item_code, d.description, d.warehouse, '']
-				f = [d.item_code, d.description]
+				stock_items = [d.item_code, d.description, d.warehouse, '']
+				non_stock_items = [d.item_code, d.description]
 
 			if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
-				if e in check_list:
+				if stock_items in check_list:
 					frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
 				else:
-					check_list.append(e)
+					check_list.append(stock_items)
 			else:
-				if f in chk_dupl_itm:
+				if non_stock_items in chk_dupl_itm:
 					frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
 				else:
-					chk_dupl_itm.append(f)
+					chk_dupl_itm.append(non_stock_items)
 
 	def validate_target_warehouse(self):
 		items = self.get("items") + (self.get("packed_items") or [])
@@ -455,4 +487,4 @@
 	for d in obj.get("items"):
 		if d.item_code:
 			if getattr(d, "income_account", None):
-				set_item_default(d.item_code, obj.company, 'income_account', d.income_account)
+				set_item_default(d.item_code, obj.company, 'income_account', d.income_account)
\ No newline at end of file
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 9feac78..8c05134 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -58,6 +58,7 @@
 	"Delivery Note": [
 		["Draft", None],
 		["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"],
+		["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
 		["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
 		["Cancelled", "eval:self.docstatus==2"],
 		["Closed", "eval:self.status=='Closed'"],
@@ -65,6 +66,7 @@
 	"Purchase Receipt": [
 		["Draft", None],
 		["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"],
+		["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
 		["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
 		["Cancelled", "eval:self.docstatus==2"],
 		["Closed", "eval:self.status=='Closed'"],
@@ -232,7 +234,7 @@
 
 			self._update_children(args, update_modified)
 
-			if "percent_join_field" in args:
+			if "percent_join_field" in args or "percent_join_field_parent" in args:
 				self._update_percent_field_in_targets(args, update_modified)
 
 	def _update_children(self, args, update_modified):
@@ -252,33 +254,43 @@
 				if not args.get("second_source_extra_cond"):
 					args["second_source_extra_cond"] = ""
 
-				args['second_source_condition'] = """ + ifnull((select sum(%(second_source_field)s)
+				args['second_source_condition'] = frappe.db.sql(""" select ifnull((select sum(%(second_source_field)s)
 					from `tab%(second_source_dt)s`
 					where `%(second_join_field)s`="%(detail_id)s"
-					and (`tab%(second_source_dt)s`.docstatus=1) %(second_source_extra_cond)s FOR UPDATE), 0)""" % args
+					and (`tab%(second_source_dt)s`.docstatus=1)
+					%(second_source_extra_cond)s), 0) """ % args)[0][0]
 
 			if args['detail_id']:
 				if not args.get("extra_cond"): args["extra_cond"] = ""
 
-				frappe.db.sql("""update `tab%(target_dt)s`
-					set %(target_field)s = (
+				args["source_dt_value"] = frappe.db.sql("""
 						(select ifnull(sum(%(source_field)s), 0)
 							from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
 							and (docstatus=1 %(cond)s) %(extra_cond)s)
-						%(second_source_condition)s
-					)
-					%(update_modified)s
+				""" % args)[0][0] or 0.0
+
+				if args['second_source_condition']:
+					args["source_dt_value"] += flt(args['second_source_condition'])
+
+				frappe.db.sql("""update `tab%(target_dt)s`
+					set %(target_field)s = %(source_dt_value)s %(update_modified)s
 					where name='%(detail_id)s'""" % args)
 
 	def _update_percent_field_in_targets(self, args, update_modified=True):
 		"""Update percent field in parent transaction"""
-		distinct_transactions = set([d.get(args['percent_join_field'])
-			for d in self.get_all_children(args['source_dt'])])
+		if args.get('percent_join_field_parent'):
+			# if reference to target doc where % is to be updated, is
+			# in source doc's parent form, consider percent_join_field_parent
+			args['name'] = self.get(args['percent_join_field_parent'])
+			self._update_percent_field(args, update_modified)
+		else:
+			distinct_transactions = set([d.get(args['percent_join_field'])
+				for d in self.get_all_children(args['source_dt'])])
 
-		for name in distinct_transactions:
-			if name:
-				args['name'] = name
-				self._update_percent_field(args, update_modified)
+			for name in distinct_transactions:
+				if name:
+					args['name'] = name
+					self._update_percent_field(args, update_modified)
 
 	def _update_percent_field(self, args, update_modified=True):
 		"""Update percent field in parent transaction"""
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index f743d70..4399976 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -6,7 +6,7 @@
 from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
 from frappe import _
 import frappe.defaults
-from erpnext.accounts.utils import get_fiscal_year
+from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced
 from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
 from erpnext.controllers.accounts_controller import AccountsController
 from erpnext.stock.stock_ledger import get_valuation_rate
@@ -24,7 +24,7 @@
 		self.validate_serialized_batch()
 		self.validate_customer_provided_item()
 
-	def make_gl_entries(self, gl_entries=None):
+	def make_gl_entries(self, gl_entries=None, from_repost=False):
 		if self.docstatus == 2:
 			make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
 
@@ -34,12 +34,12 @@
 			if self.docstatus==1:
 				if not gl_entries:
 					gl_entries = self.get_gl_entries(warehouse_account)
-				make_gl_entries(gl_entries)
+				make_gl_entries(gl_entries, from_repost=from_repost)
 
 		elif self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self.docstatus == 1:
 			gl_entries = []
 			gl_entries = self.get_asset_gl_entry(gl_entries)
-			make_gl_entries(gl_entries)
+			make_gl_entries(gl_entries, from_repost=from_repost)
 
 	def validate_serialized_batch(self):
 		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -70,14 +70,13 @@
 
 		gl_list = []
 		warehouse_with_no_account = []
-
 		precision = frappe.get_precision("GL Entry", "debit_in_account_currency")
 		for item_row in voucher_details:
 			sle_list = sle_map.get(item_row.name)
 			if sle_list:
 				for sle in sle_list:
 					if warehouse_account.get(sle.warehouse):
-						# from warehouse account/ target warehouse account
+						# from warehouse account
 
 						self.check_expense_account(item_row)
 
@@ -92,9 +91,16 @@
 
 							sle = self.update_stock_ledger_entries(sle)
 
+						# expense account/ target_warehouse / source_warehouse
+						if item_row.get('target_warehouse'):
+							warehouse = item_row.get('target_warehouse')
+							expense_account = warehouse_account[warehouse]["account"]
+						else:
+							expense_account = item_row.expense_account
+
 						gl_list.append(self.get_gl_dict({
 							"account": warehouse_account[sle.warehouse]["account"],
-							"against": item_row.expense_account,
+							"against": expense_account,
 							"cost_center": item_row.cost_center,
 							"project": item_row.project or self.get('project'),
 							"remarks": self.get("remarks") or "Accounting Entry for Stock",
@@ -102,9 +108,8 @@
 							"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
 						}, warehouse_account[sle.warehouse]["account_currency"], item=item_row))
 
-						# expense account
 						gl_list.append(self.get_gl_dict({
-							"account": item_row.expense_account,
+							"account": expense_account,
 							"against": warehouse_account[sle.warehouse]["account"],
 							"cost_center": item_row.cost_center,
 							"project": item_row.project or self.get('project'),
@@ -119,7 +124,7 @@
 		if warehouse_with_no_account:
 			for wh in warehouse_with_no_account:
 				if frappe.db.get_value("Warehouse", wh, "company"):
-					frappe.throw(_("Warehouse {0} is not linked to any account, please mention the account in  the warehouse record or set default inventory account in company {1}.").format(wh, self.company))
+					frappe.throw(_("Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}.").format(wh, self.company))
 
 		return process_gl_map(gl_list)
 
@@ -229,9 +234,9 @@
 
 	def check_expense_account(self, item):
 		if not item.get("expense_account"):
-			frappe.throw(_("Row #{0}: Expense Account not set for Item {1}. Please set an Expense \
-				Account in the Items table").format(item.idx, frappe.bold(item.item_code)),
-				title=_("Expense Account Missing"))
+			msg = _("Please set an Expense Account in the Items table")
+			frappe.throw(_("Row #{0}: Expense Account not set for the Item {1}. {2}")
+				.format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing"))
 
 		else:
 			is_expense_account = frappe.db.get_value("Account",
@@ -247,7 +252,9 @@
 		for d in self.items:
 			if not d.batch_no: continue
 
-			serial_nos = [sr.name for sr in frappe.get_all("Serial No", {'batch_no': d.batch_no})]
+			serial_nos = [sr.name for sr in frappe.get_all("Serial No",
+				{'batch_no': d.batch_no, 'status': 'Inactive'})]
+
 			if serial_nos:
 				frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None)
 
@@ -301,23 +308,6 @@
 
 		return serialized_items
 
-	def get_incoming_rate_for_return(self, item_code, against_document, against_document_no=None):
-		incoming_rate = 0.0
-		cond = ''
-		if against_document and item_code:
-			if against_document_no:
-				cond = " and voucher_detail_no = %s" %(frappe.db.escape(against_document_no))
-
-			incoming_rate = frappe.db.sql("""select abs(stock_value_difference / actual_qty)
-				from `tabStock Ledger Entry`
-				where voucher_type = %s and voucher_no = %s
-					and item_code = %s {0} limit 1""".format(cond),
-				(self.doctype, against_document, item_code))
-
-			incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0
-
-		return incoming_rate
-
 	def validate_warehouse(self):
 		from erpnext.stock.utils import validate_warehouse_company
 
@@ -338,11 +328,15 @@
 			validate_warehouse_company(w, self.company)
 
 	def update_billing_percentage(self, update_modified=True):
+		target_ref_field = "amount"
+		if self.doctype == "Delivery Note":
+			target_ref_field = "amount - (returned_qty * rate)"
+
 		self._update_percent_field({
 			"target_dt": self.doctype + " Item",
 			"target_parent_dt": self.doctype,
 			"target_parent_field": "per_billed",
-			"target_ref_field": "amount",
+			"target_ref_field": target_ref_field,
 			"target_field": "billed_amt",
 			"name": self.name,
 		}, update_modified)
@@ -397,19 +391,72 @@
 			if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
 				d.allow_zero_valuation_rate = 1
 
-def compare_existing_and_expected_gle(existing_gle, expected_gle):
-	matched = True
-	for entry in expected_gle:
-		account_existed = False
-		for e in existing_gle:
-			if entry.account == e.account:
-				account_existed = True
-			if entry.account == e.account and entry.against_account == e.against_account \
-					and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center) \
-					and (entry.debit != e.debit or entry.credit != e.credit):
-				matched = False
-				break
-		if not account_existed:
-			matched = False
+	def repost_future_sle_and_gle(self):
+		args = frappe._dict({
+			"posting_date": self.posting_date,
+			"posting_time": self.posting_time,
+			"voucher_type": self.doctype,
+			"voucher_no": self.name,
+			"company": self.company
+		})
+
+		if check_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,
+				self.company, self.doctype, self.name)
+
+def is_reposting_pending():
+	return frappe.db.exists("Repost Item Valuation",
+		{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
+
+
+def check_if_future_sle_exists(args):
+	sl_entries = frappe.db.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]))
+
+	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 matched
+	return sle_exists
+
+def get_sle(args):
+	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 voucher_no != %(voucher_no)s
+			and is_cancelled = 0
+		limit 1
+	""", args)
+
+def create_repost_item_valuation_entry(args):
+	args = frappe._dict(args)
+	repost_entry = frappe.new_doc("Repost Item Valuation")
+	repost_entry.based_on = args.based_on
+	if not args.based_on:
+		repost_entry.based_on = 'Transaction' if args.voucher_no else "Item and Warehouse"
+	repost_entry.voucher_type = args.voucher_type
+	repost_entry.voucher_no = args.voucher_no
+	repost_entry.item_code = args.item_code
+	repost_entry.warehouse = args.warehouse
+	repost_entry.posting_date = args.posting_date
+	repost_entry.posting_time = args.posting_time
+	repost_entry.company = args.company
+	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
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 81d07c1..8dd2e5b 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -519,6 +519,17 @@
 			if self.doc.docstatus == 0:
 				self.calculate_outstanding_amount()
 
+	def is_internal_invoice(self):
+		"""
+			Checks if its an internal transfer invoice
+			and decides if to calculate any out standing amount or not
+		"""
+
+		if self.doc.doctype in ('Sales Invoice', 'Purchase Invoice') and self.doc.is_internal_transfer():
+			return True
+
+		return False
+
 	def calculate_outstanding_amount(self):
 		# NOTE:
 		# write_off_amount is only for POS Invoice
@@ -526,7 +537,8 @@
 		if self.doc.doctype == "Sales Invoice":
 			self.calculate_paid_amount()
 
-		if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos'): return
+		if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos') or \
+			self.is_internal_invoice(): return
 
 		self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
 		self._set_in_company_currency(self.doc, ['write_off_amount'])
@@ -641,7 +653,8 @@
 		if default_mode_of_payment:
 			self.doc.append('payments', {
 				'mode_of_payment': default_mode_of_payment.mode_of_payment,
-				'amount': total_amount_to_pay
+				'amount': total_amount_to_pay,
+				'default': 1
 			})
 		else:
 			self.doc.is_pos = 0
diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py
index 63efeb3..2009ebf 100644
--- a/erpnext/crm/doctype/appointment/appointment.py
+++ b/erpnext/crm/doctype/appointment/appointment.py
@@ -126,7 +126,7 @@
 			add_assignemnt({
 				'doctype': self.doctype,
 				'name': self.name,
-				'assign_to': existing_assignee
+				'assign_to': [existing_assignee]
 			})
 			return
 		if self._assign:
@@ -139,7 +139,7 @@
 				add_assignemnt({
 					'doctype': self.doctype,
 					'name': self.name,
-					'assign_to': agent
+					'assign_to': [agent]
 				})
 			break
 
diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js
index 99b8214..dc3ae8b 100644
--- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js
+++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js
@@ -4,7 +4,7 @@
 		let from_time = Date.parse('01/01/2019 ' + d.from_time);
 		let to_time = Date.parse('01/01/2019 ' + d.to_time);
 		if (from_time > to_time) {
-			frappe.throw(__(`In row ${i + 1} of Appointment Booking Slots : "To Time" must be later than "From Time"`));
+			frappe.throw(__('In row {0} of Appointment Booking Slots: "To Time" must be later than "From Time".', [i + 1]));
 		}
 	});
 }
\ No newline at end of file
diff --git a/erpnext/crm/doctype/contract/contract.js b/erpnext/crm/doctype/contract/contract.js
index ee9e895..9968855 100644
--- a/erpnext/crm/doctype/contract/contract.js
+++ b/erpnext/crm/doctype/contract/contract.js
@@ -1,23 +1,31 @@
 // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 
-cur_frm.add_fetch("contract_template", "contract_terms", "contract_terms");
-cur_frm.add_fetch("contract_template", "requires_fulfilment", "requires_fulfilment");
-
-// Add fulfilment terms from contract template into contract
 frappe.ui.form.on("Contract", {
 	contract_template: function (frm) {
-		// Populate the fulfilment terms table from a contract template, if any
 		if (frm.doc.contract_template) {
-			frappe.model.with_doc("Contract Template", frm.doc.contract_template, function () {
-				var tabletransfer = frappe.model.get_doc("Contract Template", frm.doc.contract_template);
-
-				frm.doc.fulfilment_terms = [];
-				$.each(tabletransfer.fulfilment_terms, function (index, row) {
-					var d = frm.add_child("fulfilment_terms");
-					d.requirement = row.requirement;
-					frm.refresh_field("fulfilment_terms");
-				});
+			frappe.call({
+				method: 'erpnext.crm.doctype.contract_template.contract_template.get_contract_template',
+				args: {
+					template_name: frm.doc.contract_template,
+					doc: frm.doc
+				},
+				callback: function(r) {
+					if (r && r.message) {
+						let contract_template = r.message.contract_template;
+						frm.set_value("contract_terms", r.message.contract_terms);
+						frm.set_value("requires_fulfilment", contract_template.requires_fulfilment);
+						
+						if (frm.doc.requires_fulfilment) {
+							// Populate the fulfilment terms table from a contract template, if any
+							r.message.contract_template.fulfilment_terms.forEach(element => {
+								let d = frm.add_child("fulfilment_terms");
+								d.requirement = element.requirement;
+							});
+							frm.refresh_field("fulfilment_terms");
+						}		
+					}
+				}
 			});
 		}
 	}
diff --git a/erpnext/crm/doctype/contract/contract.json b/erpnext/crm/doctype/contract/contract.json
index 0026e4a..de3230f 100755
--- a/erpnext/crm/doctype/contract/contract.json
+++ b/erpnext/crm/doctype/contract/contract.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "allow_import": 1,
  "allow_rename": 1,
  "creation": "2018-04-12 06:32:04.582486",
@@ -247,7 +248,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-03-30 06:56:07.257932",
+ "modified": "2020-12-07 11:15:58.385521",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Contract",
diff --git a/erpnext/crm/doctype/contract_template/contract_template.json b/erpnext/crm/doctype/contract_template/contract_template.json
index 5e4582f..7cc5ec1 100644
--- a/erpnext/crm/doctype/contract_template/contract_template.json
+++ b/erpnext/crm/doctype/contract_template/contract_template.json
@@ -11,7 +11,9 @@
   "contract_terms",
   "sb_fulfilment",
   "requires_fulfilment",
-  "fulfilment_terms"
+  "fulfilment_terms",
+  "section_break_6",
+  "contract_template_help"
  ],
  "fields": [
   {
@@ -41,10 +43,20 @@
    "fieldtype": "Table",
    "label": "Fulfilment Terms and Conditions",
    "options": "Contract Template Fulfilment Terms"
+  },
+  {
+   "fieldname": "section_break_6",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "contract_template_help",
+   "fieldtype": "HTML",
+   "label": "Contract Template Help",
+   "options": "<h4>Contract Template Example</h4>\n\n<pre>Contract for Customer {{ party_name }}\n\n-Valid From : {{ start_date }} \n-Valid To : {{ end_date }}\n</pre>\n\n<h4>How to get fieldnames</h4>\n\n<p>The field names you can use in your Contract Template are the fields in the Contract for which you are creating the template. You can find out the fields of any documents via Setup &gt; Customize Form View and selecting the document type (e.g. Contract)</p>\n\n<h4>Templating</h4>\n\n<p>Templates are compiled using the Jinja Templating Language. To learn more about Jinja, <a class=\"strong\" href=\"http://jinja.pocoo.org/docs/dev/templates/\">read this documentation.</a></p>"
   }
  ],
  "links": [],
- "modified": "2020-11-11 17:49:44.879363",
+ "modified": "2020-12-07 10:44:22.587047",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Contract Template",
diff --git a/erpnext/crm/doctype/contract_template/contract_template.py b/erpnext/crm/doctype/contract_template/contract_template.py
index 601ee9a..69fd86f 100644
--- a/erpnext/crm/doctype/contract_template/contract_template.py
+++ b/erpnext/crm/doctype/contract_template/contract_template.py
@@ -5,6 +5,27 @@
 from __future__ import unicode_literals
 import frappe
 from frappe.model.document import Document
+from frappe.utils.jinja import validate_template
+from six import string_types
+import json
 
 class ContractTemplate(Document):
-	pass
+	def validate(self):
+		if self.contract_terms:
+			validate_template(self.contract_terms)
+
+@frappe.whitelist()
+def get_contract_template(template_name, doc):
+	if isinstance(doc, string_types):
+		doc = json.loads(doc)
+
+	contract_template = frappe.get_doc("Contract Template", template_name)
+	contract_terms = None
+
+	if contract_template.contract_terms:
+		contract_terms = frappe.render_template(contract_template.contract_terms, doc)
+	
+	return {
+		'contract_template': contract_template, 
+		'contract_terms': contract_terms
+	}
\ No newline at end of file
diff --git a/erpnext/demo/data/asset.json b/erpnext/demo/data/asset.json
index 23029ca..44db2ae 100644
--- a/erpnext/demo/data/asset.json
+++ b/erpnext/demo/data/asset.json
@@ -4,48 +4,55 @@
 		"item_code": "Computer",
 		"gross_purchase_amount": 100000,
 		"asset_owner": "Company",
-		"available_for_use_date": "2017-01-02"
+		"available_for_use_date": "2017-01-02",
+		"location": "Main Location"
 	},
 	{
 		"asset_name": "Macbook Air - 1",
 		"item_code": "Computer",
 		"gross_purchase_amount": 60000,
 		"asset_owner": "Company",
-		"available_for_use_date": "2017-10-02"
+		"available_for_use_date": "2017-10-02",
+		"location": "Avg Location"
 	},
 	{
 		"asset_name": "Conferrence Table",
 		"item_code": "Table",
 		"gross_purchase_amount": 30000,
 		"asset_owner": "Company",
-		"available_for_use_date": "2018-10-02"
+		"available_for_use_date": "2018-10-02",
+		"location": "Zany Location"
 	},
 	{
 		"asset_name": "Lunch Table",
 		"item_code": "Table",
 		"gross_purchase_amount": 20000,
 		"asset_owner": "Company",
-		"available_for_use_date": "2018-06-02"
+		"available_for_use_date": "2018-06-02",
+		"location": "Fletcher Location"
 	},
 	{
 		"asset_name": "ERPNext",
 		"item_code": "ERP",
 		"gross_purchase_amount": 100000,
 		"asset_owner": "Company",
-		"available_for_use_date": "2018-09-02"
+		"available_for_use_date": "2018-09-02",
+		"location":"Main Location"
 	},
 	{
 		"asset_name": "Chair 1",
 		"item_code": "Chair",
 		"gross_purchase_amount": 10000,
 		"asset_owner": "Company",
-		"available_for_use_date": "2018-07-02"
+		"available_for_use_date": "2018-07-02",
+		"location": "Zany Location"
 	},
 	{
 		"asset_name": "Chair 2",
 		"item_code": "Chair",
 		"gross_purchase_amount": 10000,
 		"asset_owner": "Company",
-		"available_for_use_date": "2018-07-02"
+		"available_for_use_date": "2018-07-02",
+		"location": "Avg Location"
 	}
 ]
diff --git a/erpnext/demo/data/location.json b/erpnext/demo/data/location.json
new file mode 100644
index 0000000..b521aa0
--- /dev/null
+++ b/erpnext/demo/data/location.json
@@ -0,0 +1,22 @@
+[
+    {
+        "location_name": "Main Location",
+        "latitude": 40.0,
+        "longitude": 20.0
+    },
+    {
+        "location_name": "Avg Location",
+        "latitude": 63.0,
+        "longitude": 99.3
+    },
+    {
+        "location_name": "Zany Location",
+        "latitude": 47.5,
+        "longitude": 10.0
+    },
+    {
+        "location_name": "Fletcher Location",
+        "latitude": 100.90,
+        "longitude": 80
+    }
+]
\ No newline at end of file
diff --git a/erpnext/demo/setup/manufacture.py b/erpnext/demo/setup/manufacture.py
index d384636..7d6b501 100644
--- a/erpnext/demo/setup/manufacture.py
+++ b/erpnext/demo/setup/manufacture.py
@@ -9,6 +9,7 @@
 from six import iteritems
 
 def setup_data():
+	import_json("Location")
 	import_json("Asset Category")
 	setup_item()
 	setup_workstation()
diff --git a/erpnext/demo/setup/setup_data.py b/erpnext/demo/setup/setup_data.py
index a395c7c..05ee28a 100644
--- a/erpnext/demo/setup/setup_data.py
+++ b/erpnext/demo/setup/setup_data.py
@@ -134,7 +134,7 @@
 		salary_component = frappe.get_doc('Salary Component', d.name)
 		salary_component.append('accounts', dict(
 			company=erpnext.get_default_company(),
-			default_account=frappe.get_value('Account', dict(account_name=('like', 'Salary%')))
+			account=frappe.get_value('Account', dict(account_name=('like', 'Salary%')))
 		))
 		salary_component.save()
 
diff --git a/erpnext/demo/user/stock.py b/erpnext/demo/user/stock.py
index f95a6b8..d44da7d 100644
--- a/erpnext/demo/user/stock.py
+++ b/erpnext/demo/user/stock.py
@@ -79,7 +79,7 @@
 				if item.qty:
 					item.qty = item.qty - round(random.randint(1, item.qty))
 			try:
-				stock_reco.insert(ignore_permissions=True)
+				stock_reco.insert(ignore_permissions=True, ignore_mandatory=True)
 				stock_reco.submit()
 				frappe.db.commit()
 			except OpeningEntryAccountError:
diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py
index 8aa7453..f0a05ed 100644
--- a/erpnext/erpnext_integrations/connectors/shopify_connection.py
+++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py
@@ -149,26 +149,28 @@
 		si.shopify_order_number = shopify_order.get("name")
 		si.set_posting_time = 1
 		si.posting_date = posting_date
+		si.due_date = posting_date
 		si.naming_series = shopify_settings.sales_invoice_series or "SI-Shopify-"
 		si.flags.ignore_mandatory = True
 		set_cost_center(si.items, shopify_settings.cost_center)
 		si.insert(ignore_mandatory=True)
 		si.submit()
-		make_payament_entry_against_sales_invoice(si, shopify_settings)
+		make_payament_entry_against_sales_invoice(si, shopify_settings, posting_date)
 		frappe.db.commit()
 
 def set_cost_center(items, cost_center):
 	for item in items:
 		item.cost_center = cost_center
 
-def make_payament_entry_against_sales_invoice(doc, shopify_settings):
+def make_payament_entry_against_sales_invoice(doc, shopify_settings, posting_date=None):
 	from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
-	payemnt_entry = get_payment_entry(doc.doctype, doc.name, bank_account=shopify_settings.cash_bank_account)
-	payemnt_entry.flags.ignore_mandatory = True
-	payemnt_entry.reference_no = doc.name
-	payemnt_entry.reference_date = nowdate()
-	payemnt_entry.insert(ignore_permissions=True)
-	payemnt_entry.submit()
+	payment_entry = get_payment_entry(doc.doctype, doc.name, bank_account=shopify_settings.cash_bank_account)
+	payment_entry.flags.ignore_mandatory = True
+	payment_entry.reference_no = doc.name
+	payment_entry.posting_date = posting_date or nowdate()
+	payment_entry.reference_date = posting_date or nowdate()
+	payment_entry.insert(ignore_permissions=True)
+	payment_entry.submit()
 
 def create_delivery_note(shopify_order, shopify_settings, so):
 	if not cint(shopify_settings.sync_delivery_note):
@@ -258,6 +260,15 @@
 	"""Shipping lines represents the shipping details,
 		each such shipping detail consists of a list of tax_lines"""
 	for shipping_charge in shipping_lines:
+		if shipping_charge.get("price"):
+			taxes.append({
+				"charge_type": _("Actual"),
+				"account_head": get_tax_account_head(shipping_charge),
+				"description": shipping_charge["title"],
+				"tax_amount": shipping_charge["price"],
+				"cost_center": shopify_settings.cost_center
+			})
+
 		for tax in shipping_charge.get("tax_lines"):
 			taxes.append({
 				"charge_type": _("Actual"),
diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py
index 24fc3d4..f960998 100644
--- a/erpnext/erpnext_integrations/taxjar_integration.py
+++ b/erpnext/erpnext_integrations/taxjar_integration.py
@@ -1,5 +1,7 @@
 import traceback
 
+import taxjar
+
 import frappe
 from erpnext import get_default_company
 from frappe import _
@@ -29,7 +31,6 @@
 
 
 def create_transaction(doc, method):
-	import taxjar
 	"""Create an order transaction in TaxJar"""
 
 	if not TAXJAR_CREATE_TRANSACTIONS:
diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py
index e278fd7..362f6cf 100644
--- a/erpnext/erpnext_integrations/utils.py
+++ b/erpnext/erpnext_integrations/utils.py
@@ -60,4 +60,12 @@
 				"default_account": payment_gateway_account
 			}]
 		})
-		mode_of_payment.insert(ignore_permissions=True)
\ No newline at end of file
+		mode_of_payment.insert(ignore_permissions=True)
+
+def get_tracking_url(carrier, tracking_number):
+	# Return the formatted Tracking URL.
+	tracking_url = ''
+	url_reference = frappe.get_value('Parcel Service', carrier, 'url_reference')
+	if url_reference:
+		tracking_url = frappe.render_template(url_reference, {'tracking_number': tracking_number})
+	return tracking_url
diff --git a/erpnext/healthcare/desk_page/healthcare/healthcare.json b/erpnext/healthcare/desk_page/healthcare/healthcare.json
index 6546b08..af601f3 100644
--- a/erpnext/healthcare/desk_page/healthcare/healthcare.json
+++ b/erpnext/healthcare/desk_page/healthcare/healthcare.json
@@ -32,18 +32,23 @@
   },
   {
    "hidden": 0,
+   "label": "Inpatient",
+   "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Medication Order\",\n\t\t\"label\": \"Inpatient Medication Order\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Medication Entry\",\n\t\t\"label\": \"Inpatient Medication Entry\"\n\t}\n]"
+  },
+  {
+   "hidden": 0,
    "label": "Rehabilitation and Physiotherapy",
    "links": "[\n    {\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Exercise Type\",\n\t\t\"label\": \"Exercise Type\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Type\",\n\t\t\"label\": \"Therapy Type\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Plan\",\n\t\t\"label\": \"Therapy Plan\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Session\",\n\t\t\"label\": \"Therapy Session\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Assessment Template\",\n\t\t\"label\": \"Patient Assessment Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Assessment\",\n\t\t\"label\": \"Patient Assessment\"\n\t}\n]"
   },
   {
    "hidden": 0,
    "label": "Records and History",
-   "links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]"
+   "links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t}\n]"
   },
   {
    "hidden": 0,
    "label": "Reports",
-   "links": "[\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Patient Appointment Analytics\",\n\t\t\"doctype\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Lab Test Report\",\n\t\t\"doctype\": \"Lab Test\",\n\t\t\"label\": \"Lab Test Report\"\n\t}\n]"
+   "links": "[\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Patient Appointment Analytics\",\n\t\t\"doctype\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Lab Test Report\",\n\t\t\"doctype\": \"Lab Test\",\n\t\t\"label\": \"Lab Test Report\"\n\t},\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Inpatient Medication Orders\",\n\t\t\"doctype\": \"Inpatient Medication Order\",\n\t\t\"label\": \"Inpatient Medication Orders\"\n\t}\n]"
   }
  ],
  "category": "Domains",
@@ -64,7 +69,7 @@
  "idx": 0,
  "is_standard": 1,
  "label": "Healthcare",
- "modified": "2020-06-25 23:50:56.951698",
+ "modified": "2020-11-26 22:09:09.164584",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Healthcare",
diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js
index eb7d4bd..1d4411d 100644
--- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js
+++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js
@@ -85,8 +85,7 @@
 								callback: function(r) {
 									if (r.message) {
 										frappe.show_alert({
-											message:  __('Stock Entry {0} created',
-												['<a class="bold" href="#Form/Stock Entry/'+ r.message + '">' + r.message + '</a>']),
+											message: __('Stock Entry {0} created', ['<a class="bold" href="#Form/Stock Entry/'+ r.message + '">' + r.message + '</a>']),
 											indicator: 'green'
 										});
 									}
@@ -105,8 +104,7 @@
 						callback: function(r) {
 							if (!r.exc) {
 								if (r.message == 'insufficient stock') {
-									let msg = __('Stock quantity to start the Procedure is not available in the Warehouse {0}. Do you want to record a Stock Entry?',
-										[frm.doc.warehouse.bold()]);
+									let msg = __('Stock quantity to start the Procedure is not available in the Warehouse {0}. Do you want to record a Stock Entry?', [frm.doc.warehouse.bold()]);
 									frappe.confirm(
 										msg,
 										function() {
diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
index cdf692e..7e7fd82 100644
--- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
+++ b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
@@ -7,6 +7,7 @@
 import unittest
 from frappe.utils import nowdate, add_days
 from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_appointment, create_healthcare_service_items
+from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
 
 test_dependencies = ["Company"]
 
@@ -15,6 +16,7 @@
 		frappe.db.sql("""delete from `tabPatient Appointment`""")
 		frappe.db.sql("""delete from `tabFee Validity`""")
 		frappe.db.sql("""delete from `tabPatient`""")
+		make_pos_profile()
 
 	def test_fee_validity(self):
 		item = create_healthcare_service_items()
diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js
index f523cf2..ca97489 100644
--- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js
+++ b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js
@@ -29,6 +29,29 @@
 				}
 			};
 		});
+
+		if (frm.doc.__islocal || frm.doc.docstatus !== 0 || !frm.doc.update_stock)
+			return;
+
+		frm.add_custom_button(__('Make Stock Entry'), function() {
+			frappe.call({
+				method: 'erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry.make_difference_stock_entry',
+				args: {	docname: frm.doc.name },
+				freeze: true,
+				callback: function(r) {
+					if (r.message) {
+						var doclist = frappe.model.sync(r.message);
+						frappe.set_route('Form', doclist[0].doctype, doclist[0].name);
+					} else {
+						frappe.msgprint({
+							title: __('No Drug Shortage'),
+							message: __('All the drugs are available with sufficient qty to process this Inpatient Medication Entry.'),
+							indicator: 'green'
+						});
+					}
+				}
+			});
+		});
 	},
 
 	patient: function(frm) {
diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py
index 23e7519..70ae713 100644
--- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py
+++ b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py
@@ -142,25 +142,32 @@
 		return orders, order_entry_map
 
 	def check_stock_qty(self):
-		from erpnext.stock.stock_ledger import NegativeStockError
+		drug_shortage = get_drug_shortage_map(self.medication_orders, self.warehouse)
 
-		drug_availability = dict()
-		for d in self.medication_orders:
-			if not drug_availability.get(d.drug_code):
-				drug_availability[d.drug_code] = 0
-			drug_availability[d.drug_code] += flt(d.dosage)
+		if drug_shortage:
+			message = _('Quantity not available for the following items in warehouse {0}. ').format(frappe.bold(self.warehouse))
+			message += _('Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed.')
 
-		for drug, dosage in drug_availability.items():
-			available_qty = get_latest_stock_qty(drug, self.warehouse)
+			formatted_item_rows = ''
 
-			# validate qty
-			if flt(available_qty) < flt(dosage):
-				frappe.throw(_('Quantity not available for {0} in warehouse {1}').format(
-					frappe.bold(drug), frappe.bold(self.warehouse))
-					+ '<br><br>' + _('Available quantity is {0}, you need {1}').format(
-					frappe.bold(available_qty), frappe.bold(dosage))
-					+ '<br><br>' + _('Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed.'),
-					NegativeStockError, title=_('Insufficient Stock'))
+			for drug, shortage_qty in drug_shortage.items():
+				item_link = get_link_to_form('Item', drug)
+				formatted_item_rows += """
+					<td>{0}</td>
+					<td>{1}</td>
+				</tr>""".format(item_link, frappe.bold(shortage_qty))
+
+			message += """
+				<table class='table'>
+					<thead>
+						<th>{0}</th>
+						<th>{1}</th>
+					</thead>
+					{2}
+				</table>
+			""".format(_('Drug Code'), _('Shortage Qty'), formatted_item_rows)
+
+			frappe.throw(message, title=_('Insufficient Stock'), is_minimizable=True, wide=True)
 
 	def make_stock_entry(self):
 		stock_entry = frappe.new_doc('Stock Entry')
@@ -223,7 +230,8 @@
 
 	for doc in data:
 		inpatient_record = doc.inpatient_record
-		doc['service_unit'] = get_current_healthcare_service_unit(inpatient_record)
+		if inpatient_record:
+			doc['service_unit'] = get_current_healthcare_service_unit(inpatient_record)
 
 		if entry.service_unit and doc.service_unit != entry.service_unit:
 			to_remove.append(doc)
@@ -274,4 +282,57 @@
 
 def get_current_healthcare_service_unit(inpatient_record):
 	ip_record = frappe.get_doc('Inpatient Record', inpatient_record)
-	return ip_record.inpatient_occupancies[-1].service_unit
\ No newline at end of file
+	if ip_record.inpatient_occupancies:
+		return ip_record.inpatient_occupancies[-1].service_unit
+	return
+
+
+def get_drug_shortage_map(medication_orders, warehouse):
+	"""
+		Returns a dict like { drug_code: shortage_qty }
+	"""
+	drug_requirement = dict()
+	for d in medication_orders:
+		if not drug_requirement.get(d.drug_code):
+			drug_requirement[d.drug_code] = 0
+		drug_requirement[d.drug_code] += flt(d.dosage)
+
+	drug_shortage = dict()
+	for drug, required_qty in drug_requirement.items():
+		available_qty = get_latest_stock_qty(drug, warehouse)
+		if flt(required_qty) > flt(available_qty):
+			drug_shortage[drug] = flt(flt(required_qty) - flt(available_qty))
+
+	return drug_shortage
+
+
+@frappe.whitelist()
+def make_difference_stock_entry(docname):
+	doc = frappe.get_doc('Inpatient Medication Entry', docname)
+	drug_shortage = get_drug_shortage_map(doc.medication_orders, doc.warehouse)
+
+	if not drug_shortage:
+		return None
+
+	stock_entry = frappe.new_doc('Stock Entry')
+	stock_entry.purpose = 'Material Transfer'
+	stock_entry.set_stock_entry_type()
+	stock_entry.to_warehouse = doc.warehouse
+	stock_entry.company = doc.company
+	cost_center = frappe.get_cached_value('Company',  doc.company,  'cost_center')
+	expense_account = get_account(None, 'expense_account', 'Healthcare Settings', doc.company)
+
+	for drug, shortage_qty in drug_shortage.items():
+		se_child = stock_entry.append('items')
+		se_child.item_code = drug
+		se_child.item_name = frappe.db.get_value('Item', drug, 'stock_uom')
+		se_child.uom = frappe.db.get_value('Item', drug, 'stock_uom')
+		se_child.stock_uom = se_child.uom
+		se_child.qty = flt(shortage_qty)
+		se_child.t_warehouse = doc.warehouse
+		# in stock uom
+		se_child.conversion_factor = 1
+		se_child.cost_center = cost_center
+		se_child.expense_account = expense_account
+
+	return stock_entry
diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py b/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py
index 2f1bb6b..7cb5a48 100644
--- a/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py
+++ b/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py
@@ -9,6 +9,7 @@
 from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import create_patient, create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
 from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
 from erpnext.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import create_ipmo, create_ipme
+from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_drug_shortage_map, make_difference_stock_entry
 from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_account
 
 class TestInpatientMedicationEntry(unittest.TestCase):
@@ -82,6 +83,39 @@
 		self.assertEqual(stock_entry.items[0].patient, self.patient)
 		self.assertEqual(stock_entry.items[0].inpatient_medication_entry_child, ipme.medication_orders[0].name)
 
+	def test_drug_shortage_stock_entry(self):
+		ipmo = create_ipmo(self.patient)
+		ipmo.submit()
+		ipmo.reload()
+
+		date = add_days(getdate(), -1)
+		filters = frappe._dict(
+			from_date=date,
+			to_date=date,
+			from_time='',
+			to_time='',
+			item_code='Dextromethorphan',
+			patient=self.patient
+		)
+
+		# check drug shortage
+		ipme = create_ipme(filters, update_stock=1)
+		ipme.warehouse = 'Finished Goods - _TC'
+		ipme.save()
+		drug_shortage = get_drug_shortage_map(ipme.medication_orders, ipme.warehouse)
+		self.assertEqual(drug_shortage.get('Dextromethorphan'), 3)
+
+		# check material transfer for drug shortage
+		make_stock_entry()
+		stock_entry = make_difference_stock_entry(ipme.name)
+		self.assertEqual(stock_entry.items[0].item_code, 'Dextromethorphan')
+		self.assertEqual(stock_entry.items[0].qty, 3)
+		stock_entry.from_warehouse = 'Stores - _TC'
+		stock_entry.submit()
+
+		ipme.reload()
+		ipme.submit()
+
 	def tearDown(self):
 		# cleanup - Discharge
 		schedule_discharge(frappe.as_json({'patient': self.patient}))
@@ -94,15 +128,12 @@
 		for entry in frappe.get_all('Inpatient Medication Entry'):
 			doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
 			doc.cancel()
-			frappe.db.delete('Stock Entry', {'inpatient_medication_entry': doc.name})
-			doc.delete()
 
 		for entry in frappe.get_all('Inpatient Medication Order'):
 			doc = frappe.get_doc('Inpatient Medication Order', entry.name)
 			doc.cancel()
-			doc.delete()
 
-def make_stock_entry():
+def make_stock_entry(warehouse=None):
 	frappe.db.set_value('Company', '_Test Company', {
 		'stock_adjustment_account': 'Stock Adjustment - _TC',
 		'default_inventory_account': 'Stock In Hand - _TC'
@@ -110,7 +141,7 @@
 	stock_entry = frappe.new_doc('Stock Entry')
 	stock_entry.stock_entry_type = 'Material Receipt'
 	stock_entry.company = '_Test Company'
-	stock_entry.to_warehouse = 'Stores - _TC'
+	stock_entry.to_warehouse = warehouse or 'Stores - _TC'
 	expense_account = get_account(None, 'expense_account', 'Healthcare Settings', '_Test Company')
 	se_child = stock_entry.append('items')
 	se_child.item_code = 'Dextromethorphan'
diff --git a/erpnext/healthcare/doctype/patient/patient_dashboard.py b/erpnext/healthcare/doctype/patient/patient_dashboard.py
index e3def72..39603f7 100644
--- a/erpnext/healthcare/doctype/patient/patient_dashboard.py
+++ b/erpnext/healthcare/doctype/patient/patient_dashboard.py
@@ -18,6 +18,10 @@
 			{
 				'label': _('Billing'),
 				'items': ['Sales Invoice']
+			},
+			{
+				'label': _('Orders'),
+				'items': ['Inpatient Medication Order']
 			}
 		]
 	}
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
index 2d6b645..79e1775 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
@@ -22,6 +22,7 @@
 				filters: {'status': 'Active'}
 			};
 		});
+
 		frm.set_query('practitioner', function() {
 			return {
 				filters: {
@@ -29,6 +30,7 @@
 				}
 			};
 		});
+
 		frm.set_query('service_unit', function(){
 			return {
 				filters: {
@@ -39,6 +41,16 @@
 			};
 		});
 
+		frm.set_query('therapy_plan', function() {
+			return {
+				filters: {
+					'patient': frm.doc.patient
+				}
+			};
+		});
+
+		frm.trigger('set_therapy_type_filter');
+
 		if (frm.is_new()) {
 			frm.page.set_primary_action(__('Check Availability'), function() {
 				if (!frm.doc.patient) {
@@ -136,6 +148,24 @@
 		}
 	},
 
+	therapy_plan: function(frm) {
+		frm.trigger('set_therapy_type_filter');
+	},
+
+	set_therapy_type_filter: function(frm) {
+		if (frm.doc.therapy_plan) {
+			frm.call('get_therapy_types').then(r => {
+				frm.set_query('therapy_type', function() {
+					return {
+						filters: {
+							'name': ['in', r.message]
+						}
+					};
+				});
+			});
+		}
+	},
+
 	therapy_type: function(frm) {
 		if (frm.doc.therapy_type) {
 			frappe.db.get_value('Therapy Type', frm.doc.therapy_type, 'default_duration', (r) => {
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
index ac35acc..35600e4 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
@@ -23,9 +23,9 @@
   "procedure_template",
   "get_procedure_from_encounter",
   "procedure_prescription",
+  "therapy_plan",
   "therapy_type",
   "get_prescribed_therapies",
-  "therapy_plan",
   "practitioner",
   "practitioner_name",
   "department",
@@ -284,7 +284,7 @@
    "report_hide": 1
   },
   {
-   "depends_on": "eval:doc.patient;",
+   "depends_on": "eval:doc.patient && doc.therapy_plan;",
    "fieldname": "therapy_type",
    "fieldtype": "Link",
    "label": "Therapy",
@@ -292,17 +292,16 @@
    "set_only_once": 1
   },
   {
-   "depends_on": "eval:doc.patient && doc.__islocal;",
+   "depends_on": "eval:doc.patient && doc.therapy_plan && doc.__islocal;",
    "fieldname": "get_prescribed_therapies",
    "fieldtype": "Button",
    "label": "Get Prescribed Therapies"
   },
   {
-   "depends_on": "eval: doc.patient && doc.therapy_type",
+   "depends_on": "eval: doc.patient;",
    "fieldname": "therapy_plan",
    "fieldtype": "Link",
    "label": "Therapy Plan",
-   "mandatory_depends_on": "eval: doc.patient && doc.therapy_type",
    "options": "Therapy Plan"
   },
   {
@@ -348,7 +347,7 @@
   }
  ],
  "links": [],
- "modified": "2020-05-21 03:04:21.400893",
+ "modified": "2020-12-16 13:16:58.578503",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Patient Appointment",
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index e685b20..dc820cb 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -91,6 +91,17 @@
 		if fee_validity:
 			frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till))
 
+	def get_therapy_types(self):
+		if not self.therapy_plan:
+			return
+
+		therapy_types = []
+		doc = frappe.get_doc('Therapy Plan', self.therapy_plan)
+		for entry in doc.therapy_plan_details:
+			therapy_types.append(entry.therapy_type)
+
+		return therapy_types
+
 
 @frappe.whitelist()
 def check_payment_fields_reqd(patient):
@@ -145,7 +156,7 @@
 		sales_invoice.flags.ignore_mandatory = True
 		sales_invoice.save(ignore_permissions=True)
 		sales_invoice.submit()
-		frappe.msgprint(_('Sales Invoice {0} created'.format(sales_invoice.name)), alert=True)
+		frappe.msgprint(_('Sales Invoice {0} created').format(sales_invoice.name), alert=True)
 		frappe.db.set_value('Patient Appointment', appointment_doc.name, 'invoiced', 1)
 		frappe.db.set_value('Patient Appointment', appointment_doc.name, 'ref_sales_invoice', sales_invoice.name)
 
diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
index eeed157..3df7ba1 100644
--- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
@@ -7,12 +7,14 @@
 from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter
 from frappe.utils import nowdate, add_days
 from frappe.utils.make_random import get_random
+from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
 
 class TestPatientAppointment(unittest.TestCase):
 	def setUp(self):
 		frappe.db.sql("""delete from `tabPatient Appointment`""")
 		frappe.db.sql("""delete from `tabFee Validity`""")
 		frappe.db.sql("""delete from `tabPatient Encounter`""")
+		make_pos_profile()
 
 	def test_status(self):
 		patient, medical_department, practitioner = create_healthcare_docs()
diff --git a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
index aa85a23..419d956 100644
--- a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
+++ b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
@@ -6,11 +6,13 @@
 import frappe
 from frappe.utils import nowdate
 from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_encounter, create_healthcare_docs, create_appointment
+from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
 
 class TestPatientMedicalRecord(unittest.TestCase):
 	def setUp(self):
 		frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
 		frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
+		make_pos_profile()
 
 	def test_medical_record(self):
 		patient, medical_department, practitioner = create_healthcare_docs()
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/healthcare/report/inpatient_medication_orders/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/healthcare/report/inpatient_medication_orders/__init__.py
diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js
new file mode 100644
index 0000000..a10f837
--- /dev/null
+++ b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js
@@ -0,0 +1,57 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Inpatient Medication Orders"] = {
+	"filters": [
+		{
+			fieldname: "company",
+			label: __("Company"),
+			fieldtype: "Link",
+			options: "Company",
+			default: frappe.defaults.get_user_default("Company"),
+			reqd: 1
+		},
+		{
+			fieldname: "from_date",
+			label: __("From Date"),
+			fieldtype: "Date",
+			default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+			reqd: 1
+		},
+		{
+			fieldname: "to_date",
+			label: __("To Date"),
+			fieldtype: "Date",
+			default: frappe.datetime.now_date(),
+			reqd: 1
+		},
+		{
+			fieldname: "patient",
+			label: __("Patient"),
+			fieldtype: "Link",
+			options: "Patient"
+		},
+		{
+			fieldname: "service_unit",
+			label: __("Healthcare Service Unit"),
+			fieldtype: "Link",
+			options: "Healthcare Service Unit",
+			get_query: () => {
+				var company = frappe.query_report.get_filter_value('company');
+				return {
+					filters: {
+						'company': company,
+						'is_group': 0
+					}
+				}
+			}
+		},
+		{
+			fieldname: "show_completed_orders",
+			label: __("Show Completed Orders"),
+			fieldtype: "Check",
+			default: 1
+		}
+	]
+};
diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json
new file mode 100644
index 0000000..9217fa1
--- /dev/null
+++ b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json
@@ -0,0 +1,36 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2020-11-23 17:25:58.802949",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "json": "{}",
+ "modified": "2020-11-23 19:40:20.227591",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Inpatient Medication Orders",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Inpatient Medication Order",
+ "report_name": "Inpatient Medication Orders",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "System Manager"
+  },
+  {
+   "role": "Healthcare Administrator"
+  },
+  {
+   "role": "Nursing User"
+  },
+  {
+   "role": "Physician"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py
new file mode 100644
index 0000000..b907730
--- /dev/null
+++ b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py
@@ -0,0 +1,198 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit
+
+def execute(filters=None):
+	columns = get_columns()
+	data = get_data(filters)
+	chart = get_chart_data(data)
+
+	return columns, data, None, chart
+
+def get_columns():
+	return [
+		{
+			"fieldname": "patient",
+			"fieldtype": "Link",
+			"label": "Patient",
+			"options": "Patient",
+			"width": 200
+		},
+		{
+			"fieldname": "healthcare_service_unit",
+			"fieldtype": "Link",
+			"label": "Healthcare Service Unit",
+			"options": "Healthcare Service Unit",
+			"width": 150
+		},
+		{
+			"fieldname": "drug",
+			"fieldtype": "Link",
+			"label": "Drug Code",
+			"options": "Item",
+			"width": 150
+		},
+		{
+			"fieldname": "drug_name",
+			"fieldtype": "Data",
+			"label": "Drug Name",
+			"width": 150
+		},
+		{
+			"fieldname": "dosage",
+			"fieldtype": "Link",
+			"label": "Dosage",
+			"options": "Prescription Dosage",
+			"width": 80
+		},
+		{
+			"fieldname": "dosage_form",
+			"fieldtype": "Link",
+			"label": "Dosage Form",
+			"options": "Dosage Form",
+			"width": 100
+		},
+		{
+			"fieldname": "date",
+			"fieldtype": "Date",
+			"label": "Date",
+			"width": 100
+		},
+		{
+			"fieldname": "time",
+			"fieldtype": "Time",
+			"label": "Time",
+			"width": 100
+		},
+		{
+			"fieldname": "is_completed",
+			"fieldtype": "Check",
+			"label": "Is Order Completed",
+			"width": 100
+		},
+		{
+			"fieldname": "healthcare_practitioner",
+			"fieldtype": "Link",
+			"label": "Healthcare Practitioner",
+			"options": "Healthcare Practitioner",
+			"width": 200
+		},
+		{
+			"fieldname": "inpatient_medication_entry",
+			"fieldtype": "Link",
+			"label": "Inpatient Medication Entry",
+			"options": "Inpatient Medication Entry",
+			"width": 200
+		},
+		{
+			"fieldname": "inpatient_record",
+			"fieldtype": "Link",
+			"label": "Inpatient Record",
+			"options": "Inpatient Record",
+			"width": 200
+		}
+	]
+
+def get_data(filters):
+	conditions, values = get_conditions(filters)
+
+	data = frappe.db.sql("""
+		SELECT
+			parent.patient, parent.inpatient_record, parent.practitioner,
+			child.drug, child.drug_name, child.dosage, child.dosage_form,
+			child.date, child.time, child.is_completed, child.name
+		FROM `tabInpatient Medication Order` parent
+		INNER JOIN `tabInpatient Medication Order Entry` child
+		ON child.parent = parent.name
+		WHERE
+			parent.docstatus = 1
+			{conditions}
+		ORDER BY date, time
+	""".format(conditions=conditions), values, as_dict=1)
+
+	data = get_inpatient_details(data, filters.get("service_unit"))
+
+	return data
+
+def get_conditions(filters):
+	conditions = ""
+	values = dict()
+
+	if filters.get("company"):
+		conditions += " AND parent.company = %(company)s"
+		values["company"] = filters.get("company")
+
+	if filters.get("from_date") and filters.get("to_date"):
+		conditions += " AND child.date BETWEEN %(from_date)s and %(to_date)s"
+		values["from_date"] = filters.get("from_date")
+		values["to_date"] = filters.get("to_date")
+
+	if filters.get("patient"):
+		conditions += " AND parent.patient = %(patient)s"
+		values["patient"] = filters.get("patient")
+
+	if not filters.get("show_completed_orders"):
+		conditions += " AND child.is_completed = 0"
+
+	return conditions, values
+
+
+def get_inpatient_details(data, service_unit):
+	service_unit_filtered_data = []
+
+	for entry in data:
+		entry["healthcare_service_unit"] = get_current_healthcare_service_unit(entry.inpatient_record)
+		if entry.is_completed:
+			entry["inpatient_medication_entry"] = get_inpatient_medication_entry(entry.name)
+
+		if service_unit and entry.healthcare_service_unit and service_unit != entry.healthcare_service_unit:
+			service_unit_filtered_data.append(entry)
+
+		entry.pop("name", None)
+
+	for entry in service_unit_filtered_data:
+		data.remove(entry)
+
+	return data
+
+def get_inpatient_medication_entry(order_entry):
+	return frappe.db.get_value("Inpatient Medication Entry Detail", {"against_imoe": order_entry}, "parent")
+
+def get_chart_data(data):
+	if not data:
+		return None
+
+	labels = ["Pending", "Completed"]
+	datasets = []
+
+	status_wise_data = {
+		"Pending": 0,
+		"Completed": 0
+	}
+
+	for d in data:
+		if d.is_completed:
+			status_wise_data["Completed"] += 1
+		else:
+			status_wise_data["Pending"] += 1
+
+	datasets.append({
+		"name": "Inpatient Medication Order Status",
+		"values": [status_wise_data.get("Pending"), status_wise_data.get("Completed")]
+	})
+
+	chart = {
+		"data": {
+			"labels": labels,
+			"datasets": datasets
+		},
+		"type": "donut",
+		"height": 300
+	}
+
+	chart["fieldtype"] = "Data"
+
+	return chart
\ No newline at end of file
diff --git a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py
new file mode 100644
index 0000000..0d3f45f
--- /dev/null
+++ b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py
@@ -0,0 +1,128 @@
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import unittest
+import frappe
+import datetime
+from frappe.utils import getdate, now_datetime
+from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import create_patient, create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
+from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
+from erpnext.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import create_ipmo, create_ipme
+from erpnext.healthcare.report.inpatient_medication_orders.inpatient_medication_orders import execute
+
+class TestInpatientMedicationOrders(unittest.TestCase):
+	@classmethod
+	def setUpClass(self):
+		frappe.db.sql("delete from `tabInpatient Medication Order` where company='_Test Company'")
+		frappe.db.sql("delete from `tabInpatient Medication Entry` where company='_Test Company'")
+		self.patient = create_patient()
+		self.ip_record = create_records(self.patient)
+
+	def test_inpatient_medication_orders_report(self):
+		filters = {
+			'company': '_Test Company',
+			'from_date': getdate(),
+			'to_date': getdate(),
+			'patient': '_Test IPD Patient',
+			'service_unit': 'Test Service Unit Ip Occupancy - _TC'
+		}
+
+		report = execute(filters)
+
+		expected_data = [
+			{
+				'patient': '_Test IPD Patient',
+				'inpatient_record': self.ip_record.name,
+				'practitioner': None,
+				'drug': 'Dextromethorphan',
+				'drug_name': 'Dextromethorphan',
+				'dosage': 1.0,
+				'dosage_form': 'Tablet',
+				'date': getdate(),
+				'time': datetime.timedelta(seconds=32400),
+				'is_completed': 0,
+				'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC'
+			},
+			{
+				'patient': '_Test IPD Patient',
+				'inpatient_record': self.ip_record.name,
+				'practitioner': None,
+				'drug': 'Dextromethorphan',
+				'drug_name': 'Dextromethorphan',
+				'dosage': 1.0,
+				'dosage_form': 'Tablet',
+				'date': getdate(),
+				'time': datetime.timedelta(seconds=50400),
+				'is_completed': 0,
+				'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC'
+			},
+			{
+				'patient': '_Test IPD Patient',
+				'inpatient_record': self.ip_record.name,
+				'practitioner': None,
+				'drug': 'Dextromethorphan',
+				'drug_name': 'Dextromethorphan',
+				'dosage': 1.0,
+				'dosage_form': 'Tablet',
+				'date': getdate(),
+				'time': datetime.timedelta(seconds=75600),
+				'is_completed': 0,
+				'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC'
+			}
+		]
+
+		self.assertEqual(expected_data, report[1])
+
+		filters = frappe._dict(from_date=getdate(), to_date=getdate(), from_time='', to_time='')
+		ipme = create_ipme(filters)
+		ipme.submit()
+
+		filters = {
+			'company': '_Test Company',
+			'from_date': getdate(),
+			'to_date': getdate(),
+			'patient': '_Test IPD Patient',
+			'service_unit': 'Test Service Unit Ip Occupancy - _TC',
+			'show_completed_orders': 0
+		}
+
+		report = execute(filters)
+		self.assertEqual(len(report[1]), 0)
+
+	def tearDown(self):
+		if frappe.db.get_value('Patient', self.patient, 'inpatient_record'):
+			# cleanup - Discharge
+			schedule_discharge(frappe.as_json({'patient': self.patient}))
+			self.ip_record.reload()
+			mark_invoiced_inpatient_occupancy(self.ip_record)
+
+			self.ip_record.reload()
+			discharge_patient(self.ip_record)
+
+		for entry in frappe.get_all('Inpatient Medication Entry'):
+			doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
+			doc.cancel()
+			doc.delete()
+
+		for entry in frappe.get_all('Inpatient Medication Order'):
+			doc = frappe.get_doc('Inpatient Medication Order', entry.name)
+			doc.cancel()
+			doc.delete()
+
+
+def create_records(patient):
+	frappe.db.sql("""delete from `tabInpatient Record`""")
+
+	# Admit
+	ip_record = create_inpatient(patient)
+	ip_record.expected_length_of_stay = 0
+	ip_record.save()
+	ip_record.reload()
+	service_unit = get_healthcare_service_unit()
+	admit_patient(ip_record, service_unit, now_datetime())
+
+	ipmo = create_ipmo(patient)
+	ipmo.submit()
+
+	return ip_record
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index b4c57d7..a2d9d86 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -237,6 +237,9 @@
 	"Website Settings": {
 		"validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products"
 	},
+	"Tax Category": {
+		"validate": "erpnext.regional.india.utils.validate_tax_category"
+	},
 	"Sales Invoice": {
 		"on_submit": [
 			"erpnext.regional.create_transaction_log",
@@ -268,11 +271,11 @@
 	},
 	"Contact": {
 		"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
-		"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information",
+		"after_insert": "erpnext.telephony.doctype.call_log.call_log.set_caller_information",
 		"validate": "erpnext.crm.utils.update_lead_phone_numbers"
 	},
 	"Lead": {
-		"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information"
+		"after_insert": "erpnext.telephony.doctype.call_log.call_log.set_caller_information"
 	},
 	"Email Unsubscribe": {
 		"after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient"
@@ -344,14 +347,16 @@
 		"erpnext.setup.doctype.email_digest.email_digest.send",
 		"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms",
 		"erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation",
+		"erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.automatically_allocate_leaves_based_on_leave_policy",
 		"erpnext.hr.utils.generate_leave_encashment",
+		"erpnext.hr.utils.allocate_earned_leaves",
+		"erpnext.hr.utils.grant_leaves_automatically",
 		"erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall.create_process_loan_security_shortfall",
 		"erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
 		"erpnext.crm.doctype.lead.lead.daily_open_lead"
 	],
 	"monthly_long": [
 		"erpnext.accounts.deferred_revenue.process_deferred_accounting",
-		"erpnext.hr.utils.allocate_earned_leaves",
 		"erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_demand_loans"
 	]
 }
@@ -392,7 +397,8 @@
 		'erpnext.accounts.party.get_regional_address_details': 'erpnext.regional.india.utils.get_regional_address_details',
 		'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
 		'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
-		'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries'
+		'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries',
+		'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields'
 	},
 	'United Arab Emirates': {
 		'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data',
@@ -436,42 +442,43 @@
 		{"doctype": "Sales Order", "index": 8},
 		{"doctype": "Quotation", "index": 9},
 		{"doctype": "Work Order", "index": 10},
-		{"doctype": "Purchase Receipt", "index": 11},
-		{"doctype": "Purchase Invoice", "index": 12},
-		{"doctype": "Delivery Note", "index": 13},
-		{"doctype": "Stock Entry", "index": 14},
-		{"doctype": "Material Request", "index": 15},
-		{"doctype": "Delivery Trip", "index": 16},
-		{"doctype": "Pick List", "index": 17},
-		{"doctype": "Salary Slip", "index": 18},
-		{"doctype": "Leave Application", "index": 19},
-		{"doctype": "Expense Claim", "index": 20},
-		{"doctype": "Payment Entry", "index": 21},
-		{"doctype": "Lead", "index": 22},
-		{"doctype": "Opportunity", "index": 23},
-		{"doctype": "Item Price", "index": 24},
-		{"doctype": "Purchase Taxes and Charges Template", "index": 25},
-		{"doctype": "Sales Taxes and Charges", "index": 26},
-		{"doctype": "Asset", "index": 27},
-		{"doctype": "Project", "index": 28},
-		{"doctype": "Task", "index": 29},
-		{"doctype": "Timesheet", "index": 30},
-		{"doctype": "Issue", "index": 31},
-		{"doctype": "Serial No", "index": 32},
-		{"doctype": "Batch", "index": 33},
-		{"doctype": "Branch", "index": 34},
-		{"doctype": "Department", "index": 35},
-		{"doctype": "Employee Grade", "index": 36},
-		{"doctype": "Designation", "index": 37},
-		{"doctype": "Job Opening", "index": 38},
-		{"doctype": "Job Applicant", "index": 39},
-		{"doctype": "Job Offer", "index": 40},
-		{"doctype": "Salary Structure Assignment", "index": 41},
-		{"doctype": "Appraisal", "index": 42},
-		{"doctype": "Loan", "index": 43},
-		{"doctype": "Maintenance Schedule", "index": 44},
-		{"doctype": "Maintenance Visit", "index": 45},
-		{"doctype": "Warranty Claim", "index": 46},
+		{"doctype": "Purchase Order", "index": 11},
+		{"doctype": "Purchase Receipt", "index": 12},
+		{"doctype": "Purchase Invoice", "index": 13},
+		{"doctype": "Delivery Note", "index": 14},
+		{"doctype": "Stock Entry", "index": 15},
+		{"doctype": "Material Request", "index": 16},
+		{"doctype": "Delivery Trip", "index": 17},
+		{"doctype": "Pick List", "index": 18},
+		{"doctype": "Salary Slip", "index": 19},
+		{"doctype": "Leave Application", "index": 20},
+		{"doctype": "Expense Claim", "index": 21},
+		{"doctype": "Payment Entry", "index": 22},
+		{"doctype": "Lead", "index": 23},
+		{"doctype": "Opportunity", "index": 24},
+		{"doctype": "Item Price", "index": 25},
+		{"doctype": "Purchase Taxes and Charges Template", "index": 26},
+		{"doctype": "Sales Taxes and Charges", "index": 27},
+		{"doctype": "Asset", "index": 28},
+		{"doctype": "Project", "index": 29},
+		{"doctype": "Task", "index": 30},
+		{"doctype": "Timesheet", "index": 31},
+		{"doctype": "Issue", "index": 32},
+		{"doctype": "Serial No", "index": 33},
+		{"doctype": "Batch", "index": 34},
+		{"doctype": "Branch", "index": 35},
+		{"doctype": "Department", "index": 36},
+		{"doctype": "Employee Grade", "index": 37},
+		{"doctype": "Designation", "index": 38},
+		{"doctype": "Job Opening", "index": 39},
+		{"doctype": "Job Applicant", "index": 40},
+		{"doctype": "Job Offer", "index": 41},
+		{"doctype": "Salary Structure Assignment", "index": 42},
+		{"doctype": "Appraisal", "index": 43},
+		{"doctype": "Loan", "index": 44},
+		{"doctype": "Maintenance Schedule", "index": 45},
+		{"doctype": "Maintenance Visit", "index": 46},
+		{"doctype": "Warranty Claim", "index": 47},
 	],
 	"Healthcare": [
 		{'doctype': 'Patient', 'index': 1},
diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py
index 9b2de0e..d337959 100644
--- a/erpnext/hr/doctype/department_approver/department_approver.py
+++ b/erpnext/hr/doctype/department_approver/department_approver.py
@@ -20,7 +20,7 @@
 	approvers = []
 	department_details = {}
 	department_list = []
-	employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver", "expense_approver", "shift_request_approver"], as_dict=True)
+	employee = frappe.get_value("Employee", filters.get("employee"), ["employee_name","department", "leave_approver", "expense_approver", "shift_request_approver"], as_dict=True)
 
 	employee_department = filters.get("department") or employee.department
 	if employee_department:
@@ -59,11 +59,9 @@
 				and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True)
 
 	if len(approvers) == 0:
-		frappe.throw(_("Please set {0} for the Employee or for Department: {1}").
-			format(
-				field_name, frappe.bold(employee_department),
-				frappe.bold(employee.name)
-			),
-			title=_(field_name + " Missing"))
+		error_msg = _("Please set {0} for the Employee: {1}").format(field_name, frappe.bold(employee.employee_name))
+		if department_list:
+			error_msg += _(" or for Department: {0}").format(frappe.bold(employee_department))
+		frappe.throw(error_msg, title=_(field_name + " Missing"))
 
 	return set(tuple(approver) for approver in approvers)
diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json
index da78919..4f1c04f 100644
--- a/erpnext/hr/doctype/employee/employee.json
+++ b/erpnext/hr/doctype/employee/employee.json
@@ -57,7 +57,6 @@
   "column_break_45",
   "shift_request_approver",
   "attendance_and_leave_details",
-  "leave_policy",
   "attendance_device_id",
   "column_break_44",
   "holiday_list",
@@ -412,14 +411,6 @@
    "options": "Branch"
   },
   {
-   "fetch_from": "grade.default_leave_policy",
-   "fetch_if_empty": 1,
-   "fieldname": "leave_policy",
-   "fieldtype": "Link",
-   "label": "Leave Policy",
-   "options": "Leave Policy"
-  },
-  {
    "description": "Applicable Holiday List",
    "fieldname": "holiday_list",
    "fieldtype": "Link",
@@ -672,10 +663,10 @@
    "oldfieldtype": "Date"
   },
   {
-   "depends_on": "eval:doc.status == \"Left\"",
    "fieldname": "relieving_date",
    "fieldtype": "Date",
    "label": "Relieving Date",
+   "mandatory_depends_on": "eval:doc.status == \"Left\"",
    "oldfieldname": "relieving_date",
    "oldfieldtype": "Date"
   },
@@ -822,7 +813,7 @@
  "idx": 24,
  "image_field": "image",
  "links": [],
- "modified": "2020-10-06 15:58:23.805489",
+ "modified": "2020-10-16 15:02:04.283657",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee",
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index dfc600c..0fde3a1 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -135,7 +135,7 @@
 				try:
 					frappe.get_doc({
 						"doctype": "File",
-						"file_name": self.image,
+						"file_url": self.image,
 						"attached_to_doctype": "User",
 						"attached_to_name": self.user_id
 					}).insert()
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js
index cba8ee9..5037ceb 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.js
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.js
@@ -15,11 +15,21 @@
 		});
 
 		frm.set_query("advance_account", function() {
+			if (!frm.doc.employee) {
+				frappe.msgprint(__("Please select employee first"));
+			}
+			let company_currency = erpnext.get_currency(frm.doc.company);
+			let currencies = [company_currency];
+			if (frm.doc.currency && (frm.doc.currency != company_currency)) {
+				currencies.push(frm.doc.currency);
+			}
+
 			return {
 				filters: {
 					"root_type": "Asset",
 					"is_group": 0,
-					"company": frm.doc.company
+					"company": frm.doc.company,
+					"account_currency": ["in", currencies],
 				}
 			};
 		});
@@ -63,7 +73,7 @@
 				}, __('Create'));
 			}else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")){
 				frm.add_custom_button(__("Deduction from salary"),  function() {
-					frm.events.make_deduction_via_additional_salary(frm)
+					frm.events.make_deduction_via_additional_salary(frm);
 				}, __('Create'));
 			}
 		}
@@ -127,7 +137,9 @@
 				'employee_advance_name': frm.doc.name,
 				'return_amount': flt(frm.doc.paid_amount - frm.doc.claimed_amount),
 				'advance_account': frm.doc.advance_account,
-				'mode_of_payment': frm.doc.mode_of_payment
+				'mode_of_payment': frm.doc.mode_of_payment,
+				'currency': frm.doc.currency,
+				'exchange_rate': frm.doc.exchange_rate
 			},
 			callback: function(r) {
 				const doclist = frappe.model.sync(r.message);
@@ -138,16 +150,74 @@
 
 	employee: function (frm) {
 		if (frm.doc.employee) {
-			return frappe.call({
-				method: "erpnext.hr.doctype.employee_advance.employee_advance.get_pending_amount",
-				args: {
-					"employee": frm.doc.employee,
-					"posting_date": frm.doc.posting_date
-				},
-				callback: function(r) {
-					frm.set_value("pending_amount",r.message);
-				}
-			});
+			frappe.run_serially([
+				() => 	frm.trigger('get_employee_currency'),
+				() => 	frm.trigger('get_pending_amount')
+			]);
 		}
+	},
+
+	get_pending_amount: function(frm) {
+		frappe.call({
+			method: "erpnext.hr.doctype.employee_advance.employee_advance.get_pending_amount",
+			args: {
+				"employee": frm.doc.employee,
+				"posting_date": frm.doc.posting_date
+			},
+			callback: function(r) {
+				frm.set_value("pending_amount", r.message);
+			}
+		});
+	},
+
+	get_employee_currency: function(frm) {
+		frappe.call({
+			method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+			args: {
+				employee: frm.doc.employee,
+			},
+			callback: function(r) {
+				if (r.message) {
+					frm.set_value('currency', r.message);
+					frm.refresh_fields();
+				}
+			}
+		});
+	},
+
+	currency: function(frm) {
+		if (frm.doc.currency) {
+			var from_currency = frm.doc.currency;
+			var company_currency;
+			if (!frm.doc.company) {
+				company_currency = erpnext.get_currency(frappe.defaults.get_default("Company"));
+			} else {
+				company_currency = erpnext.get_currency(frm.doc.company);
+			}
+			if (from_currency != company_currency) {
+				frm.events.set_exchange_rate(frm, from_currency, company_currency);
+			} else {
+				frm.set_value("exchange_rate", 1.0);
+				frm.set_df_property('exchange_rate', 'hidden', 1);
+				frm.set_df_property("exchange_rate", "description", "" );
+			}
+			frm.refresh_fields();
+		}
+	},
+
+	set_exchange_rate: function(frm, from_currency, company_currency) {
+		frappe.call({
+			method: "erpnext.setup.utils.get_exchange_rate",
+			args: {
+				from_currency: from_currency,
+				to_currency: company_currency,
+			},
+			callback: function(r) {
+				frm.set_value("exchange_rate", flt(r.message));
+				frm.set_df_property('exchange_rate', 'hidden', 0);
+				frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
+					+ " = [?] " + company_currency);
+			}
+		});
 	}
 });
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json
index 0d90913..cf6b540 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.json
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.json
@@ -13,6 +13,8 @@
   "department",
   "column_break_4",
   "posting_date",
+  "currency",
+  "exchange_rate",
   "repay_unclaimed_amount_from_salary",
   "section_break_8",
   "purpose",
@@ -91,7 +93,7 @@
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Advance Amount",
-   "options": "Company:company:default_currency",
+   "options": "currency",
    "reqd": 1
   },
   {
@@ -99,7 +101,7 @@
    "fieldtype": "Currency",
    "label": "Paid Amount",
    "no_copy": 1,
-   "options": "Company:company:default_currency",
+   "options": "currency",
    "read_only": 1
   },
   {
@@ -107,7 +109,7 @@
    "fieldtype": "Currency",
    "label": "Claimed Amount",
    "no_copy": 1,
-   "options": "Company:company:default_currency",
+   "options": "currency",
    "read_only": 1
   },
   {
@@ -161,7 +163,7 @@
    "fieldname": "return_amount",
    "fieldtype": "Currency",
    "label": "Returned Amount",
-   "options": "Company:company:default_currency",
+   "options": "currency",
    "read_only": 1
   },
   {
@@ -175,13 +177,31 @@
    "fieldname": "pending_amount",
    "fieldtype": "Currency",
    "label": "Pending Amount",
-   "options": "Company:company:default_currency",
+   "options": "currency",
    "read_only": 1
+  },
+  {
+   "default": "Company:company:default_currency",
+   "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "label": "Currency",
+   "options": "Currency",
+   "reqd": 1
+  },
+  {
+   "depends_on": "currency",
+   "fieldname": "exchange_rate",
+   "fieldtype": "Float",
+   "label": "Exchange Rate",
+   "precision": "9",
+   "print_hide": 1,
+   "reqd": 1
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-12 12:42:39.833818",
+ "modified": "2020-11-25 12:01:55.980721",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee Advance",
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py
index 3c435b8..cb72f6b 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.py
@@ -19,7 +19,6 @@
 
 	def validate(self):
 		self.set_status()
-		self.validate_employee_advance_account()
 
 	def on_cancel(self):
 		self.ignore_linked_doctypes = ('GL Entry')
@@ -38,16 +37,9 @@
 		elif self.docstatus == 2:
 			self.status = "Cancelled"
 
-	def validate_employee_advance_account(self):
-		company_currency = erpnext.get_company_currency(self.company)
-		if (self.advance_account and
-			company_currency != frappe.db.get_value('Account', self.advance_account, 'account_currency')):
-			frappe.throw(_("Advance account currency should be same as company currency {0}")
-				.format(company_currency))
-
 	def set_total_advance_paid(self):
 		paid_amount = frappe.db.sql("""
-			select ifnull(sum(debit_in_account_currency), 0) as paid_amount
+			select ifnull(sum(debit), 0) as paid_amount
 			from `tabGL Entry`
 			where against_voucher_type = 'Employee Advance'
 				and against_voucher = %s
@@ -56,7 +48,7 @@
 		""", (self.name, self.employee), as_dict=1)[0].paid_amount
 
 		return_amount = frappe.db.sql("""
-			select name, ifnull(sum(credit_in_account_currency), 0) as return_amount
+			select ifnull(sum(credit), 0) as return_amount
 			from `tabGL Entry`
 			where against_voucher_type = 'Employee Advance'
 				and voucher_type != 'Expense Claim'
@@ -65,6 +57,11 @@
 				and party = %s
 		""", (self.name, self.employee), as_dict=1)[0].return_amount
 
+		if paid_amount != 0:
+			paid_amount = flt(paid_amount) / flt(self.exchange_rate)
+		if return_amount != 0:
+			return_amount = flt(return_amount) / flt(self.exchange_rate)
+
 		if flt(paid_amount) > self.advance_amount:
 			frappe.throw(_("Row {0}# Paid Amount cannot be greater than requested advance amount"),
 				EmployeeAdvanceOverPayment)
@@ -107,16 +104,27 @@
 	doc = frappe.get_doc(dt, dn)
 	payment_account = get_default_bank_cash_account(doc.company, account_type="Cash",
 		mode_of_payment=doc.mode_of_payment)
+	if not payment_account:
+		frappe.throw(_("Please set a Default Cash Account in Company defaults"))
+
+	advance_account_currency = frappe.db.get_value('Account', doc.advance_account, 'account_currency')
+
+	advance_amount, advance_exchange_rate = get_advance_amount_advance_exchange_rate(advance_account_currency,doc )
+
+	paying_amount, paying_exchange_rate = get_paying_amount_paying_exchange_rate(payment_account, doc)
 
 	je = frappe.new_doc("Journal Entry")
 	je.posting_date = nowdate()
 	je.voucher_type = 'Bank Entry'
 	je.company = doc.company
 	je.remark = 'Payment against Employee Advance: ' + dn + '\n' + doc.purpose
+	je.multi_currency = 1 if advance_account_currency != payment_account.account_currency else 0
 
 	je.append("accounts", {
 		"account": doc.advance_account,
-		"debit_in_account_currency": flt(doc.advance_amount),
+		"account_currency": advance_account_currency,
+		"exchange_rate": flt(advance_exchange_rate),
+		"debit_in_account_currency": flt(advance_amount),
 		"reference_type": "Employee Advance",
 		"reference_name": doc.name,
 		"party_type": "Employee",
@@ -128,19 +136,41 @@
 	je.append("accounts", {
 		"account": payment_account.account,
 		"cost_center": erpnext.get_default_cost_center(doc.company),
-		"credit_in_account_currency": flt(doc.advance_amount),
+		"credit_in_account_currency": flt(paying_amount),
 		"account_currency": payment_account.account_currency,
-		"account_type": payment_account.account_type
+		"account_type": payment_account.account_type,
+		"exchange_rate": flt(paying_exchange_rate)
 	})
 
 	return je.as_dict()
 
+def get_advance_amount_advance_exchange_rate(advance_account_currency, doc):
+	if advance_account_currency != doc.currency:
+		advance_amount = flt(doc.advance_amount) * flt(doc.exchange_rate)
+		advance_exchange_rate = 1
+	else:
+		advance_amount = doc.advance_amount
+		advance_exchange_rate = doc.exchange_rate
+
+	return advance_amount, advance_exchange_rate
+
+def get_paying_amount_paying_exchange_rate(payment_account, doc):
+	if payment_account.account_currency != doc.currency:
+		paying_amount = flt(doc.advance_amount) * flt(doc.exchange_rate)
+		paying_exchange_rate = 1
+	else:
+		paying_amount = doc.advance_amount
+		paying_exchange_rate = doc.exchange_rate
+
+	return paying_amount, paying_exchange_rate
+
 @frappe.whitelist()
 def create_return_through_additional_salary(doc):
 	import json
 	doc = frappe._dict(json.loads(doc))
 	additional_salary = frappe.new_doc('Additional Salary')
 	additional_salary.employee = doc.employee
+	additional_salary.currency = doc.currency
 	additional_salary.amount = doc.paid_amount - doc.claimed_amount
 	additional_salary.company = doc.company
 	additional_salary.ref_doctype = doc.doctype
@@ -149,26 +179,28 @@
 	return additional_salary
 
 @frappe.whitelist()
-def make_return_entry(employee, company, employee_advance_name, return_amount,  advance_account, mode_of_payment=None):
-	return_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
-
-	mode_of_payment_type = ''
-	if mode_of_payment:
-		mode_of_payment_type = frappe.get_cached_value('Mode of Payment', mode_of_payment, 'type')
-		if mode_of_payment_type not in ["Cash", "Bank"]:
-			# if mode of payment is General then it unset the type
-			mode_of_payment_type = None
-
+def make_return_entry(employee, company, employee_advance_name, return_amount,  advance_account, currency, exchange_rate, mode_of_payment=None):
+	bank_cash_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
+	if not bank_cash_account:
+		frappe.throw(_("Please set a Default Cash Account in Company defaults"))
+	
+	advance_account_currency = frappe.db.get_value('Account', advance_account, 'account_currency')
+	
 	je = frappe.new_doc('Journal Entry')
 	je.posting_date = nowdate()
-	# if mode of payment is Bank then voucher type is Bank Entry
-	je.voucher_type = '{} Entry'.format(mode_of_payment_type) if mode_of_payment_type else 'Cash Entry'
+	je.voucher_type = get_voucher_type(mode_of_payment)
 	je.company = company
 	je.remark = 'Return against Employee Advance: ' + employee_advance_name
+	je.multi_currency = 1 if advance_account_currency != bank_cash_account.account_currency else 0
+
+	advance_account_amount = flt(return_amount) if advance_account_currency==currency \
+		else flt(return_amount) * flt(exchange_rate)
 
 	je.append('accounts', {
 		'account': advance_account,
-		'credit_in_account_currency': return_amount,
+		'credit_in_account_currency': advance_account_amount,
+		'account_currency': advance_account_currency,
+		'exchange_rate': flt(exchange_rate) if advance_account_currency == currency else 1,
 		'reference_type': 'Employee Advance',
 		'reference_name': employee_advance_name,
 		'party_type': 'Employee',
@@ -176,13 +208,25 @@
 		'is_advance': 'Yes'
 	})
 
+	bank_amount = flt(return_amount) if bank_cash_account.account_currency==currency \
+		else flt(return_amount) * flt(exchange_rate)
+
 	je.append("accounts", {
-		"account": return_account.account,
-		"debit_in_account_currency": return_amount,
-		"account_currency": return_account.account_currency,
-		"account_type": return_account.account_type
+		"account": bank_cash_account.account,
+		"debit_in_account_currency": bank_amount,
+		"account_currency": bank_cash_account.account_currency,
+		"account_type": bank_cash_account.account_type,
+		"exchange_rate": flt(exchange_rate) if bank_cash_account.account_currency == currency else 1
 	})
 
 	return je.as_dict()
 
+def get_voucher_type(mode_of_payment=None):
+	voucher_type = "Cash Entry"
 
+	if mode_of_payment:
+		mode_of_payment_type = frappe.get_cached_value('Mode of Payment', mode_of_payment, 'type')
+		if mode_of_payment_type == "Bank":
+			voucher_type = "Bank Entry"
+
+	return voucher_type
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
index 2097e71..c88b2b8 100644
--- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
@@ -3,15 +3,17 @@
 # See license.txt
 from __future__ import unicode_literals
 
-import frappe
+import frappe, erpnext
 import unittest
 from frappe.utils import nowdate
 from erpnext.hr.doctype.employee_advance.employee_advance import make_bank_entry
 from erpnext.hr.doctype.employee_advance.employee_advance import EmployeeAdvanceOverPayment
+from erpnext.hr.doctype.employee.test_employee import make_employee
 
 class TestEmployeeAdvance(unittest.TestCase):
 	def test_paid_amount_and_status(self):
-		advance = make_employee_advance()
+		employee_name = make_employee("_T@employe.advance")
+		advance = make_employee_advance(employee_name)
 
 		journal_entry = make_payment_entry(advance)
 		journal_entry.submit()
@@ -33,11 +35,13 @@
 
 	return journal_entry
 
-def make_employee_advance():
+def make_employee_advance(employee_name):
 	doc = frappe.new_doc("Employee Advance")
-	doc.employee = "_T-Employee-00001"
+	doc.employee = employee_name
 	doc.company  = "_Test company"
 	doc.purpose = "For site visit"
+	doc.currency = erpnext.get_company_currency("_Test company")
+	doc.exchange_rate = 1
 	doc.advance_amount = 1000
 	doc.posting_date = nowdate()
 	doc.advance_account = "_Test Employee Advance - _TC"
diff --git a/erpnext/hr/doctype/employee_grade/employee_grade.json b/erpnext/hr/doctype/employee_grade/employee_grade.json
index e63ffae..88b061a 100644
--- a/erpnext/hr/doctype/employee_grade/employee_grade.json
+++ b/erpnext/hr/doctype/employee_grade/employee_grade.json
@@ -1,167 +1,69 @@
 {
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 1, 
- "allow_rename": 1, 
- "autoname": "Prompt", 
- "beta": 0, 
- "creation": "2018-04-13 16:14:24.174138", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "Prompt",
+ "creation": "2018-04-13 16:14:24.174138",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "default_salary_structure"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "default_leave_policy",
-   "fieldtype": "Link",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "Default Leave Policy",
-   "length": 0,
-   "no_copy": 0,
-   "options": "Leave Policy",
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "default_salary_structure",
    "fieldtype": "Link",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
    "label": "Default Salary Structure",
-   "length": 0,
-   "no_copy": 0,
-   "options": "Salary Structure",
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "options": "Salary Structure"
   }
  ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-09-18 17:17:45.617624",
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-08-26 13:12:07.815330",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee Grade",
- "name_case": "",
  "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "System Manager",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
   },
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "HR Manager",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
   },
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "HR User",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
   }
  ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
  "sort_field": "modified",
  "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
index 6e97f05..4a0908d 100644
--- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
@@ -7,6 +7,7 @@
 from frappe.utils import random_string, nowdate
 from erpnext.hr.doctype.expense_claim.expense_claim import make_bank_entry
 from erpnext.accounts.doctype.account.test_account import create_account
+from erpnext.hr.doctype.employee.test_employee import make_employee
 
 test_records = frappe.get_test_records('Expense Claim')
 test_dependencies = ['Employee']
@@ -126,6 +127,9 @@
 
 def make_expense_claim(payable_account, amount, sanctioned_amount, company, account, project=None, task_name=None, do_not_submit=False, taxes=None):
 	employee = frappe.db.get_value("Employee", {"status": "Active"})
+	if not employee:
+		employee = make_employee("test_employee@expense_claim.com", company=company)
+
 	currency, cost_center = frappe.db.get_value('Company', company, ['default_currency', 'cost_center'])
 	expense_claim = {
 		 "doctype": "Expense Claim",
diff --git a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json
index 885e3ee..020457d 100644
--- a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json
+++ b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json
@@ -71,9 +71,7 @@
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Amount",
-   "oldfieldname": "tax_amount",
-   "oldfieldtype": "Currency",
-   "options": "Company:company:default_currency"
+   "options": "currency"
   },
   {
    "columns": 2,
@@ -81,9 +79,7 @@
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Total",
-   "oldfieldname": "total",
-   "oldfieldtype": "Currency",
-   "options": "Company:company:default_currency",
+   "options": "currency",
    "read_only": 1
   },
   {
@@ -106,7 +102,7 @@
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-05-11 19:01:26.611758",
+ "modified": "2020-09-23 20:27:36.027728",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Expense Taxes and Charges",
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json
index 4374d29..f999635 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.json
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.json
@@ -21,6 +21,7 @@
   "show_leaves_of_all_department_members_in_calendar",
   "auto_leave_encashment",
   "restrict_backdated_leave_application",
+  "automatically_allocate_leaves_based_on_leave_policy",
   "hiring_settings",
   "check_vacancies"
  ],
@@ -41,7 +42,7 @@
    "description": "Employee records are created using the selected field",
    "fieldname": "emp_created_by",
    "fieldtype": "Select",
-   "label": "Employee Records to Be Created By",
+   "label": "Employee Records to be created by",
    "options": "Naming Series\nEmployee Number\nFull Name"
   },
   {
@@ -117,7 +118,7 @@
    "default": "0",
    "fieldname": "restrict_backdated_leave_application",
    "fieldtype": "Check",
-   "label": "Restrict Backdated Leave Applications"
+   "label": "Restrict Backdated Leave Application"
   },
   {
    "depends_on": "eval:doc.restrict_backdated_leave_application == 1",
@@ -125,13 +126,19 @@
    "fieldtype": "Link",
    "label": "Role Allowed to Create Backdated Leave Application",
    "options": "Role"
+  },
+  {
+   "default": "0",
+   "fieldname": "automatically_allocate_leaves_based_on_leave_policy",
+   "fieldtype": "Check",
+   "label": "Automatically Allocate Leaves Based On Leave Policy"
   }
  ],
  "icon": "fa fa-cog",
  "idx": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-10-13 11:49:46.168027",
+ "modified": "2020-08-27 14:30:28.995324",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "HR Settings",
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
index 007497e..4b31501 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "allow_import": 1,
  "autoname": "naming_series:",
  "creation": "2013-02-20 19:10:38",
@@ -24,6 +25,7 @@
   "compensatory_request",
   "leave_period",
   "leave_policy",
+  "leave_policy_assignment",
   "carry_forwarded_leaves_count",
   "expired",
   "amended_from",
@@ -160,9 +162,10 @@
    "read_only": 1
   },
   {
-   "fetch_from": "employee.leave_policy",
+   "fetch_from": "leave_policy_assignment.leave_policy",
    "fieldname": "leave_policy",
    "fieldtype": "Link",
+   "hidden": 1,
    "in_standard_filter": 1,
    "label": "Leave Policy",
    "options": "Leave Policy",
@@ -209,12 +212,21 @@
    "fieldtype": "Float",
    "label": "Carry Forwarded Leaves",
    "read_only": 1
+  },
+  {
+   "fieldname": "leave_policy_assignment",
+   "fieldtype": "Link",
+   "label": "Leave Policy Assignment",
+   "options": "Leave Policy Assignment",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-ok",
  "idx": 1,
+ "index_web_pages_for_search": 1,
  "is_submittable": 1,
- "modified": "2019-08-08 15:08:42.440909",
+ "links": [],
+ "modified": "2020-08-20 14:25:10.314323",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Allocation",
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
index 03fe3fa..a09cd2e 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
@@ -51,9 +51,19 @@
 
 	def on_cancel(self):
 		self.create_leave_ledger_entry(submit=False)
+		if self.leave_policy_assignment:
+			self.update_leave_policy_assignments_when_no_allocations_left()
 		if self.carry_forward:
 			self.set_carry_forwarded_leaves_in_previous_allocation(on_cancel=True)
 
+	def update_leave_policy_assignments_when_no_allocations_left(self):
+		allocations = frappe.db.get_list("Leave Allocation", filters = {
+			"docstatus": 1,
+			"leave_policy_assignment": self.leave_policy_assignment
+		})
+		if len(allocations) == 0:
+			frappe.db.set_value("Leave Policy Assignment", self.leave_policy_assignment ,"leaves_allocated", 0)
+
 	def validate_period(self):
 		if date_diff(self.to_date, self.from_date) <= 0:
 			frappe.throw(_("To date cannot be before from date"))
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 3f25f58..4f3e462 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -130,8 +130,7 @@
 		if self.status == "Approved":
 			for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
 				date = dt.strftime("%Y-%m-%d")
-				status = "Half Day" if getdate(date) == getdate(self.half_day_date) else "On Leave"
-
+				status = "Half Day" if self.half_day_date and getdate(date) == getdate(self.half_day_date) else "On Leave"
 				attendance_name = frappe.db.exists('Attendance', dict(employee = self.employee,
 					attendance_date = date, docstatus = ('!=', 2)))
 
@@ -293,7 +292,8 @@
 	def set_half_day_date(self):
 		if self.from_date == self.to_date and self.half_day == 1:
 			self.half_day_date = self.from_date
-		elif self.half_day == 0:
+
+		if self.half_day == 0:
 			self.half_day_date = None
 
 	def notify_employee(self):
@@ -376,24 +376,32 @@
 		if expiry_date:
 			self.create_ledger_entry_for_intermediate_allocation_expiry(expiry_date, submit, lwp)
 		else:
+			raise_exception = True
+			if frappe.flags.in_patch:
+				raise_exception=False
+
 			args = dict(
 				leaves=self.total_leave_days * -1,
 				from_date=self.from_date,
 				to_date=self.to_date,
 				is_lwp=lwp,
-				holiday_list=get_holiday_list_for_employee(self.employee)
+				holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception) or ''
 			)
 			create_leave_ledger_entry(self, args, submit)
 
 	def create_ledger_entry_for_intermediate_allocation_expiry(self, expiry_date, submit, lwp):
 		''' splits leave application into two ledger entries to consider expiry of allocation '''
+
+		raise_exception = True
+		if frappe.flags.in_patch:
+			raise_exception=False
+
 		args = dict(
 			from_date=self.from_date,
 			to_date=expiry_date,
 			leaves=(date_diff(expiry_date, self.from_date) + 1) * -1,
 			is_lwp=lwp,
-			holiday_list=get_holiday_list_for_employee(self.employee),
-
+			holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception) or ''
 		)
 		create_leave_ledger_entry(self, args, submit)
 
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index 6e909c3..53b7a39 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -10,6 +10,7 @@
 from frappe.utils import add_days, nowdate, now_datetime, getdate, add_months
 from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
 from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
+from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
 
 test_dependencies = ["Leave Allocation", "Leave Block List"]
 
@@ -410,25 +411,39 @@
 		self.assertEqual(get_leave_balance_on(employee.name, leave_type.name, nowdate(), add_days(nowdate(), 8)), 21)
 
 	def test_earned_leaves_creation(self):
+
+		frappe.db.sql('''delete from `tabLeave Period`''')
+		frappe.db.sql('''delete from `tabLeave Policy Assignment`''')
+		frappe.db.sql('''delete from `tabLeave Allocation`''')
+		frappe.db.sql('''delete from `tabLeave Ledger Entry`''')
+
 		leave_period = get_leave_period()
 		employee = get_employee()
 		leave_type = 'Test Earned Leave Type'
-		if not frappe.db.exists('Leave Type', leave_type):
-			frappe.get_doc(dict(
-				leave_type_name = leave_type,
-				doctype = 'Leave Type',
-				is_earned_leave = 1,
-				earned_leave_frequency = 'Monthly',
-				rounding = 0.5,
-				max_leaves_allowed = 6
-			)).insert()
+		frappe.delete_doc_if_exists("Leave Type", 'Test Earned Leave Type', force=1)
+		frappe.get_doc(dict(
+			leave_type_name = leave_type,
+			doctype = 'Leave Type',
+			is_earned_leave = 1,
+			earned_leave_frequency = 'Monthly',
+			rounding = 0.5,
+			max_leaves_allowed = 6
+		)).insert()
+
 		leave_policy = frappe.get_doc({
 			"doctype": "Leave Policy",
 			"leave_policy_details": [{"leave_type": leave_type, "annual_allocation": 6}]
 		}).insert()
-		frappe.db.set_value("Employee", employee.name, "leave_policy", leave_policy.name)
 
-		allocate_leaves(employee, leave_period, leave_type, 0, eligible_leaves = 12)
+		data = {
+			"assignment_based_on": "Leave Period",
+			"leave_policy": leave_policy.name,
+			"leave_period": leave_period.name
+		}
+
+		leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
+
+		frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee()
 
 		from erpnext.hr.utils import allocate_earned_leaves
 		i = 0
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.js b/erpnext/hr/doctype/leave_encashment/leave_encashment.js
index 71a3422..81936a4 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.js
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.js
@@ -22,7 +22,12 @@
 		}
 	},
 	employee: function(frm) {
-		frm.trigger("get_leave_details_for_encashment");
+		if (frm.doc.employee) {
+			frappe.run_serially([
+				() => 	frm.trigger('get_employee_currency'),
+				() => 	frm.trigger('get_leave_details_for_encashment')
+			]);
+		}
 	},
 	leave_type: function(frm) {
 		frm.trigger("get_leave_details_for_encashment");
@@ -40,5 +45,20 @@
 				}
 			});
 		}
-	}
+	},
+
+	get_employee_currency: function(frm) {
+		frappe.call({
+			method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+			args: {
+				employee: frm.doc.employee,
+			},
+			callback: function(r) {
+				if (r.message) {
+					frm.set_value('currency', r.message);
+					frm.refresh_fields();
+				}
+			}
+		});
+	},
 });
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.json b/erpnext/hr/doctype/leave_encashment/leave_encashment.json
index 2cf6ccf..83eeae3 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.json
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.json
@@ -12,6 +12,7 @@
   "employee",
   "employee_name",
   "department",
+  "company",
   "column_break_4",
   "leave_type",
   "leave_allocation",
@@ -19,9 +20,11 @@
   "encashable_days",
   "amended_from",
   "payroll",
-  "encashment_amount",
   "encashment_date",
-  "additional_salary"
+  "additional_salary",
+  "column_break_14",
+  "currency",
+  "encashment_amount"
  ],
  "fields": [
   {
@@ -109,6 +112,7 @@
    "in_list_view": 1,
    "label": "Encashment Amount",
    "no_copy": 1,
+   "options": "currency",
    "read_only": 1
   },
   {
@@ -124,11 +128,34 @@
    "no_copy": 1,
    "options": "Additional Salary",
    "read_only": 1
+  },
+  {
+   "default": "Company:company:default_currency",
+   "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "label": "Currency",
+   "options": "Currency",
+   "print_hide": 1,
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "fieldname": "column_break_14",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fetch_from": "employee.company",
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company",
+   "reqd": 1
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2019-12-16 11:51:57.732223",
+ "modified": "2020-11-25 11:56:06.777241",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Encashment",
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
index c1dcc97..4c1a465 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
@@ -16,10 +16,16 @@
 	def validate(self):
 		set_employee_name(self)
 		self.get_leave_details_for_encashment()
+		self.validate_salary_structure()
 
 		if not self.encashment_date:
 			self.encashment_date = getdate(nowdate())
 
+	def validate_salary_structure(self):
+		if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
+			frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(self.employee))
+
+
 	def before_submit(self):
 		if self.encashment_amount <= 0:
 			frappe.throw(_("You can only submit Leave Encashment for a valid encashment amount"))
@@ -30,6 +36,7 @@
 		additional_salary = frappe.new_doc("Additional Salary")
 		additional_salary.company = frappe.get_value("Employee", self.employee, "company")
 		additional_salary.employee = self.employee
+		additional_salary.currency = self.currency
 		earning_component = frappe.get_value("Leave Type", self.leave_type, "earning_component")
 		if not earning_component:
 			frappe.throw(_("Please set Earning Component for Leave type: {0}.").format(self.leave_type))
diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
index 99f6463..aafc964 100644
--- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
@@ -9,6 +9,7 @@
 from erpnext.hr.doctype.employee.test_employee import make_employee
 from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
 from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period
+from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
 from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy\
 
 test_dependencies = ["Leave Type"]
@@ -16,6 +17,7 @@
 class TestLeaveEncashment(unittest.TestCase):
 	def setUp(self):
 		frappe.db.sql('''delete from `tabLeave Period`''')
+		frappe.db.sql('''delete from `tabLeave Policy Assignment`''')
 		frappe.db.sql('''delete from `tabLeave Allocation`''')
 		frappe.db.sql('''delete from `tabLeave Ledger Entry`''')
 		frappe.db.sql('''delete from `tabAdditional Salary`''')
@@ -29,14 +31,26 @@
 		# create employee, salary structure and assignment
 		self.employee = make_employee("test_employee_encashment@example.com")
 
-		frappe.db.set_value("Employee", self.employee, "leave_policy", leave_policy.name)
+		self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
+
+		data = {
+			"assignment_based_on": "Leave Period",
+			"leave_policy": leave_policy.name,
+			"leave_period": self.leave_period.name
+		}
+
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee], frappe._dict(data))
 
 		salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee,
 			other_details={"leave_encashment_amount_per_day": 50})
 
-		# create the leave period and assign the leaves
-		self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
-		self.leave_period.grant_leave_allocation(employee=self.employee)
+		#grant Leaves
+		frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee()
+
+
+	def tearDown(self):
+		for dt in ["Leave Period", "Leave Allocation", "Leave Ledger Entry", "Additional Salary", "Leave Encashment", "Salary Structure", "Leave Policy"]:
+			frappe.db.sql("delete from `tab%s`" % dt)
 
 	def test_leave_balance_value_and_amount(self):
 		frappe.db.sql('''delete from `tabLeave Encashment`''')
@@ -45,7 +59,8 @@
 			employee=self.employee,
 			leave_type="_Test Leave Type Encashment",
 			leave_period=self.leave_period.name,
-			payroll_date=today()
+			payroll_date=today(),
+			currency="INR"
 		)).insert()
 
 		self.assertEqual(leave_encashment.leave_balance, 10)
@@ -65,7 +80,8 @@
 			employee=self.employee,
 			leave_type="_Test Leave Type Encashment",
 			leave_period=self.leave_period.name,
-			payroll_date=today()
+			payroll_date=today(),
+			currency="INR"
 		)).insert()
 
 		leave_encashment.submit()
diff --git a/erpnext/hr/doctype/leave_period/leave_period.js b/erpnext/hr/doctype/leave_period/leave_period.js
index bad2b87..0e88bc1 100644
--- a/erpnext/hr/doctype/leave_period/leave_period.js
+++ b/erpnext/hr/doctype/leave_period/leave_period.js
@@ -2,14 +2,6 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Leave Period', {
-	refresh: (frm)=>{
-		frm.set_df_property("grant_leaves", "hidden", frm.doc.__islocal ? 1:0);
-		if(!frm.is_new()) {
-			frm.add_custom_button(__('Grant Leaves'), function () {
-				frm.trigger("grant_leaves");
-			});
-		}
-	},
 	from_date: (frm)=>{
 		if (frm.doc.from_date && !frm.doc.to_date) {
 			var a_year_from_start = frappe.datetime.add_months(frm.doc.from_date, 12);
@@ -22,73 +14,7 @@
 				"filters": {
 					"company": frm.doc.company,
 				}
-			}
-		})
-	},
-	grant_leaves: function(frm) {
-		var d = new frappe.ui.Dialog({
-			title: __('Grant Leaves'),
-			fields: [
-				{
-					"label": "Filter Employees By (Optional)",
-					"fieldname": "sec_break",
-					"fieldtype": "Section Break",
-				},
-				{
-					"label": "Employee Grade",
-					"fieldname": "grade",
-					"fieldtype": "Link",
-					"options": "Employee Grade"
-				},
-				{
-					"label": "Department",
-					"fieldname": "department",
-					"fieldtype": "Link",
-					"options": "Department"
-				},
-				{
-					"fieldname": "col_break",
-					"fieldtype": "Column Break",
-				},
-				{
-					"label": "Designation",
-					"fieldname": "designation",
-					"fieldtype": "Link",
-					"options": "Designation"
-				},
-				{
-					"label": "Employee",
-					"fieldname": "employee",
-					"fieldtype": "Link",
-					"options": "Employee"
-				},
-				{
-					"fieldname": "sec_break",
-					"fieldtype": "Section Break",
-				},
-				{
-					"label": "Add unused leaves from previous allocations",
-					"fieldname": "carry_forward",
-					"fieldtype": "Check"
-				}
-			],
-			primary_action: function() {
-				var data = d.get_values();
-
-				frappe.call({
-					doc: frm.doc,
-					method: "grant_leave_allocation",
-					args: data,
-					callback: function(r) {
-						if(!r.exc) {
-							d.hide();
-							frm.reload_doc();
-						}
-					}
-				});
-			},
-			primary_action_label: __('Grant')
+			};
 		});
-		d.show();
-	}
+	},
 });
diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py
index 0973ac7..28a33f6 100644
--- a/erpnext/hr/doctype/leave_period/leave_period.py
+++ b/erpnext/hr/doctype/leave_period/leave_period.py
@@ -7,24 +7,10 @@
 from frappe import _
 from frappe.utils import getdate, cstr, add_days, date_diff, getdate, ceil
 from frappe.model.document import Document
-from erpnext.hr.utils import validate_overlap, get_employee_leave_policy
+from erpnext.hr.utils import validate_overlap
 from frappe.utils.background_jobs import enqueue
-from six import iteritems
 
 class LeavePeriod(Document):
-	def get_employees(self, args):
-		conditions, values = [], []
-		for field, value in iteritems(args):
-			if value:
-				conditions.append("{0}=%s".format(field))
-				values.append(value)
-
-		condition_str = " and " + " and ".join(conditions) if len(conditions) else ""
-
-		employees = frappe._dict(frappe.db.sql("select name, date_of_joining from tabEmployee where status='Active' {condition}" #nosec
-			.format(condition=condition_str), tuple(values)))
-
-		return employees
 
 	def validate(self):
 		self.validate_dates()
@@ -33,96 +19,3 @@
 	def validate_dates(self):
 		if getdate(self.from_date) >= getdate(self.to_date):
 			frappe.throw(_("To date can not be equal or less than from date"))
-
-
-	def grant_leave_allocation(self, grade=None, department=None, designation=None,
-			employee=None, carry_forward=0):
-		employee_records = self.get_employees({
-			"grade": grade,
-			"department": department,
-			"designation": designation,
-			"name": employee
-		})
-
-		if employee_records:
-			if len(employee_records) > 20:
-				frappe.enqueue(grant_leave_alloc_for_employees, timeout=600,
-					employee_records=employee_records, leave_period=self, carry_forward=carry_forward)
-			else:
-				grant_leave_alloc_for_employees(employee_records, self, carry_forward)
-		else:
-			frappe.msgprint(_("No Employee Found"))
-
-def grant_leave_alloc_for_employees(employee_records, leave_period, carry_forward=0):
-	leave_allocations = []
-	existing_allocations_for = get_existing_allocations(list(employee_records.keys()), leave_period.name)
-	leave_type_details = get_leave_type_details()
-	count = 0
-	for employee in employee_records.keys():
-		if employee in existing_allocations_for:
-			continue
-		count +=1
-		leave_policy = get_employee_leave_policy(employee)
-		if leave_policy:
-			for leave_policy_detail in leave_policy.leave_policy_details:
-				if not leave_type_details.get(leave_policy_detail.leave_type).is_lwp:
-					leave_allocation = create_leave_allocation(employee, leave_policy_detail.leave_type,
-						leave_policy_detail.annual_allocation, leave_type_details, leave_period, carry_forward, employee_records.get(employee))
-					leave_allocations.append(leave_allocation)
-		frappe.db.commit()
-		frappe.publish_progress(count*100/len(set(employee_records.keys()) - set(existing_allocations_for)), title = _("Allocating leaves..."))
-
-	if leave_allocations:
-		frappe.msgprint(_("Leaves has been granted sucessfully"))
-
-def get_existing_allocations(employees, leave_period):
-	leave_allocations = frappe.db.sql_list("""
-		SELECT DISTINCT
-			employee
-		FROM `tabLeave Allocation`
-		WHERE
-			leave_period=%s
-			AND employee in (%s)
-			AND carry_forward=0
-			AND docstatus=1
-	""" % ('%s', ', '.join(['%s']*len(employees))), [leave_period] + employees)
-	if leave_allocations:
-		frappe.msgprint(_("Skipping Leave Allocation for the following employees, as Leave Allocation records already exists against them. {0}")
-			.format("\n".join(leave_allocations)))
-	return leave_allocations
-
-def get_leave_type_details():
-	leave_type_details = frappe._dict()
-	leave_types = frappe.get_all("Leave Type",
-		fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "expire_carry_forwarded_leaves_after_days"])
-	for d in leave_types:
-		leave_type_details.setdefault(d.name, d)
-	return leave_type_details
-
-def create_leave_allocation(employee, leave_type, new_leaves_allocated, leave_type_details, leave_period, carry_forward, date_of_joining):
-	''' Creates leave allocation for the given employee in the provided leave period '''
-	if carry_forward and not leave_type_details.get(leave_type).is_carry_forward:
-		carry_forward = 0
-
-	# Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period
-	if getdate(date_of_joining) > getdate(leave_period.from_date):
-		remaining_period = ((date_diff(leave_period.to_date, date_of_joining) + 1) / (date_diff(leave_period.to_date, leave_period.from_date) + 1))
-		new_leaves_allocated = ceil(new_leaves_allocated * remaining_period)
-
-	# Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0
-	if leave_type_details.get(leave_type).is_earned_leave == 1 or leave_type_details.get(leave_type).is_compensatory == 1:
-		new_leaves_allocated = 0
-
-	allocation = frappe.get_doc(dict(
-		doctype="Leave Allocation",
-		employee=employee,
-		leave_type=leave_type,
-		from_date=leave_period.from_date,
-		to_date=leave_period.to_date,
-		new_leaves_allocated=new_leaves_allocated,
-		leave_period=leave_period.name,
-		carry_forward=carry_forward
-		))
-	allocation.save(ignore_permissions = True)
-	allocation.submit()
-	return allocation.name
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.py b/erpnext/hr/doctype/leave_period/test_leave_period.py
index 1762cf9..b5857bc 100644
--- a/erpnext/hr/doctype/leave_period/test_leave_period.py
+++ b/erpnext/hr/doctype/leave_period/test_leave_period.py
@@ -5,43 +5,11 @@
 
 import frappe, erpnext
 import unittest
-from frappe.utils import today, add_months
-from erpnext.hr.doctype.employee.test_employee import make_employee
-from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
 
 test_dependencies = ["Employee", "Leave Type", "Leave Policy"]
 
 class TestLeavePeriod(unittest.TestCase):
-	def setUp(self):
-		frappe.db.sql("delete from `tabLeave Period`")
-
-	def test_leave_grant(self):
-		leave_type = "_Test Leave Type"
-
-		# create the leave policy
-		leave_policy = frappe.get_doc({
-			"doctype": "Leave Policy",
-			"leave_policy_details": [{
-				"leave_type": leave_type,
-				"annual_allocation": 20
-			}]
-		}).insert()
-		leave_policy.submit()
-
-		# create employee and assign the leave period
-		employee = "test_leave_period@employee.com"
-		employee_doc_name = make_employee(employee)
-		frappe.db.set_value("Employee", employee_doc_name, "leave_policy", leave_policy.name)
-
-		# clear the already allocated leave
-		frappe.db.sql('''delete from `tabLeave Allocation` where employee=%s''', "test_leave_period@employee.com")
-
-		# create the leave period
-		leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
-
-		# test leave_allocation
-		leave_period.grant_leave_allocation(employee=employee_doc_name)
-		self.assertEqual(get_leave_balance_on(employee_doc_name, leave_type, today()), 20)
+	pass
 
 def create_leave_period(from_date, to_date, company=None):
 	leave_period = frappe.db.get_value('Leave Period',
diff --git a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
index ff5dc2f..e0ec4be 100644
--- a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
+++ b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
@@ -4,22 +4,10 @@
 def get_data():
 	return {
 		'fieldname':  'leave_policy',
-		'non_standard_fieldnames': {
-			'Employee Grade': 'default_leave_policy'
-		},
 		'transactions': [
 			{
-				'label': _('Employees'),
-				'items': ['Employee', 'Employee Grade']
-			},
-			{
 				'label': _('Leaves'),
 				'items': ['Leave Allocation']
 			},
 		]
-	}
-
-
-
-
-	
\ No newline at end of file
+	}
\ No newline at end of file
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/hr/doctype/leave_policy_assignment/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/hr/doctype/leave_policy_assignment/__init__.py
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js
new file mode 100644
index 0000000..7c32a0d
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js
@@ -0,0 +1,72 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Leave Policy Assignment', {
+	onload: function(frm) {
+		frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
+	},
+
+	refresh: function(frm) {
+		if (frm.doc.docstatus === 1 && frm.doc.leaves_allocated === 0) {
+			frm.add_custom_button(__("Grant Leave"), function() {
+
+				frappe.call({
+					doc: frm.doc,
+					method: "grant_leave_alloc_for_employee",
+					callback: function(r) {
+						let leave_allocations = r.message;
+						let msg = frm.events.get_success_message(leave_allocations);
+						frappe.msgprint(msg);
+						cur_frm.refresh();
+					}
+				});
+			});
+		}
+	},
+
+	get_success_message: function(leave_allocations) {
+		let msg = __("Leaves has been granted successfully");
+		msg += "<br><table class='table table-bordered'>";
+		msg += "<tr><th>"+__('Leave Type')+"</th><th>"+__("Leave Allocation")+"</th><th>"+__("Leaves Granted")+"</th><tr>";
+		for (let key in leave_allocations) {
+			msg += "<tr><th>"+key+"</th><td>"+leave_allocations[key]["name"]+"</td><td>"+leave_allocations[key]["leaves"]+"</td></tr>";
+		}
+		msg += "</table>";
+		return msg;
+	},
+
+	assignment_based_on: function(frm) {
+		if (frm.doc.assignment_based_on) {
+			frm.events.set_effective_date(frm);
+		} else {
+			frm.set_value("effective_from", '');
+			frm.set_value("effective_to", '');
+		}
+	},
+
+	leave_period: function(frm) {
+		if (frm.doc.leave_period) {
+			frm.events.set_effective_date(frm);
+		}
+	},
+
+	set_effective_date: function(frm) {
+		if (frm.doc.assignment_based_on == "Leave Period" && frm.doc.leave_period) {
+			frappe.model.with_doc("Leave Period", frm.doc.leave_period, function () {
+				let from_date = frappe.model.get_value("Leave Period", frm.doc.leave_period, "from_date");
+				let to_date = frappe.model.get_value("Leave Period", frm.doc.leave_period, "to_date");
+				frm.set_value("effective_from", from_date);
+				frm.set_value("effective_to", to_date);
+
+			});
+		} else if (frm.doc.assignment_based_on == "Joining Date" && frm.doc.employee) {
+			frappe.model.with_doc("Employee", frm.doc.employee, function () {
+				let from_date = frappe.model.get_value("Employee", frm.doc.employee, "date_of_joining");
+				frm.set_value("effective_from", from_date);
+				frm.set_value("effective_to", frappe.datetime.add_months(frm.doc.effective_from, 12));
+			});
+		}
+		frm.refresh();
+	}
+
+});
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json
new file mode 100644
index 0000000..a0327bd
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json
@@ -0,0 +1,166 @@
+{
+ "actions": [],
+ "autoname": "HR-LPOL-ASSGN-.#####",
+ "creation": "2020-08-19 13:02:43.343666",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "employee",
+  "employee_name",
+  "company",
+  "leave_policy",
+  "carry_forward",
+  "column_break_5",
+  "assignment_based_on",
+  "leave_period",
+  "effective_from",
+  "effective_to",
+  "leaves_allocated",
+  "amended_from"
+ ],
+ "fields": [
+  {
+   "fieldname": "employee",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Employee",
+   "options": "Employee",
+   "reqd": 1
+  },
+  {
+   "fetch_from": "employee.employee_name",
+   "fieldname": "employee_name",
+   "fieldtype": "Data",
+   "label": "Employee name",
+   "read_only": 1
+  },
+  {
+   "fieldname": "leave_policy",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Leave Policy",
+   "options": "Leave Policy",
+   "reqd": 1
+  },
+  {
+   "fieldname": "assignment_based_on",
+   "fieldtype": "Select",
+   "label": "Assignment based on",
+   "options": "\nLeave Period\nJoining Date"
+  },
+  {
+   "depends_on": "eval:doc.assignment_based_on == \"Leave Period\"",
+   "fieldname": "leave_period",
+   "fieldtype": "Link",
+   "label": "Leave Period",
+   "mandatory_depends_on": "eval:doc.assignment_based_on == \"Leave Period\"",
+   "options": "Leave Period"
+  },
+  {
+   "fieldname": "effective_from",
+   "fieldtype": "Date",
+   "label": "Effective From",
+   "read_only_depends_on": "eval:doc.assignment_based_on",
+   "reqd": 1
+  },
+  {
+   "fieldname": "effective_to",
+   "fieldtype": "Date",
+   "label": "Effective To",
+   "read_only_depends_on": "eval:doc.assignment_based_on == \"Leave Period\"",
+   "reqd": 1
+  },
+  {
+   "fetch_from": "employee.company",
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "in_standard_filter": 1,
+   "label": "Company",
+   "options": "Company",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_5",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "amended_from",
+   "fieldtype": "Link",
+   "label": "Amended From",
+   "no_copy": 1,
+   "options": "Leave Policy Assignment",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "carry_forward",
+   "fieldtype": "Check",
+   "label": "Add unused leaves from previous allocations"
+  },
+  {
+   "default": "0",
+   "fieldname": "leaves_allocated",
+   "fieldtype": "Check",
+   "hidden": 1,
+   "label": "Leaves Allocated"
+  }
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-12-31 16:43:30.695206",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Leave Policy Assignment",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR User",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
new file mode 100644
index 0000000..a5068bc
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+from frappe import _, bold
+from frappe.utils import getdate, date_diff, comma_and, formatdate
+from math import ceil
+import json
+from six import string_types
+
+class LeavePolicyAssignment(Document):
+
+	def validate(self):
+		self.validate_policy_assignment_overlap()
+		self.set_dates()
+
+	def set_dates(self):
+		if self.assignment_based_on == "Leave Period":
+			self.effective_from, self.effective_to = frappe.db.get_value("Leave Period", self.leave_period, ["from_date", "to_date"])
+		elif self.assignment_based_on == "Joining Date":
+			self.effective_from = frappe.db.get_value("Employee", self.employee, "date_of_joining")
+
+	def validate_policy_assignment_overlap(self):
+		leave_policy_assignments = frappe.get_all("Leave Policy Assignment", filters = {
+			"employee": self.employee,
+			"name": ("!=", self.name),
+			"docstatus": 1,
+			"effective_to": (">=", self.effective_from),
+			"effective_from": ("<=", self.effective_to)
+		})
+
+		if len(leave_policy_assignments):
+			frappe.throw(_("Leave Policy: {0} already assigned for Employee {1} for period {2} to {3}")
+				.format(bold(self.leave_policy), bold(self.employee), bold(formatdate(self.effective_from)), bold(formatdate(self.effective_to))))
+
+	def grant_leave_alloc_for_employee(self):
+		if self.leaves_allocated:
+			frappe.throw(_("Leave already have been assigned for this Leave Policy Assignment"))
+		else:
+			leave_allocations = {}
+			leave_type_details = get_leave_type_details()
+
+			leave_policy = frappe.get_doc("Leave Policy", self.leave_policy)
+			date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
+
+			for leave_policy_detail in leave_policy.leave_policy_details:
+				if not leave_type_details.get(leave_policy_detail.leave_type).is_lwp:
+					leave_allocation, new_leaves_allocated = self.create_leave_allocation(
+						leave_policy_detail.leave_type, leave_policy_detail.annual_allocation,
+						leave_type_details, date_of_joining
+					)
+
+				leave_allocations[leave_policy_detail.leave_type] = {"name": leave_allocation, "leaves": new_leaves_allocated}
+
+			self.db_set("leaves_allocated", 1)
+			return leave_allocations
+
+	def create_leave_allocation(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
+		# Creates leave allocation for the given employee in the provided leave period
+		carry_forward = self.carry_forward
+		if self.carry_forward and not leave_type_details.get(leave_type).is_carry_forward:
+			carry_forward = 0
+
+		new_leaves_allocated = self.get_new_leaves(leave_type, new_leaves_allocated,
+			leave_type_details, date_of_joining)
+
+		allocation = frappe.get_doc(dict(
+			doctype="Leave Allocation",
+			employee=self.employee,
+			leave_type=leave_type,
+			from_date=self.effective_from,
+			to_date=self.effective_to,
+			new_leaves_allocated=new_leaves_allocated,
+			leave_period=self.leave_period or None,
+			leave_policy_assignment = self.name,
+			leave_policy = self.leave_policy,
+			carry_forward=carry_forward
+			))
+		allocation.save(ignore_permissions = True)
+		allocation.submit()
+		return allocation.name, new_leaves_allocated
+
+	def get_new_leaves(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
+		# Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period
+		if getdate(date_of_joining) > getdate(self.effective_from):
+			remaining_period = ((date_diff(self.effective_to, date_of_joining) + 1) / (date_diff(self.effective_to, self.effective_from) + 1))
+			new_leaves_allocated = ceil(new_leaves_allocated * remaining_period)
+
+		# Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0
+		if leave_type_details.get(leave_type).is_earned_leave == 1 or leave_type_details.get(leave_type).is_compensatory == 1:
+			new_leaves_allocated = 0
+
+		return new_leaves_allocated
+
+@frappe.whitelist()
+def grant_leave_for_multiple_employees(leave_policy_assignments):
+	leave_policy_assignments = json.loads(leave_policy_assignments)
+	not_granted = []
+	for assignment in leave_policy_assignments:
+		try:
+			frappe.get_doc("Leave Policy Assignment", assignment).grant_leave_alloc_for_employee()
+		except Exception:
+			not_granted.append(assignment)
+
+		if len(not_granted):
+			msg = _("Leave not Granted for Assignments:")+ bold(comma_and(not_granted)) + _(". Please Check documents")
+		else:
+			msg = _("Leave granted Successfully")
+	frappe.msgprint(msg)
+
+@frappe.whitelist()
+def create_assignment_for_multiple_employees(employees, data):
+
+	if isinstance(employees, string_types):
+		employees= json.loads(employees)
+
+	if isinstance(data, string_types):
+		data = frappe._dict(json.loads(data))
+
+	docs_name = []
+	for employee in employees:
+		assignment = frappe.new_doc("Leave Policy Assignment")
+		assignment.employee = employee
+		assignment.assignment_based_on = data.assignment_based_on or None
+		assignment.leave_policy = data.leave_policy
+		assignment.effective_from = getdate(data.effective_from) or None
+		assignment.effective_to = getdate(data.effective_to) or None
+		assignment.leave_period = data.leave_period or None
+		assignment.carry_forward = data.carry_forward
+
+		assignment.save()
+		assignment.submit()
+		docs_name.append(assignment.name)
+	return docs_name
+
+
+def automatically_allocate_leaves_based_on_leave_policy():
+	today = getdate()
+	automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_single_value(
+		'HR Settings', 'automatically_allocate_leaves_based_on_leave_policy'
+	)
+
+	pending_assignments = frappe.get_list(
+		"Leave Policy Assignment",
+		filters = {"docstatus": 1, "leaves_allocated": 0, "effective_from": today}
+	)
+
+	if len(pending_assignments) and automatically_allocate_leaves_based_on_leave_policy:
+		for assignment in pending_assignments:
+			frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee()
+
+
+def get_leave_type_details():
+	leave_type_details = frappe._dict()
+	leave_types = frappe.get_all("Leave Type",
+		fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "expire_carry_forwarded_leaves_after_days"])
+	for d in leave_types:
+		leave_type_details.setdefault(d.name, d)
+	return leave_type_details
+
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js
new file mode 100644
index 0000000..468f243
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js
@@ -0,0 +1,138 @@
+frappe.listview_settings['Leave Policy Assignment'] = {
+	onload: function (list_view) {
+		let me = this;
+		list_view.page.add_inner_button(__("Bulk Leave Policy Assignment"), function () {
+			me.dialog = new frappe.ui.form.MultiSelectDialog({
+				doctype: "Employee",
+				target: cur_list,
+				setters: {
+					company: '',
+					department: '',
+				},
+				data_fields: [{
+					fieldname: 'leave_policy',
+					fieldtype: 'Link',
+					options: 'Leave Policy',
+					label: __('Leave Policy'),
+					reqd: 1
+				},
+				{
+					fieldname: 'assignment_based_on',
+					fieldtype: 'Select',
+					options: ["", "Leave Period"],
+					label: __('Assignment Based On'),
+					onchange: () => {
+						if (cur_dialog.fields_dict.assignment_based_on.value === "Leave Period") {
+							cur_dialog.set_df_property("effective_from", "read_only", 1);
+							cur_dialog.set_df_property("leave_period", "reqd", 1);
+							cur_dialog.set_df_property("effective_to", "read_only", 1);
+						} else {
+							cur_dialog.set_df_property("effective_from", "read_only", 0);
+							cur_dialog.set_df_property("leave_period", "reqd", 0);
+							cur_dialog.set_df_property("effective_to", "read_only", 0);
+							cur_dialog.set_value("effective_from", "");
+							cur_dialog.set_value("effective_to", "");
+						}
+					}
+				},
+				{
+					fieldname: "leave_period",
+					fieldtype: 'Link',
+					options: "Leave Period",
+					label: __('Leave Period'),
+					depends_on: doc => {
+						return doc.assignment_based_on == 'Leave Period';
+					},
+					onchange: () => {
+						if (cur_dialog.fields_dict.leave_period.value) {
+							me.set_effective_date();
+						}
+					}
+				},
+				{
+					fieldtype: "Column Break"
+				},
+				{
+					fieldname: 'effective_from',
+					fieldtype: 'Date',
+					label: __('Effective From'),
+					reqd: 1
+				},
+				{
+					fieldname: 'effective_to',
+					fieldtype: 'Date',
+					label: __('Effective To'),
+					reqd: 1
+				},
+				{
+					fieldname: 'carry_forward',
+					fieldtype: 'Check',
+					label: __('Add unused leaves from previous allocations')
+				}
+				],
+				get_query() {
+					return {
+						filters: {
+							status: ['=', 'Active']
+						}
+					};
+				},
+				add_filters_group: 1,
+				primary_action_label: "Assign",
+				action(employees, data) {
+					frappe.call({
+						method: 'erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.create_assignment_for_multiple_employees',
+						async: false,
+						args: {
+							employees: employees,
+							data: data
+						}
+					});
+					cur_dialog.hide();
+				}
+			});
+		});
+
+		list_view.page.add_inner_button(__("Grant Leaves"), function () {
+			me.dialog = new frappe.ui.form.MultiSelectDialog({
+				doctype: "Leave Policy Assignment",
+				target: cur_list,
+				setters: {
+					company: '',
+					employee: '',
+				},
+				get_query() {
+					return {
+						filters: {
+							docstatus: ['=', 1],
+							leaves_allocated: ['=', 0]
+						}
+					};
+				},
+				add_filters_group: 1,
+				primary_action_label: "Grant Leaves",
+				action(leave_policy_assignments) {
+					frappe.call({
+						method: 'erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.grant_leave_for_multiple_employees',
+						async: false,
+						args: {
+							leave_policy_assignments: leave_policy_assignments
+						}
+					});
+					me.dialog.hide();
+				}
+			});
+		});
+	},
+
+	set_effective_date: function () {
+		if (cur_dialog.fields_dict.assignment_based_on.value === "Leave Period" && cur_dialog.fields_dict.leave_period.value) {
+			frappe.model.with_doc("Leave Period", cur_dialog.fields_dict.leave_period.value, function () {
+				let from_date = frappe.model.get_value("Leave Period", cur_dialog.fields_dict.leave_period.value, "from_date");
+				let to_date = frappe.model.get_value("Leave Period", cur_dialog.fields_dict.leave_period.value, "to_date");
+				cur_dialog.set_value("effective_from", from_date);
+				cur_dialog.set_value("effective_to", to_date);
+			});
+		}
+	}
+};
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
new file mode 100644
index 0000000..c7bc6fb
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+from erpnext.hr.doctype.leave_application.test_leave_application import get_leave_period, get_employee
+from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
+from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy
+
+class TestLeavePolicyAssignment(unittest.TestCase):
+
+	def setUp(self):
+		for doctype in ["Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]:
+			frappe.db.sql("delete from `tab{0}`".format(doctype)) #nosec
+
+	def test_grant_leaves(self):
+		leave_period = get_leave_period()
+		employee = get_employee()
+
+		# create the leave policy with leave type "_Test Leave Type", allocation = 10
+		leave_policy = create_leave_policy()
+		leave_policy.submit()
+
+
+		data = {
+			"assignment_based_on": "Leave Period",
+			"leave_policy": leave_policy.name,
+			"leave_period": leave_period.name
+		}
+
+		leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
+
+		leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
+		leave_policy_assignment_doc.grant_leave_alloc_for_employee()
+		leave_policy_assignment_doc.reload()
+
+		self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
+
+		leave_allocation = frappe.get_list("Leave Allocation", filters={
+			"employee": employee.name,
+			"leave_policy":leave_policy.name,
+			"leave_policy_assignment": leave_policy_assignments[0],
+			"docstatus": 1})[0]
+
+		leave_alloc_doc = frappe.get_doc("Leave Allocation", leave_allocation)
+
+		self.assertEqual(leave_alloc_doc.new_leaves_allocated, 10)
+		self.assertEqual(leave_alloc_doc.leave_type, "_Test Leave Type")
+		self.assertEqual(leave_alloc_doc.from_date, leave_period.from_date)
+		self.assertEqual(leave_alloc_doc.to_date, leave_period.to_date)
+		self.assertEqual(leave_alloc_doc.leave_policy, leave_policy.name)
+		self.assertEqual(leave_alloc_doc.leave_policy_assignment, leave_policy_assignments[0])
+
+	def test_allow_to_grant_all_leave_after_cancellation_of_every_leave_allocation(self):
+		leave_period = get_leave_period()
+		employee = get_employee()
+
+		# create the leave policy with leave type "_Test Leave Type", allocation = 10
+		leave_policy = create_leave_policy()
+		leave_policy.submit()
+
+
+		data = {
+			"assignment_based_on": "Leave Period",
+			"leave_policy": leave_policy.name,
+			"leave_period": leave_period.name
+		}
+
+		leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
+
+		leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
+		leave_policy_assignment_doc.grant_leave_alloc_for_employee()
+		leave_policy_assignment_doc.reload()
+
+
+		# every leave is allocated no more leave can be granted now
+		self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
+
+		leave_allocation = frappe.get_list("Leave Allocation", filters={
+			"employee": employee.name,
+			"leave_policy":leave_policy.name,
+			"leave_policy_assignment": leave_policy_assignments[0],
+			"docstatus": 1})[0]
+
+		leave_alloc_doc = frappe.get_doc("Leave Allocation", leave_allocation)
+
+		# User all allowed to grant leave when there is no allocation against assignment
+		leave_alloc_doc.cancel()
+		leave_alloc_doc.delete()
+
+		leave_policy_assignment_doc.reload()
+
+
+		# User are now allowed to grant leave
+		self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 0)
+
+	def tearDown(self):
+		for doctype in ["Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]:
+			frappe.db.sql("delete from `tab{0}`".format(doctype)) #nosec
+
+
diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json
index 0af832f..a209291 100644
--- a/erpnext/hr/doctype/leave_type/leave_type.json
+++ b/erpnext/hr/doctype/leave_type/leave_type.json
@@ -15,6 +15,8 @@
   "column_break_3",
   "is_carry_forward",
   "is_lwp",
+  "is_ppl",
+  "fraction_of_daily_salary_per_leave",
   "is_optional_leave",
   "allow_negative",
   "include_holiday",
@@ -31,6 +33,7 @@
   "is_earned_leave",
   "earned_leave_frequency",
   "column_break_22",
+  "based_on_date_of_joining",
   "rounding"
  ],
  "fields": [
@@ -77,6 +80,7 @@
   },
   {
    "default": "0",
+   "depends_on": "eval:doc.is_ppl == 0",
    "fieldname": "is_lwp",
    "fieldtype": "Check",
    "label": "Is Leave Without Pay"
@@ -183,12 +187,33 @@
   {
    "fieldname": "column_break_22",
    "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval:doc.is_earned_leave",
+   "description": "If checked, leave will be granted on the day of joining every month.",
+   "fieldname": "based_on_date_of_joining",
+   "fieldtype": "Check",
+   "label": "Based On Date Of Joining"
+  },
+  {
+   "depends_on": "eval:doc.is_lwp == 0",
+   "fieldname": "is_ppl",
+   "fieldtype": "Check",
+   "label": "Is Partially Paid Leave"
+  },
+  {
+   "depends_on": "eval:doc.is_ppl == 1",
+   "fieldname": "fraction_of_daily_salary_per_leave",
+   "fieldtype": "Float",
+   "label": "Fraction of Daily Salary per Leave",
+   "mandatory_depends_on": "eval:doc.is_ppl == 1"
   }
  ],
  "icon": "fa fa-flag",
  "idx": 1,
  "links": [],
- "modified": "2019-12-12 12:48:37.780254",
+ "modified": "2020-10-15 15:49:47.555105",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Type",
diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py
index c0d1296..21f180b 100644
--- a/erpnext/hr/doctype/leave_type/leave_type.py
+++ b/erpnext/hr/doctype/leave_type/leave_type.py
@@ -21,3 +21,9 @@
 			leave_allocation = [l['name'] for l in leave_allocation]
 			if leave_allocation:
 				frappe.throw(_('Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay').format(", ".join(leave_allocation))) #nosec
+
+		if self.is_lwp and self.is_ppl:
+			frappe.throw(_("Leave Type can be either without pay or partial pay"))
+
+		if self.is_ppl and (self.fraction_of_daily_salary_per_leave < 0 or  self.fraction_of_daily_salary_per_leave > 1):
+			frappe.throw(_("The fraction of Daily Salary per Leave should be between 0 and 1"))
diff --git a/erpnext/hr/doctype/leave_type/test_leave_type.py b/erpnext/hr/doctype/leave_type/test_leave_type.py
index 0c4f435..7fef297 100644
--- a/erpnext/hr/doctype/leave_type/test_leave_type.py
+++ b/erpnext/hr/doctype/leave_type/test_leave_type.py
@@ -18,9 +18,14 @@
         "allow_encashment": args.allow_encashment or 0,
         "is_earned_leave": args.is_earned_leave or 0,
         "is_lwp": args.is_lwp or 0,
+        "is_ppl":args.is_ppl or 0,
         "is_carry_forward": args.is_carry_forward or 0,
         "expire_carry_forwarded_leaves_after_days": args.expire_carry_forwarded_leaves_after_days or 0,
         "encashment_threshold_days": args.encashment_threshold_days or 5,
         "earning_component": "Leave Encashment"
     })
+
+    if leave_type.is_ppl:
+        leave_type.fraction_of_daily_salary_per_leave = args.fraction_of_daily_salary_per_leave or 0.5
+
     return leave_type
\ No newline at end of file
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index 8d95924..d700e7f 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -215,19 +215,6 @@
 		+ _(") for {0}").format(exists_for)
 	frappe.throw(msg)
 
-def get_employee_leave_policy(employee):
-	leave_policy = frappe.db.get_value("Employee", employee, "leave_policy")
-	if not leave_policy:
-		employee_grade = frappe.db.get_value("Employee", employee, "grade")
-		if employee_grade:
-			leave_policy = frappe.db.get_value("Employee Grade", employee_grade, "default_leave_policy")
-			if not leave_policy:
-				frappe.throw(_("Employee {0} of grade {1} have no default leave policy").format(employee, employee_grade))
-	if leave_policy:
-		return frappe.get_doc("Leave Policy", leave_policy)
-	else:
-		frappe.throw(_("Please set leave policy for employee {0} in Employee / Grade record").format(employee))
-
 def validate_duplicate_exemption_for_payroll_period(doctype, docname, payroll_period, employee):
 	existing_record = frappe.db.exists(doctype, {
 		"payroll_period": payroll_period,
@@ -300,43 +287,68 @@
 
 def allocate_earned_leaves():
 	'''Allocate earned leaves to Employees'''
-	e_leave_types = frappe.get_all("Leave Type",
-		fields=["name", "max_leaves_allowed", "earned_leave_frequency", "rounding"],
-		filters={'is_earned_leave' : 1})
+	e_leave_types = get_earned_leaves()
 	today = getdate()
-	divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12}
 
 	for e_leave_type in e_leave_types:
-		leave_allocations = frappe.db.sql("""select name, employee, from_date, to_date from `tabLeave Allocation` where %s
-			between from_date and to_date and docstatus=1 and leave_type=%s""", (today, e_leave_type.name), as_dict=1)
+
+		leave_allocations = get_leave_allocations(today, e_leave_type.name)
+
 		for allocation in leave_allocations:
-			leave_policy = get_employee_leave_policy(allocation.employee)
-			if not leave_policy:
+
+			if not allocation.leave_policy_assignment and not allocation.leave_policy:
 				continue
-			if not e_leave_type.earned_leave_frequency == "Monthly":
-				if not check_frequency_hit(allocation.from_date, today, e_leave_type.earned_leave_frequency):
-					continue
+
+			leave_policy = allocation.leave_policy if allocation.leave_policy else frappe.db.get_value(
+					"Leave Policy Assignment", allocation.leave_policy_assignment, ["leave_policy"])
+
 			annual_allocation = frappe.db.get_value("Leave Policy Detail", filters={
-				'parent': leave_policy.name,
+				'parent': leave_policy,
 				'leave_type': e_leave_type.name
 			}, fieldname=['annual_allocation'])
-			if annual_allocation:
-				earned_leaves = flt(annual_allocation) / divide_by_frequency[e_leave_type.earned_leave_frequency]
-				if e_leave_type.rounding == "0.5":
-					earned_leaves = round(earned_leaves * 2) / 2
-				else:
-					earned_leaves = round(earned_leaves)
 
-				allocation = frappe.get_doc('Leave Allocation', allocation.name)
-				new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves)
+			from_date=allocation.from_date
 
-				if new_allocation > e_leave_type.max_leaves_allowed and e_leave_type.max_leaves_allowed > 0:
-					new_allocation = e_leave_type.max_leaves_allowed
+			if e_leave_type.based_on_date_of_joining_date:
+				from_date  = frappe.db.get_value("Employee", allocation.employee, "date_of_joining")
 
-				if new_allocation == allocation.total_leaves_allocated:
-					continue
-				allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
-				create_additional_leave_ledger_entry(allocation, earned_leaves, today)
+			if check_effective_date(from_date, today, e_leave_type.earned_leave_frequency, e_leave_type.based_on_date_of_joining_date):
+				update_previous_leave_allocation(allocation, annual_allocation, e_leave_type)
+
+def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type):
+	divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12}
+	if annual_allocation:
+		earned_leaves = flt(annual_allocation) / divide_by_frequency[e_leave_type.earned_leave_frequency]
+		if e_leave_type.rounding == "0.5":
+			earned_leaves = round(earned_leaves * 2) / 2
+		else:
+			earned_leaves = round(earned_leaves)
+
+		allocation = frappe.get_doc('Leave Allocation', allocation.name)
+		new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves)
+
+		if new_allocation > e_leave_type.max_leaves_allowed and e_leave_type.max_leaves_allowed > 0:
+			new_allocation = e_leave_type.max_leaves_allowed
+
+		if new_allocation != allocation.total_leaves_allocated:
+			allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
+			today_date = today()
+			create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
+
+
+def get_leave_allocations(date, leave_type):
+	return frappe.db.sql("""select name, employee, from_date, to_date, leave_policy_assignment, leave_policy
+		from `tabLeave Allocation`
+		where
+			%s between from_date and to_date and docstatus=1
+			and leave_type=%s""",
+	(date, leave_type), as_dict=1)
+
+
+def get_earned_leaves():
+	return frappe.get_all("Leave Type",
+		fields=["name", "max_leaves_allowed", "earned_leave_frequency", "rounding", "based_on_date_of_joining"],
+		filters={'is_earned_leave' : 1})
 
 def create_additional_leave_ledger_entry(allocation, leaves, date):
 	''' Create leave ledger entry for leave types '''
@@ -345,24 +357,32 @@
 	allocation.unused_leaves = 0
 	allocation.create_leave_ledger_entry()
 
-def check_frequency_hit(from_date, to_date, frequency):
-	'''Return True if current date matches frequency'''
-	from_dt = get_datetime(from_date)
-	to_dt = get_datetime(to_date)
+def check_effective_date(from_date, to_date, frequency, based_on_date_of_joining_date):
+	import calendar
 	from dateutil import relativedelta
-	rd = relativedelta.relativedelta(to_dt, from_dt)
-	months = rd.months
-	if frequency == "Quarterly":
-		if not months % 3:
+
+	from_date = get_datetime(from_date)
+	to_date = get_datetime(to_date)
+	rd = relativedelta.relativedelta(to_date, from_date)
+	#last day of month
+	last_day =  calendar.monthrange(to_date.year, to_date.month)[1]
+
+	if (from_date.day == to_date.day and based_on_date_of_joining_date) or (not based_on_date_of_joining_date and to_date.day == last_day):
+		if frequency == "Monthly":
 			return True
-	elif frequency == "Half-Yearly":
-		if not months % 6:
+		elif frequency == "Quarterly" and rd.months % 3:
 			return True
-	elif frequency == "Yearly":
-		if not months % 12:
+		elif frequency == "Half-Yearly" and rd.months % 6:
 			return True
+		elif frequency == "Yearly" and rd.months % 12:
+			return True
+
+	if frappe.flags.in_test:
+		return True
+
 	return False
 
+
 def get_salary_assignment(employee, date):
 	assignment = frappe.db.sql("""
 		select * from `tabSalary Structure Assignment`
@@ -454,3 +474,10 @@
 	if sum_of_claimed_amount and flt(sum_of_claimed_amount[0].total_amount) > 0:
 		total_claimed_amount = sum_of_claimed_amount[0].total_amount
 	return total_claimed_amount
+
+def grant_leaves_automatically():
+	automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_singles_value("HR Settings", "automatically_allocate_leaves_based_on_leave_policy")
+	if automatically_allocate_leaves_based_on_leave_policy:
+		lpa = frappe.db.get_all("Leave Policy Assignment", filters={"effective_from": getdate(), "docstatus": 1, "leaves_allocated":0})
+		for assignment in lpa:
+			frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee()
diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json
index e8ecf01..acf09f5 100644
--- a/erpnext/loan_management/doctype/loan/loan.json
+++ b/erpnext/loan_management/doctype/loan/loan.json
@@ -26,11 +26,11 @@
   "disbursed_amount",
   "column_break_11",
   "maximum_loan_amount",
-  "is_term_loan",
   "repayment_method",
   "repayment_periods",
   "monthly_repayment_amount",
   "repayment_start_date",
+  "is_term_loan",
   "account_info",
   "mode_of_payment",
   "payment_account",
@@ -332,6 +332,7 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:doc.is_secured_loan",
    "fetch_from": "loan_application.maximum_loan_amount",
    "fieldname": "maximum_loan_amount",
    "fieldtype": "Currency",
@@ -352,7 +353,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-05 10:04:00.762975",
+ "modified": "2020-11-24 12:27:23.208240",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan",
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index 8405d6e..2e0a4d1 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -6,6 +6,7 @@
 import frappe, math, json
 import erpnext
 from frappe import _
+from six import string_types
 from frappe.utils import flt, rounded, add_months, nowdate, getdate, now_datetime
 from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
 from erpnext.controllers.accounts_controller import AccountsController
@@ -13,6 +14,8 @@
 
 class Loan(AccountsController):
 	def validate(self):
+		if self.applicant_type == 'Employee' and self.repay_from_salary:
+			validate_employee_currency_with_company_currency(self.applicant, self.company)
 		self.set_loan_amount()
 		self.validate_loan_amount()
 		self.set_missing_fields()
@@ -278,10 +281,13 @@
 		return write_off
 
 @frappe.whitelist()
-def unpledge_security(loan=None, loan_security_pledge=None, as_dict=0, save=0, submit=0, approve=0):
-	# if loan is passed it will be considered as full unpledge
+def unpledge_security(loan=None, loan_security_pledge=None, security_map=None, as_dict=0, save=0, submit=0, approve=0):
+	# if no security_map is passed it will be considered as full unpledge
+	if security_map and isinstance(security_map, string_types):
+		security_map = json.loads(security_map)
+
 	if loan:
-		pledge_qty_map = get_pledged_security_qty(loan)
+		pledge_qty_map = security_map or get_pledged_security_qty(loan)
 		loan_doc = frappe.get_doc('Loan', loan)
 		unpledge_request = create_loan_security_unpledge(pledge_qty_map, loan_doc.name, loan_doc.company,
 			loan_doc.applicant_type, loan_doc.applicant)
@@ -329,5 +335,14 @@
 
 	return unpledge_request
 
-
-
+def validate_employee_currency_with_company_currency(applicant, company):
+		from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_employee_currency
+		if not applicant:
+			frappe.throw(_("Please select Applicant"))
+		if not company:
+			frappe.throw(_("Please select Company"))
+		employee_currency = get_employee_currency(applicant)
+		company_currency = erpnext.get_company_currency(company)
+		if employee_currency != company_currency:
+			frappe.throw(_("Loan cannot be repayed from salary for Employee {0} because salary is processed in currency {1}")
+				.format(applicant, employee_currency))
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 10a7b11..8b1f9a2 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -19,6 +19,7 @@
 from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge
 from erpnext.loan_management.doctype.loan_disbursement.loan_disbursement import get_disbursal_amount
 from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts
+from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
 
 class TestLoan(unittest.TestCase):
 	def setUp(self):
@@ -44,6 +45,7 @@
 		create_loan_security_price("Test Security 2", 250, "Nos", get_datetime() , get_datetime(add_to_date(nowdate(), hours=24)))
 
 		self.applicant1 = make_employee("robert_loan@loan.com")
+		make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant1, currency='INR', company="_Test Company")
 		if not frappe.db.exists("Customer", "_Test Loan Customer"):
 			frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True)
 
@@ -323,6 +325,43 @@
 		self.assertEquals(amounts['payable_principal_amount'], 0.0)
 		self.assertEqual(amounts['interest_amount'], 0)
 
+	def test_partial_loan_security_unpledge(self):
+		pledge = [{
+			"loan_security": "Test Security 1",
+			"qty": 2000.00
+		},
+		{
+			"loan_security": "Test Security 2",
+			"qty": 4000.00
+		}]
+
+		loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+		create_pledge(loan_application)
+
+		loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+		loan.submit()
+
+		self.assertEquals(loan.loan_amount, 1000000)
+
+		first_date = '2019-10-01'
+		last_date = '2019-10-30'
+
+		make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
+		process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
+
+		repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), 600000)
+		repayment_entry.submit()
+
+		unpledge_map = {'Test Security 2': 2000}
+
+		unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1)
+		unpledge_request.submit()
+		unpledge_request.status = 'Approved'
+		unpledge_request.save()
+		unpledge_request.submit()
+		unpledge_request.load_from_db()
+		self.assertEqual(unpledge_request.docstatus, 1)
+
 	def test_disbursal_check_with_shortfall(self):
 		pledges = [{
 			"loan_security": "Test Security 2",
diff --git a/erpnext/loan_management/doctype/loan_application/test_loan_application.py b/erpnext/loan_management/doctype/loan_application/test_loan_application.py
index 687c580..2a659e9 100644
--- a/erpnext/loan_management/doctype/loan_application/test_loan_application.py
+++ b/erpnext/loan_management/doctype/loan_application/test_loan_application.py
@@ -5,7 +5,7 @@
 
 import frappe
 import unittest
-from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_employee
+from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_employee, make_salary_structure
 from erpnext.loan_management.doctype.loan.test_loan import create_loan_type, create_loan_accounts
 
 class TestLoanApplication(unittest.TestCase):
@@ -14,6 +14,7 @@
 		create_loan_type("Home Loan", 500000, 9.2, 0, 1, 0, 'Cash', 'Payment Account - _TC', 'Loan Account - _TC',
 			'Interest Income Account - _TC', 'Penalty Income Account - _TC', 'Repay Over Number of Periods', 18)
 		self.applicant = make_employee("kate_loan@loan.com", "_Test Company")
+		make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant, currency='INR')
 		self.create_loan_application()
 
 	def create_loan_application(self):
@@ -29,7 +30,6 @@
 		})
 		loan_application.insert()
 
-
 	def test_loan_totals(self):
 		loan_application = frappe.get_doc("Loan Application", {"applicant":self.applicant})
 
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
index 233862b..f341e81 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
@@ -171,10 +171,10 @@
 	return security_value
 
 @frappe.whitelist()
-def get_disbursal_amount(loan):
-	loan_details = frappe.get_all("Loan", fields = ["loan_amount", "disbursed_amount", "total_payment",
-		"total_principal_paid", "total_interest_payable", "status", "is_term_loan", "is_secured_loan"],
-		filters= { "name": loan })[0]
+def get_disbursal_amount(loan, on_current_security_price=0):
+	loan_details = frappe.get_value("Loan", loan, ["loan_amount", "disbursed_amount", "total_payment",
+		"total_principal_paid", "total_interest_payable", "status", "is_term_loan", "is_secured_loan",
+		"maximum_loan_amount"], as_dict=1)
 
 	if loan_details.is_secured_loan and frappe.get_all('Loan Security Shortfall', filters={'loan': loan,
 		'status': 'Pending'}):
@@ -188,9 +188,12 @@
 			- flt(loan_details.total_principal_paid)
 
 	security_value = 0.0
-	if loan_details.is_secured_loan:
+	if loan_details.is_secured_loan and on_current_security_price:
 		security_value = get_total_pledged_security_value(loan)
 
+	if loan_details.is_secured_loan and not on_current_security_price:
+		security_value = flt(loan_details.maximum_loan_amount)
+
 	if not security_value and not loan_details.is_secured_loan:
 		security_value = flt(loan_details.loan_amount)
 
diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
index 8ec0bfb..6469806 100644
--- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
+++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
@@ -81,7 +81,6 @@
 				process_loan_security_shortfall)
 
 def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, process_loan_security_shortfall):
-
 	existing_shortfall = frappe.db.get_value("Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name")
 
 	if existing_shortfall:
diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
index c29f325..61c418d 100644
--- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
+++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
@@ -30,6 +30,8 @@
 					d.idx, frappe.bold(d.loan_security)))
 
 	def validate_unpledge_qty(self):
+		from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import get_ltv_ratio
+
 		pledge_qty_map = get_pledged_security_qty(self.loan)
 
 		ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type",
@@ -47,6 +49,8 @@
 
 		pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount)
 		security_value = 0
+		unpledge_qty_map = {}
+		ltv_ratio = 0
 
 		for security in self.securities:
 			pledged_qty = pledge_qty_map.get(security.loan_security, 0)
@@ -57,13 +61,15 @@
 				msg += _("You are trying to unpledge more.")
 				frappe.throw(msg, title=_("Loan Security Unpledge Error"))
 
-			qty_after_unpledge = pledged_qty - security.qty
-			ltv_ratio = ltv_ratio_map.get(security.loan_security_type)
+			unpledge_qty_map.setdefault(security.loan_security, 0)
+			unpledge_qty_map[security.loan_security] += security.qty
 
-			current_price = loan_security_price_map.get(security.loan_security)
-			if not current_price:
-				frappe.throw(_("No valid Loan Security Price found for {0}").format(frappe.bold(security.loan_security)))
+		for security in pledge_qty_map:
+			if not ltv_ratio:
+				ltv_ratio = get_ltv_ratio(security)
 
+			qty_after_unpledge = pledge_qty_map.get(security, 0) - unpledge_qty_map.get(security, 0)
+			current_price = loan_security_price_map.get(security)
 			security_value += qty_after_unpledge * current_price
 
 		if not security_value and flt(pending_principal_amount, 2) > 0:
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 8888a96..6363242 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -169,8 +169,8 @@
 			 'qty'			: args.get("qty") or args.get("stock_qty") or 1,
 			 'stock_qty'	: args.get("qty") or args.get("stock_qty") or 1,
 			 'base_rate'	: flt(rate) * (flt(self.conversion_rate) or 1),
-			 'include_item_in_manufacturing': cint(args['transfer_for_manufacture']) or 0,
-			 'sourced_by_supplier'		: args['sourced_by_supplier'] or 0
+			 'include_item_in_manufacturing': cint(args.get('transfer_for_manufacture')),
+			 'sourced_by_supplier'		: args.get('sourced_by_supplier', 0)
 		}
 
 		return ret_item
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js
index b051b32..4e8dd41 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card.js
@@ -31,6 +31,16 @@
 			}
 		}
 
+		frm.set_query("quality_inspection", function() {
+			return {
+				query: "erpnext.stock.doctype.quality_inspection.quality_inspection.quality_inspection_query",
+				filters: {
+					"item_code": frm.doc.production_item,
+					"reference_name": frm.doc.name
+				}
+			};
+		});
+
 		frm.trigger("toggle_operation_number");
 
 		if (frm.doc.docstatus == 0 && (frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity)
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json
index 575e719..5713f69 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.json
+++ b/erpnext/manufacturing/doctype/job_card/job_card.json
@@ -20,6 +20,7 @@
   "production_item",
   "item_name",
   "for_quantity",
+  "quality_inspection",
   "wip_warehouse",
   "column_break_12",
   "employee",
@@ -305,11 +306,19 @@
    "label": "Sequence Id",
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "depends_on": "eval:!doc.__islocal;",
+   "fieldname": "quality_inspection",
+   "fieldtype": "Link",
+   "label": "Quality Inspection",
+   "no_copy": 1,
+   "options": "Quality Inspection"
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-14 12:58:25.327897",
+ "modified": "2020-11-19 18:26:50.531664",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Job Card",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 4dfa78b..ec28eb7 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -17,6 +17,7 @@
 
 class OperationMismatchError(frappe.ValidationError): pass
 class OperationSequenceError(frappe.ValidationError): pass
+class JobCardCancelError(frappe.ValidationError): pass
 
 class JobCard(Document):
 	def validate(self):
@@ -217,33 +218,49 @@
 		field = "operation_id"
 		data = self.get_current_operation_data()
 		if data and len(data) > 0:
-			for_quantity = data[0].completed_qty
-			time_in_mins = data[0].time_in_mins
+			for_quantity = flt(data[0].completed_qty)
+			time_in_mins = flt(data[0].time_in_mins)
 
-		if self.get(field):
-			time_data = frappe.db.sql("""
+		wo = frappe.get_doc('Work Order', self.work_order)
+		if self.operation_id:
+			self.validate_produced_quantity(for_quantity, wo)
+			self.update_work_order_data(for_quantity, time_in_mins, wo)
+
+	def validate_produced_quantity(self, for_quantity, wo):
+		if self.docstatus < 2: return
+
+		if wo.produced_qty > for_quantity:
+			first_part_msg = (_("The {0} {1} is used to calculate the valuation cost for the finished good {2}.")
+				.format(frappe.bold(_("Job Card")), frappe.bold(self.name), frappe.bold(self.production_item)))
+
+			second_part_msg = (_("Kindly cancel the Manufacturing Entries first against the work order {0}.")
+				.format(frappe.bold(get_link_to_form("Work Order", self.work_order))))
+
+			frappe.throw(_("{0} {1}").format(first_part_msg, second_part_msg),
+				JobCardCancelError, title = _("Error"))
+
+	def update_work_order_data(self, for_quantity, time_in_mins, wo):
+		time_data = frappe.db.sql("""
 				SELECT
 					min(from_time) as start_time, max(to_time) as end_time
 				FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
 				WHERE
 					jctl.parent = jc.name and jc.work_order = %s
-					and jc.{0} = %s and jc.docstatus = 1
-			""".format(field), (self.work_order, self.get(field)), as_dict=1)
+					and jc.operation_id = %s and jc.docstatus = 1
+			""", (self.work_order, self.operation_id), as_dict=1)
 
-			wo = frappe.get_doc('Work Order', self.work_order)
+		for data in wo.operations:
+			if data.get("name") == self.operation_id:
+				data.completed_qty = for_quantity
+				data.actual_operation_time = time_in_mins
+				data.actual_start_time = time_data[0].start_time if time_data else None
+				data.actual_end_time = time_data[0].end_time if time_data else None
 
-			for data in wo.operations:
-				if data.get("name") == self.get(field):
-					data.completed_qty = for_quantity
-					data.actual_operation_time = time_in_mins
-					data.actual_start_time = time_data[0].start_time if time_data else None
-					data.actual_end_time = time_data[0].end_time if time_data else None
-
-			wo.flags.ignore_validate_update_after_submit = True
-			wo.update_operation_status()
-			wo.calculate_operating_cost()
-			wo.set_actual_dates()
-			wo.save()
+		wo.flags.ignore_validate_update_after_submit = True
+		wo.update_operation_status()
+		wo.calculate_operating_cost()
+		wo.set_actual_dates()
+		wo.save()
 
 	def get_current_operation_data(self):
 		return frappe.get_all('Job Card',
@@ -353,17 +370,19 @@
 
 @frappe.whitelist()
 def get_operations(doctype, txt, searchfield, start, page_len, filters):
-	if filters.get("work_order"):
-		args = {"parent": filters.get("work_order")}
-		if txt:
-			args["operation"] = ("like", "%{0}%".format(txt))
+	if not filters.get("work_order"):
+		frappe.msgprint(_("Please select a Work Order first."))
+		return []
+	args = {"parent": filters.get("work_order")}
+	if txt:
+		args["operation"] = ("like", "%{0}%".format(txt))
 
-		return frappe.get_all("Work Order Operation",
-			filters = args,
-			fields = ["distinct operation as operation"],
-			limit_start = start,
-			limit_page_length = page_len,
-			order_by="idx asc", as_list=1)
+	return frappe.get_all("Work Order Operation",
+		filters = args,
+		fields = ["distinct operation as operation"],
+		limit_start = start,
+		limit_page_length = page_len,
+		order_by="idx asc", as_list=1)
 
 @frappe.whitelist()
 def make_material_request(source_name, target_doc=None):
diff --git a/erpnext/manufacturing/doctype/job_card/job_card_calendar.js b/erpnext/manufacturing/doctype/job_card/job_card_calendar.js
index cf07698..f4877fd 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card_calendar.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card_calendar.js
@@ -8,7 +8,17 @@
 		"allDay": "allDay",
 		"progress": "progress"
 	},
-	gantt: true,
+	gantt: {
+		field_map: {
+			"start": "started_time",
+			"end": "started_time",
+			"id": "name",
+			"title": "subject",
+			"color": "color",
+			"allDay": "allDay",
+			"progress": "progress"
+		}
+	},
 	filters: [
 		{
 			"fieldtype": "Link",
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index e539279..a77bd15 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -5,8 +5,7 @@
 from __future__ import unicode_literals
 import unittest
 import frappe
-from frappe.utils import flt, time_diff_in_hours, now, add_months, cint, today
-from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
+from frappe.utils import flt, now, add_months, cint, today, add_to_date
 from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry,
 	ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError, CapacityError)
 from erpnext.stock.doctype.stock_entry import test_stock_entry
@@ -15,10 +14,10 @@
 from erpnext.stock.doctype.item.test_item import make_item
 from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
 from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError
 
 class TestWorkOrder(unittest.TestCase):
 	def setUp(self):
-		set_perpetual_inventory(0)
 		self.warehouse = '_Test Warehouse 2 - _TC'
 		self.item = '_Test Item'
 
@@ -371,21 +370,49 @@
 		self.assertEqual(ste.total_additional_costs, 1000)
 
 	def test_job_card(self):
+		stock_entries = []
 		data = frappe.get_cached_value('BOM',
 			{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
 
-		if data:
-			frappe.db.set_value("Manufacturing Settings",
-				None, "disable_capacity_planning", 0)
+		bom, bom_item = data
 
-			bom, bom_item = data
+		bom_doc = frappe.get_doc('BOM', bom)
+		work_order = make_wo_order_test_record(item=bom_item, qty=1,
+			bom_no=bom, source_warehouse="_Test Warehouse - _TC")
 
-			bom_doc = frappe.get_doc('BOM', bom)
-			work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
-			self.assertTrue(work_order.planned_end_date)
+		for row in work_order.required_items:
+			stock_entry_doc = test_stock_entry.make_stock_entry(item_code=row.item_code,
+				target="_Test Warehouse - _TC", qty=row.required_qty, basic_rate=100)
+			stock_entries.append(stock_entry_doc)
 
-			job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
-			self.assertEqual(len(job_cards), len(bom_doc.operations))
+		ste = frappe.get_doc(make_stock_entry(work_order.name, "Material Transfer for Manufacture", 1))
+		ste.submit()
+		stock_entries.append(ste)
+
+		job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
+		self.assertEqual(len(job_cards), len(bom_doc.operations))
+
+		for i, job_card in enumerate(job_cards):
+			doc = frappe.get_doc("Job Card", job_card)
+			doc.append("time_logs", {
+				"from_time": now(),
+				"hours": i,
+				"to_time": add_to_date(now(), i),
+				"completed_qty": doc.for_quantity
+			})
+			doc.submit()
+
+		ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
+		ste1.submit()
+		stock_entries.append(ste1)
+
+		for job_card in job_cards:
+			doc = frappe.get_doc("Job Card", job_card)
+			self.assertRaises(JobCardCancelError, doc.cancel)
+
+		stock_entries.reverse()
+		for stock_entry in stock_entries:
+			stock_entry.cancel()
 
 	def test_capcity_planning(self):
 		frappe.db.set_value("Manufacturing Settings", None, {
@@ -491,6 +518,38 @@
 		work_order1.save()
 		self.assertEqual(work_order1.operations[0].time_in_mins, 40.0)
 
+	def test_partial_material_consumption(self):
+		frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1)
+		wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
+
+		ste_cancel_list = []
+		ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
+			target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0)
+		ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
+			target="_Test Warehouse - _TC", qty=20, basic_rate=1000.0)
+
+		ste_cancel_list.extend([ste1, ste2])
+
+		s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 4))
+		s.submit()
+		ste_cancel_list.append(s)
+
+		ste1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
+		ste1.submit()
+		ste_cancel_list.append(ste1)
+
+		ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2))
+		self.assertEquals(ste3.fg_completed_qty, 2)
+
+		expected_qty = {"_Test Item": 2, "_Test Item Home Desktop 100": 4}
+		for row in ste3.items:
+			self.assertEquals(row.qty, expected_qty.get(row.item_code))
+
+		for ste_doc in ste_cancel_list:
+			ste_doc.cancel()
+
+		frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0)
+
 def get_scrap_item_details(bom_no):
 	scrap_items = {}
 	for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 9ce465c..a6086fb 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -545,7 +545,8 @@
 						var tbl = frm.doc.required_items || [];
 						var tbl_lenght = tbl.length;
 						for (var i = 0, len = tbl_lenght; i < len; i++) {
-							if (flt(frm.doc.required_items[i].required_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
+							let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
+							if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
 								counter += 1;
 							}
 						}
diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js
index 2ac6fa0..8cd0164 100644
--- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js
+++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js
@@ -25,11 +25,11 @@
 	],
 	"formatter": function(value, row, column, data, default_formatter) {
 		value = default_formatter(value, row, column, data);
-		if (column.id == "Item"){
-			if (data["Enough Parts to Build"] > 0){
-				value = `<a style='color:green' href="#Form/Item/${data['Item']}" data-doctype="Item">${data['Item']}</a>`
+		if (column.id == "item") {
+			if (data["enough_parts_to_build"] > 0) {
+				value = `<a style='color:green' href="#Form/Item/${data['item']}" data-doctype="Item">${data['item']}</a>`;
 			} else {
-				value = `<a style='color:red' href="#Form/Item/${data['Item']}" data-doctype="Item">${data['Item']}</a>`
+				value = `<a style='color:red' href="#Form/Item/${data['item']}" data-doctype="Item">${data['item']}</a>`;
 			}
 		}
 		return value
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
index ebc01c6..806d268 100644
--- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
@@ -124,7 +124,7 @@
 				if self.filters.include_subassembly_raw_materials else "(bom_item.qty / bom.quantity)")
 
 			raw_materials = frappe.db.sql(""" SELECT bom_item.parent, bom_item.item_code,
-					bom_item.item_name as raw_material_name, {0} as required_qty
+					bom_item.item_name as raw_material_name, {0} as required_qty_per_unit
 				FROM
 					`tabBOM` as bom, `tab{1}` as bom_item
 				WHERE
@@ -208,7 +208,7 @@
 		warehouses = self.mrp_warehouses or []
 		for d in self.raw_materials_dict.get(key):
 			if self.filters.based_on != "Work Order":
-				d.required_qty = d.required_qty * data.qty_to_manufacture
+				d.required_qty = d.required_qty_per_unit * data.qty_to_manufacture
 
 			if not warehouses:
 				warehouses = [data.warehouse]
diff --git a/erpnext/modules.txt b/erpnext/modules.txt
index 1e2aeea..62f5dce 100644
--- a/erpnext/modules.txt
+++ b/erpnext/modules.txt
@@ -25,4 +25,5 @@
 Quality Management
 Communication
 Loan Management
-Payroll
\ No newline at end of file
+Payroll
+Telephony
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py
index 44b975e..25d6b53 100644
--- a/erpnext/non_profit/doctype/member/member.py
+++ b/erpnext/non_profit/doctype/member/member.py
@@ -59,7 +59,7 @@
 			frappe.msgprint(_("A customer is already linked to this Member"))
 		cust = create_customer(frappe._dict({
 			'fullname': self.member_name,
-			'email': self.email_id or self.user,
+			'email': self.email_id or self.email,
 			'phone': None
 		}))
 
@@ -177,4 +177,4 @@
 			mobile=mobile
 		))
 
-		return member.name
\ No newline at end of file
+		return member.name
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 25be884..f2e4f72 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -450,7 +450,6 @@
 erpnext.patches.v9_0.add_user_to_child_table_in_pos_profile
 erpnext.patches.v9_0.set_schedule_date_for_material_request_and_purchase_order
 erpnext.patches.v9_0.student_admission_childtable_migrate
-erpnext.patches.v9_0.fix_subscription_next_date #2017-10-23
 erpnext.patches.v9_0.add_healthcare_domain
 erpnext.patches.v9_0.set_variant_item_description
 erpnext.patches.v9_0.set_uoms_in_variant_field
@@ -691,6 +690,7 @@
 erpnext.patches.v12_0.set_serial_no_status #2020-05-21
 erpnext.patches.v12_0.update_price_list_currency_in_bom
 execute:frappe.reload_doctype('Dashboard')
+execute:frappe.reload_doc('desk', 'doctype', 'number_card_link')
 execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')
 erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo
 erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2020-05-25
@@ -711,6 +711,7 @@
 execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation")
 erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020
 erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020
+execute:frappe.reload_doc("regional", "doctype", "e_invoice_settings")
 erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020
 erpnext.patches.v13_0.loyalty_points_entry_for_pos_invoice #22-07-2020
 erpnext.patches.v12_0.add_taxjar_integration_field
@@ -732,6 +733,12 @@
 erpnext.patches.v13_0.print_uom_after_quantity_patch
 erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account
 erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail
+erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02
+erpnext.patches.v13_0.updates_for_multi_currency_payroll
 erpnext.patches.v13_0.update_reason_for_resignation_in_employee
 erpnext.patches.v13_0.update_custom_fields_for_shopify
 execute:frappe.delete_doc("Report", "Quoted Item Comparison")
+erpnext.patches.v13_0.updates_for_multi_currency_payroll
+erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
+erpnext.patches.v13_0.add_po_to_global_search
+erpnext.patches.v13_0.update_returned_qty_in_pr_dn
diff --git a/erpnext/patches/v11_0/create_salary_structure_assignments.py b/erpnext/patches/v11_0/create_salary_structure_assignments.py
index c51c381..a908c16 100644
--- a/erpnext/patches/v11_0/create_salary_structure_assignments.py
+++ b/erpnext/patches/v11_0/create_salary_structure_assignments.py
@@ -8,8 +8,8 @@
 from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import DuplicateAssignment
 
 def execute():
-	frappe.reload_doc('Payroll', 'doctype', 'salary_structure')
-	frappe.reload_doc("Payroll", "doctype", "salary_structure_assignment")
+	frappe.reload_doc('Payroll', 'doctype', 'Salary Structure')
+	frappe.reload_doc("Payroll", "doctype", "Salary Structure Assignment")
 	frappe.db.sql("""
 		delete from `tabSalary Structure Assignment`
 		where salary_structure in (select name from `tabSalary Structure` where is_active='No' or docstatus!=1)
@@ -33,6 +33,13 @@
 			AND employee in (select name from `tabEmployee` where ifNull(status, '') != 'Left')
 		""".format(cols), as_dict=1)
 
+	all_companies = frappe.db.get_all("Company", fields=["name", "default_currency"])
+	for d in all_companies:
+		company = d.name
+		company_currency = d.default_currency
+
+		frappe.db.sql("""update `tabSalary Structure` set currency = %s where company=%s""", (company_currency, company))
+
 	for d in ss_details:
 		try:
 			joining_date, relieving_date = frappe.db.get_value("Employee", d.employee,
@@ -42,6 +49,7 @@
 				from_date = joining_date
 			elif relieving_date and getdate(from_date) > relieving_date:
 				continue
+			company_currency = frappe.db.get_value('Company', d.company, 'default_currency')
 
 			s = frappe.new_doc("Salary Structure Assignment")
 			s.employee = d.employee
@@ -52,6 +60,7 @@
 			s.base = d.get("base")
 			s.variable = d.get("variable")
 			s.company = d.company
+			s.currency = company_currency
 
 			# to migrate the data of the old employees
 			s.flags.old_employee = True
diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py
new file mode 100644
index 0000000..2474bc3
--- /dev/null
+++ b/erpnext/patches/v12_0/setup_einvoice_fields.py
@@ -0,0 +1,56 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+from erpnext.regional.india.setup import add_permissions, add_print_formats
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'India'})
+	if not company:
+		return
+
+	frappe.reload_doc("custom", "doctype", "custom_field")
+	frappe.reload_doc("regional", "doctype", "e_invoice_settings")
+	custom_fields = {
+		'Sales Invoice': [
+			dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1,
+				depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'),
+			
+			dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1),
+		
+			dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
+
+			dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+				depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+
+			dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+				depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+
+			dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+			dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+			dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1)
+		]
+	}
+	create_custom_fields(custom_fields, update=True)
+	add_permissions()
+	add_print_formats()
+
+	einvoice_cond = 'in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category)'
+	t = {
+		'mode_of_transport': [{'default': None}],
+		'distance': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.transporter'}],
+		'gst_vehicle_type': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}],
+		'lr_date': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}],
+		'lr_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}],
+		'vehicle_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}],
+		'ewaybill': [
+			{'read_only_depends_on': 'eval:doc.irn && doc.ewaybill'},
+			{'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)'}
+		]
+	}
+
+	for field, conditions in t.items():
+		for c in conditions:
+			[(prop, value)] = c.items()
+			frappe.db.set_value('Custom Field', { 'fieldname': field }, prop, value)
diff --git a/erpnext/patches/v13_0/add_po_to_global_search.py b/erpnext/patches/v13_0/add_po_to_global_search.py
new file mode 100644
index 0000000..1c60b18
--- /dev/null
+++ b/erpnext/patches/v13_0/add_po_to_global_search.py
@@ -0,0 +1,17 @@
+from __future__ import unicode_literals
+import frappe
+
+
+def execute():
+    global_search_settings = frappe.get_single("Global Search Settings")
+
+    if "Purchase Order" in (
+        dt.document_type for dt in global_search_settings.allowed_in_global_search
+    ):
+        return
+
+    global_search_settings.append(
+        "allowed_in_global_search", {"document_type": "Purchase Order"}
+    )
+
+    global_search_settings.save(ignore_permissions=True)
diff --git a/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py b/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py
new file mode 100644
index 0000000..90dc0e2
--- /dev/null
+++ b/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py
@@ -0,0 +1,79 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+def execute():
+    if "leave_policy" in frappe.db.get_table_columns("Employee"):
+        employees_with_leave_policy = frappe.db.sql("SELECT name, leave_policy FROM `tabEmployee` WHERE leave_policy IS NOT NULL and leave_policy != ''", as_dict = 1)
+
+        employee_with_assignment = []
+        leave_policy =[]
+
+        #for employee
+
+        for employee in employees_with_leave_policy:
+            alloc = frappe.db.exists("Leave Allocation", {"employee":employee.name, "leave_policy": employee.leave_policy, "docstatus": 1})
+            if not alloc:
+                create_assignment(employee.name, employee.leave_policy)
+
+            employee_with_assignment.append(employee.name)
+            leave_policy.append(employee.leave_policy)
+
+
+    if "default_leave_policy" in frappe.db.get_table_columns("Employee"):
+        employee_grade_with_leave_policy = frappe.db.sql("SELECT name, default_leave_policy FROM `tabEmployee Grade` WHERE default_leave_policy IS NOT NULL and default_leave_policy!=''", as_dict = 1)
+
+        #for whole employee Grade
+
+        for grade in employee_grade_with_leave_policy:
+            employees = get_employee_with_grade(grade.name)
+            for employee in employees:
+
+                if employee not in employee_with_assignment: #Will ensure no duplicate
+                    alloc = frappe.db.exists("Leave Allocation", {"employee":employee.name, "leave_policy": grade.default_leave_policy, "docstatus": 1})
+                    if not alloc:
+                        create_assignment(employee.name, grade.default_leave_policy)
+                    leave_policy.append(grade.default_leave_policy)
+
+    #for old Leave allocation and leave policy from allocation, which may got updated in employee grade.
+    leave_allocations = frappe.db.sql("SELECT leave_policy, leave_period, employee FROM `tabLeave Allocation` WHERE leave_policy IS NOT NULL and leave_policy != '' and docstatus = 1 ", as_dict = 1)
+
+    for allocation in leave_allocations:
+        if allocation.leave_policy not in leave_policy:
+            create_assignment(allocation.employee, allocation.leave_policy, leave_period=allocation.leave_period,
+                allocation_exists=True)
+
+def create_assignment(employee, leave_policy, leave_period=None, allocation_exists = False):
+
+    filters = {"employee":employee, "leave_policy": leave_policy}
+    if leave_period:
+        filters["leave_period"] = leave_period
+
+    frappe.reload_doc('hr', 'doctype', 'leave_policy_assignment')
+
+    if not frappe.db.exists("Leave Policy Assignment" , filters):
+        lpa = frappe.new_doc("Leave Policy Assignment")
+        lpa.employee = employee
+        lpa.leave_policy = leave_policy
+
+        lpa.flags.ignore_mandatory = True
+        if allocation_exists:
+            lpa.assignment_based_on = 'Leave Period'
+            lpa.leave_period = leave_period
+            lpa.leaves_allocated = 1
+
+        lpa.save()
+        if allocation_exists:
+            lpa.submit()
+            #Updating old Leave Allocation
+            frappe.db.sql("Update `tabLeave Allocation` set leave_policy_assignment = %s", lpa.name)
+
+
+def get_employee_with_grade(grade):
+    return frappe.get_list("Employee", filters = {"grade": grade})
+
+
+
diff --git a/erpnext/patches/v13_0/rename_issue_doctype_fields.py b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
index 96a6362..fa1dfed 100644
--- a/erpnext/patches/v13_0/rename_issue_doctype_fields.py
+++ b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
@@ -29,7 +29,7 @@
 				'response_by_variance': response_by_variance,
 				'resolution_by_variance': resolution_by_variance,
 				'first_response_time': mins_to_first_response
-			})
+			}, update_modified=False)
 			# commit after every 100 updates
 			count += 1
 			if count%100 == 0:
@@ -44,7 +44,7 @@
 		count = 0
 		for entry in opportunities:
 			mins_to_first_response = convert_to_seconds(entry.mins_to_first_response, 'Minutes')
-			frappe.db.set_value('Opportunity', entry.name, 'first_response_time', mins_to_first_response)
+			frappe.db.set_value('Opportunity', entry.name, 'first_response_time', mins_to_first_response, update_modified=False)
 			# commit after every 100 updates
 			count += 1
 			if count%100 == 0:
diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py
index 7723942..8cf09aa 100644
--- a/erpnext/patches/v13_0/update_old_loans.py
+++ b/erpnext/patches/v13_0/update_old_loans.py
@@ -1,10 +1,12 @@
 from __future__ import unicode_literals
 import frappe
 from frappe import _
-from frappe.utils import nowdate
+from frappe.utils import nowdate, flt
 from erpnext.accounts.doctype.account.test_account import create_account
 from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
 from erpnext.loan_management.doctype.loan.loan import make_repayment_entry
+from erpnext.loan_management.doctype.loan_repayment.loan_repayment import get_accrued_interest_entries
+from frappe.model.naming import make_autoname
 
 def execute():
 
@@ -18,15 +20,29 @@
 	frappe.reload_doc('loan_management', 'doctype', 'loan_repayment_detail')
 	frappe.reload_doc('loan_management', 'doctype', 'loan_interest_accrual')
 	frappe.reload_doc('accounts', 'doctype', 'gl_entry')
+	frappe.reload_doc('accounts', 'doctype', 'journal_entry_account')
 
 	updated_loan_types = []
+	loans_to_close = []
+
+	# Update old loan status as closed
+	if frappe.db.has_column('Repayment Schedule', 'paid'):
+		loans_list = frappe.db.sql("""SELECT distinct parent from `tabRepayment Schedule`
+			where paid = 0 and docstatus = 1""", as_dict=1)
+
+		loans_to_close = [d.parent for d in loans_list]
+
+	if loans_to_close:
+		frappe.db.sql("UPDATE `tabLoan` set status = 'Closed' where name not in (%s)" % (', '.join(['%s'] * len(loans_to_close))), tuple(loans_to_close))
 
 	loans = frappe.get_all('Loan', fields=['name', 'loan_type', 'company', 'status', 'mode_of_payment',
-		'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account'])
+		'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account'],
+		filters={'docstatus': 1, 'status': ('!=', 'Closed')})
 
 	for loan in loans:
 		# Update details in Loan Types and Loan
 		loan_type_company = frappe.db.get_value('Loan Type', loan.loan_type, 'company')
+		loan_type = loan.loan_type
 
 		group_income_account = frappe.get_value('Account', {'company': loan.company,
 			'is_group': 1, 'root_type': 'Income', 'account_name': _('Indirect Income')})
@@ -38,7 +54,26 @@
 		penalty_account = create_account(company=loan.company, account_type='Income Account',
 			account_name='Penalty Account', parent_account=group_income_account)
 
-		if not loan_type_company:
+		# Same loan type used for multiple companies
+		if loan_type_company and loan_type_company != loan.company:
+			# get loan type for appropriate company
+			loan_type_name = frappe.get_value('Loan Type', {'company': loan.company,
+				'mode_of_payment': loan.mode_of_payment, 'loan_account': loan.loan_account,
+				'payment_account': loan.payment_account, 'interest_income_account': loan.interest_income_account,
+				'penalty_income_account': loan.penalty_income_account}, 'name')
+
+			if not loan_type_name:
+				loan_type_name = create_loan_type(loan, loan_type_name, penalty_account)
+
+			# update loan type in loan
+			frappe.db.sql("UPDATE `tabLoan` set loan_type = %s where name = %s", (loan_type_name,
+				loan.name))
+
+			loan_type = loan_type_name
+			if loan_type_name not in updated_loan_types:
+				updated_loan_types.append(loan_type_name)
+
+		elif not loan_type_company:
 			loan_type_doc = frappe.get_doc('Loan Type', loan.loan_type)
 			loan_type_doc.is_term_loan = 1
 			loan_type_doc.company = loan.company
@@ -49,8 +84,9 @@
 			loan_type_doc.penalty_income_account = penalty_account
 			loan_type_doc.submit()
 			updated_loan_types.append(loan.loan_type)
+			loan_type = loan.loan_type
 
-		if loan.loan_type in updated_loan_types:
+		if loan_type in updated_loan_types:
 			if loan.status == 'Fully Disbursed':
 				status = 'Disbursed'
 			elif loan.status == 'Repaid/Closed':
@@ -64,25 +100,48 @@
 				'status': status
 			})
 
-			process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan.loan_type,
+			process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan_type,
 				loan=loan.name)
 
-			payments = frappe.db.sql(''' SELECT j.name, a.debit, a.debit_in_account_currency, j.posting_date
-				FROM `tabJournal Entry` j, `tabJournal Entry Account` a
-				WHERE a.parent = j.name and a.reference_type='Loan' and a.reference_name = %s
-				and account = %s
-			''', (loan.name, loan.loan_account), as_dict=1)
 
-			for payment in payments:
-				repayment_entry = make_repayment_entry(loan.name, loan.loan_applicant_type, loan.applicant,
-					loan.loan_type, loan.company)
+			if frappe.db.has_column('Repayment Schedule', 'paid'):
+				total_principal, total_interest = frappe.db.get_value('Repayment Schedule', {'paid': 1, 'parent': loan.name},
+					['sum(principal_amount) as total_principal', 'sum(interest_amount) as total_interest'])
 
-				repayment_entry.amount_paid = payment.debit_in_account_currency
-				repayment_entry.posting_date = payment.posting_date
-				repayment_entry.save()
-				repayment_entry.submit()
+				accrued_entries = get_accrued_interest_entries(loan.name)
+				for entry in accrued_entries:
+					interest_paid = 0
+					principal_paid = 0
 
-				jv = frappe.get_doc('Journal Entry', payment.name)
-				jv.flags.ignore_links = True
-				jv.cancel()
+					if flt(total_interest) > flt(entry.interest_amount):
+						interest_paid = flt(entry.interest_amount)
+					else:
+						interest_paid = flt(total_interest)
 
+					if flt(total_principal) > flt(entry.payable_principal_amount):
+						principal_paid = flt(entry.payable_principal_amount)
+					else:
+						principal_paid = flt(total_principal)
+
+					frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
+						SET paid_principal_amount = `paid_principal_amount` + %s,
+							paid_interest_amount = `paid_interest_amount` + %s
+						WHERE name = %s""",
+						(principal_paid, interest_paid, entry.name))
+
+					total_principal = flt(total_principal) - principal_paid
+					total_interest = flt(total_interest) - interest_paid
+
+def create_loan_type(loan, loan_type_name, penalty_account):
+	loan_type_doc = frappe.new_doc('Loan Type')
+	loan_type_doc.loan_name = make_autoname("Loan Type-.####")
+	loan_type_doc.is_term_loan = 1
+	loan_type_doc.company = loan.company
+	loan_type_doc.mode_of_payment = loan.mode_of_payment
+	loan_type_doc.payment_account = loan.payment_account
+	loan_type_doc.loan_account = loan.loan_account
+	loan_type_doc.interest_income_account = loan.interest_income_account
+	loan_type_doc.penalty_income_account = penalty_account
+	loan_type_doc.submit()
+
+	return loan_type_doc.name
diff --git a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py
new file mode 100644
index 0000000..7f42cd9
--- /dev/null
+++ b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py
@@ -0,0 +1,27 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	frappe.reload_doc('stock', 'doctype', 'purchase_receipt')
+	frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item')
+	frappe.reload_doc('stock', 'doctype', 'delivery_note')
+	frappe.reload_doc('stock', 'doctype', 'delivery_note_item')
+
+	def update_from_return_docs(doctype):
+		for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1}):
+			# Update original receipt/delivery document from return
+			return_doc = frappe.get_cached_doc(doctype, return_doc.name)
+			return_doc.update_prevdoc_status()
+			return_against = frappe.get_doc(doctype, return_doc.return_against)
+			return_against.update_billing_status()
+
+	# Set received qty in stock uom in PR, as returned qty is checked against it
+	frappe.db.sql(""" update `tabPurchase Receipt Item`
+		set received_stock_qty = received_qty * conversion_factor
+		where docstatus = 1 """)
+
+	for doctype in ('Purchase Receipt', 'Delivery Note'):
+		update_from_return_docs(doctype)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py b/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py
new file mode 100644
index 0000000..340bf49
--- /dev/null
+++ b/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py
@@ -0,0 +1,136 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+from frappe import _
+from frappe.model.utils.rename_field import rename_field
+
+def execute():
+
+	frappe.reload_doc('Accounts', 'doctype', 'Salary Component Account')
+	if frappe.db.has_column('Salary Component Account', 'default_account'):
+		rename_field("Salary Component Account", "default_account", "account")
+
+	doctype_list = [
+		{
+		'module':'HR',
+		'doctype':'Employee Advance'
+		},
+		{
+		'module':'HR',
+		'doctype':'Leave Encashment'
+		},
+		{
+		'module':'Payroll',
+		'doctype':'Additional Salary'
+		},
+		{
+		'module':'Payroll',
+		'doctype':'Employee Benefit Application'
+		},
+		{
+		'module':'Payroll',
+		'doctype':'Employee Benefit Claim'
+		},
+		{
+		'module':'Payroll',
+		'doctype':'Employee Incentive'
+		},
+		{
+		'module':'Payroll',
+		'doctype':'Employee Tax Exemption Declaration'
+		},
+		{
+		'module':'Payroll',
+		'doctype':'Employee Tax Exemption Proof Submission'
+		},
+		{
+		'module':'Payroll',
+		'doctype':'Income Tax Slab'
+		},
+		{
+		'module':'Payroll',
+		'doctype':'Payroll Entry'
+		},
+		{
+		'module':'Payroll',
+		'doctype':'Retention Bonus'
+		},
+		{
+		'module':'Payroll',
+		'doctype':'Salary Structure'
+		},
+		{
+		'module':'Payroll',
+		'doctype':'Salary Structure Assignment'
+		},
+		{
+		'module':'Payroll',
+		'doctype':'Salary Slip'
+		},
+	]
+
+	for item in doctype_list:
+		frappe.reload_doc(item['module'], 'doctype', item['doctype'])
+
+	# update company in employee advance based on employee company
+	for dt in ['Employee Incentive', 'Leave Encashment', 'Employee Benefit Application', 'Employee Benefit Claim']:
+		frappe.db.sql("""
+			update `tab{doctype}`
+			set company = (select company from tabEmployee where name=`tab{doctype}`.employee)
+		""".format(doctype=dt))
+
+	# update exchange rate for employee advance
+	frappe.db.sql("update `tabEmployee Advance` set exchange_rate=1")
+
+	# get all companies and it's currency
+	all_companies = frappe.db.get_all("Company", fields=["name", "default_currency", "default_payroll_payable_account"])
+	for d in all_companies:
+		company = d.name
+		company_currency = d.default_currency
+		default_payroll_payable_account = d.default_payroll_payable_account
+
+		if not default_payroll_payable_account:
+			default_payroll_payable_account = frappe.db.get_value("Account",
+				{"account_name": _("Payroll Payable"), "company": company, "account_currency": company_currency, "is_group": 0})
+
+		# update currency in following doctypes based on company currency
+		doctypes_for_currency = ['Employee Advance', 'Leave Encashment', 'Employee Benefit Application',
+			'Employee Benefit Claim', 'Employee Incentive', 'Additional Salary', 
+			'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission', 
+			'Income Tax Slab', 'Retention Bonus', 'Salary Structure']
+
+		for dt in doctypes_for_currency:
+			frappe.db.sql("""update `tab{doctype}` set currency = %s where company=%s"""
+				.format(doctype=dt), (company_currency, company))
+
+		# update fields in payroll entry
+		frappe.db.sql("""
+			update `tabPayroll Entry`
+			set currency = %s,
+				exchange_rate = 1,
+				payroll_payable_account=%s
+			where company=%s
+		""", (company_currency, default_payroll_payable_account, company))
+
+		# update fields in Salary Structure Assignment
+		frappe.db.sql("""
+			update `tabSalary Structure Assignment`
+			set currency = %s,
+				payroll_payable_account=%s
+			where company=%s
+		""", (company_currency, default_payroll_payable_account, company))
+
+		# update fields in Salary Slip
+		frappe.db.sql("""
+			update `tabSalary Slip`
+			set currency = %s,
+				exchange_rate = 1,
+				base_hour_rate = hour_rate,
+				base_gross_pay = gross_pay,
+				base_total_deduction = total_deduction,
+				base_net_pay = net_pay,
+				base_rounded_total = rounded_total,
+				base_total_in_words = total_in_words
+			where company=%s
+		""", (company_currency, company))
diff --git a/erpnext/patches/v7_0/po_status_issue_for_pr_return.py b/erpnext/patches/v7_0/po_status_issue_for_pr_return.py
index 6e92ffb..910814f 100644
--- a/erpnext/patches/v7_0/po_status_issue_for_pr_return.py
+++ b/erpnext/patches/v7_0/po_status_issue_for_pr_return.py
@@ -7,19 +7,23 @@
 def execute():
 	parent_list = []
 	count = 0
-	for data in frappe.db.sql(""" 
-		select 
+
+	frappe.reload_doc('stock', 'doctype', 'purchase_receipt')
+	frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item')
+
+	for data in frappe.db.sql("""
+		select
 			`tabPurchase Receipt Item`.purchase_order, `tabPurchase Receipt Item`.name,
 			`tabPurchase Receipt Item`.item_code, `tabPurchase Receipt Item`.idx,
 			`tabPurchase Receipt Item`.parent
-		from 
+		from
 			`tabPurchase Receipt Item`, `tabPurchase Receipt`
 		where
 			`tabPurchase Receipt Item`.parent = `tabPurchase Receipt`.name and
 			`tabPurchase Receipt Item`.purchase_order_item is null and
 			`tabPurchase Receipt Item`.purchase_order is not null and
 			`tabPurchase Receipt`.is_return = 1""", as_dict=1):
-			name = frappe.db.get_value('Purchase Order Item', 
+			name = frappe.db.get_value('Purchase Order Item',
 				{'item_code': data.item_code, 'parent': data.purchase_order, 'idx': data.idx}, 'name')
 
 			if name:
diff --git a/erpnext/patches/v9_0/fix_subscription_next_date.py b/erpnext/patches/v9_0/fix_subscription_next_date.py
deleted file mode 100644
index 4595c8d..0000000
--- a/erpnext/patches/v9_0/fix_subscription_next_date.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright (c) 2017, Frappe and Contributors
-# License: GNU General Public License v3. See license.txt
-
-from __future__ import unicode_literals
-import frappe
-from frappe.utils import getdate
-from frappe.automation.doctype.auto_repeat.auto_repeat import get_next_schedule_date
-
-def execute():
-	frappe.reload_doc('accounts', 'doctype', 'subscription')
-	fields = ["name", "reference_doctype", "reference_document",
-			"start_date", "frequency", "repeat_on_day"]
-
-	for d in fields:
-		if not frappe.db.has_column('Subscription', d):
-			return
-
-	doctypes = ('Purchase Order', 'Sales Order', 'Purchase Invoice', 'Sales Invoice')
-	for data in frappe.get_all('Subscription',
-		fields = fields,
-		filters = {'reference_doctype': ('in', doctypes), 'docstatus': 1}):
-
-		recurring_id = frappe.db.get_value(data.reference_doctype, data.reference_document, "recurring_id")
-		if recurring_id:
-			frappe.db.sql("update `tab{0}` set subscription=%s where recurring_id=%s"
-				.format(data.reference_doctype), (data.name, recurring_id))
-
-		date_field = 'transaction_date'
-		if data.reference_doctype in ['Sales Invoice', 'Purchase Invoice']:
-			date_field = 'posting_date'
-
-		start_date = frappe.db.get_value(data.reference_doctype, data.reference_document, date_field)
-
-		if start_date and getdate(start_date) != getdate(data.start_date):
-			last_ref_date = frappe.db.sql("""
-				select {0}
-				from `tab{1}`
-				where subscription=%s and docstatus < 2
-				order by creation desc
-				limit 1
-			""".format(date_field, data.reference_doctype), data.name)[0][0]
-
-			next_schedule_date = get_next_schedule_date(last_ref_date, data.frequency, data.repeat_on_day)
-
-			frappe.db.set_value("Subscription", data.name, {
-				"start_date": start_date,
-				"next_schedule_date": next_schedule_date
-			}, None)
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.js b/erpnext/payroll/doctype/additional_salary/additional_salary.js
index d56cd4e..7737e6c 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.js
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.js
@@ -13,4 +13,48 @@
 			};
 		});
 	},
+
+	employee: function(frm) {
+		if (frm.doc.employee) {
+			frappe.run_serially([
+				() => 	frm.trigger('get_employee_currency'),
+				() => 	frm.trigger('set_company')
+			]);
+		} else {
+			frm.set_value("company", null);
+		}
+	},
+
+	set_company: function(frm) {
+		frappe.call({
+			method: "frappe.client.get_value",
+			args: {
+				doctype: "Employee",
+				fieldname: "company",
+				filters: {
+					name: frm.doc.employee
+				}
+			},
+			callback: function(data) {
+				if (data.message) {
+					frm.set_value("company", data.message.company);
+				}
+			}
+		});
+	},
+
+	get_employee_currency: function(frm) {
+		frappe.call({
+			method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+			args: {
+				employee: frm.doc.employee,
+			},
+			callback: function(r) {
+				if (r.message) {
+					frm.set_value('currency', r.message);
+					frm.refresh_fields();
+				}
+			}
+		});
+	},
 });
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.json b/erpnext/payroll/doctype/additional_salary/additional_salary.json
index 69cb5da..2b29f66 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.json
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.json
@@ -11,20 +11,21 @@
   "employee",
   "employee_name",
   "salary_component",
-  "overwrite_salary_structure_amount",
-  "deduct_full_tax_on_selected_payroll_date",
+  "type",
+  "amount",
   "ref_doctype",
   "ref_docname",
+  "amended_from",
   "column_break_5",
   "company",
-  "is_recurring",
+  "department",
+  "currency",
   "from_date",
   "to_date",
   "payroll_date",
-  "type",
-  "department",
-  "amount",
-  "amended_from"
+  "is_recurring",
+  "overwrite_salary_structure_amount",
+  "deduct_full_tax_on_selected_payroll_date"
  ],
  "fields": [
   {
@@ -59,6 +60,7 @@
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Amount",
+   "options": "currency",
    "reqd": 1
   },
   {
@@ -159,11 +161,22 @@
    "label": "Reference Document",
    "options": "ref_doctype",
    "read_only": 1
+  },
+  {
+   "default": "Company:company:default_currency",
+   "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "label": "Currency",
+   "options": "Currency",
+   "print_hide": 1,
+   "read_only": 1,
+   "reqd": 1
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-22 21:10:50.374063",
+ "modified": "2020-10-20 17:51:13.419716",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Additional Salary",
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index e3dc907..f5af677 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -22,10 +22,15 @@
 
 	def validate(self):
 		self.validate_dates()
+		self.validate_salary_structure()
 		self.validate_recurring_additional_salary_overlap()
 		if self.amount < 0:
 			frappe.throw(_("Amount should not be less than zero."))
 
+	def validate_salary_structure(self):
+		if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
+			frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(self.employee))
+
 	def validate_recurring_additional_salary_overlap(self):
 		if self.is_recurring:
 			additional_salaries = frappe.db.sql("""
diff --git a/erpnext/payroll/doctype/additional_salary/test_additional_salary.py b/erpnext/payroll/doctype/additional_salary/test_additional_salary.py
index de26543..4d47f25 100644
--- a/erpnext/payroll/doctype/additional_salary/test_additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/test_additional_salary.py
@@ -8,6 +8,7 @@
 from erpnext.hr.doctype.employee.test_employee import make_employee
 from erpnext.payroll.doctype.salary_component.test_salary_component import create_salary_component
 from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_employee_salary_slip, setup_test
+from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
 
 
 class TestAdditionalSalary(unittest.TestCase):
@@ -15,12 +16,19 @@
 	def setUp(self):
 		setup_test()
 
+	def tearDown(self):
+		for dt in ["Salary Slip", "Additional Salary", "Salary Structure Assignment", "Salary Structure"]:
+			frappe.db.sql("delete from `tab%s`" % dt)
+
 	def test_recurring_additional_salary(self):
+		amount = 0
+		salary_component = None
 		emp_id = make_employee("test_additional@salary.com")
 		frappe.db.set_value("Employee", emp_id, "relieving_date", add_days(nowdate(), 1800))
+		salary_structure = make_salary_structure("Test Salary Structure Additional Salary", "Monthly", employee=emp_id)
 		add_sal = get_additional_salary(emp_id)
-
-		ss = make_employee_salary_slip("test_additional@salary.com", "Monthly")
+		
+		ss = make_employee_salary_slip("test_additional@salary.com", "Monthly", salary_structure=salary_structure.name)
 		for earning in ss.earnings:
 			if earning.salary_component == "Recurring Salary Component":
 				amount = earning.amount
@@ -29,8 +37,6 @@
 		self.assertEqual(amount, add_sal.amount)
 		self.assertEqual(salary_component, add_sal.salary_component)
 
-
-
 def get_additional_salary(emp_id):
 	create_salary_component("Recurring Salary Component")
 	add_sal = frappe.new_doc("Additional Salary")
@@ -40,6 +46,7 @@
 	add_sal.from_date = add_days(nowdate(), -50)
 	add_sal.to_date = add_days(nowdate(), 180)
 	add_sal.amount = 5000
+	add_sal.currency = erpnext.get_default_currency()
 	add_sal.save()
 	add_sal.submit()
 
diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.js b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.js
index f509df3..6756cd9 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.js
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.js
@@ -3,7 +3,12 @@
 
 frappe.ui.form.on('Employee Benefit Application', {
 	employee: function(frm) {
-		frm.trigger('set_earning_component');
+		if (frm.doc.employee) {
+			frappe.run_serially([
+				() => 	frm.trigger('get_employee_currency'),
+				() => 	frm.trigger('set_earning_component')
+			]);
+		}
 		var method, args;
 		if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){
 			method = "erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application.get_max_benefits_remaining";
@@ -38,9 +43,26 @@
 		});
 	},
 
+	get_employee_currency: function(frm) {
+		if (frm.doc.employee) {
+			frappe.call({
+				method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+				args: {
+					employee: frm.doc.employee,
+				},
+				callback: function(r) {
+					if (r.message) {
+						frm.set_value('currency', r.message);
+						frm.refresh_fields();
+					}
+				}
+			});
+		}
+	},
+
 	payroll_period: function(frm) {
 		var method, args;
-		if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){
+		if (frm.doc.employee && frm.doc.date && frm.doc.payroll_period) {
 			method = "erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application.get_max_benefits_remaining";
 			args = {
 				employee: frm.doc.employee,
@@ -60,11 +82,14 @@
 		method: method,
 		args: args,
 		callback: function (data) {
-			if(!data.exc){
-				if(data.message){
+			if (!data.exc) {
+				if (data.message) {
 					frm.set_value("max_benefits", data.message);
+				} else {
+					frm.set_value("max_benefits", 0);
 				}
 			}
+			frm.refresh_fields();
 		}
 	});
 };
@@ -82,14 +107,19 @@
 	var tbl = doc.employee_benefits || [];
 	var pro_rata_dispensed_amount = 0;
 	var total_amount = 0;
-	for(var i = 0; i < tbl.length; i++){
-		if(cint(tbl[i].amount) > 0) {
-			total_amount += flt(tbl[i].amount);
-		}
-		if(tbl[i].pay_against_benefit_claim != 1){
-			pro_rata_dispensed_amount += flt(tbl[i].amount);
+	if (doc.max_benefits === 0) {
+		doc.employee_benefits = [];
+	} else {
+		for (var i = 0; i < tbl.length; i++) {
+			if (cint(tbl[i].amount) > 0) {
+				total_amount += flt(tbl[i].amount);
+			}
+			if (tbl[i].pay_against_benefit_claim != 1) {
+				pro_rata_dispensed_amount += flt(tbl[i].amount);
+			}
 		}
 	}
+
 	doc.total_amount = total_amount;
 	doc.remaining_benefit = doc.max_benefits - total_amount;
 	doc.pro_rata_dispensed_amount = pro_rata_dispensed_amount;
diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
index b0c1bd6..4c45580 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
@@ -10,17 +10,20 @@
  "field_order": [
   "employee",
   "employee_name",
+  "currency",
   "max_benefits",
   "remaining_benefit",
   "column_break_2",
   "date",
   "payroll_period",
   "department",
+  "company",
   "amended_from",
   "section_break_4",
   "employee_benefits",
   "totals",
   "total_amount",
+  "column_break",
   "pro_rata_dispensed_amount"
  ],
  "fields": [
@@ -43,12 +46,14 @@
    "fieldname": "max_benefits",
    "fieldtype": "Currency",
    "label": "Max Benefits (Yearly)",
+   "options": "currency",
    "read_only": 1
   },
   {
    "fieldname": "remaining_benefit",
    "fieldtype": "Currency",
    "label": "Remaining Benefits (Yearly)",
+   "options": "currency",
    "read_only": 1
   },
   {
@@ -108,18 +113,42 @@
    "fieldname": "total_amount",
    "fieldtype": "Currency",
    "label": "Total Amount",
+   "options": "currency",
    "read_only": 1
   },
   {
    "fieldname": "pro_rata_dispensed_amount",
    "fieldtype": "Currency",
    "label": "Dispensed Amount (Pro-rated)",
+   "options": "currency",
    "read_only": 1
+  },
+  {
+   "default": "Company:company:default_currency",
+   "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "label": "Currency",
+   "options": "Currency",
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "fetch_from": "employee.company",
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company",
+   "reqd": 1
+  },
+  {
+   "fieldname": "column_break",
+   "fieldtype": "Column Break"
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-22 22:58:31.271922",
+ "modified": "2020-12-14 15:52:08.566418",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Benefit Application",
diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
index ef844fb..27df30a 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
@@ -33,8 +33,8 @@
 					benefit_given = get_sal_slip_total_benefit_given(self.employee, payroll_period, component = benefit.earning_component)
 					benefit_claim_remining = benefit_claimed - benefit_given
 					if benefit_claimed > 0 and benefit_claim_remining > benefit.amount:
-						frappe.throw(_("An amount of {0} already claimed for the component {1},\
-						 set the amount equal or greater than {2}").format(benefit_claimed, benefit.earning_component, benefit_claim_remining))
+						frappe.throw(_("An amount of {0} already claimed for the component {1}, set the amount equal or greater than {2}").format(
+							benefit_claimed, benefit.earning_component, benefit_claim_remining))
 
 	def validate_remaining_benefit_amount(self):
 		# check salary structure earnings have flexi component (sum of max_benefit_amount)
@@ -62,11 +62,11 @@
 			if pro_rata_amount == 0  and non_pro_rata_amount == 0:
 				frappe.throw(_("Please add the remaining benefits {0} to any of the existing component").format(self.remaining_benefit))
 			elif non_pro_rata_amount > 0 and non_pro_rata_amount < rounded(self.remaining_benefit):
-				frappe.throw(_("You can claim only an amount of {0}, the rest amount {1} should be in the application \
-				as pro-rata component").format(non_pro_rata_amount, self.remaining_benefit - non_pro_rata_amount))
+				frappe.throw(_("You can claim only an amount of {0}, the rest amount {1} should be in the application as pro-rata component").format(
+					non_pro_rata_amount, self.remaining_benefit - non_pro_rata_amount))
 			elif non_pro_rata_amount == 0:
-				frappe.throw(_("Please add the remaining benefits {0} to the application as \
-				pro-rata component").format(self.remaining_benefit))
+				frappe.throw(_("Please add the remaining benefits {0} to the application as pro-rata component").format(
+					self.remaining_benefit))
 
 	def validate_max_benefit_for_component(self):
 		if self.employee_benefits:
@@ -115,7 +115,7 @@
 	if max_benefits and max_benefits > 0:
 		have_depends_on_payment_days = False
 		per_day_amount_total = 0
-		payroll_period_days = get_payroll_period_days(on_date, on_date, employee)[0]
+		payroll_period_days = get_payroll_period_days(on_date, on_date, employee)[1]
 		payroll_period_obj = frappe.get_doc("Payroll Period", payroll_period)
 
 		# Get all salary slip flexi amount in the payroll period
@@ -239,4 +239,17 @@
 		""", salary_structure)
 	else:
 		frappe.throw(_("Salary Structure not found for employee {0} and date {1}")
-			.format(filters['employee'], filters['date']))
\ No newline at end of file
+			.format(filters['employee'], filters['date']))
+
+@frappe.whitelist()
+def get_earning_components_max_benefits(employee, date, earning_component):
+	salary_structure = get_assigned_salary_structure(employee, date)
+	amount = frappe.db.sql("""
+			select amount
+			from `tabSalary Detail`
+			where parent = %s and is_flexible_benefit = 1
+			and salary_component = %s
+			order by name
+		""", salary_structure, earning_component)
+
+	return amount if amount else 0
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/employee_benefit_application_detail/employee_benefit_application_detail.json b/erpnext/payroll/doctype/employee_benefit_application_detail/employee_benefit_application_detail.json
index fa6b4da..c93d356 100644
--- a/erpnext/payroll/doctype/employee_benefit_application_detail/employee_benefit_application_detail.json
+++ b/erpnext/payroll/doctype/employee_benefit_application_detail/employee_benefit_application_detail.json
@@ -33,6 +33,7 @@
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Max Benefit Amount",
+   "options": "currency",
    "read_only": 1
   },
   {
@@ -40,12 +41,13 @@
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Amount",
+   "options": "currency",
    "reqd": 1
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-06-22 23:45:00.519134",
+ "modified": "2020-09-29 16:22:15.783854",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Benefit Application Detail",
diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js
index 6db6cb8..ea9ccd5 100644
--- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js
+++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js
@@ -12,5 +12,24 @@
 	},
 	employee: function(frm) {
 		frm.set_value("earning_component", null);
+		if (frm.doc.employee) {
+			frappe.call({
+				method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+				args: {
+					employee: frm.doc.employee,
+				},
+				callback: function(r) {
+					if (r.message) {
+						frm.set_value('currency', r.message);
+						frm.set_df_property('currency', 'hidden', 0);
+					}
+				}
+			});
+		}
+		if (!frm.doc.earning_component) {
+			frm.doc.max_amount_eligible = null;
+			frm.doc.claimed_amount = null;
+		}
+		frm.refresh_fields();
 	}
 });
diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
index ae4c218..da24aac 100644
--- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
+++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
@@ -12,6 +12,8 @@
   "department",
   "column_break_3",
   "claim_date",
+  "currency",
+  "company",
   "benefit_type_and_amount",
   "earning_component",
   "max_amount_eligible",
@@ -76,6 +78,7 @@
    "fieldname": "max_amount_eligible",
    "fieldtype": "Currency",
    "label": "Max Amount Eligible",
+   "options": "currency",
    "read_only": 1
   },
   {
@@ -92,6 +95,7 @@
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Claimed Amount",
+   "options": "currency",
    "reqd": 1
   },
   {
@@ -119,11 +123,29 @@
    "fieldname": "attachments",
    "fieldtype": "Attach",
    "label": "Attachments"
+  },
+  {
+   "default": "Company:company:default_currency",
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "label": "Currency",
+   "options": "Currency",
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "fetch_from": "employee.company",
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company",
+   "reqd": 1
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-22 23:01:50.791676",
+ "modified": "2020-11-25 11:49:56.097352",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Benefit Claim",
diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.js b/erpnext/payroll/doctype/employee_incentive/employee_incentive.js
index db0f83a..182ce0f 100644
--- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.js
+++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.js
@@ -11,12 +11,57 @@
 			};
 		});
 
+		if (!frm.doc.company) return;
 		frm.set_query("salary_component", function() {
 			return {
-				filters: {
-					"type": "Earning"
-				}
+				query: "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
+				filters: {type: "earning", company: frm.doc.company}
 			};
 		});
-	}
+
+	},
+
+	employee: function(frm) {
+		if (frm.doc.employee) {
+			frappe.run_serially([
+				() => 	frm.trigger('get_employee_currency'),
+				() => 	frm.trigger('set_company')
+			]);
+		} else {
+			frm.set_value("company", null);
+		}
+	},
+
+	set_company: function(frm) {
+		frappe.call({
+			method: "frappe.client.get_value",
+			args: {
+				doctype: "Employee",
+				fieldname: "company",
+				filters: {
+					name: frm.doc.employee
+				}
+			},
+			callback: function(data) {
+				if (data.message) {
+					frm.set_value("company", data.message.company);
+				}
+			}
+		});
+	},
+
+	get_employee_currency: function(frm) {
+		frappe.call({
+			method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+			args: {
+				employee: frm.doc.employee,
+			},
+			callback: function(r) {
+				if (r.message) {
+					frm.set_value('currency', r.message);
+					frm.refresh_fields();
+				}
+			}
+		});
+	},
 });
diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
index 204c9a4..e5b1052 100644
--- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
+++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
@@ -7,10 +7,12 @@
  "engine": "InnoDB",
  "field_order": [
   "employee",
-  "incentive_amount",
   "employee_name",
-  "salary_component",
+  "company",
+  "currency",
+  "incentive_amount",
   "column_break_5",
+  "salary_component",
   "payroll_date",
   "department",
   "amended_from"
@@ -28,6 +30,7 @@
    "fieldname": "incentive_amount",
    "fieldtype": "Currency",
    "label": "Incentive Amount",
+   "options": "currency",
    "reqd": 1
   },
   {
@@ -70,11 +73,29 @@
    "label": "Salary Component",
    "options": "Salary Component",
    "reqd": 1
+  },
+  {
+   "default": "Company:company:default_currency",
+   "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "label": "Currency",
+   "options": "Currency",
+   "print_hide": 1,
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company",
+   "reqd": 1
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-22 22:42:51.209630",
+ "modified": "2020-10-20 17:22:16.468042",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Incentive",
diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
index 84a97f6..ead3db1 100644
--- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
+++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
@@ -4,14 +4,23 @@
 
 from __future__ import unicode_literals
 import frappe
+from frappe import _
 from frappe.model.document import Document
 
 class EmployeeIncentive(Document):
+	def validate(self):
+		self.validate_salary_structure()
+
+	def validate_salary_structure(self):
+		if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
+			frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(self.employee))
+
 	def on_submit(self):
 		company = frappe.db.get_value('Employee', self.employee, 'company')
 
 		additional_salary = frappe.new_doc('Additional Salary')
 		additional_salary.employee = self.employee
+		additional_salary.currency = self.currency
 		additional_salary.salary_component = self.salary_component
 		additional_salary.overwrite_salary_structure_amount = 0
 		additional_salary.amount = self.incentive_amount
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
index de7c348..83d4ae5 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
@@ -14,6 +14,7 @@
   "column_break_2",
   "payroll_period",
   "company",
+  "currency",
   "amended_from",
   "section_break_8",
   "declarations",
@@ -92,6 +93,7 @@
    "fieldname": "total_declared_amount",
    "fieldtype": "Currency",
    "label": "Total Declared Amount",
+   "options": "currency",
    "read_only": 1
   },
   {
@@ -102,12 +104,22 @@
    "fieldname": "total_exemption_amount",
    "fieldtype": "Currency",
    "label": "Total Exemption Amount",
+   "options": "currency",
    "read_only": 1
+  },
+  {
+   "default": "Company:company:default_currency",
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "label": "Currency",
+   "options": "Currency",
+   "print_hide": 1,
+   "reqd": 1
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-22 22:49:43.829892",
+ "modified": "2020-10-20 16:42:24.493761",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Tax Exemption Declaration",
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py b/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py
index 9549fd1..311f352 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py
@@ -22,6 +22,7 @@
 			"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
 			"company": erpnext.get_default_company(),
 			"payroll_period": "_Test Payroll Period",
+			"currency": erpnext.get_default_currency(),
 			"declarations": [
 				dict(exemption_sub_category = "_Test Sub Category",
 					exemption_category = "_Test Category",
@@ -39,6 +40,7 @@
 			"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
 			"company":  erpnext.get_default_company(),
 			"payroll_period": "_Test Payroll Period",
+			"currency": erpnext.get_default_currency(),
 			"declarations": [
 				dict(exemption_sub_category = "_Test Sub Category",
 					exemption_category = "_Test Category",
@@ -54,6 +56,7 @@
 			"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
 			"company":  erpnext.get_default_company(),
 			"payroll_period": "_Test Payroll Period",
+			"currency": erpnext.get_default_currency(),
 			"declarations": [
 				dict(exemption_sub_category = "_Test Sub Category",
 					exemption_category = "_Test Category",
@@ -70,6 +73,7 @@
 			"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
 			"company":  erpnext.get_default_company(),
 			"payroll_period": "_Test Payroll Period",
+			"currency": erpnext.get_default_currency(),
 			"declarations": [
 				dict(exemption_sub_category = "_Test Sub Category",
 					exemption_category = "_Test Category",
@@ -82,19 +86,21 @@
 
 		self.assertEqual(declaration.total_exemption_amount, 100000)
 
-def create_payroll_period():
-	if not frappe.db.exists("Payroll Period", "_Test Payroll Period"):
+def create_payroll_period(**args):
+	args = frappe._dict(args)
+	name = args.name or "_Test Payroll Period"
+	if not frappe.db.exists("Payroll Period", name):
 		from datetime import date
 		payroll_period = frappe.get_doc(dict(
 			doctype = 'Payroll Period',
-			name = "_Test Payroll Period",
-			company =  erpnext.get_default_company(),
-			start_date = date(date.today().year, 1, 1),
-			end_date = date(date.today().year, 12, 31)
+			name = name,
+			company =  args.company or erpnext.get_default_company(),
+			start_date = args.start_date or date(date.today().year, 1, 1),
+			end_date = args.end_date or date(date.today().year, 12, 31)
 		)).insert()
 		return payroll_period
 	else:
-		return frappe.get_doc("Payroll Period", "_Test Payroll Period")
+		return frappe.get_doc("Payroll Period", name)
 
 def create_exemption_category():
 	if not frappe.db.exists("Employee Tax Exemption Category", "_Test Category"):
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.json b/erpnext/payroll/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.json
index 8c2f9aa..723a3df 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.json
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.json
@@ -35,6 +35,7 @@
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Maximum Exempted Amount",
+   "options": "currency",
    "read_only": 1,
    "reqd": 1
   },
@@ -43,12 +44,13 @@
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Declared Amount",
+   "options": "currency",
    "reqd": 1
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-06-22 23:41:03.638739",
+ "modified": "2020-10-20 16:43:09.606265",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Tax Exemption Declaration Category",
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js
index 715d755..497f35c 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js
+++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js
@@ -54,5 +54,9 @@
 				});
 			});
 		}
+	},
+
+	currency: function(frm) {
+		frm.refresh_fields();
 	}
 });
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
index b62b5aa..53f18cb 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
+++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
@@ -11,6 +11,7 @@
   "employee",
   "employee_name",
   "department",
+  "currency",
   "column_break_2",
   "submission_date",
   "payroll_period",
@@ -97,6 +98,7 @@
    "fieldname": "total_actual_amount",
    "fieldtype": "Currency",
    "label": "Total Actual Amount",
+   "options": "currency",
    "read_only": 1
   },
   {
@@ -107,6 +109,7 @@
    "fieldname": "exemption_amount",
    "fieldtype": "Currency",
    "label": "Total Exemption Amount",
+   "options": "currency",
    "read_only": 1
   },
   {
@@ -126,11 +129,20 @@
    "options": "Employee Tax Exemption Proof Submission",
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "default": "Company:company:default_currency",
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "label": "Currency",
+   "options": "Currency",
+   "print_hide": 1,
+   "reqd": 1
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-22 22:53:10.412321",
+ "modified": "2020-10-20 16:47:03.410020",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Tax Exemption Proof Submission",
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.json b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.json
index c1f5320..2fd8b94 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.json
+++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.json
@@ -34,6 +34,7 @@
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Maximum Exemption Amount",
+   "options": "currency",
    "read_only": 1,
    "reqd": 1
   },
@@ -48,12 +49,13 @@
    "fieldname": "amount",
    "fieldtype": "Currency",
    "in_list_view": 1,
-   "label": "Actual Amount"
+   "label": "Actual Amount",
+   "options": "currency"
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-06-22 23:37:08.265600",
+ "modified": "2020-10-20 16:47:31.480870",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Tax Exemption Proof Submission Detail",
diff --git a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.js b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.js
index 73a54eb..7d780d3 100644
--- a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.js
+++ b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.js
@@ -2,5 +2,7 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Income Tax Slab', {
-
+	currency: function(frm) {
+		frm.refresh_fields();
+	}
 });
diff --git a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json
index 6337d5a..9fa261d 100644
--- a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json
+++ b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json
@@ -9,8 +9,9 @@
   "effective_from",
   "company",
   "column_break_3",
-  "allow_tax_exemption",
+  "currency",
   "standard_tax_exemption_amount",
+  "allow_tax_exemption",
   "disabled",
   "amended_from",
   "taxable_salary_slabs_section",
@@ -70,7 +71,7 @@
    "fieldname": "standard_tax_exemption_amount",
    "fieldtype": "Currency",
    "label": "Standard Tax Exemption Amount",
-   "options": "Company:company:default_currency"
+   "options": "currency"
   },
   {
    "fieldname": "company",
@@ -90,11 +91,20 @@
    "fieldtype": "Table",
    "label": "Other Taxes and Charges",
    "options": "Income Tax Slab Other Charges"
+  },
+  {
+   "default": "Company:company:default_currency",
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "label": "Currency",
+   "options": "Currency",
+   "print_hide": 1,
+   "reqd": 1
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-22 20:27:13.425084",
+ "modified": "2020-10-19 13:54:24.728075",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Income Tax Slab",
diff --git a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py
index 253f023..81e3647 100644
--- a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py
+++ b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py
@@ -3,8 +3,11 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
-# import frappe
+#import frappe
+import erpnext
 from frappe.model.document import Document
 
 class IncomeTaxSlab(Document):
-	pass
+	def validate(self):
+		if self.company:
+			self.currency = erpnext.get_company_currency(self.company)
diff --git a/erpnext/payroll/doctype/income_tax_slab_other_charges/income_tax_slab_other_charges.json b/erpnext/payroll/doctype/income_tax_slab_other_charges/income_tax_slab_other_charges.json
index 7f21204..0dba338 100644
--- a/erpnext/payroll/doctype/income_tax_slab_other_charges/income_tax_slab_other_charges.json
+++ b/erpnext/payroll/doctype/income_tax_slab_other_charges/income_tax_slab_other_charges.json
@@ -45,7 +45,7 @@
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Min Taxable Income",
-   "options": "Company:company:default_currency"
+   "options": "currency"
   },
   {
    "fieldname": "column_break_7",
@@ -57,12 +57,12 @@
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Max Taxable Income",
-   "options": "Company:company:default_currency"
+   "options": "currency"
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-06-22 23:33:17.931912",
+ "modified": "2020-10-19 13:45:12.850090",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Income Tax Slab Other Charges",
diff --git a/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.json b/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.json
index bb68e18..09c7eb9 100644
--- a/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.json
+++ b/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.json
@@ -17,8 +17,7 @@
    "fieldtype": "Link",
    "in_list_view": 1,
    "label": "Employee",
-   "options": "Employee",
-   "read_only": 1
+   "options": "Employee"
   },
   {
    "fetch_from": "employee.employee_name",
@@ -52,7 +51,7 @@
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-06-22 23:25:13.779032",
+ "modified": "2020-12-17 15:43:29.542977",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Payroll Employee Detail",
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
index 1abc869..2288a27 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
@@ -10,7 +10,12 @@
 		}
 		frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet);
 
-		frm.set_query("department", function() {
+		frm.events.department_filters(frm);
+		frm.events.payroll_payable_account_filters(frm);
+	},
+
+	department_filters: function (frm) {
+		frm.set_query("department", function () {
 			return {
 				"filters": {
 					"company": frm.doc.company,
@@ -19,12 +24,24 @@
 		});
 	},
 
-	refresh: function(frm) {
+	payroll_payable_account_filters: function (frm) {
+		frm.set_query("payroll_payable_account", function () {
+			return {
+				filters: {
+					"company": frm.doc.company,
+					"root_type": "Liability",
+					"is_group": 0,
+				}
+			};
+		});
+	},
+
+	refresh: function (frm) {
 		if (frm.doc.docstatus == 0) {
-			if(!frm.is_new()) {
+			if (!frm.is_new()) {
 				frm.page.clear_primary_action();
 				frm.add_custom_button(__("Get Employees"),
-					function() {
+					function () {
 						frm.events.get_employee_details(frm);
 					}
 				).toggleClass('btn-primary', !(frm.doc.employees || []).length);
@@ -32,7 +49,7 @@
 			if ((frm.doc.employees || []).length) {
 				frm.page.clear_primary_action();
 				frm.page.set_primary_action(__('Create Salary Slips'), () => {
-					frm.save('Submit').then(()=>{
+					frm.save('Submit').then(() => {
 						frm.page.clear_primary_action();
 						frm.refresh();
 						frm.events.refresh(frm);
@@ -51,48 +68,48 @@
 			doc: frm.doc,
 			method: 'fill_employee_details',
 		}).then(r => {
-			if (r.docs && r.docs[0].employees){
+			if (r.docs && r.docs[0].employees) {
 				frm.employees = r.docs[0].employees;
 				frm.dirty();
 				frm.save();
 				frm.refresh();
-				if(r.docs[0].validate_attendance){
+				if (r.docs[0].validate_attendance) {
 					render_employee_attendance(frm, r.message);
 				}
 			}
-		})
+		});
 	},
 
-	create_salary_slips: function(frm) {
+	create_salary_slips: function (frm) {
 		frm.call({
 			doc: frm.doc,
 			method: "create_salary_slips",
-			callback: function(r) {
+			callback: function () {
 				frm.refresh();
 				frm.toolbar.refresh();
 			}
-		})
+		});
 	},
 
-	add_context_buttons: function(frm) {
-		if(frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) {
+	add_context_buttons: function (frm) {
+		if (frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) {
 			frm.events.add_bank_entry_button(frm);
-		} else if(frm.doc.salary_slips_created) {
-			frm.add_custom_button(__("Submit Salary Slip"), function() {
+		} else if (frm.doc.salary_slips_created) {
+			frm.add_custom_button(__("Submit Salary Slip"), function () {
 				submit_salary_slip(frm);
 			}).addClass("btn-primary");
 		}
 	},
 
-	add_bank_entry_button: function(frm) {
+	add_bank_entry_button: function (frm) {
 		frappe.call({
 			method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.payroll_entry_has_bank_entries',
 			args: {
 				'name': frm.doc.name
 			},
-			callback: function(r) {
+			callback: function (r) {
 				if (r.message && !r.message.submitted) {
-					frm.add_custom_button("Make Bank Entry", function() {
+					frm.add_custom_button("Make Bank Entry", function () {
 						make_bank_entry(frm);
 					}).addClass("btn-primary");
 				}
@@ -131,14 +148,73 @@
 	},
 
 	payroll_frequency: function (frm) {
-		frm.trigger("set_start_end_dates");
-		frm.events.clear_employee_table(frm);
+		frm.trigger("set_start_end_dates").then( ()=> {
+			frm.events.clear_employee_table(frm);
+			frm.events.get_employee_with_salary_slip_and_set_query(frm);
+		});
+	},
+
+	employee_filters: function (frm, emp_list) {
+		frm.set_query('employee', 'employees', () => {
+			return {
+				filters: {
+					name: ["not in", emp_list]
+				}
+			};
+		});
+	},
+
+	get_employee_with_salary_slip_and_set_query: function (frm) {
+		frappe.db.get_list('Salary Slip', {
+			filters: {
+				start_date: frm.doc.start_date,
+				end_date: frm.doc.end_date,
+				docstatus: 1,
+			},
+			fields: ['employee']
+		}).then((emp) => {
+			var emp_list = [];
+			emp.forEach((employee_data) => {
+				emp_list.push(Object.values(employee_data)[0]);
+			});
+			frm.events.employee_filters(frm, emp_list);
+		});
 	},
 
 	company: function (frm) {
 		frm.events.clear_employee_table(frm);
 	},
 
+	currency: function (frm) {
+		var company_currency;
+		if (!frm.doc.company) {
+			company_currency = erpnext.get_currency(frappe.defaults.get_default("Company"));
+		} else {
+			company_currency = erpnext.get_currency(frm.doc.company);
+		}
+		if (frm.doc.currency) {
+			if (company_currency != frm.doc.currency) {
+				frappe.call({
+					method: "erpnext.setup.utils.get_exchange_rate",
+					args: {
+						from_currency: frm.doc.currency,
+						to_currency: company_currency,
+					},
+					callback: function (r) {
+						frm.set_value("exchange_rate", flt(r.message));
+						frm.set_df_property('exchange_rate', 'hidden', 0);
+						frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency +
+							" = [?] " + company_currency);
+					}
+				});
+			} else {
+				frm.set_value("exchange_rate", 1.0);
+				frm.set_df_property('exchange_rate', 'hidden', 1);
+				frm.set_df_property("exchange_rate", "description", "");
+			}
+		}
+	},
+
 	department: function (frm) {
 		frm.events.clear_employee_table(frm);
 	},
@@ -152,9 +228,9 @@
 	},
 
 	start_date: function (frm) {
-		if(!in_progress && frm.doc.start_date){
+		if (!in_progress && frm.doc.start_date) {
 			frm.trigger("set_end_date");
-		}else{
+		} else {
 			// reset flag
 			in_progress = false;
 		}
@@ -188,7 +264,7 @@
 		}
 	},
 
-	set_end_date: function(frm){
+	set_end_date: function (frm) {
 		frappe.call({
 			method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.get_end_date',
 			args: {
@@ -203,19 +279,19 @@
 		});
 	},
 
-	validate_attendance: function(frm){
-		if(frm.doc.validate_attendance && frm.doc.employees){
+	validate_attendance: function (frm) {
+		if (frm.doc.validate_attendance && frm.doc.employees) {
 			frappe.call({
 				method: 'validate_employee_attendance',
 				args: {},
-				callback: function(r) {
+				callback: function (r) {
 					render_employee_attendance(frm, r.message);
 				},
 				doc: frm.doc,
 				freeze: true,
 				freeze_message: __('Validating Employee Attendance...')
 			});
-		}else{
+		} else {
 			frm.fields_dict.attendance_detail_html.html("");
 		}
 	},
@@ -230,18 +306,20 @@
 
 const submit_salary_slip = function (frm) {
 	frappe.confirm(__('This will submit Salary Slips and create accrual Journal Entry. Do you want to proceed?'),
-		function() {
+		function () {
 			frappe.call({
 				method: 'submit_salary_slips',
 				args: {},
-				callback: function() {frm.events.refresh(frm);},
+				callback: function () {
+					frm.events.refresh(frm);
+				},
 				doc: frm.doc,
 				freeze: true,
 				freeze_message: __('Submitting Salary Slips and creating Journal Entry...')
 			});
 		},
-		function() {
-			if(frappe.dom.freeze_count) {
+		function () {
+			if (frappe.dom.freeze_count) {
 				frappe.dom.unfreeze();
 				frm.events.refresh(frm);
 			}
@@ -255,9 +333,11 @@
 		return frappe.call({
 			doc: cur_frm.doc,
 			method: "make_payment_entry",
-			callback: function() {
+			callback: function () {
 				frappe.set_route(
-					'List', 'Journal Entry', {"Journal Entry Account.reference_name": frm.doc.name}
+					'List', 'Journal Entry', {
+						"Journal Entry Account.reference_name": frm.doc.name
+					}
 				);
 			},
 			freeze: true,
@@ -269,11 +349,19 @@
 	}
 };
 
-
-let render_employee_attendance = function(frm, data) {
+let render_employee_attendance = function (frm, data) {
 	frm.fields_dict.attendance_detail_html.html(
 		frappe.render_template('employees_to_mark_attendance', {
 			data: data
 		})
 	);
-}
+};
+
+frappe.ui.form.on('Payroll Employee Detail', {
+	employee: function(frm) {
+		frm.events.clear_employee_table(frm);
+		if (!frm.doc.payroll_frequency) {
+			frappe.throw(__("Please set a Payroll Frequency"));
+		}
+	}
+});
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.json b/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
index 31a8996..0444134 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
@@ -11,8 +11,11 @@
   "column_break0",
   "posting_date",
   "payroll_frequency",
-  "column_break1",
   "company",
+  "column_break1",
+  "currency",
+  "exchange_rate",
+  "payroll_payable_account",
   "section_break_8",
   "branch",
   "department",
@@ -126,8 +129,7 @@
    "fieldname": "employees",
    "fieldtype": "Table",
    "label": "Employee Details",
-   "options": "Payroll Employee Detail",
-   "read_only": 1
+   "options": "Payroll Employee Detail"
   },
   {
    "fieldname": "section_break_13",
@@ -257,12 +259,37 @@
   {
    "fieldname": "column_break_33",
    "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "company",
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Currency",
+   "options": "Currency",
+   "reqd": 1
+  },
+  {
+   "depends_on": "company",
+   "fieldname": "exchange_rate",
+   "fieldtype": "Float",
+   "label": "Exchange Rate",
+   "precision": "9",
+   "reqd": 1
+  },
+  {
+   "depends_on": "company",
+   "fieldname": "payroll_payable_account",
+   "fieldtype": "Link",
+   "label": "Payroll Payable Account",
+   "options": "Account",
+   "reqd": 1
   }
  ],
  "icon": "fa fa-cog",
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-22 20:06:06.953904",
+ "modified": "2020-12-17 15:13:17.766210",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Payroll Entry",
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index a3d12c3..a25a6e7 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -3,10 +3,10 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
-import frappe
+import frappe, erpnext
 from frappe.model.document import Document
 from dateutil.relativedelta import relativedelta
-from frappe.utils import cint, flt, nowdate, add_days, getdate, fmt_money, add_to_date, DATE_FORMAT, date_diff
+from frappe.utils import cint, flt, add_days, getdate, add_to_date, DATE_FORMAT, date_diff, comma_and
 from frappe import _
 from erpnext.accounts.utils import get_fiscal_year
 from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
@@ -19,16 +19,26 @@
 		# check if salary slips were manually submitted
 		entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name'])
 		if cint(entries) == len(self.employees):
-    			self.set_onload("submitted_ss", True)
+				self.set_onload("submitted_ss", True)
 
 	def on_submit(self):
 		self.create_salary_slips()
 
 	def before_submit(self):
+		self.validate_employee_details()
 		if self.validate_attendance:
 			if self.validate_employee_attendance():
 				frappe.throw(_("Cannot Submit, Employees left to mark attendance"))
 
+	def validate_employee_details(self):
+		emp_with_sal_slip = []
+		for employee_details in self.employees:
+			if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}):
+				emp_with_sal_slip.append(employee_details.employee)
+
+		if len(emp_with_sal_slip):
+			frappe.throw(_("Salary Slip already exists for {0} ").format(comma_and(emp_with_sal_slip)))
+
 	def on_cancel(self):
 		frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip`
 			where payroll_entry=%s """, (self.name)))
@@ -51,13 +61,15 @@
 				where
 					docstatus = 1 and
 					is_active = 'Yes'
-					and company = %(company)s and
+					and company = %(company)s
+					and currency = %(currency)s and
 					ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
 					{condition}""".format(condition=condition),
-				{"company": self.company, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
+				{"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
 
 		if sal_struct:
 			cond += "and t2.salary_structure IN %(sal_struct)s "
+			cond += "and t2.payroll_payable_account = %(payroll_payable_account)s "
 			cond += "and %(from_date)s >= t2.from_date"
 			emp_list = frappe.db.sql("""
 				select
@@ -68,14 +80,35 @@
 					t1.name = t2.employee
 					and t2.docstatus = 1
 			%s order by t2.from_date desc
-			""" % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date}, as_dict=True)
+			""" % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True)
+
+			emp_list = self.remove_payrolled_employees(emp_list)
 			return emp_list
 
+	def remove_payrolled_employees(self, emp_list):
+		for employee_details in emp_list:
+			if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}):
+				emp_list.remove(employee_details)
+
+		return emp_list
+
 	def fill_employee_details(self):
 		self.set('employees', [])
 		employees = self.get_emp_list()
 		if not employees:
-			frappe.throw(_("No employees for the mentioned criteria"))
+			error_msg = _("No employees found for the mentioned criteria:<br>Company: {0}<br> Currency: {1}<br>Payroll Payable Account: {2}").format(
+				frappe.bold(self.company), frappe.bold(self.currency), frappe.bold(self.payroll_payable_account))
+			if self.branch:
+				error_msg += "<br>" + _("Branch: {0}").format(frappe.bold(self.branch))
+			if self.department:
+				error_msg += "<br>" + _("Department: {0}").format(frappe.bold(self.department))
+			if self.designation:
+				error_msg += "<br>" + _("Designation: {0}").format(frappe.bold(self.designation))
+			if self.start_date:
+				error_msg += "<br>" + _("Start date: {0}").format(frappe.bold(self.start_date))
+			if self.end_date:
+				error_msg += "<br>" + _("End date: {0}").format(frappe.bold(self.end_date))
+			frappe.throw(error_msg, title=_("No employees found"))
 
 		for d in employees:
 			self.append('employees', d)
@@ -123,7 +156,9 @@
 				"posting_date": self.posting_date,
 				"deduct_tax_for_unclaimed_employee_benefits": self.deduct_tax_for_unclaimed_employee_benefits,
 				"deduct_tax_for_unsubmitted_tax_exemption_proof": self.deduct_tax_for_unsubmitted_tax_exemption_proof,
-				"payroll_entry": self.name
+				"payroll_entry": self.name,
+				"exchange_rate": self.exchange_rate,
+				"currency": self.currency
 			})
 			if len(emp_list) > 30:
 				frappe.enqueue(create_salary_slips_for_employees, timeout=600, employees=emp_list, args=args)
@@ -160,10 +195,10 @@
 
 	def get_salary_component_account(self, salary_component):
 		account = frappe.db.get_value("Salary Component Account",
-			{"parent": salary_component, "company": self.company}, "default_account")
+			{"parent": salary_component, "company": self.company}, "account")
 
 		if not account:
-			frappe.throw(_("Please set default account in Salary Component {0}")
+			frappe.throw(_("Please set account in Salary Component {0}")
 				.format(salary_component))
 
 		return account
@@ -203,21 +238,11 @@
 			account_dict[(account, key[1])] = account_dict.get((account, key[1]), 0) + amount
 		return account_dict
 
-	def get_default_payroll_payable_account(self):
-		payroll_payable_account = frappe.get_cached_value('Company',
-			{"company_name": self.company},  "default_payroll_payable_account")
-
-		if not payroll_payable_account:
-			frappe.throw(_("Please set Default Payroll Payable Account in Company {0}")
-				.format(self.company))
-
-		return payroll_payable_account
-
 	def make_accrual_jv_entry(self):
 		self.check_permission('write')
 		earnings = self.get_salary_component_total(component_type = "earnings") or {}
 		deductions = self.get_salary_component_total(component_type = "deductions") or {}
-		default_payroll_payable_account = self.get_default_payroll_payable_account()
+		payroll_payable_account = self.payroll_payable_account
 		jv_name = ""
 		precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
 
@@ -230,14 +255,19 @@
 			journal_entry.posting_date = self.posting_date
 
 			accounts = []
+			currencies = []
 			payable_amount = 0
+			multi_currency = 0
+			company_currency = erpnext.get_company_currency(self.company)
 
 			# Earnings
 			for acc_cc, amount in earnings.items():
+				exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
 				payable_amount += flt(amount, precision)
 				accounts.append({
 						"account": acc_cc[0],
-						"debit_in_account_currency": flt(amount, precision),
+						"debit_in_account_currency": flt(amt, precision),
+						"exchange_rate": flt(exchange_rate),
 						"party_type": '',
 						"cost_center": acc_cc[1] or self.cost_center,
 						"project": self.project
@@ -245,25 +275,32 @@
 
 			# Deductions
 			for acc_cc, amount in deductions.items():
+				exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
 				payable_amount -= flt(amount, precision)
 				accounts.append({
 						"account": acc_cc[0],
-						"credit_in_account_currency": flt(amount, precision),
+						"credit_in_account_currency": flt(amt, precision),
+						"exchange_rate": flt(exchange_rate),
 						"cost_center": acc_cc[1] or self.cost_center,
 						"party_type": '',
 						"project": self.project
 					})
 
 			# Payable amount
+			exchange_rate, payable_amt = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, payable_amount, company_currency, currencies)
 			accounts.append({
-				"account": default_payroll_payable_account,
-				"credit_in_account_currency": flt(payable_amount, precision),
+				"account": payroll_payable_account,
+				"credit_in_account_currency": flt(payable_amt, precision),
+				"exchange_rate": flt(exchange_rate),
 				"party_type": '',
 				"cost_center": self.cost_center
 			})
 
 			journal_entry.set("accounts", accounts)
-			journal_entry.title = default_payroll_payable_account
+			if len(currencies) > 1:
+				multi_currency = 1
+			journal_entry.multi_currency = multi_currency
+			journal_entry.title = payroll_payable_account
 			journal_entry.save()
 
 			try:
@@ -271,10 +308,24 @@
 				jv_name = journal_entry.name
 				self.update_salary_slip_status(jv_name = jv_name)
 			except Exception as e:
-				frappe.msgprint(e)
+				if type(e) in (str, list, tuple):
+					frappe.msgprint(e)
+				raise
 
 		return jv_name
 
+	def get_amount_and_exchange_rate_for_journal_entry(self, account, amount, company_currency, currencies):
+		conversion_rate = 1
+		exchange_rate = self.exchange_rate
+		account_currency = frappe.db.get_value('Account', account, 'account_currency')
+		if account_currency not in currencies:
+			currencies.append(account_currency)
+		if account_currency == company_currency:
+			conversion_rate = self.exchange_rate
+			exchange_rate = 1
+		amount = flt(amount) * flt(conversion_rate)
+		return exchange_rate, amount
+
 	def make_payment_entry(self):
 		self.check_permission('write')
 
@@ -303,31 +354,43 @@
 				self.create_journal_entry(salary_slip_total, "salary")
 
 	def create_journal_entry(self, je_payment_amount, user_remark):
-		default_payroll_payable_account = self.get_default_payroll_payable_account()
+		payroll_payable_account = self.payroll_payable_account
 		precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
 
+		accounts = []
+		currencies = []
+		multi_currency = 0
+		company_currency = erpnext.get_company_currency(self.company)
+
+		exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(self.payment_account, je_payment_amount, company_currency, currencies)
+		accounts.append({
+				"account": self.payment_account,
+				"bank_account": self.bank_account,
+				"credit_in_account_currency": flt(amount, precision),
+				"exchange_rate": flt(exchange_rate),
+			})
+
+		exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, je_payment_amount, company_currency, currencies)
+		accounts.append({
+				"account": payroll_payable_account,
+				"debit_in_account_currency": flt(amount, precision),
+				"exchange_rate": flt(exchange_rate),
+				"reference_type": self.doctype,
+				"reference_name": self.name
+			})
+
+		if len(currencies) > 1:
+				multi_currency = 1
+
 		journal_entry = frappe.new_doc('Journal Entry')
 		journal_entry.voucher_type = 'Bank Entry'
 		journal_entry.user_remark = _('Payment of {0} from {1} to {2}')\
 			.format(user_remark, self.start_date, self.end_date)
 		journal_entry.company = self.company
 		journal_entry.posting_date = self.posting_date
+		journal_entry.multi_currency = multi_currency
 
-		payment_amount = flt(je_payment_amount, precision)
-
-		journal_entry.set("accounts", [
-			{
-				"account": self.payment_account,
-				"bank_account": self.bank_account,
-				"credit_in_account_currency": payment_amount
-			},
-			{
-				"account": default_payroll_payable_account,
-				"debit_in_account_currency": payment_amount,
-				"reference_type": self.doctype,
-				"reference_name": self.name
-			}
-		])
+		journal_entry.set("accounts", accounts)
 		journal_entry.save(ignore_permissions = True)
 
 	def update_salary_slip_status(self, jv_name = None):
@@ -496,6 +559,21 @@
 			if publish_progress:
 				frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)),
 					title = _("Creating Salary Slips..."))
+		else:
+			salary_slip_name = frappe.db.sql(
+				'''SELECT
+						name
+					FROM `tabSalary Slip`
+					WHERE company=%s
+					AND start_date >= %s
+					AND end_date <= %s
+					AND employee = %s
+				''', (args.company, args.start_date, args.end_date, emp), as_dict=True)
+
+			salary_slip_doc = frappe.get_doc('Salary Slip', salary_slip_name[0].name)
+			salary_slip_doc.exchange_rate = args.exchange_rate
+			salary_slip_doc.set_totals()
+			salary_slip_doc.db_update()
 
 	payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry)
 	payroll_entry.db_set("salary_slips_created", 1)
diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
index b0f225d..e098ec7 100644
--- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
@@ -10,8 +10,8 @@
 from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_dates, get_end_date
 from erpnext.hr.doctype.employee.test_employee import make_employee
 from erpnext.payroll.doctype.salary_slip.test_salary_slip import get_salary_component_account, \
-		make_earning_salary_component, make_deduction_salary_component, create_account
-from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
+		make_earning_salary_component, make_deduction_salary_component, create_account, make_employee_salary_slip
+from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure, create_salary_structure_assignment
 from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry
 from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
 
@@ -22,7 +22,7 @@
 				frappe.db.sql("delete from `tab%s`" % dt)
 
 		make_earning_salary_component(setup=True, company_list=["_Test Company"])
-		make_deduction_salary_component(setup=True, company_list=["_Test Company"])
+		make_deduction_salary_component(setup=True, test_tax=False, company_list=["_Test Company"])
 
 		frappe.db.set_value("Payroll Settings", None, "email_salary_slip_to_employee", 0)
 
@@ -34,10 +34,47 @@
 				get_salary_component_account(data.name)
 
 		employee = frappe.db.get_value("Employee", {'company': company})
-		make_salary_structure("_Test Salary Structure", "Monthly", employee, company=company)
+		company_doc = frappe.get_doc('Company', company)
+		make_salary_structure("_Test Salary Structure", "Monthly", employee, company=company, currency=company_doc.default_currency)
 		dates = get_start_end_dates('Monthly', nowdate())
 		if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}):
-			make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date)
+			make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date, payable_account=company_doc.default_payroll_payable_account,
+				currency=company_doc.default_currency)
+
+	def test_multi_currency_payroll_entry(self): # pylint: disable=no-self-use
+		company = erpnext.get_default_company()
+		employee = make_employee("test_muti_currency_employee@payroll.com", company=company)
+		for data in frappe.get_all('Salary Component', fields = ["name"]):
+			if not frappe.db.get_value('Salary Component Account',
+				{'parent': data.name, 'company': company}, 'name'):
+				get_salary_component_account(data.name)
+
+		company_doc = frappe.get_doc('Company', company)
+		salary_structure = make_salary_structure("_Test Multi Currency Salary Structure", "Monthly", company=company, currency='USD')
+		create_salary_structure_assignment(employee, salary_structure.name, company=company)
+		frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""",(frappe.db.get_value("Employee", {"user_id": "test_muti_currency_employee@payroll.com"})))
+		salary_slip = get_salary_slip("test_muti_currency_employee@payroll.com", "Monthly", "_Test Multi Currency Salary Structure")
+		dates = get_start_end_dates('Monthly', nowdate())
+		payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date, 
+			payable_account=company_doc.default_payroll_payable_account, currency='USD', exchange_rate=70)
+		payroll_entry.make_payment_entry()
+
+		salary_slip.load_from_db()
+
+		payroll_je = salary_slip.journal_entry
+		payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je)
+
+		self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit)
+		self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit)
+
+		payment_entry = frappe.db.sql('''
+			Select ifnull(sum(je.total_debit),0) as total_debit, ifnull(sum(je.total_credit),0) as total_credit from `tabJournal Entry` je, `tabJournal Entry Account` jea
+			Where je.name = jea.parent
+			And jea.reference_name = %s
+			''', (payroll_entry.name), as_dict=1)
+
+		self.assertEqual(salary_slip.base_net_pay, payment_entry[0].total_debit)
+		self.assertEqual(salary_slip.base_net_pay, payment_entry[0].total_credit)
 
 	def test_payroll_entry_with_employee_cost_center(self): # pylint: disable=no-self-use
 		for data in frappe.get_all('Salary Component', fields = ["name"]):
@@ -52,24 +89,32 @@
 				"company": "_Test Company"
 			}).insert()
 
+		frappe.db.sql("""delete from `tabEmployee` where employee_name='test_employee1@example.com' """)
+		frappe.db.sql("""delete from `tabEmployee` where employee_name='test_employee2@example.com' """)
+		frappe.db.sql("""delete from `tabSalary Structure` where name='_Test Salary Structure 1' """)
+		frappe.db.sql("""delete from `tabSalary Structure` where name='_Test Salary Structure 2' """)
+
 		employee1 = make_employee("test_employee1@example.com", payroll_cost_center="_Test Cost Center - _TC",
 			department="cc - _TC", company="_Test Company")
 		employee2 = make_employee("test_employee2@example.com", payroll_cost_center="_Test Cost Center 2 - _TC",
 			department="cc - _TC", company="_Test Company")
 
-		make_salary_structure("_Test Salary Structure 1", "Monthly", employee1, company="_Test Company")
-		make_salary_structure("_Test Salary Structure 2", "Monthly", employee2, company="_Test Company")
-
 		if not frappe.db.exists("Account", "_Test Payroll Payable - _TC"):
-			create_account(account_name="_Test Payroll Payable",
-				company="_Test Company", parent_account="Current Liabilities - _TC")
-			frappe.db.set_value("Company", "_Test Company", "default_payroll_payable_account",
-				"_Test Payroll Payable - _TC")
+				create_account(account_name="_Test Payroll Payable",
+					company="_Test Company", parent_account="Current Liabilities - _TC")
+
+		if not frappe.db.get_value("Company", "_Test Company", "default_payroll_payable_account") or \
+			frappe.db.get_value("Company", "_Test Company", "default_payroll_payable_account") != "_Test Payroll Payable - _TC":
+				frappe.db.set_value("Company", "_Test Company", "default_payroll_payable_account",
+					"_Test Payroll Payable - _TC")
+		currency=frappe.db.get_value("Company", "_Test Company", "default_currency")
+		make_salary_structure("_Test Salary Structure 1", "Monthly", employee1, company="_Test Company", currency=currency, test_tax=False)
+		make_salary_structure("_Test Salary Structure 2", "Monthly", employee2, company="_Test Company", currency=currency, test_tax=False)
 
 		dates = get_start_end_dates('Monthly', nowdate())
 		if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}):
-			pe = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
-				department="cc - _TC", company="_Test Company", payment_account="Cash - _TC", cost_center="Main - _TC")
+			pe = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date, payable_account="_Test Payroll Payable - _TC",
+				currency=frappe.db.get_value("Company", "_Test Company", "default_currency"), department="cc - _TC", company="_Test Company", payment_account="Cash - _TC", cost_center="Main - _TC")
 			je = frappe.db.get_value("Salary Slip", {"payroll_entry": pe.name}, "journal_entry")
 			je_entries = frappe.db.sql("""
 				select account, cost_center, debit, credit
@@ -121,7 +166,7 @@
 		employee_doc.save()
 
 		salary_structure = "Test Salary Structure for Loan"
-		make_salary_structure(salary_structure, "Monthly", employee=employee_doc.name, company="_Test Company")
+		make_salary_structure(salary_structure, "Monthly", employee=employee_doc.name, company="_Test Company", currency=company_doc.default_currency)
 
 		loan = create_loan(applicant, "Car Loan", 280000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1))
 		loan.repay_from_salary = 1
@@ -133,8 +178,8 @@
 
 
 		dates = get_start_end_dates('Monthly', nowdate())
-		make_payroll_entry(company="_Test Company", start_date=dates.start_date,
-			end_date=dates.end_date, branch=branch, cost_center="Main - _TC", payment_account="Cash - _TC")
+		make_payroll_entry(company="_Test Company", start_date=dates.start_date, payable_account=company_doc.default_payroll_payable_account,
+			currency=company_doc.default_currency, end_date=dates.end_date, branch=branch, cost_center="Main - _TC", payment_account="Cash - _TC")
 
 		name = frappe.db.get_value('Salary Slip',
 			{'posting_date': nowdate(), 'employee': applicant}, 'name')
@@ -165,6 +210,9 @@
 	payroll_entry.payroll_frequency = "Monthly"
 	payroll_entry.branch = args.branch or None
 	payroll_entry.department = args.department or None
+	payroll_entry.payroll_payable_account = args.payable_account
+	payroll_entry.currency = args.currency
+	payroll_entry.exchange_rate = args.exchange_rate or 1
 
 	if args.cost_center:
 		payroll_entry.cost_center = args.cost_center
@@ -212,3 +260,11 @@
 		}).insert()
 
 	return holiday_list_name
+
+def get_salary_slip(user, period, salary_structure):
+	salary_slip = make_employee_salary_slip(user, period, salary_structure)
+	salary_slip.exchange_rate = 70
+	salary_slip.calculate_net_pay()
+	salary_slip.db_update()
+
+	return salary_slip
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/payroll_entry/test_set_salary_components.js b/erpnext/payroll/doctype/payroll_entry/test_set_salary_components.js
index 8ff5515..092cbd8 100644
--- a/erpnext/payroll/doctype/payroll_entry/test_set_salary_components.js
+++ b/erpnext/payroll/doctype/payroll_entry/test_set_salary_components.js
@@ -9,45 +9,45 @@
 		() => {
 			var row = frappe.model.add_child(cur_frm.doc, "Salary Component Account", "accounts");
 			row.company = 'For Testing';
-			row.default_account = 'Salary - FT';
+			row.account = 'Salary - FT';
 		},
 
 		() => cur_frm.save(),
 		() => frappe.timeout(2),
-		() => assert.equal(cur_frm.doc.accounts[0].default_account, 'Salary - FT'),
+		() => assert.equal(cur_frm.doc.accounts[0].account, 'Salary - FT'),
 
 		() => frappe.set_route('Form', 'Salary Component', 'Basic'),
 		() => {
 			var row = frappe.model.add_child(cur_frm.doc, "Salary Component Account", "accounts");
 			row.company = 'For Testing';
-			row.default_account = 'Salary - FT';
+			row.account = 'Salary - FT';
 		},
 
 		() => cur_frm.save(),
 		() => frappe.timeout(2),
-		() => assert.equal(cur_frm.doc.accounts[0].default_account, 'Salary - FT'),
+		() => assert.equal(cur_frm.doc.accounts[0].account, 'Salary - FT'),
 
 		() => frappe.set_route('Form', 'Salary Component', 'Income Tax'),
 		() => {
 			var row = frappe.model.add_child(cur_frm.doc, "Salary Component Account", "accounts");
 			row.company = 'For Testing';
-			row.default_account = 'Salary - FT';
+			row.account = 'Salary - FT';
 		},
 
 		() => cur_frm.save(),
 		() => frappe.timeout(2),
-		() => assert.equal(cur_frm.doc.accounts[0].default_account, 'Salary - FT'),
+		() => assert.equal(cur_frm.doc.accounts[0].account, 'Salary - FT'),
 
 		() => frappe.set_route('Form', 'Salary Component', 'Arrear'),
 		() => {
 			var row = frappe.model.add_child(cur_frm.doc, "Salary Component Account", "accounts");
 			row.company = 'For Testing';
-			row.default_account = 'Salary - FT';
+			row.account = 'Salary - FT';
 		},
 
 		() => cur_frm.save(),
 		() => frappe.timeout(2),
-		() => assert.equal(cur_frm.doc.accounts[0].default_account, 'Salary - FT'),
+		() => assert.equal(cur_frm.doc.accounts[0].account, 'Salary - FT'),
 
 		() => frappe.set_route('Form', 'Company', 'For Testing'),
 		() => cur_frm.set_value('default_payroll_payable_account', 'Payroll Payable - FT'),
diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.js b/erpnext/payroll/doctype/retention_bonus/retention_bonus.js
index 64e726d..f8bb40a 100644
--- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.js
+++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.js
@@ -4,9 +4,13 @@
 frappe.ui.form.on('Retention Bonus', {
 	setup: function(frm) {
 		frm.set_query("employee", function() {
+			if (!frm.doc.company) {
+				frappe.msgprint(__("Please Select Company First"));
+			}
 			return {
 				filters: {
-					"status": "Active"
+					"status": "Active",
+					"company": frm.doc.company
 				}
 			};
 		});
@@ -18,5 +22,22 @@
 				}
 			};
 		});
+	},
+
+	employee: function(frm) {
+		if (frm.doc.employee) {
+			frappe.call({
+				method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+				args: {
+					employee: frm.doc.employee,
+				},
+				callback: function(r) {
+					if (r.message) {
+						frm.set_value('currency', r.message);
+						frm.refresh_fields();
+					}
+				}
+			});
+		}
 	}
 });
diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
index da884c2..6647230 100644
--- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
+++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
@@ -17,7 +17,8 @@
   "column_break_6",
   "employee_name",
   "department",
-  "date_of_joining"
+  "date_of_joining",
+  "currency"
  ],
  "fields": [
   {
@@ -46,6 +47,7 @@
    "fieldname": "bonus_amount",
    "fieldtype": "Currency",
    "label": "Bonus Amount",
+   "options": "currency",
    "reqd": 1
   },
   {
@@ -89,11 +91,22 @@
    "label": "Salary Component",
    "options": "Salary Component",
    "reqd": 1
+  },
+  {
+   "default": "Company:company:default_currency",
+   "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "label": "Currency",
+   "options": "Currency",
+   "print_hide": 1,
+   "read_only": 1,
+   "reqd": 1
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-22 22:42:05.251951",
+ "modified": "2020-10-20 17:27:47.003134",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Retention Bonus",
@@ -151,7 +164,6 @@
    "share": 1
   }
  ],
- "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
  "track_changes": 1
diff --git a/erpnext/payroll/doctype/salary_component/salary_component.js b/erpnext/payroll/doctype/salary_component/salary_component.js
index c455eb3..dbf7514 100644
--- a/erpnext/payroll/doctype/salary_component/salary_component.js
+++ b/erpnext/payroll/doctype/salary_component/salary_component.js
@@ -3,7 +3,7 @@
 
 frappe.ui.form.on('Salary Component', {
 	setup: function(frm) {
-		frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
+		frm.set_query("account", "accounts", function(doc, cdt, cdn) {
 			var d = locals[cdt][cdn];
 			return {
 				filters: {
diff --git a/erpnext/payroll/doctype/salary_detail/salary_detail.json b/erpnext/payroll/doctype/salary_detail/salary_detail.json
index eedb56e..5c1eb61 100644
--- a/erpnext/payroll/doctype/salary_detail/salary_detail.json
+++ b/erpnext/payroll/doctype/salary_detail/salary_detail.json
@@ -147,7 +147,7 @@
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Amount",
-   "options": "Company:company:default_currency"
+   "options": "currency"
   },
   {
    "default": "0",
@@ -160,7 +160,7 @@
    "fieldname": "default_amount",
    "fieldtype": "Currency",
    "label": "Default Amount",
-   "options": "Company:company:default_currency",
+   "options": "currency",
    "print_hide": 1
   },
   {
@@ -169,6 +169,7 @@
    "hidden": 1,
    "label": "Additional Amount",
    "no_copy": 1,
+   "options": "currency",
    "print_hide": 1,
    "read_only": 1
   },
@@ -177,6 +178,7 @@
    "fieldname": "tax_on_flexible_benefit",
    "fieldtype": "Currency",
    "label": "Tax on flexible benefit",
+   "options": "currency",
    "read_only": 1
   },
   {
@@ -184,6 +186,7 @@
    "fieldname": "tax_on_additional_salary",
    "fieldtype": "Currency",
    "label": "Tax on additional salary",
+   "options": "currency",
    "read_only": 1
   },
   {
@@ -227,7 +230,7 @@
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-10-07 20:39:41.619283",
+ "modified": "2020-11-25 13:12:41.081106",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Salary Detail",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js
index 7b69dbe..8e05bb2 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.js
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js
@@ -13,12 +13,12 @@
 			];
 		});
 
-		frm.fields_dict["timesheets"].grid.get_field("time_sheet").get_query = function(){
+		frm.fields_dict["timesheets"].grid.get_field("time_sheet").get_query = function() {
 			return {
 				filters: {
 					employee: frm.doc.employee
 				}
-			}
+			};
 		};
 
 		frm.set_query("salary_component", "earnings", function() {
@@ -26,7 +26,7 @@
 				filters: {
 					type: "earning"
 				}
-			}
+			};
 		});
 
 		frm.set_query("salary_component", "deductions", function() {
@@ -34,18 +34,18 @@
 				filters: {
 					type: "deduction"
 				}
-			}
+			};
 		});
 
 		frm.set_query("employee", function() {
-			return{
+			return {
 				query: "erpnext.controllers.queries.employee_query"
-			}
+			};
 		});
 	},
 
-	start_date: function(frm){
-		if(frm.doc.start_date){
+	start_date: function(frm) {
+		if (frm.doc.start_date) {
 			frm.trigger("set_end_date");
 		}
 	},
@@ -54,7 +54,7 @@
 		frm.events.get_emp_and_working_day_details(frm);
 	},
 
-	set_end_date: function(frm){
+	set_end_date: function(frm) {
 		frappe.call({
 			method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.get_end_date',
 			args: {
@@ -66,22 +66,93 @@
 					frm.set_value('end_date', r.message.end_date);
 				}
 			}
-		})
+		});
 	},
 
 	company: function(frm) {
 		var company = locals[':Company'][frm.doc.company];
-		if(!frm.doc.letter_head && company.default_letter_head) {
+		if (!frm.doc.letter_head && company.default_letter_head) {
 			frm.set_value('letter_head', company.default_letter_head);
 		}
+		frm.trigger("set_dynamic_labels");
+	},
+
+	set_dynamic_labels: function(frm) {
+		var company_currency = frm.doc.company? erpnext.get_currency(frm.doc.company): frappe.defaults.get_default("currency");
+		frappe.run_serially([
+			() => 	frm.events.set_exchange_rate(frm, company_currency),
+			() => 	frm.events.change_form_labels(frm, company_currency),
+			() => 	frm.events.change_grid_labels(frm),
+			() => 	frm.refresh_fields()
+		]);
+	},
+
+	set_exchange_rate: function(frm, company_currency) {
+		if (frm.doc.docstatus === 0) {
+			if (frm.doc.currency) {
+				var from_currency = frm.doc.currency;
+				if (from_currency != company_currency) {
+					frm.events.hide_loan_section(frm);
+					frappe.call({
+						method: "erpnext.setup.utils.get_exchange_rate",
+						args: {
+							from_currency: from_currency,
+							to_currency: company_currency,
+						},
+						callback: function(r) {
+							frm.set_value("exchange_rate", flt(r.message));
+							frm.set_df_property('exchange_rate', 'hidden', 0);
+							frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
+								+ " = [?] " + company_currency);
+						}
+					});
+				} else {
+					frm.set_value("exchange_rate", 1.0);
+					frm.set_df_property('exchange_rate', 'hidden', 1);
+					frm.set_df_property("exchange_rate", "description", "" );
+				}
+			}
+		}
 	},
 
+	exchange_rate: function(frm) {
+		calculate_totals(frm);
+	},
+
+	hide_loan_section: function(frm) {
+		frm.set_df_property('section_break_43', 'hidden', 1);
+	},
+
+	change_form_labels: function(frm, company_currency) {
+		frm.set_currency_labels(["base_hour_rate", "base_gross_pay", "base_total_deduction",
+			"base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date"],
+		company_currency);
+
+		frm.set_currency_labels(["hour_rate", "gross_pay", "total_deduction", "net_pay", "rounded_total", "total_in_words", "year_to_date", "month_to_date"],
+			frm.doc.currency);
+
+		// toggle fields
+		frm.toggle_display(["exchange_rate", "base_hour_rate", "base_gross_pay", "base_total_deduction",
+			"base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date"],
+		frm.doc.currency != company_currency);
+	},
+
+	change_grid_labels: function(frm) {
+		frm.set_currency_labels(["amount", "default_amount", "additional_amount", "tax_on_flexible_benefit",
+			"tax_on_additional_salary"], frm.doc.currency, "earnings");
+
+		frm.set_currency_labels(["amount", "default_amount", "additional_amount", "tax_on_flexible_benefit",
+			"tax_on_additional_salary"], frm.doc.currency, "deductions");
+	},
+
 	refresh: function(frm) {
-		frm.trigger("toggle_fields")
+		frm.trigger("toggle_fields");
 
 		var salary_detail_fields = ["formula", "abbr", "statistical_component", "variable_based_on_taxable_salary"];
-		cur_frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields,false);
-		cur_frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields,false);
+		frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields, false);
+		frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields, false);
+		calculate_totals(frm);
+		frm.trigger("set_dynamic_labels");
 	},
 
 	salary_slip_based_on_timesheet: function(frm) {
@@ -98,12 +169,12 @@
 		frm.events.get_emp_and_working_day_details(frm);
 	},
 
-	leave_without_pay: function(frm){
+	leave_without_pay: function(frm) {
 		if (frm.doc.employee && frm.doc.start_date && frm.doc.end_date) {
 			return frappe.call({
 				method: 'process_salary_based_on_working_days',
 				doc: frm.doc,
-				callback: function(r, rt) {
+				callback: function() {
 					frm.refresh();
 				}
 			});
@@ -118,51 +189,96 @@
 	},
 
 	get_emp_and_working_day_details: function(frm) {
-		return frappe.call({
-			method: 'get_emp_and_working_day_details',
-			doc: frm.doc,
-			callback: function(r, rt) {
-				frm.refresh();
-				if (r.message){
-					frm.fields_dict.absent_days.set_description("Unmarked Days is treated as "+ r.message +". You can can change this in " + frappe.utils.get_form_link("Payroll Settings", "Payroll Settings", true));
+		if (frm.doc.employee) {
+			return frappe.call({
+				method: 'get_emp_and_working_day_details',
+				doc: frm.doc,
+				callback: function(r) {
+					if (r.message[1] !== "Leave" && r.message[0]) {
+						frm.fields_dict.absent_days.set_description(__("Unmarked Days is treated as {0}. You can can change this in {1}", [r.message, frappe.utils.get_form_link("Payroll Settings", "Payroll Settings", true)]));
+					}
+					frm.refresh();
 				}
-			}
-		});
+			});
+		}
 	}
 });
 
 frappe.ui.form.on('Salary Slip Timesheet', {
-	time_sheet: function(frm, dt, dn) {
-		total_work_hours(frm, dt, dn);
+	time_sheet: function(frm) {
+		calculate_totals(frm);
 	},
-	timesheets_remove: function(frm, dt, dn) {
-		total_work_hours(frm, dt, dn);
+	timesheets_remove: function(frm) {
+		calculate_totals(frm);
 	}
 });
 
-// calculate total working hours, earnings based on hourly wages and totals
-var total_work_hours = function(frm, dt, dn) {
-	var total_working_hours = 0.0;
-	$.each(frm.doc["timesheets"] || [], function(i, timesheet) {
-		total_working_hours += timesheet.working_hours;
-	});
-	frm.set_value('total_working_hours', total_working_hours);
+var calculate_totals = function(frm) {
+	if (frm.doc.docstatus === 0) {
+		if (frm.doc.earnings || frm.doc.deductions) {
+			frappe.call({
+				method: "set_totals",
+				doc: frm.doc,
+				callback: function() {
+					frm.refresh_fields();
+				}
+			});
+		}
+	}
+};
 
-	var wages_amount = frm.doc.total_working_hours * frm.doc.hour_rate;
+frappe.ui.form.on('Salary Detail', {
+	amount: function(frm) {
+		calculate_totals(frm);
+	},
 
-	frappe.db.get_value('Salary Structure', {'name': frm.doc.salary_structure}, 'salary_component', (r) => {
-		var gross_pay = 0.0;
-		$.each(frm.doc["earnings"], function(i, earning) {
-			if (earning.salary_component == r.salary_component) {
-				earning.amount = wages_amount;
-				frm.refresh_fields('earnings');
-			}
-			gross_pay += earning.amount;
-		});
-		frm.set_value('gross_pay', gross_pay);
+	earnings_remove: function(frm) {
+		calculate_totals(frm);
+	},
 
-		frm.doc.net_pay = flt(frm.doc.gross_pay) - flt(frm.doc.total_deduction);
-		frm.doc.rounded_total = Math.round(frm.doc.net_pay);
-		refresh_many(['net_pay', 'rounded_total']);
-	});
-}
+	deductions_remove: function(frm) {
+		calculate_totals(frm);
+	},
+
+	salary_component: function(frm, cdt, cdn) {
+		var child = locals[cdt][cdn];
+		if (child.salary_component) {
+			frappe.call({
+				method: "frappe.client.get",
+				args: {
+					doctype: "Salary Component",
+					name: child.salary_component
+				},
+				callback: function(data) {
+					if (data.message) {
+						var result = data.message;
+						frappe.model.set_value(cdt, cdn, 'condition', result.condition);
+						frappe.model.set_value(cdt, cdn, 'amount_based_on_formula', result.amount_based_on_formula);
+						if (result.amount_based_on_formula === 1) {
+							frappe.model.set_value(cdt, cdn, 'formula', result.formula);
+						} else {
+							frappe.model.set_value(cdt, cdn, 'amount', result.amount);
+						}
+						frappe.model.set_value(cdt, cdn, 'statistical_component', result.statistical_component);
+						frappe.model.set_value(cdt, cdn, 'depends_on_payment_days', result.depends_on_payment_days);
+						frappe.model.set_value(cdt, cdn, 'do_not_include_in_total', result.do_not_include_in_total);
+						frappe.model.set_value(cdt, cdn, 'variable_based_on_taxable_salary', result.variable_based_on_taxable_salary);
+						frappe.model.set_value(cdt, cdn, 'is_tax_applicable', result.is_tax_applicable);
+						frappe.model.set_value(cdt, cdn, 'is_flexible_benefit', result.is_flexible_benefit);
+						refresh_field("earnings");
+						refresh_field("deductions");
+					}
+				}
+			});
+		}
+	},
+
+	amount_based_on_formula: function(frm, cdt, cdn) {
+		var child = locals[cdt][cdn];
+		if (child.amount_based_on_formula === 1) {
+			frappe.model.set_value(cdt, cdn, 'amount', null);
+		} else {
+			frappe.model.set_value(cdt, cdn, 'formula', null);
+		}
+	}
+});
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json
index 619c45f..43deee4 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.json
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json
@@ -18,6 +18,8 @@
   "journal_entry",
   "payroll_entry",
   "company",
+  "currency",
+  "exchange_rate",
   "letter_head",
   "section_break_10",
   "start_date",
@@ -38,6 +40,7 @@
   "column_break_20",
   "total_working_hours",
   "hour_rate",
+  "base_hour_rate",
   "section_break_26",
   "bank_name",
   "bank_account_no",
@@ -52,8 +55,10 @@
   "deductions",
   "totals",
   "gross_pay",
+  "base_gross_pay",
   "column_break_25",
   "total_deduction",
+  "base_total_deduction",
   "loan_repayment",
   "loans",
   "section_break_43",
@@ -63,10 +68,19 @@
   "total_loan_repayment",
   "net_pay_info",
   "net_pay",
+  "base_net_pay",
+  "year_to_date",
+  "base_year_to_date",
   "column_break_53",
   "rounded_total",
+  "base_rounded_total",
+  "month_to_date",
+  "base_month_to_date",
   "section_break_55",
   "total_in_words",
+  "column_break_69",
+  "base_total_in_words",
+  "section_break_75",
   "amended_from"
  ],
  "fields": [
@@ -205,9 +219,13 @@
   {
    "fieldname": "salary_structure",
    "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
    "label": "Salary Structure",
    "options": "Salary Structure",
-   "read_only": 1
+   "read_only": 1,
+   "reqd": 1,
+   "search_index": 1
   },
   {
    "depends_on": "eval:(!doc.salary_slip_based_on_timesheet)",
@@ -265,7 +283,7 @@
    "fieldname": "hour_rate",
    "fieldtype": "Currency",
    "label": "Hour Rate",
-   "options": "Company:company:default_currency",
+   "options": "currency",
    "print_hide_if_no_value": 1
   },
   {
@@ -347,9 +365,7 @@
    "fieldname": "gross_pay",
    "fieldtype": "Currency",
    "label": "Gross Pay",
-   "oldfieldname": "gross_pay",
-   "oldfieldtype": "Currency",
-   "options": "Company:company:default_currency",
+   "options": "currency",
    "read_only": 1
   },
   {
@@ -357,15 +373,6 @@
    "fieldtype": "Column Break"
   },
   {
-   "fieldname": "total_deduction",
-   "fieldtype": "Currency",
-   "label": "Total Deduction",
-   "oldfieldname": "total_deduction",
-   "oldfieldtype": "Currency",
-   "options": "Company:company:default_currency",
-   "read_only": 1
-  },
-  {
    "depends_on": "total_loan_repayment",
    "fieldname": "loan_repayment",
    "fieldtype": "Section Break",
@@ -379,6 +386,7 @@
    "print_hide": 1
   },
   {
+   "depends_on": "eval:doc.docstatus != 0",
    "fieldname": "section_break_43",
    "fieldtype": "Section Break"
   },
@@ -416,13 +424,10 @@
    "label": "net pay info"
   },
   {
-   "description": "Gross Pay - Total Deduction - Loan Repayment",
    "fieldname": "net_pay",
    "fieldtype": "Currency",
    "label": "Net Pay",
-   "oldfieldname": "net_pay",
-   "oldfieldtype": "Currency",
-   "options": "Company:company:default_currency",
+   "options": "currency",
    "read_only": 1
   },
   {
@@ -434,7 +439,7 @@
    "fieldname": "rounded_total",
    "fieldtype": "Currency",
    "label": "Rounded Total",
-   "options": "Company:company:default_currency",
+   "options": "currency",
    "read_only": 1
   },
   {
@@ -442,15 +447,6 @@
    "fieldtype": "Section Break"
   },
   {
-   "description": "Net Pay (in words) will be visible once you save the Salary Slip.",
-   "fieldname": "total_in_words",
-   "fieldtype": "Data",
-   "label": "Total in words",
-   "oldfieldname": "net_pay_in_words",
-   "oldfieldtype": "Data",
-   "read_only": 1
-  },
-  {
    "fieldname": "amended_from",
    "fieldtype": "Link",
    "ignore_user_permissions": 1,
@@ -500,13 +496,127 @@
   {
    "fieldname": "column_break_18",
    "fieldtype": "Column Break"
+  },
+  {
+   "default": "Company:company:default_currency",
+   "depends_on": "eval:(doc.docstatus==1 || doc.salary_structure)",
+   "fetch_from": "salary_structure.currency",
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "label": "Currency",
+   "options": "Currency",
+   "print_hide": 1,
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "fieldname": "total_deduction",
+   "fieldtype": "Currency",
+   "label": "Total Deduction",
+   "options": "currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "total_in_words",
+   "fieldtype": "Data",
+   "label": "Total in words",
+   "length": 240,
+   "read_only": 1
+  },
+  {
+   "fieldname": "section_break_75",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "base_hour_rate",
+   "fieldtype": "Currency",
+   "label": "Hour Rate (Company Currency)",
+   "options": "Company:company:default_currency",
+   "print_hide_if_no_value": 1
+  },
+  {
+   "fieldname": "base_gross_pay",
+   "fieldtype": "Currency",
+   "label": "Gross Pay (Company Currency)",
+   "options": "Company:company:default_currency",
+   "read_only": 1
+  },
+  {
+   "default": "1.0",
+   "fieldname": "exchange_rate",
+   "fieldtype": "Float",
+   "hidden": 1,
+   "label": "Exchange Rate",
+   "print_hide": 1,
+   "reqd": 1
+  },
+  {
+   "fieldname": "base_total_deduction",
+   "fieldtype": "Currency",
+   "label": "Total Deduction (Company Currency)",
+   "options": "Company:company:default_currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "base_net_pay",
+   "fieldtype": "Currency",
+   "label": "Net Pay (Company Currency)",
+   "options": "Company:company:default_currency",
+   "read_only": 1
+  },
+  {
+   "bold": 1,
+   "fieldname": "base_rounded_total",
+   "fieldtype": "Currency",
+   "label": "Rounded Total (Company Currency)",
+   "options": "Company:company:default_currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "base_total_in_words",
+   "fieldtype": "Data",
+   "label": "Total in words (Company Currency)",
+   "length": 240,
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_69",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "year_to_date",
+   "fieldtype": "Currency",
+   "label": "Year To Date",
+   "options": "currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "month_to_date",
+   "fieldtype": "Currency",
+   "label": "Month To Date",
+   "options": "currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "base_year_to_date",
+   "fieldtype": "Currency",
+   "label": "Year To Date(Company Currency)",
+   "options": "Company:company:default_currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "base_month_to_date",
+   "fieldtype": "Currency",
+   "label": "Month To Date(Company Currency)",
+   "options": "Company:company:default_currency",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 9,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-08-11 17:37:54.274384",
+ "modified": "2020-12-21 23:43:44.959840",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Salary Slip",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index cecb8cd..99d8a83 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -5,7 +5,7 @@
 import frappe, erpnext
 import datetime, math
 
-from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate
+from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate, get_first_day
 from frappe.model.naming import make_autoname
 
 from frappe import msgprint, _
@@ -18,6 +18,7 @@
 from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount
 from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits
 from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry
+from erpnext.accounts.utils import get_fiscal_year
 
 class SalarySlip(TransactionBase):
 	def __init__(self, *args, **kwargs):
@@ -49,10 +50,8 @@
 			self.get_working_days_details(lwp = self.leave_without_pay)
 
 		self.calculate_net_pay()
-
-		company_currency = erpnext.get_company_currency(self.company)
-		total = self.net_pay if self.is_rounding_total_disabled() else self.rounded_total
-		self.total_in_words = money_in_words(total, company_currency)
+		self.compute_year_to_date()
+		self.compute_month_to_date()
 
 		if frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet"):
 			max_working_hours = frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet")
@@ -60,6 +59,14 @@
 				frappe.msgprint(_("Total working hours should not be greater than max working hours {0}").
 								format(max_working_hours), alert=True)
 
+	def set_net_total_in_words(self):
+		doc_currency = self.currency
+		company_currency = erpnext.get_company_currency(self.company)
+		total = self.net_pay if self.is_rounding_total_disabled() else self.rounded_total
+		base_total = self.base_net_pay if self.is_rounding_total_disabled() else self.base_rounded_total
+		self.total_in_words = money_in_words(total, doc_currency)
+		self.base_total_in_words = money_in_words(base_total, company_currency)
+
 	def on_submit(self):
 		if self.net_pay < 0:
 			frappe.throw(_("Net Pay cannot be less than 0"))
@@ -136,8 +143,8 @@
 				self.salary_slip_based_on_timesheet = self._salary_structure_doc.salary_slip_based_on_timesheet or 0
 				self.set_time_sheet()
 				self.pull_sal_struct()
-				consider_unmarked_attendance_as = frappe.db.get_value("Payroll Settings", None, "consider_unmarked_attendance_as") or "Present"
-				return consider_unmarked_attendance_as
+				payroll_based_on, consider_unmarked_attendance_as = frappe.db.get_value("Payroll Settings", None, ["payroll_based_on","consider_unmarked_attendance_as"])
+				return [payroll_based_on, consider_unmarked_attendance_as]
 
 	def set_time_sheet(self):
 		if self.salary_slip_based_on_timesheet:
@@ -182,6 +189,7 @@
 		if self.salary_slip_based_on_timesheet:
 			self.salary_structure = self._salary_structure_doc.name
 			self.hour_rate = self._salary_structure_doc.hour_rate
+			self.base_hour_rate = flt(self.hour_rate) * flt(self.exchange_rate)
 			self.total_working_hours = sum([d.working_hours or 0.0 for d in self.timesheets]) or 0.0
 			wages_amount = self.hour_rate * self.total_working_hours
 
@@ -210,10 +218,10 @@
 			frappe.throw(_("Please set Payroll based on in Payroll settings"))
 
 		if payroll_based_on == "Attendance":
-			actual_lwp, absent = self.calculate_lwp_and_absent_days_based_on_attendance(holidays)
+			actual_lwp, absent = self.calculate_lwp_ppl_and_absent_days_based_on_attendance(holidays)
 			self.absent_days = absent
 		else:
-			actual_lwp = self.calculate_lwp_based_on_leave_application(holidays, working_days)
+			actual_lwp = self.calculate_lwp_or_ppl_based_on_leave_application(holidays, working_days)
 
 		if not lwp:
 			lwp = actual_lwp
@@ -300,7 +308,7 @@
 
 		return holidays
 
-	def calculate_lwp_based_on_leave_application(self, holidays, working_days):
+	def calculate_lwp_or_ppl_based_on_leave_application(self, holidays, working_days):
 		lwp = 0
 		holidays = "','".join(holidays)
 		daily_wages_fraction_for_half_day = \
@@ -311,10 +319,12 @@
 			leave = frappe.db.sql("""
 				SELECT t1.name,
 					CASE WHEN (t1.half_day_date = %(dt)s or t1.to_date = t1.from_date)
-					THEN t1.half_day else 0 END
+					THEN t1.half_day else 0 END,
+					t2.is_ppl,
+					t2.fraction_of_daily_salary_per_leave
 				FROM `tabLeave Application` t1, `tabLeave Type` t2
 				WHERE t2.name = t1.leave_type
-				AND t2.is_lwp = 1
+				AND (t2.is_lwp = 1 or t2.is_ppl = 1)
 				AND t1.docstatus = 1
 				AND t1.employee = %(employee)s
 				AND ifnull(t1.salary_slip, '') = ''
@@ -327,19 +337,35 @@
 				""".format(holidays), {"employee": self.employee, "dt": dt})
 
 			if leave:
+				equivalent_lwp_count = 0
 				is_half_day_leave = cint(leave[0][1])
-				lwp += (1 - daily_wages_fraction_for_half_day) if is_half_day_leave else 1
+				is_partially_paid_leave = cint(leave[0][2])
+				fraction_of_daily_salary_per_leave = flt(leave[0][3])
+
+				equivalent_lwp_count =  (1 - daily_wages_fraction_for_half_day) if is_half_day_leave else 1
+
+				if is_partially_paid_leave:
+					equivalent_lwp_count *= fraction_of_daily_salary_per_leave if fraction_of_daily_salary_per_leave else 1
+
+				lwp += equivalent_lwp_count
 
 		return lwp
 
-	def calculate_lwp_and_absent_days_based_on_attendance(self, holidays):
+	def calculate_lwp_ppl_and_absent_days_based_on_attendance(self, holidays):
 		lwp = 0
 		absent = 0
 
 		daily_wages_fraction_for_half_day = \
 			flt(frappe.db.get_value("Payroll Settings", None, "daily_wages_fraction_for_half_day")) or 0.5
 
-		lwp_leave_types = dict(frappe.get_all("Leave Type", {"is_lwp": 1}, ["name", "include_holiday"], as_list=1))
+		leave_types = frappe.get_all("Leave Type",
+			or_filters=[["is_ppl", "=", 1], ["is_lwp", "=", 1]],
+			fields =["name", "is_lwp", "is_ppl", "fraction_of_daily_salary_per_leave", "include_holiday"])
+
+		leave_type_map = {}
+		for leave_type in leave_types:
+			leave_type_map[leave_type.name] = leave_type
+
 		attendances = frappe.db.sql('''
 			SELECT attendance_date, status, leave_type
 			FROM `tabAttendance`
@@ -351,21 +377,30 @@
 		''', values=(self.employee, self.start_date, self.end_date), as_dict=1)
 
 		for d in attendances:
-			if d.status in ('Half Day', 'On Leave') and d.leave_type and d.leave_type not in lwp_leave_types:
+			if d.status in ('Half Day', 'On Leave') and d.leave_type and d.leave_type not in leave_type_map.keys():
 				continue
 
 			if formatdate(d.attendance_date, "yyyy-mm-dd") in holidays:
 				if d.status == "Absent" or \
-					(d.leave_type and d.leave_type in lwp_leave_types and not lwp_leave_types[d.leave_type]):
+					(d.leave_type and d.leave_type in leave_type_map.keys() and not leave_type_map[d.leave_type]['include_holiday']):
 						continue
 
+			if d.leave_type:
+				fraction_of_daily_salary_per_leave = leave_type_map[d.leave_type]["fraction_of_daily_salary_per_leave"]
+
 			if d.status == "Half Day":
-				lwp += (1 - daily_wages_fraction_for_half_day)
-			elif d.status == "On Leave" and d.leave_type in lwp_leave_types:
-				lwp += 1
+				equivalent_lwp =  (1 - daily_wages_fraction_for_half_day)
+
+				if d.leave_type in leave_type_map.keys() and leave_type_map[d.leave_type]["is_ppl"]:
+					equivalent_lwp *= fraction_of_daily_salary_per_leave if fraction_of_daily_salary_per_leave else 1
+				lwp += equivalent_lwp
+			elif d.status == "On Leave" and d.leave_type and d.leave_type in leave_type_map.keys():
+				equivalent_lwp = 1
+				if leave_type_map[d.leave_type]["is_ppl"]:
+					equivalent_lwp *= fraction_of_daily_salary_per_leave if fraction_of_daily_salary_per_leave else 1
+				lwp += equivalent_lwp
 			elif d.status == "Absent":
 				absent += 1
-
 		return lwp, absent
 
 	def add_earning_for_hourly_wages(self, doc, salary_component, amount):
@@ -390,15 +425,22 @@
 		if self.salary_structure:
 			self.calculate_component_amounts("earnings")
 		self.gross_pay = self.get_component_totals("earnings")
+		self.base_gross_pay = flt(flt(self.gross_pay) * flt(self.exchange_rate), self.precision('base_gross_pay'))
 
 		if self.salary_structure:
 			self.calculate_component_amounts("deductions")
 		self.total_deduction = self.get_component_totals("deductions")
+		self.base_total_deduction = flt(flt(self.total_deduction) * flt(self.exchange_rate), self.precision('base_total_deduction'))
 
 		self.set_loan_repayment()
 
 		self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
 		self.rounded_total = rounded(self.net_pay)
+		self.base_net_pay = flt(flt(self.net_pay) * flt(self.exchange_rate), self.precision('base_net_pay'))
+		self.base_rounded_total = flt(rounded(self.base_net_pay), self.precision('base_net_pay'))
+		if self.hour_rate:
+			self.base_hour_rate = flt(flt(self.hour_rate) * flt(self.exchange_rate), self.precision('base_hour_rate'))
+		self.set_net_total_in_words()
 
 	def calculate_component_amounts(self, component_type):
 		if not getattr(self, '_salary_structure_doc', None):
@@ -949,9 +991,9 @@
 			amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment")
 			total_amount = amounts['interest_amount'] + amounts['payable_principal_amount']
 			if payment.total_payment > total_amount:
-				frappe.throw(_("""Row {0}: Paid amount {1} is greater than pending accrued amount {2}
-					against loan {3}""").format(payment.idx, frappe.bold(payment.total_payment),
-					frappe.bold(total_amount), frappe.bold(payment.loan)))
+				frappe.throw(_("""Row {0}: Paid amount {1} is greater than pending accrued amount {2} against loan {3}""")
+					.format(payment.idx, frappe.bold(payment.total_payment),
+						frappe.bold(total_amount), frappe.bold(payment.loan)))
 
 			self.total_interest_amount += payment.interest_amount
 			self.total_principal_amount += payment.principal_amount
@@ -1046,6 +1088,86 @@
 		self.get_working_days_details(lwp=self.leave_without_pay)
 		self.calculate_net_pay()
 
+	def set_totals(self):
+		self.gross_pay = 0
+		if self.salary_slip_based_on_timesheet == 1:
+			self.calculate_total_for_salary_slip_based_on_timesheet()
+		else:
+			self.total_deduction = 0
+			if self.earnings:
+				for earning in self.earnings:
+					self.gross_pay += flt(earning.amount)
+			if self.deductions:
+				for deduction in self.deductions:
+					self.total_deduction += flt(deduction.amount)
+			self.net_pay = flt(self.gross_pay) - flt(self.total_deduction) - flt(self.total_loan_repayment)
+		self.set_base_totals()
+
+	def set_base_totals(self):
+		self.base_gross_pay = flt(self.gross_pay) * flt(self.exchange_rate)
+		self.base_total_deduction = flt(self.total_deduction) * flt(self.exchange_rate)
+		self.rounded_total = rounded(self.net_pay)
+		self.base_net_pay = flt(self.net_pay) * flt(self.exchange_rate)
+		self.base_rounded_total = rounded(self.base_net_pay)
+		self.set_net_total_in_words()
+
+	#calculate total working hours, earnings based on hourly wages and totals
+	def calculate_total_for_salary_slip_based_on_timesheet(self):
+		if self.timesheets:
+			for timesheet in self.timesheets:
+				if timesheet.working_hours:
+					self.total_working_hours += timesheet.working_hours
+
+		wages_amount = self.total_working_hours * self.hour_rate
+		self.base_hour_rate = flt(self.hour_rate) * flt(self.exchange_rate)
+		salary_component = frappe.db.get_value('Salary Structure', {'name': self.salary_structure}, 'salary_component')
+		if self.earnings:
+			for i, earning in enumerate(self.earnings):
+				if earning.salary_component == salary_component:
+					self.earnings[i].amount = wages_amount
+				self.gross_pay += self.earnings[i].amount
+		self.net_pay = flt(self.gross_pay) - flt(self.total_deduction)
+
+	def compute_year_to_date(self):
+		year_to_date = 0
+		payroll_period = get_payroll_period(self.start_date, self.end_date, self.company)
+
+		if payroll_period:
+			period_start_date = payroll_period.start_date
+			period_end_date = payroll_period.end_date
+		else:
+			# get dates based on fiscal year if no payroll period exists
+			fiscal_year = get_fiscal_year(date=self.start_date, company=self.company, as_dict=1)
+			period_start_date = fiscal_year.year_start_date
+			period_end_date = fiscal_year.year_end_date
+
+		salary_slip_sum = frappe.get_list('Salary Slip',
+			fields = ['sum(net_pay) as sum'],
+			filters = {'employee_name' : self.employee_name,
+				'start_date' : ['>=', period_start_date],
+				'end_date' : ['<', period_end_date]})
+
+
+		year_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0
+
+		year_to_date += self.net_pay
+		self.year_to_date = year_to_date
+
+	def compute_month_to_date(self):
+		month_to_date = 0
+		first_day_of_the_month = get_first_day(self.start_date)
+		salary_slip_sum = frappe.get_list('Salary Slip',
+			fields = ['sum(net_pay) as sum'],
+			filters = {'employee_name' : self.employee_name,
+				'start_date' : ['>=', first_day_of_the_month],
+				'end_date' : ['<', self.start_date]
+			})
+
+		month_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0
+
+		month_to_date += self.net_pay
+		self.month_to_date = month_to_date
+
 def unlink_ref_doc_from_salary_slip(ref_no):
 	linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip`
 	where journal_entry=%s and docstatus < 2""", (ref_no))
@@ -1056,4 +1178,4 @@
 
 def generate_password_for_pdf(policy_template, employee):
 	employee = frappe.get_doc("Employee", employee)
-	return policy_template.format(**employee.as_dict())
+	return policy_template.format(**employee.as_dict())
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 7fe4165..d6fb419 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -9,10 +9,12 @@
 import random
 from erpnext.accounts.utils import get_fiscal_year
 from frappe.utils.make_random import get_random
-from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_day, get_last_day
+from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_day, get_last_day, cstr
 from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
 from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_details
 from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
+from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
 from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration \
 	import create_payroll_period, create_exemption_category
 
@@ -31,7 +33,7 @@
 		frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
 		frappe.db.set_value("Payroll Settings", None, "daily_wages_fraction_for_half_day", 0.75)
 
-		emp_id = make_employee("test_for_attendance@salary.com")
+		emp_id = make_employee("test_payment_days_based_on_attendance@salary.com")
 		frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"})
 
 		frappe.db.set_value("Leave Type", "Leave Without Pay", "include_holiday", 0)
@@ -53,7 +55,7 @@
 		mark_attendance(emp_id, add_days(first_sunday, 4), 'On Leave', leave_type='Casual Leave', ignore_validate=True) # invalid lwp
 		mark_attendance(emp_id, add_days(first_sunday, 7), 'On Leave', leave_type='Leave Without Pay', ignore_validate=True) # invalid lwp
 
-		ss = make_employee_salary_slip("test_for_attendance@salary.com", "Monthly")
+		ss = make_employee_salary_slip("test_payment_days_based_on_attendance@salary.com", "Monthly", "Test Payment Based On Attendence")
 
 		self.assertEqual(ss.leave_without_pay, 1.25)
 		self.assertEqual(ss.absent_days, 1)
@@ -76,7 +78,7 @@
 		# Payroll based on attendance
 		frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
 
-		emp_id = make_employee("test_for_attendance@salary.com")
+		emp_id = make_employee("test_payment_days_based_on_leave_application@salary.com")
 		frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"})
 
 		frappe.db.set_value("Leave Type", "Leave Without Pay", "include_holiday", 0)
@@ -93,31 +95,40 @@
 
 		make_leave_application(emp_id, first_sunday, add_days(first_sunday, 3), "Leave Without Pay")
 
-		ss = make_employee_salary_slip("test_for_attendance@salary.com", "Monthly")
+		leave_type_ppl = create_leave_type(leave_type_name="Test Partially Paid Leave", is_ppl = 1)
+		leave_type_ppl.save()
 
-		self.assertEqual(ss.leave_without_pay, 3)
+		alloc = create_leave_allocation(
+			employee = emp_id, from_date = add_days(first_sunday, 4),
+			to_date = add_days(first_sunday, 10), new_leaves_allocated = 3,
+			leave_type = "Test Partially Paid Leave")
+		alloc.save()
+		alloc.submit()
+
+		#two day leave ppl with fraction_of_daily_salary_per_leave = 0.5 equivalent to single day lwp
+		make_leave_application(emp_id, add_days(first_sunday, 4), add_days(first_sunday, 5), "Test Partially Paid Leave")
+
+		ss = make_employee_salary_slip("test_payment_days_based_on_leave_application@salary.com", "Monthly", "Test Payment Based On Leave Application")
+
+
+		self.assertEqual(ss.leave_without_pay, 4)
 
 		days_in_month = no_of_days[0]
 		no_of_holidays = no_of_days[1]
 
-		self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 3)
-
-		#Gross pay calculation based on attendances
-		gross_pay = 78000 - ((78000 / (days_in_month - no_of_holidays)) * flt(ss.leave_without_pay))
-
-		self.assertEqual(flt(ss.gross_pay, 2), flt(gross_pay, 2))
+		self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 4)
 
 		frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
 
 	def test_salary_slip_with_holidays_included(self):
 		no_of_days = self.get_no_of_days()
 		frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1)
-		make_employee("test_employee@salary.com")
+		make_employee("test_salary_slip_with_holidays_included@salary.com")
 		frappe.db.set_value("Employee", frappe.get_value("Employee",
-			{"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
+			{"employee_name":"test_salary_slip_with_holidays_included@salary.com"}, "name"), "relieving_date", None)
 		frappe.db.set_value("Employee", frappe.get_value("Employee",
-			{"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
-		ss = make_employee_salary_slip("test_employee@salary.com", "Monthly")
+			{"employee_name":"test_salary_slip_with_holidays_included@salary.com"}, "name"), "status", "Active")
+		ss = make_employee_salary_slip("test_salary_slip_with_holidays_included@salary.com", "Monthly", "Test Salary Slip With Holidays Included")
 
 		self.assertEqual(ss.total_working_days, no_of_days[0])
 		self.assertEqual(ss.payment_days, no_of_days[0])
@@ -128,12 +139,12 @@
 	def test_salary_slip_with_holidays_excluded(self):
 		no_of_days = self.get_no_of_days()
 		frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0)
-		make_employee("test_employee@salary.com")
+		make_employee("test_salary_slip_with_holidays_excluded@salary.com")
 		frappe.db.set_value("Employee", frappe.get_value("Employee",
-			{"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
+			{"employee_name":"test_salary_slip_with_holidays_excluded@salary.com"}, "name"), "relieving_date", None)
 		frappe.db.set_value("Employee", frappe.get_value("Employee",
-			{"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
-		ss = make_employee_salary_slip("test_employee@salary.com", "Monthly")
+			{"employee_name":"test_salary_slip_with_holidays_excluded@salary.com"}, "name"), "status", "Active")
+		ss = make_employee_salary_slip("test_salary_slip_with_holidays_excluded@salary.com", "Monthly",  "Test Salary Slip With Holidays Excluded")
 
 		self.assertEqual(ss.total_working_days, no_of_days[0] - no_of_days[1])
 		self.assertEqual(ss.payment_days, no_of_days[0] - no_of_days[1])
@@ -148,7 +159,7 @@
 		frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1)
 
 		# set joinng date in the same month
-		make_employee("test_employee@salary.com")
+		make_employee("test_payment_days@salary.com")
 		if getdate(nowdate()).day >= 15:
 			relieving_date = getdate(add_days(nowdate(),-10))
 			date_of_joining = getdate(add_days(nowdate(),-10))
@@ -163,39 +174,39 @@
 			relieving_date = getdate(nowdate())
 
 		frappe.db.set_value("Employee", frappe.get_value("Employee",
-			{"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", date_of_joining)
+			{"employee_name":"test_payment_days@salary.com"}, "name"), "date_of_joining", date_of_joining)
 		frappe.db.set_value("Employee", frappe.get_value("Employee",
-			{"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
+			{"employee_name":"test_payment_days@salary.com"}, "name"), "relieving_date", None)
 		frappe.db.set_value("Employee", frappe.get_value("Employee",
-			{"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
+			{"employee_name":"test_payment_days@salary.com"}, "name"), "status", "Active")
 
-		ss = make_employee_salary_slip("test_employee@salary.com", "Monthly")
+		ss = make_employee_salary_slip("test_payment_days@salary.com", "Monthly", "Test Payment Days")
 
 		self.assertEqual(ss.total_working_days, no_of_days[0])
 		self.assertEqual(ss.payment_days, (no_of_days[0] - getdate(date_of_joining).day + 1))
 
 		# set relieving date in the same month
 		frappe.db.set_value("Employee",frappe.get_value("Employee",
-			{"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", (add_days(nowdate(),-60)))
+			{"employee_name":"test_payment_days@salary.com"}, "name"), "date_of_joining", (add_days(nowdate(),-60)))
 		frappe.db.set_value("Employee", frappe.get_value("Employee",
-			{"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", relieving_date)
+			{"employee_name":"test_payment_days@salary.com"}, "name"), "relieving_date", relieving_date)
 		frappe.db.set_value("Employee", frappe.get_value("Employee",
-			{"employee_name":"test_employee@salary.com"}, "name"), "status", "Left")
+			{"employee_name":"test_payment_days@salary.com"}, "name"), "status", "Left")
 		ss.save()
 
 		self.assertEqual(ss.total_working_days, no_of_days[0])
 		self.assertEqual(ss.payment_days, getdate(relieving_date).day)
 
 		frappe.db.set_value("Employee", frappe.get_value("Employee",
-			{"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
+			{"employee_name":"test_payment_days@salary.com"}, "name"), "relieving_date", None)
 		frappe.db.set_value("Employee", frappe.get_value("Employee",
-			{"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
+			{"employee_name":"test_payment_days@salary.com"}, "name"), "status", "Active")
 
 	def test_employee_salary_slip_read_permission(self):
-		make_employee("test_employee@salary.com")
+		make_employee("test_employee_salary_slip_read_permission@salary.com")
 
-		salary_slip_test_employee = make_employee_salary_slip("test_employee@salary.com", "Monthly")
-		frappe.set_user("test_employee@salary.com")
+		salary_slip_test_employee = make_employee_salary_slip("test_employee_salary_slip_read_permission@salary.com", "Monthly", "Test Employee Salary Slip Read Permission")
+		frappe.set_user("test_employee_salary_slip_read_permission@salary.com")
 		self.assertTrue(salary_slip_test_employee.has_permission("read"))
 
 	def test_email_salary_slip(self):
@@ -203,8 +214,8 @@
 
 		frappe.db.set_value("Payroll Settings", None, "email_salary_slip_to_employee", 1)
 
-		make_employee("test_employee@salary.com")
-		ss = make_employee_salary_slip("test_employee@salary.com", "Monthly")
+		make_employee("test_email_salary_slip@salary.com")
+		ss = make_employee_salary_slip("test_email_salary_slip@salary.com", "Monthly", "Test Salary Slip Email")
 		ss.company = "_Test Company"
 		ss.save()
 		ss.submit()
@@ -215,8 +226,9 @@
 	def test_loan_repayment_salary_slip(self):
 		from erpnext.loan_management.doctype.loan.test_loan import create_loan_type, create_loan, make_loan_disbursement_entry, create_loan_accounts
 		from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
+		from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
 
-		applicant = make_employee("test_loanemployee@salary.com", company="_Test Company")
+		applicant = make_employee("test_loan_repayment_salary_slip@salary.com", company="_Test Company")
 
 		create_loan_accounts()
 
@@ -228,6 +240,12 @@
 			interest_income_account='Interest Income Account - _TC',
 			penalty_income_account='Penalty Income Account - _TC')
 
+		payroll_period = create_payroll_period(name="_Test Payroll Period 1", company="_Test Company")
+
+		make_salary_structure("Test Loan Repayment Salary Structure", "Monthly", employee=applicant, currency='INR',
+			payroll_period=payroll_period)
+
+		frappe.db.sql("""delete from `tabLoan""")
 		loan = create_loan(applicant, "Car Loan", 11000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1))
 		loan.repay_from_salary = 1
 		loan.submit()
@@ -236,7 +254,7 @@
 
 		process_loan_interest_accrual_for_term_loans(posting_date=nowdate())
 
-		ss = make_employee_salary_slip("test_loanemployee@salary.com", "Monthly")
+		ss = make_employee_salary_slip("test_loan_repayment_salary_slip@salary.com", "Monthly", "Test Loan Repayment Salary Structure")
 		ss.submit()
 
 		self.assertEqual(ss.total_loan_repayment, 592)
@@ -249,7 +267,7 @@
 
 		for payroll_frequency in ["Monthly", "Bimonthly", "Fortnightly", "Weekly", "Daily"]:
 			make_employee(payroll_frequency + "_test_employee@salary.com")
-			ss = make_employee_salary_slip(payroll_frequency + "_test_employee@salary.com", payroll_frequency)
+			ss = make_employee_salary_slip(payroll_frequency + "_test_employee@salary.com", payroll_frequency, payroll_frequency + "_Test Payroll Frequency")
 			if payroll_frequency == "Monthly":
 				self.assertEqual(ss.end_date, m['month_end_date'])
 			elif payroll_frequency == "Bimonthly":
@@ -264,6 +282,45 @@
 			elif payroll_frequency == "Daily":
 				self.assertEqual(ss.end_date, nowdate())
 
+	def test_multi_currency_salary_slip(self):
+		from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
+		applicant = make_employee("test_multi_currency_salary_slip@salary.com", company="_Test Company")
+		frappe.db.sql("""delete from `tabSalary Structure` where name='Test Multi Currency Salary Slip'""")
+		salary_structure = make_salary_structure("Test Multi Currency Salary Slip", "Monthly", employee=applicant, company="_Test Company", currency='USD')
+		salary_slip = make_salary_slip(salary_structure.name, employee = applicant)
+		salary_slip.exchange_rate = 70
+		salary_slip.calculate_net_pay()
+
+		self.assertEqual(salary_slip.gross_pay, 78000)
+		self.assertEqual(salary_slip.base_gross_pay, 78000*70)
+
+	def test_year_to_date_computation(self):
+		from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
+
+		applicant = make_employee("test_ytd@salary.com", company="_Test Company")
+
+		payroll_period = create_payroll_period(name="_Test Payroll Period 1", company="_Test Company")
+
+		create_tax_slab(payroll_period, allow_tax_exemption=True, currency="INR", effective_date=getdate("2019-04-01"),
+			company="_Test Company")
+
+		salary_structure = make_salary_structure("Monthly Salary Structure Test for Salary Slip YTD",
+			"Monthly", employee=applicant, company="_Test Company", currency="INR", payroll_period=payroll_period)
+
+		# clear salary slip for this employee
+		frappe.db.sql("DELETE FROM `tabSalary Slip` where employee_name = 'test_ytd@salary.com'")
+
+		create_salary_slips_for_payroll_period(applicant, salary_structure.name,
+			payroll_period, deduct_random=False)
+
+		salary_slips = frappe.get_all('Salary Slip', fields=['year_to_date', 'net_pay'], filters={'employee_name':
+			'test_ytd@salary.com'}, order_by = 'posting_date')
+
+		year_to_date = 0
+		for slip in salary_slips:
+			year_to_date += slip.net_pay
+			self.assertEqual(slip.year_to_date, year_to_date)
+
 	def test_tax_for_payroll_period(self):
 		data = {}
 		# test the impact of tax exemption declaration, tax exemption proof submission
@@ -384,16 +441,18 @@
 		salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip"
 
 	employee = frappe.db.get_value("Employee", {"user_id": user})
-	salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee)
-	salary_slip = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
+	salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee)
+	salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
 
-	if not salary_slip:
+	if not salary_slip_name:
 		salary_slip = make_salary_slip(salary_structure_doc.name, employee = employee)
 		salary_slip.employee_name = frappe.get_value("Employee",
 			{"name":frappe.db.get_value("Employee", {"user_id": user})}, "employee_name")
 		salary_slip.payroll_frequency = payroll_frequency
 		salary_slip.posting_date = nowdate()
 		salary_slip.insert()
+	else:
+		salary_slip = frappe.get_doc('Salary Slip', salary_slip_name)
 
 	return salary_slip
 
@@ -434,7 +493,7 @@
 
 			sal_comp.append("accounts", {
 				"company": d,
-				"default_account": create_account(account_name, d, parent_account)
+				"account": create_account(account_name, d, parent_account)
 			})
 			sal_comp.save()
 
@@ -526,14 +585,6 @@
 			"amount": 200,
 			"exempted_from_income_tax": 1
 
-		},
-		{
-			"salary_component": 'TDS',
-			"abbr":'T',
-			"type": "Deduction",
-			"depends_on_payment_days": 0,
-			"variable_based_on_taxable_salary": 1,
-			"round_to_the_nearest_integer": 1
 		}
 	]
 	if not test_tax:
@@ -544,6 +595,15 @@
 			"type": "Deduction",
 			"round_to_the_nearest_integer": 1
 		})
+	else:
+		data.append({
+			"salary_component": 'TDS',
+			"abbr":'T',
+			"type": "Deduction",
+			"depends_on_payment_days": 0,
+			"variable_based_on_taxable_salary": 1,
+			"round_to_the_nearest_integer": 1
+		})
 	if setup or test_tax:
 		make_salary_component(data, test_tax, company_list)
 
@@ -561,7 +621,8 @@
 		"doctype": "Employee Tax Exemption Declaration",
 		"employee": employee,
 		"payroll_period": payroll_period,
-		"company": erpnext.get_default_company()
+		"company": erpnext.get_default_company(),
+		"currency": erpnext.get_default_currency()
 	})
 	declaration.append("declarations", {
 		"exemption_sub_category": "_Test Sub Category",
@@ -576,7 +637,8 @@
 		"doctype": "Employee Tax Exemption Proof Submission",
 		"employee": employee,
 		"payroll_period": payroll_period.name,
-		"submission_date": submission_date
+		"submission_date": submission_date,
+		"currency": erpnext.get_default_currency()
 	})
 	proof_submission.append("tax_exemption_proofs", {
 		"exemption_sub_category": "_Test Sub Category",
@@ -593,13 +655,18 @@
 		"employee": employee,
 		"claimed_amount": amount,
 		"claim_date": claim_date,
-		"earning_component": component
+		"earning_component": component,
+		"currency": erpnext.get_default_currency()
 	}).submit()
 	return claim_date
 
-def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False):
-	if frappe.db.exists("Income Tax Slab", "Tax Slab: " + payroll_period.name):
-		return
+def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False, currency=None,
+	company=None):
+	if not currency:
+		currency = erpnext.get_default_currency()
+
+	if company:
+		currency = erpnext.get_company_currency(company)
 
 	slabs = [
 		{
@@ -619,25 +686,33 @@
 		}
 	]
 
-	income_tax_slab = frappe.new_doc("Income Tax Slab")
-	income_tax_slab.name = "Tax Slab: " + payroll_period.name
-	income_tax_slab.effective_from = effective_date or add_days(payroll_period.start_date, -2)
+	income_tax_slab_name = frappe.db.get_value("Income Tax Slab", {"currency": currency})
+	if not income_tax_slab_name:
+		income_tax_slab = frappe.new_doc("Income Tax Slab")
+		income_tax_slab.name = "Tax Slab: " + payroll_period.name + " " + cstr(currency)
+		income_tax_slab.effective_from = effective_date or add_days(payroll_period.start_date, -2)
+		income_tax_slab.company = company or ''
+		income_tax_slab.currency = currency
 
-	if allow_tax_exemption:
-		income_tax_slab.allow_tax_exemption = 1
-		income_tax_slab.standard_tax_exemption_amount = 50000
+		if allow_tax_exemption:
+			income_tax_slab.allow_tax_exemption = 1
+			income_tax_slab.standard_tax_exemption_amount = 50000
 
-	for item in slabs:
-		income_tax_slab.append("slabs", item)
+		for item in slabs:
+			income_tax_slab.append("slabs", item)
 
-	income_tax_slab.append("other_taxes_and_charges", {
-		"description": "cess",
-		"percent": 4
-	})
+		income_tax_slab.append("other_taxes_and_charges", {
+			"description": "cess",
+			"percent": 4
+		})
 
-	income_tax_slab.save()
-	if not dont_submit:
-		income_tax_slab.submit()
+		income_tax_slab.save()
+		if not dont_submit:
+			income_tax_slab.submit()
+
+		return income_tax_slab.name
+	else:
+		return income_tax_slab_name
 
 def create_salary_slips_for_payroll_period(employee, salary_structure, payroll_period, deduct_random=True):
 	deducted_dates = []
@@ -672,7 +747,8 @@
 		"salary_component": "Performance Bonus",
 		"payroll_date": salary_date,
 		"amount": amount,
-		"type": "Earning"
+		"type": "Earning",
+		"currency": erpnext.get_default_currency()
 	}).submit()
 	return salary_date
 
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js
index ad93a2f..ba824c5 100755
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.js
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js
@@ -41,20 +41,6 @@
 
 		frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet)
 
-		frm.set_query("salary_component", "earnings", function() {
-			return {
-				filters: {
-					type: "earning"
-				}
-			}
-		});
-		frm.set_query("salary_component", "deductions", function() {
-			return {
-				filters: {
-					type: "deduction"
-				}
-			}
-		});
 		frm.set_query("payment_account", function () {
 			var account_types = ["Bank", "Cash"];
 			return {
@@ -65,9 +51,47 @@
 				}
 			};
 		});
+		frm.trigger('set_earning_deduction_component');
+	},
+
+	set_earning_deduction_component: function(frm) {
+		if(!frm.doc.company) return;
+		frm.set_query("salary_component", "earnings", function() {
+			return {
+				query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
+				filters: {type: "earning", company: frm.doc.company}
+			};
+		});
+		frm.set_query("salary_component", "deductions", function() {
+			return {
+				query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
+				filters: {type: "deduction", company: frm.doc.company}
+			};
+		});
+	},
+
+
+	currency: function(frm) {
+		calculate_totals(frm.doc);
+		frm.trigger("set_dynamic_labels")
+		frm.refresh()
+	},
+
+	set_dynamic_labels: function(frm) {
+		frm.set_currency_labels(["net_pay","hour_rate", "leave_encashment_amount_per_day", "max_benefits", "total_earning",
+			"total_deduction"], frm.doc.currency);
+
+		frm.set_currency_labels(["amount", "additional_amount", "tax_on_flexible_benefit", "tax_on_additional_salary"],
+			frm.doc.currency, "earnings");
+
+		frm.set_currency_labels(["amount", "additional_amount", "tax_on_flexible_benefit", "tax_on_additional_salary"],
+			frm.doc.currency, "deductions");
+
+		frm.refresh_fields();
 	},
 
 	refresh: function(frm) {
+		frm.trigger("set_dynamic_labels")
 		frm.trigger("toggle_fields");
 		frm.fields_dict['earnings'].grid.set_column_disp("default_amount", false);
 		frm.fields_dict['deductions'].grid.set_column_disp("default_amount", false);
@@ -101,10 +125,12 @@
 			fields: [
 				{fieldname: "sec_break", fieldtype: "Section Break", label: __("Filter Employees By (Optional)")},
 				{fieldname: "company", fieldtype: "Link", options: "Company", label: __("Company"), default: frm.doc.company, read_only:1},
+				{fieldname: "currency", fieldtype: "Link", options: "Currency", label: __("Currency"), default: frm.doc.currency, read_only:1},
 				{fieldname: "grade", fieldtype: "Link", options: "Employee Grade", label: __("Employee Grade")},
 				{fieldname:'department', fieldtype:'Link', options: 'Department', label: __('Department')},
 				{fieldname:'designation', fieldtype:'Link', options: 'Designation', label: __('Designation')},
-                {fieldname:"employee", fieldtype: "Link", options: "Employee", label: __("Employee")},
+				{fieldname:"employee", fieldtype: "Link", options: "Employee", label: __("Employee")},
+				{fieldname:"payroll_payable_account", fieldtype: "Link", options: "Account", filters: {"company": frm.doc.company, "root_type": "Liability", "is_group": 0, "account_currency": frm.doc.currency}, label: __("Payroll Payable Account")},
 				{fieldname:'base_variable', fieldtype:'Section Break'},
 				{fieldname:'from_date', fieldtype:'Date', label: __('From Date'), "reqd": 1},
 				{fieldname:'income_tax_slab', fieldtype:'Link', label: __('Income Tax Slab'), options: 'Income Tax Slab'},
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.json b/erpnext/payroll/doctype/salary_structure/salary_structure.json
index 5f94929..de56fc8 100644
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.json
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.json
@@ -13,6 +13,7 @@
   "column_break1",
   "is_active",
   "payroll_frequency",
+  "currency",
   "is_default",
   "time_sheet_earning_detail",
   "salary_slip_based_on_timesheet",
@@ -26,9 +27,9 @@
   "deductions",
   "conditions_and_formula_variable_and_example",
   "net_pay_detail",
-  "column_break2",
   "total_earning",
   "total_deduction",
+  "column_break2",
   "net_pay",
   "account",
   "mode_of_payment",
@@ -43,23 +44,17 @@
    "label": "Company",
    "options": "Company",
    "remember_last_selected_value": 1,
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "reqd": 1
   },
   {
    "fieldname": "letter_head",
    "fieldtype": "Link",
    "label": "Letter Head",
-   "options": "Letter Head",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Letter Head"
   },
   {
    "fieldname": "column_break1",
    "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1,
    "width": "50%"
   },
   {
@@ -72,9 +67,7 @@
    "oldfieldname": "is_active",
    "oldfieldtype": "Select",
    "options": "\nYes\nNo",
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "reqd": 1
   },
   {
    "default": "Monthly",
@@ -82,9 +75,7 @@
    "fieldname": "payroll_frequency",
    "fieldtype": "Select",
    "label": "Payroll Frequency",
-   "options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily"
   },
   {
    "default": "No",
@@ -95,62 +86,46 @@
    "no_copy": 1,
    "options": "Yes\nNo",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "time_sheet_earning_detail",
-   "fieldtype": "Section Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Section Break"
   },
   {
    "default": "0",
    "fieldname": "salary_slip_based_on_timesheet",
    "fieldtype": "Check",
-   "label": "Salary Slip Based on Timesheet",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Salary Slip Based on Timesheet"
   },
   {
    "fieldname": "column_break_17",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "description": "Salary Component for timesheet based payroll.",
    "fieldname": "salary_component",
    "fieldtype": "Link",
    "label": "Salary Component",
-   "options": "Salary Component",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Salary Component"
   },
   {
    "fieldname": "hour_rate",
    "fieldtype": "Currency",
    "label": "Hour Rate",
-   "options": "Company:company:default_currency",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "currency"
   },
   {
    "fieldname": "leave_encashment_amount_per_day",
    "fieldtype": "Currency",
    "label": "Leave Encashment Amount Per Day",
-   "options": "Company:company:default_currency",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "currency"
   },
   {
    "fieldname": "max_benefits",
    "fieldtype": "Currency",
    "label": "Max Benefits (Amount)",
-   "options": "Company:company:default_currency",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "currency"
   },
   {
    "description": "Salary breakup based on Earning and Deduction.",
@@ -158,9 +133,7 @@
    "fieldtype": "Section Break",
    "oldfieldname": "earning_deduction",
    "oldfieldtype": "Section Break",
-   "precision": "2",
-   "show_days": 1,
-   "show_seconds": 1
+   "precision": "2"
   },
   {
    "fieldname": "earnings",
@@ -168,9 +141,7 @@
    "label": "Earnings",
    "oldfieldname": "earning_details",
    "oldfieldtype": "Table",
-   "options": "Salary Detail",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Salary Detail"
   },
   {
    "fieldname": "deductions",
@@ -178,22 +149,16 @@
    "label": "Deductions",
    "oldfieldname": "deduction_details",
    "oldfieldtype": "Table",
-   "options": "Salary Detail",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Salary Detail"
   },
   {
    "fieldname": "net_pay_detail",
    "fieldtype": "Section Break",
-   "options": "Simple",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Simple"
   },
   {
    "fieldname": "column_break2",
    "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1,
    "width": "50%"
   },
   {
@@ -201,63 +166,45 @@
    "fieldtype": "Currency",
    "hidden": 1,
    "label": "Total Earning",
-   "oldfieldname": "total_earning",
-   "oldfieldtype": "Currency",
-   "options": "Company:company:default_currency",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "currency",
+   "read_only": 1
   },
   {
    "fieldname": "total_deduction",
    "fieldtype": "Currency",
    "hidden": 1,
    "label": "Total Deduction",
-   "oldfieldname": "total_deduction",
-   "oldfieldtype": "Currency",
-   "options": "Company:company:default_currency",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "currency",
+   "read_only": 1
   },
   {
    "fieldname": "net_pay",
    "fieldtype": "Currency",
    "hidden": 1,
    "label": "Net Pay",
-   "options": "Company:company:default_currency",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "currency",
+   "read_only": 1
   },
   {
    "fieldname": "account",
    "fieldtype": "Section Break",
-   "label": "Account",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Account"
   },
   {
    "fieldname": "mode_of_payment",
    "fieldtype": "Link",
    "label": "Mode of Payment",
-   "options": "Mode of Payment",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Mode of Payment"
   },
   {
    "fieldname": "column_break_28",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "payment_account",
    "fieldtype": "Link",
    "label": "Payment Account",
-   "options": "Account",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Account"
   },
   {
    "fieldname": "amended_from",
@@ -266,23 +213,26 @@
    "no_copy": 1,
    "options": "Salary Structure",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "conditions_and_formula_variable_and_example",
    "fieldtype": "HTML",
-   "label": "Conditions and Formula variable and example",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Conditions and Formula variable and example"
+  },
+  {
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "label": "Currency",
+   "options": "Currency",
+   "reqd": 1
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-22 17:07:26.129355",
+ "modified": "2020-09-30 11:30:32.190798",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Salary Structure",
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.py b/erpnext/payroll/doctype/salary_structure/salary_structure.py
index ffc16d7..77914bb 100644
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.py
@@ -2,7 +2,7 @@
 # License: GNU General Public License v3. See license.txt
 
 from __future__ import unicode_literals
-import frappe
+import frappe, erpnext
 
 from frappe.utils import flt, cint, cstr
 from frappe import _
@@ -88,24 +88,26 @@
 		return employees
 
 	@frappe.whitelist()
-	def assign_salary_structure(self, company=None, grade=None, department=None, designation=None,employee=None,
-			from_date=None, base=None, variable=None, income_tax_slab=None):
-		employees = self.get_employees(company= company, grade= grade,department= department,designation= designation,name=employee)
+	def assign_salary_structure(self, grade=None, department=None, designation=None,employee=None,
+			payroll_payable_account=None, from_date=None, base=None, variable=None, income_tax_slab=None):
+		employees = self.get_employees(company= self.company, grade= grade,department= department,designation= designation,name=employee)
 
 		if employees:
 			if len(employees) > 20:
 				frappe.enqueue(assign_salary_structure_for_employees, timeout=600,
-					employees=employees, salary_structure=self,from_date=from_date,
-					base=base, variable=variable, income_tax_slab=income_tax_slab)
+					employees=employees, salary_structure=self,
+					payroll_payable_account=payroll_payable_account,
+					from_date=from_date, base=base, variable=variable, income_tax_slab=income_tax_slab)
 			else:
-				assign_salary_structure_for_employees(employees, self, from_date=from_date,
-					base=base, variable=variable, income_tax_slab=income_tax_slab)
+				assign_salary_structure_for_employees(employees, self,
+					payroll_payable_account=payroll_payable_account, 
+					from_date=from_date, base=base, variable=variable, income_tax_slab=income_tax_slab)
 		else:
 			frappe.msgprint(_("No Employee Found"))
 
 
 
-def assign_salary_structure_for_employees(employees, salary_structure, from_date=None, base=None, variable=None, income_tax_slab=None):
+def assign_salary_structure_for_employees(employees, salary_structure, payroll_payable_account=None, from_date=None, base=None, variable=None, income_tax_slab=None):
 	salary_structures_assignments = []
 	existing_assignments_for = get_existing_assignments(employees, salary_structure, from_date)
 	count=0
@@ -115,7 +117,7 @@
 		count +=1
 
 		salary_structures_assignment = create_salary_structures_assignment(employee,
-			salary_structure, from_date, base, variable, income_tax_slab)
+			salary_structure, payroll_payable_account, from_date, base, variable, income_tax_slab)
 		salary_structures_assignments.append(salary_structures_assignment)
 		frappe.publish_progress(count*100/len(set(employees) - set(existing_assignments_for)), title = _("Assigning Structures..."))
 
@@ -123,11 +125,22 @@
 		frappe.msgprint(_("Structures have been assigned successfully"))
 
 
-def create_salary_structures_assignment(employee, salary_structure, from_date, base, variable, income_tax_slab=None):
+def create_salary_structures_assignment(employee, salary_structure, payroll_payable_account, from_date, base, variable, income_tax_slab=None):
+	if not payroll_payable_account:
+		payroll_payable_account = frappe.db.get_value('Company', salary_structure.company, 'default_payroll_payable_account')
+		if not payroll_payable_account:
+			frappe.throw(_('Please set "Default Payroll Payable Account" in Company Defaults'))
+	payroll_payable_account_currency = frappe.db.get_value('Account',  payroll_payable_account, 'account_currency')
+	company_curency = erpnext.get_company_currency(salary_structure.company)
+	if payroll_payable_account_currency != salary_structure.currency and payroll_payable_account_currency != company_curency:
+		frappe.throw(_("Invalid Payroll Payable Account. The account currency must be {0} or {1}").format(salary_structure.currency, company_curency))
+
 	assignment = frappe.new_doc("Salary Structure Assignment")
 	assignment.employee = employee
 	assignment.salary_structure = salary_structure.name
 	assignment.company = salary_structure.company
+	assignment.currency = salary_structure.currency
+	assignment.payroll_payable_account = payroll_payable_account
 	assignment.from_date = from_date
 	assignment.base = base
 	assignment.variable = variable
@@ -170,7 +183,8 @@
 			"doctype": "Salary Slip",
 			"field_map": {
 				"total_earning": "gross_pay",
-				"name": "salary_structure"
+				"name": "salary_structure",
+				"currency": "currency"
 			}
 		}
 	}, target_doc, postprocess, ignore_child_tables=True, ignore_permissions=ignore_permissions)
@@ -188,7 +202,22 @@
 		filters={'salary_structure': salary_structure, 'docstatus': 1}, fields=['employee'])
 
 	if not employees:
-		frappe.throw(_("There's no Employee with Salary Structure: {0}. \
-			Assign {1} to an Employee to preview Salary Slip").format(salary_structure, salary_structure))
+		frappe.throw(_("There's no Employee with Salary Structure: {0}. Assign {1} to an Employee to preview Salary Slip").format(
+			salary_structure, salary_structure))
 
 	return list(set([d.employee for d in employees]))
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_earning_deduction_components(doctype, txt, searchfield, start, page_len, filters):
+	if len(filters) < 2:
+		return {}
+
+	return frappe.db.sql("""
+		select t1.salary_component
+		from `tabSalary Component` t1, `tabSalary Component Account` t2
+		where t1.salary_component = t2.parent
+		and t1.type = %s 
+		and t2.company = %s
+		order by salary_component
+	""", (filters['type'], filters['company']) )
diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
index e04fda8..f2fb558 100644
--- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
@@ -94,7 +94,8 @@
 			self.assertFalse(("\n" in row.formula) or ("\n" in row.condition))
 
 	def test_salary_structures_assignment(self):
-		salary_structure = make_salary_structure("Salary Structure Sample", "Monthly")
+		company_currency = erpnext.get_default_currency()
+		salary_structure = make_salary_structure("Salary Structure Sample", "Monthly", currency=company_currency)
 		employee = "test_assign_stucture@salary.com"
 		employee_doc_name = make_employee(employee)
 		# clear the already assigned stuctures
@@ -107,8 +108,13 @@
 		self.assertEqual(salary_structure_assignment.base, 5000)
 		self.assertEqual(salary_structure_assignment.variable, 200)
 
+	def test_multi_currency_salary_structure(self):
+		make_employee("test_muti_currency_employee@salary.com")
+		sal_struct = make_salary_structure("Salary Structure Multi Currency", "Monthly", currency='USD')
+		self.assertEqual(sal_struct.currency, 'USD')
+
 def make_salary_structure(salary_structure, payroll_frequency, employee=None, dont_submit=False, other_details=None,
-	test_tax=False, company=None):
+	test_tax=False, company=None, currency=erpnext.get_default_currency(), payroll_period=None):
 	if test_tax:
 		frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure))
 
@@ -120,7 +126,8 @@
 			"earnings": make_earning_salary_component(test_tax=test_tax, company_list=["_Test Company"]),
 			"deductions": make_deduction_salary_component(test_tax=test_tax, company_list=["_Test Company"]),
 			"payroll_frequency": payroll_frequency,
-			"payment_account": get_random("Account")
+			"payment_account": get_random("Account", filters={'account_currency': currency}),
+			"currency": currency
 		}
 		if other_details and isinstance(other_details, dict):
 			details.update(other_details)
@@ -134,16 +141,24 @@
 
 	if employee and not frappe.db.get_value("Salary Structure Assignment",
 		{'employee':employee, 'docstatus': 1}) and salary_structure_doc.docstatus==1:
-			create_salary_structure_assignment(employee, salary_structure, company=company)
+			create_salary_structure_assignment(employee, salary_structure, company=company, currency=currency,
+			payroll_period=payroll_period)
 
 	return salary_structure_doc
 
-def create_salary_structure_assignment(employee, salary_structure, from_date=None, company=None):
+def create_salary_structure_assignment(employee, salary_structure, from_date=None, company=None, currency=erpnext.get_default_currency(),
+	payroll_period=None):
+
 	if frappe.db.exists("Salary Structure Assignment", {"employee": employee}):
 		frappe.db.sql("""delete from `tabSalary Structure Assignment` where employee=%s""",(employee))
 
-	payroll_period = create_payroll_period()
-	create_tax_slab(payroll_period, allow_tax_exemption=True)
+	if not payroll_period:
+		payroll_period = create_payroll_period()
+
+	income_tax_slab = frappe.db.get_value("Income Tax Slab", {"currency": currency})
+
+	if not income_tax_slab:
+		income_tax_slab = create_tax_slab(payroll_period, allow_tax_exemption=True, currency=currency)
 
 	salary_structure_assignment = frappe.new_doc("Salary Structure Assignment")
 	salary_structure_assignment.employee = employee
@@ -151,8 +166,15 @@
 	salary_structure_assignment.variable = 5000
 	salary_structure_assignment.from_date = from_date or add_days(nowdate(), -1)
 	salary_structure_assignment.salary_structure = salary_structure
+	salary_structure_assignment.currency = currency
+	salary_structure_assignment.payroll_payable_account = get_payable_account(company)
 	salary_structure_assignment.company = company or erpnext.get_default_company()
 	salary_structure_assignment.save(ignore_permissions=True)
-	salary_structure_assignment.income_tax_slab = "Tax Slab: _Test Payroll Period"
+	salary_structure_assignment.income_tax_slab = income_tax_slab
 	salary_structure_assignment.submit()
 	return salary_structure_assignment
+
+def get_payable_account(company=None):
+	if not company:
+		company = erpnext.get_default_company()
+	return frappe.db.get_value("Company", company, "default_payroll_payable_account")
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js
index 818e853..6cd897e 100644
--- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js
+++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js
@@ -6,9 +6,6 @@
 		frm.set_query("employee", function() {
 			return {
 				query: "erpnext.controllers.queries.employee_query",
-				filters: {
-					company: frm.doc.company
-				}
 			}
 		});
 		frm.set_query("salary_structure", function() {
@@ -26,11 +23,25 @@
 				filters: {
 					company: frm.doc.company,
 					docstatus: 1,
-					disabled: 0
+					disabled: 0,
+					currency: frm.doc.currency
+				}
+			};
+		});
+
+		frm.set_query("payroll_payable_account", function() {
+			var company_currency = erpnext.get_currency(frm.doc.company);
+			return {
+				filters: {
+					"company": frm.doc.company,
+					"root_type": "Liability",
+					"is_group": 0,
+					"account_currency": ["in", [frm.doc.currency, company_currency]],
 				}
 			}
 		});
 	},
+
 	employee: function(frm) {
 		if(frm.doc.employee){
 			frappe.call({
@@ -52,5 +63,13 @@
 		else{
 			frm.set_value("company", null);
 		}
+	},
+
+	company: function(frm) {
+		if (frm.doc.company) {
+			frappe.db.get_value("Company", frm.doc.company, "default_payroll_payable_account", (r) => {
+				frm.set_value("payroll_payable_account", r.default_payroll_payable_account);
+			});
+		}
 	}
 });
diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
index c84e034..92bb347 100644
--- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
+++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
@@ -11,11 +11,13 @@
   "employee_name",
   "department",
   "company",
+  "payroll_payable_account",
   "column_break_6",
   "designation",
   "salary_structure",
   "from_date",
   "income_tax_slab",
+  "currency",
   "section_break_7",
   "base",
   "column_break_9",
@@ -94,7 +96,7 @@
    "fieldname": "base",
    "fieldtype": "Currency",
    "label": "Base",
-   "options": "Company:company:default_currency"
+   "options": "currency"
   },
   {
    "fieldname": "column_break_9",
@@ -104,7 +106,7 @@
    "fieldname": "variable",
    "fieldtype": "Currency",
    "label": "Variable",
-   "options": "Company:company:default_currency"
+   "options": "currency"
   },
   {
    "fieldname": "amended_from",
@@ -116,15 +118,35 @@
    "read_only": 1
   },
   {
+   "depends_on": "salary_structure",
    "fieldname": "income_tax_slab",
    "fieldtype": "Link",
    "label": "Income Tax Slab",
    "options": "Income Tax Slab"
+  },
+  {
+   "default": "Company:company:default_currency",
+   "depends_on": "eval:(doc.docstatus==1 || doc.salary_structure)",
+   "fetch_from": "salary_structure.currency",
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "label": "Currency",
+   "options": "Currency",
+   "print_hide": 1,
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "depends_on": "employee",
+   "fieldname": "payroll_payable_account",
+   "fieldtype": "Link",
+   "label": "Payroll Payable Account",
+   "options": "Account"
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-22 19:58:09.964692",
+ "modified": "2020-11-30 18:07:48.251311",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Salary Structure Assignment",
diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py
index 668e0ec..a0c3013 100644
--- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py
+++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py
@@ -13,6 +13,8 @@
 class SalaryStructureAssignment(Document):
 	def validate(self):
 		self.validate_dates()
+		self.validate_income_tax_slab()
+		self.set_payroll_payable_account()
 
 	def validate_dates(self):
 		joining_date, relieving_date = frappe.db.get_value("Employee", self.employee,
@@ -31,6 +33,24 @@
 				frappe.throw(_("From Date {0} cannot be after employee's relieving Date {1}")
 					.format(self.from_date, relieving_date))
 
+	def validate_income_tax_slab(self):
+		if not self.income_tax_slab:
+			return
+		
+		income_tax_slab_currency = frappe.db.get_value('Income Tax Slab', self.income_tax_slab, 'currency')
+		if self.currency != income_tax_slab_currency:
+			frappe.throw(_("Currency of selected Income Tax Slab should be {0} instead of {1}").format(self.currency, income_tax_slab_currency))
+
+	def set_payroll_payable_account(self):
+		if not self.payroll_payable_account:
+			payroll_payable_account = frappe.db.get_value('Company', self.company, 'default_payroll_payable_account')
+			if not payroll_payable_account:
+				payroll_payable_account = frappe.db.get_value(
+					"Account", {
+						"account_name": _("Payroll Payable"), "company": self.company, "account_currency": frappe.db.get_value(
+							"Company", self.company, "default_currency"), "is_group": 0})
+			self.payroll_payable_account = payroll_payable_account
+
 def get_assigned_salary_structure(employee, on_date):
 	if not employee or not on_date:
 		return None
@@ -43,3 +63,10 @@
 			'on_date': on_date,
 		})
 	return salary_structure[0][0] if salary_structure else None
+
+@frappe.whitelist()
+def get_employee_currency(employee):
+	employee_currency = frappe.db.get_value('Salary Structure Assignment', {'employee': employee}, 'currency')
+	if not employee_currency:
+		frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(employee))
+	return employee_currency
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/taxable_salary_slab/taxable_salary_slab.json b/erpnext/payroll/doctype/taxable_salary_slab/taxable_salary_slab.json
index 94eda4c..65d3824 100644
--- a/erpnext/payroll/doctype/taxable_salary_slab/taxable_salary_slab.json
+++ b/erpnext/payroll/doctype/taxable_salary_slab/taxable_salary_slab.json
@@ -19,13 +19,15 @@
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "From Amount",
+   "options": "currency",
    "reqd": 1
   },
   {
    "fieldname": "to_amount",
    "fieldtype": "Currency",
    "in_list_view": 1,
-   "label": "To Amount"
+   "label": "To Amount",
+   "options": "currency"
   },
   {
    "default": "0",
@@ -53,7 +55,7 @@
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-06-22 18:16:07.596493",
+ "modified": "2020-10-19 13:44:39.549337",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Taxable Salary Slab",
diff --git a/erpnext/payroll/report/salary_register/salary_register.js b/erpnext/payroll/report/salary_register/salary_register.js
index 885e3d1..eb4acb9 100644
--- a/erpnext/payroll/report/salary_register/salary_register.js
+++ b/erpnext/payroll/report/salary_register/salary_register.js
@@ -8,34 +8,48 @@
 			"label": __("From"),
 			"fieldtype": "Date",
 			"default": frappe.datetime.add_months(frappe.datetime.get_today(),-1),
-			"reqd": 1
+			"reqd": 1,
+			"width": "100px"
 		},
 		{
 			"fieldname":"to_date",
 			"label": __("To"),
 			"fieldtype": "Date",
 			"default": frappe.datetime.get_today(),
-			"reqd": 1
+			"reqd": 1,
+			"width": "100px"
+		},
+		{
+			"fieldname": "currency",
+			"fieldtype": "Link",
+			"options": "Currency",
+			"label": __("Currency"),
+			"default": erpnext.get_currency(frappe.defaults.get_default("Company")),
+			"width": "50px"
 		},
 		{
 			"fieldname":"employee",
 			"label": __("Employee"),
 			"fieldtype": "Link",
-			"options": "Employee"
+			"options": "Employee",
+			"width": "100px"
 		},
 		{
 			"fieldname":"company",
 			"label": __("Company"),
 			"fieldtype": "Link",
 			"options": "Company",
-			"default": frappe.defaults.get_user_default("Company")
+			"default": frappe.defaults.get_user_default("Company"),
+			"width": "100px",
+			"reqd": 1
 		},
 		{
 			"fieldname":"docstatus",
 			"label":__("Document Status"),
 			"fieldtype":"Select",
 			"options":["Draft", "Submitted", "Cancelled"],
-			"default":"Submitted"
+			"default": "Submitted",
+			"width": "100px"
 		}
 	]
 }
diff --git a/erpnext/payroll/report/salary_register/salary_register.py b/erpnext/payroll/report/salary_register/salary_register.py
index 8701085..a1b1a8c 100644
--- a/erpnext/payroll/report/salary_register/salary_register.py
+++ b/erpnext/payroll/report/salary_register/salary_register.py
@@ -2,18 +2,22 @@
 # License: GNU General Public License v3. See license.txt
 
 from __future__ import unicode_literals
-import frappe
+import frappe, erpnext
 from frappe.utils import flt
 from frappe import _
 
 def execute(filters=None):
 	if not filters: filters = {}
-	salary_slips = get_salary_slips(filters)
+	currency = None
+	if filters.get('currency'):
+		currency = filters.get('currency')
+	company_currency = erpnext.get_company_currency(filters.get("company"))
+	salary_slips = get_salary_slips(filters, company_currency)
 	if not salary_slips: return [], []
 
 	columns, earning_types, ded_types = get_columns(salary_slips)
-	ss_earning_map = get_ss_earning_map(salary_slips)
-	ss_ded_map = get_ss_ded_map(salary_slips)
+	ss_earning_map = get_ss_earning_map(salary_slips, currency, company_currency)
+	ss_ded_map = get_ss_ded_map(salary_slips,currency, company_currency)
 	doj_map = get_employee_doj_map()
 
 	data = []
@@ -21,24 +25,30 @@
 		row = [ss.name, ss.employee, ss.employee_name, doj_map.get(ss.employee), ss.branch, ss.department, ss.designation,
 			ss.company, ss.start_date, ss.end_date, ss.leave_without_pay, ss.payment_days]
 
-		if not ss.branch == None:columns[3] = columns[3].replace('-1','120')
-		if not ss.department  == None: columns[4] = columns[4].replace('-1','120')
-		if not ss.designation  == None: columns[5] = columns[5].replace('-1','120')
-		if not ss.leave_without_pay  == None: columns[9] = columns[9].replace('-1','130')
+		if ss.branch is not None: columns[3] = columns[3].replace('-1','120')
+		if ss.department is not None: columns[4] = columns[4].replace('-1','120')
+		if ss.designation is not None: columns[5] = columns[5].replace('-1','120')
+		if ss.leave_without_pay is not None: columns[9] = columns[9].replace('-1','130')
 
 
 		for e in earning_types:
 			row.append(ss_earning_map.get(ss.name, {}).get(e))
 
-		row += [ss.gross_pay]
+		if currency == company_currency:
+			row += [flt(ss.gross_pay) * flt(ss.exchange_rate)]
+		else:
+			row += [ss.gross_pay]
 
 		for d in ded_types:
 			row.append(ss_ded_map.get(ss.name, {}).get(d))
 
 		row.append(ss.total_loan_repayment)
 
-		row += [ss.total_deduction, ss.net_pay]
-
+		if currency == company_currency:
+			row += [flt(ss.total_deduction) * flt(ss.exchange_rate), flt(ss.net_pay) * flt(ss.exchange_rate)]
+		else:
+			row += [ss.total_deduction, ss.net_pay]
+		row.append(currency or company_currency)
 		data.append(row)
 
 	return columns, data
@@ -46,10 +56,19 @@
 def get_columns(salary_slips):
 	"""
 	columns = [
-		_("Salary Slip ID") + ":Link/Salary Slip:150",_("Employee") + ":Link/Employee:120", _("Employee Name") + "::140",
-		_("Date of Joining") + "::80", _("Branch") + ":Link/Branch:120", _("Department") + ":Link/Department:120",
-		_("Designation") + ":Link/Designation:120", _("Company") + ":Link/Company:120", _("Start Date") + "::80",
-		_("End Date") + "::80", _("Leave Without Pay") + ":Float:130", _("Payment Days") + ":Float:120"
+		_("Salary Slip ID") + ":Link/Salary Slip:150",
+		_("Employee") + ":Link/Employee:120",
+		_("Employee Name") + "::140",
+		_("Date of Joining") + "::80",
+		_("Branch") + ":Link/Branch:120",
+		_("Department") + ":Link/Department:120",
+		_("Designation") + ":Link/Designation:120",
+		_("Company") + ":Link/Company:120",
+		_("Start Date") + "::80",
+		_("End Date") + "::80",
+		_("Leave Without Pay") + ":Float:130",
+		_("Payment Days") + ":Float:120",
+		_("Currency") + ":Link/Currency:80"
 	]
 	"""
 	columns = [
@@ -73,15 +92,15 @@
 
 	return columns, salary_components[_("Earning")], salary_components[_("Deduction")]
 
-def get_salary_slips(filters):
+def get_salary_slips(filters, company_currency):
 	filters.update({"from_date": filters.get("from_date"), "to_date":filters.get("to_date")})
-	conditions, filters = get_conditions(filters)
+	conditions, filters = get_conditions(filters, company_currency)
 	salary_slips = frappe.db.sql("""select * from `tabSalary Slip` where %s
 		order by employee""" % conditions, filters, as_dict=1)
 
 	return salary_slips or []
 
-def get_conditions(filters):
+def get_conditions(filters, company_currency):
 	conditions = ""
 	doc_status = {"Draft": 0, "Submitted": 1, "Cancelled": 2}
 
@@ -92,6 +111,8 @@
 	if filters.get("to_date"): conditions += " and end_date <= %(to_date)s"
 	if filters.get("company"): conditions += " and company = %(company)s"
 	if filters.get("employee"): conditions += " and employee = %(employee)s"
+	if filters.get("currency") and filters.get("currency") != company_currency:
+		conditions += " and currency = %(currency)s"
 
 	return conditions, filters
 
@@ -103,26 +124,32 @@
 				FROM `tabEmployee`
 				"""))
 
-def get_ss_earning_map(salary_slips):
-	ss_earnings = frappe.db.sql("""select parent, salary_component, amount
-		from `tabSalary Detail` where parent in (%s)""" %
+def get_ss_earning_map(salary_slips, currency, company_currency):
+	ss_earnings = frappe.db.sql("""select sd.parent, sd.salary_component, sd.amount, ss.exchange_rate, ss.name
+		from `tabSalary Detail` sd, `tabSalary Slip` ss where sd.parent=ss.name and sd.parent in (%s)""" %
 		(', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]), as_dict=1)
 
 	ss_earning_map = {}
 	for d in ss_earnings:
 		ss_earning_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, [])
-		ss_earning_map[d.parent][d.salary_component] = flt(d.amount)
+		if currency == company_currency:
+			ss_earning_map[d.parent][d.salary_component] = flt(d.amount) * flt(d.exchange_rate if d.exchange_rate else 1)
+		else:
+			ss_earning_map[d.parent][d.salary_component] = flt(d.amount)
 
 	return ss_earning_map
 
-def get_ss_ded_map(salary_slips):
-	ss_deductions = frappe.db.sql("""select parent, salary_component, amount
-		from `tabSalary Detail` where parent in (%s)""" %
+def get_ss_ded_map(salary_slips, currency, company_currency):
+	ss_deductions = frappe.db.sql("""select sd.parent, sd.salary_component, sd.amount, ss.exchange_rate, ss.name
+		from `tabSalary Detail` sd, `tabSalary Slip` ss where sd.parent=ss.name and sd.parent in (%s)""" %
 		(', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]), as_dict=1)
 
 	ss_ded_map = {}
 	for d in ss_deductions:
 		ss_ded_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, [])
-		ss_ded_map[d.parent][d.salary_component] = flt(d.amount)
+		if currency == company_currency:
+			ss_ded_map[d.parent][d.salary_component] = flt(d.amount) * flt(d.exchange_rate if d.exchange_rate else 1)
+		else:
+			ss_ded_map[d.parent][d.salary_component] = flt(d.amount)
 
 	return ss_ded_map
diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js
index 8c6a9cf..002ddb2 100644
--- a/erpnext/projects/doctype/task/task.js
+++ b/erpnext/projects/doctype/task/task.js
@@ -49,7 +49,10 @@
 			},
 			callback: function (r) {
 				if (r.message.length > 0) {
-					frappe.msgprint(__(`Cannot convert it to non-group. The following child Tasks exist: ${r.message.join(", ")}.`));
+					let message = __('Cannot convert Task to non-group because the following child Tasks exist: {0}.',
+						[r.message.join(", ")]
+					);
+					frappe.msgprint(message);
 					frm.reload_doc();
 				}
 			}
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index 2695502..2f15cbc 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -49,7 +49,8 @@
 		"public/js/education/assessment_result_tool.html",
 		"public/js/hub/hub_factory.js",
 		"public/js/call_popup/call_popup.js",
-		"public/js/utils/dimension_tree_filter.js"
+		"public/js/utils/dimension_tree_filter.js",
+		"public/js/telephony.js"
 	],
 	"js/item-dashboard.min.js": [
 		"stock/dashboard/item_dashboard.html",
diff --git a/erpnext/public/js/call_popup/call_popup.js b/erpnext/public/js/call_popup/call_popup.js
index 5e4d4a5..aeb3b38 100644
--- a/erpnext/public/js/call_popup/call_popup.js
+++ b/erpnext/public/js/call_popup/call_popup.js
@@ -74,7 +74,7 @@
 				'click': () => {
 					const call_summary = this.dialog.get_value('call_summary');
 					if (!call_summary) return;
-					frappe.xcall('erpnext.communication.doctype.call_log.call_log.add_call_summary', {
+					frappe.xcall('erpnext.telephony.doctype.call_log.call_log.add_call_summary', {
 						'call_log': this.call_log.name,
 						'summary': call_summary,
 					}).then(() => {
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 58ac38f..db85a3e 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -189,6 +189,7 @@
 
 			frappe.model.round_floats_in(item, ["qty", "received_qty"]);
 			item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item));
+			item.received_stock_qty = flt(item.conversion_factor, precision("conversion_factor", item)) * flt(item.received_qty);
 		}
 
 		this._super(doc, cdt, cdn);
@@ -218,8 +219,7 @@
 		var is_negative_qty = false;
 		for(var i = 0; i<fieldnames.length; i++) {
 			if(item[fieldnames[i]] < 0){
-				frappe.msgprint(__("Row #{0}: {1} can not be negative for item {2}",
-					[item.idx,__(frappe.meta.get_label(cdt, fieldnames[i], cdn)), item.item_code]));
+				frappe.msgprint(__("Row #{0}: {1} can not be negative for item {2}", [item.idx,__(frappe.meta.get_label(cdt, fieldnames[i], cdn)), item.item_code]));
 				is_negative_qty = true;
 				break;
 			}
@@ -294,69 +294,6 @@
 		this.get_terms();
 	},
 
-	link_to_mrs: function() {
-		var my_items = [];
-		for (var i in cur_frm.doc.items) {
-			if(!cur_frm.doc.items[i].material_request){
-				my_items.push(cur_frm.doc.items[i].item_code);
-			}
-		}
-		frappe.call({
-			method: "erpnext.buying.utils.get_linked_material_requests",
-			args:{
-				items: my_items
-			},
-			callback: function(r) {
-				if(!r.message || r.message.length == 0) {
-					frappe.throw(__("No pending Material Requests found to link for the given items."))
-				}
-				else {
-					var i = 0;
-					var item_length = cur_frm.doc.items.length;
-					while (i < item_length) {
-						var qty = cur_frm.doc.items[i].qty;
-						(r.message[0] || []).forEach(function(d) {
-							if (d.qty > 0 && qty > 0 && cur_frm.doc.items[i].item_code == d.item_code && !cur_frm.doc.items[i].material_request_item)
-							{
-								cur_frm.doc.items[i].material_request = d.mr_name;
-								cur_frm.doc.items[i].material_request_item = d.mr_item;
-								var my_qty = Math.min(qty, d.qty);
-								qty = qty - my_qty;
-								d.qty = d.qty  - my_qty;
-								cur_frm.doc.items[i].stock_qty = my_qty*cur_frm.doc.items[i].conversion_factor;
-								cur_frm.doc.items[i].qty = my_qty;
-
-								frappe.msgprint("Assigning " + d.mr_name + " to " + d.item_code + " (row " + cur_frm.doc.items[i].idx + ")");
-								if (qty > 0)
-								{
-									frappe.msgprint("Splitting " + qty + " units of " + d.item_code);
-									var newrow = frappe.model.add_child(cur_frm.doc, cur_frm.doc.items[i].doctype, "items");
-									item_length++;
-
-									for (var key in cur_frm.doc.items[i])
-									{
-										newrow[key] = cur_frm.doc.items[i][key];
-									}
-
-									newrow.idx = item_length;
-									newrow["stock_qty"] = newrow.conversion_factor*qty;
-									newrow["qty"] = qty;
-
-									newrow["material_request"] = "";
-									newrow["material_request_item"] = "";
-
-								}
-							}
-						});
-						i++;
-					}
-					refresh_field("items");
-					//cur_frm.save();
-				}
-			}
-		});
-	},
-
 	update_auto_repeat_reference: function(doc) {
 		if (doc.auto_repeat) {
 			frappe.call({
@@ -422,6 +359,62 @@
 
 cur_frm.add_fetch('project', 'cost_center', 'cost_center');
 
+erpnext.buying.link_to_mrs = function(frm) {
+	frappe.call({
+		method: "erpnext.buying.utils.get_linked_material_requests",
+		args:{
+			items: frm.doc.items.map((item) => item.item_code)
+		},
+		callback: function(r) {
+			if (!r.message || r.message.length == 0) {
+				frappe.throw({
+					message: __("No pending Material Requests found to link for the given items."),
+					title: __("Note")
+				});
+			}
+
+			var item_length = frm.doc.items.length;
+			for (let item of frm.doc.items) {
+				var qty = item.qty;
+				(r.message[0] || []).forEach(function(d) {
+					if (d.qty > 0 && qty > 0 && item.item_code == d.item_code && !item.material_request_item)
+					{
+						item.material_request = d.mr_name;
+						item.material_request_item = d.mr_item;
+						var my_qty = Math.min(qty, d.qty);
+						qty = qty - my_qty;
+						d.qty = d.qty - my_qty;
+						item.stock_qty = my_qty*item.conversion_factor;
+						item.qty = my_qty;
+
+						frappe.msgprint("Assigning " + d.mr_name + " to " + d.item_code + " (row " + item.idx + ")");
+						if (qty > 0)
+						{
+							frappe.msgprint("Splitting " + qty + " units of " + d.item_code);
+							var newrow = frappe.model.add_child(frm.doc, item.doctype, "items");
+							item_length++;
+
+							for (var key in item)
+							{
+								newrow[key] = item[key];
+							}
+
+							newrow.idx = item_length;
+							newrow["stock_qty"] = newrow.conversion_factor*qty;
+							newrow["qty"] = qty;
+
+							newrow["material_request"] = "";
+							newrow["material_request_item"] = "";
+
+						}
+					}
+				});
+			}
+			refresh_field("items");
+		}
+	});
+}
+
 erpnext.buying.get_default_bom = function(frm) {
 	$.each(frm.doc["items"] || [], function(i, d) {
 		if (d.item_code && d.bom === "") {
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 99f3995..22e7578 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -609,6 +609,15 @@
 		this.calculate_outstanding_amount(update_paid_amount);
 	},
 
+	is_internal_invoice: function() {
+		if (['Sales Invoice', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
+			if (this.frm.doc.company === this.frm.doc.represents_company) {
+				return true;
+			}
+		}
+		return false;
+	},
+
 	calculate_outstanding_amount: function(update_paid_amount) {
 		// NOTE:
 		// paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice
@@ -617,7 +626,7 @@
 			this.calculate_paid_amount();
 		}
 
-		if(this.frm.doc.is_return || this.frm.doc.docstatus > 0) return;
+		if (this.frm.doc.is_return || (this.frm.doc.docstatus > 0) || this.is_internal_invoice()) return;
 
 		frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]);
 
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 1358a4b..3bc20f8 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -209,6 +209,17 @@
 			});
 		}
 
+		if (this.frm.fields_dict.taxes_and_charges) {
+			this.frm.set_query("taxes_and_charges", function() {
+				return {
+					filters: [
+						['company', '=', me.frm.doc.company],
+						['docstatus', '!=', 2]
+					]
+				};
+			});
+		}
+
 	},
 	onload: function() {
 		var me = this;
@@ -397,7 +408,7 @@
 
 				show_description(row_to_modify.idx, row_to_modify.item_code);
 
-				this.frm.from_barcode = true;
+				this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1;
 				frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, {
 					item_code: data.item_code,
 					qty: (row_to_modify.qty || 0) + 1
@@ -481,7 +492,7 @@
 			d.item_code = "";
 		}
 
-		this.frm.from_barcode = true;
+		this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1;
 		this.item_code(doc, cdt, cdn);
 	},
 
@@ -498,11 +509,12 @@
 			show_batch_dialog = 1;
 		}
 		// clear barcode if setting item (else barcode will take priority)
-		if(!this.frm.from_barcode) {
+		if (this.frm.from_barcode == 0) {
 			item.barcode = null;
 		}
+		this.frm.from_barcode = this.frm.from_barcode - 1 >= 0 ? this.frm.from_barcode - 1 : 0;
 
-		this.frm.from_barcode = false;
+
 		if(item.item_code || item.barcode || item.serial_no) {
 			if(!this.validate_company_and_party()) {
 				this.frm.fields_dict["items"].grid.grid_rows[item.idx - 1].remove();
diff --git a/erpnext/public/js/hub/pages/Category.vue b/erpnext/public/js/hub/pages/Category.vue
index 057fe8b..16d0601 100644
--- a/erpnext/public/js/hub/pages/Category.vue
+++ b/erpnext/public/js/hub/pages/Category.vue
@@ -32,7 +32,7 @@
 			item_id_fieldname: 'name',
 
 			// Constants
-			empty_state_message: __(`No items in this category yet.`),
+			empty_state_message: __('No items in this category yet.'),
 
 			search_value: '',
 
diff --git a/erpnext/public/js/hub/pages/FeaturedItems.vue b/erpnext/public/js/hub/pages/FeaturedItems.vue
index ab9990a..63ae7e9 100644
--- a/erpnext/public/js/hub/pages/FeaturedItems.vue
+++ b/erpnext/public/js/hub/pages/FeaturedItems.vue
@@ -33,10 +33,8 @@
 
 			// Constants
 			page_title: __('Your Featured Items'),
-            empty_state_message: __(`No featured items yet. Got to your 
-                                <a href="#marketplace/published-items">
-                                Published Items</a> 
-                                and feature upto 8 items that you want to highlight to your customers.`)
+			empty_state_message: __('No featured items yet. Got to your {0} and feature up to eight items that you want to highlight to your customers.',
+				[`<a href="#marketplace/published-items">${__("Published Items")}</a>`])
 		};
 	},
 	created() {
@@ -71,9 +69,9 @@
 
 			const item_name = this.items.filter(item => item.hub_item_name === hub_item_name);
 
-			alert = frappe.show_alert(__(`<span>${item_name} removed.
-				<a href="#" data-action="undo-remove"><b>Undo</b></a></span>`),
-				grace_period/1000,
+			alert_message = __('{0} removed. {1}', [item_name, 
+				`<a href="#" data-action="undo-remove"><b>${__('Undo')}</b></a>`]);
+			alert = frappe.show_alert(alert_message, grace_period / 1000,
 				{
 					'undo-remove': undo_remove.bind(this)
 				}
diff --git a/erpnext/public/js/hub/pages/Item.vue b/erpnext/public/js/hub/pages/Item.vue
index 51ade42..93002a7 100644
--- a/erpnext/public/js/hub/pages/Item.vue
+++ b/erpnext/public/js/hub/pages/Item.vue
@@ -113,12 +113,12 @@
 
 			let stats = __('No views yet');
 			if (this.item.view_count) {
-				const views_message = __(`${this.item.view_count} Views`);
+				const views_message = __('{0} Views', [this.item.view_count]);
 
 				const rating_html = get_rating_html(this.item.average_rating);
 				const rating_count =
 					this.item.no_of_ratings > 0
-						? `${this.item.no_of_ratings} reviews`
+						? __('{0} reviews', [this.item.no_of_ratings])
 						: __('No reviews yet');
 
 				stats = [views_message, rating_html, rating_count];
@@ -310,7 +310,7 @@
 					return this.get_item_details();
 				})
 				.then(() => {
-					frappe.show_alert(__(`${this.item.item_name} Updated`));
+					frappe.show_alert(__('{0} Updated', [this.item.item_name]));
 				});
 		},
 
@@ -337,7 +337,7 @@
 		},
 
 		unpublish_item() {
-			frappe.confirm(__(`Unpublish {0}?`, [this.item.item_name]), () => {
+			frappe.confirm(__('Unpublish {0}?', [this.item.item_name]), () => {
 				frappe
 					.call('erpnext.hub_node.api.unpublish_item', {
 						item_code: this.item.item_code,
diff --git a/erpnext/public/js/hub/pages/NotFound.vue b/erpnext/public/js/hub/pages/NotFound.vue
index 246d31b..8901b97 100644
--- a/erpnext/public/js/hub/pages/NotFound.vue
+++ b/erpnext/public/js/hub/pages/NotFound.vue
@@ -27,7 +27,7 @@
 			},
 
 			// Constants
-			empty_state_message: __(`Sorry! I could not find what you were looking for.`)
+			empty_state_message: __('Sorry! We could not find what you were looking for.')
 		};
 	},
 }
diff --git a/erpnext/public/js/hub/pages/Publish.vue b/erpnext/public/js/hub/pages/Publish.vue
index 735f2b9..96fa0aa 100644
--- a/erpnext/public/js/hub/pages/Publish.vue
+++ b/erpnext/public/js/hub/pages/Publish.vue
@@ -75,14 +75,11 @@
 			// TODO: multiline translations don't work
 			page_title: __('Publish Items'),
 			search_placeholder: __('Search Items ...'),
-			empty_state_message: __(`No Items selected yet. Browse and click on items below to publish.`),
-			valid_items_instruction: __(`Only items with an image and description can be published. Please update them if an item in your inventory does not appear.`),
+			empty_state_message: __('No Items selected yet. Browse and click on items below to publish.'),
+			valid_items_instruction: __('Only items with an image and description can be published. Please update them if an item in your inventory does not appear.'),
 			last_sync_message: (hub.settings.last_sync_datetime)
-				? __(`Last sync was
-				<a href="#marketplace/profile">
-					${comment_when(hub.settings.last_sync_datetime)}</a>.
-				<a href="#marketplace/published-items">
-					See your Published Items</a>.`)
+				? __('Last sync was {0}.', [`<a href="#marketplace/profile">${comment_when(hub.settings.last_sync_datetime)}</a>`]) + 
+				  ` <a href="#marketplace/published-items">${__('See your Published Items.')}</a>`
 				: ''
 		};
 	},
@@ -147,11 +144,9 @@
 		},
 
 		add_last_sync_message() {
-			this.last_sync_message = __(`Last sync was
-				<a href="#marketplace/profile">
-					${comment_when(hub.settings.last_sync_datetime)}</a>.
-				<a href="#marketplace/published-items">
-					See your Published Items</a>.`);
+			this.last_sync_message = __('Last sync was {0}.',
+				[`<a href="#marketplace/profile">${comment_when(hub.settings.last_sync_datetime)}</a>`]
+			) + `<a href="#marketplace/published-items">${__('See your Published Items')}</a>.`;
 		},
 
 		clear_last_sync_message() {
diff --git a/erpnext/public/js/hub/pages/SavedItems.vue b/erpnext/public/js/hub/pages/SavedItems.vue
index c29675a..7007ddc 100644
--- a/erpnext/public/js/hub/pages/SavedItems.vue
+++ b/erpnext/public/js/hub/pages/SavedItems.vue
@@ -29,7 +29,7 @@
 
 			// Constants
 			page_title: __('Saved Items'),
-			empty_state_message: __(`You haven't saved any items yet.`)
+			empty_state_message: __('You have not saved any items yet.')
 		};
 	},
 	created() {
@@ -64,8 +64,13 @@
 
 			const item_name = this.items.filter(item => item.hub_item_name === hub_item_name);
 
-			alert = frappe.show_alert(__(`<span>${item_name} removed.
-				<a href="#" data-action="undo-remove"><b>Undo</b></a></span>`),
+			alert = frappe.show_alert(`
+				<span>
+					${__('{0} removed.', [item_name], 'A specific Item has been removed.')}
+					<a href="#" data-action="undo-remove">
+						<b>${__('Undo', None, 'Undo removal of item.')}</b>
+					</a>
+				</span>`,
 				grace_period/1000,
 				{
 					'undo-remove': undo_remove.bind(this)
diff --git a/erpnext/public/js/hub/pages/Search.vue b/erpnext/public/js/hub/pages/Search.vue
index 1032842..c10841e 100644
--- a/erpnext/public/js/hub/pages/Search.vue
+++ b/erpnext/public/js/hub/pages/Search.vue
@@ -42,7 +42,10 @@
 	computed: {
 		page_title() {
 			return this.items.length
-				? __(`Results for "${this.search_value}" ${this.category !== 'All'? `in category ${this.category}` : ''}`)
+				? __('Results for "{0}" {1}', [
+					this.search_value,
+					this.category !== 'All' ? __('in category {0}', [this.category]) : ''
+				  ])
 				: __('No Items found.');
 		}
 	},
diff --git a/erpnext/public/js/hub/pages/Seller.vue b/erpnext/public/js/hub/pages/Seller.vue
index e339eaa..c0903c6 100644
--- a/erpnext/public/js/hub/pages/Seller.vue
+++ b/erpnext/public/js/hub/pages/Seller.vue
@@ -136,7 +136,7 @@
 				this.init = false;
 				this.profile = data.profile;
 				this.items = data.items;
-				this.item_container_heading = data.is_featured_item? "Features Items":"Popular Items";
+				this.item_container_heading = data.is_featured_item ? __('Featured Items') : __('Popular Items');
 				this.hub_seller = this.items[0].hub_seller;
 				this.recent_seller_reviews = data.recent_seller_reviews;
 				this.seller_product_view_stats = data.seller_product_view_stats;
@@ -147,7 +147,7 @@
 
 				this.country = __(profile.country);
 				this.site_name = __(profile.site_name);
-				this.joined_when = __(`Joined ${comment_when(profile.creation)}`);
+				this.joined_when = __('Joined {0}', [comment_when(profile.creation)]);
 
 				this.image = profile.logo;
 				this.sections = [
diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js
index 5d21190..092f839 100644
--- a/erpnext/public/js/setup_wizard.js
+++ b/erpnext/public/js/setup_wizard.js
@@ -161,7 +161,10 @@
 						if(r.message){
 							exist = r.message;
 							me.get_field("bank_account").set_value("");
-							frappe.msgprint(__(`Account ${me.values.bank_account} already exists, enter a different name for your bank account`));
+							let message = __('Account {0} already exists. Please enter a different name for your bank account.',
+								[me.values.bank_account]
+							);
+							frappe.msgprint(message);
 						}
 					}
 				});
diff --git a/erpnext/public/js/telephony.js b/erpnext/public/js/telephony.js
new file mode 100644
index 0000000..f9caade
--- /dev/null
+++ b/erpnext/public/js/telephony.js
@@ -0,0 +1,23 @@
+frappe.ui.form.ControlData = frappe.ui.form.ControlData.extend( {
+	make_input() {
+		this._super();
+		if (this.df.options == 'Phone') {
+			this.setup_phone();
+		}
+	},
+	setup_phone() {
+		if (frappe.phone_call.handler) {
+			this.$wrapper.find('.control-input')
+				.append(`
+					<span class="phone-btn">
+						<a class="btn-open no-decoration" title="${__('Make a call')}">
+							<i class="fa fa-phone"></i></a>
+					</span>
+				`)
+				.find('.phone-btn')
+				.click(() => {
+					frappe.phone_call.handler(this.get_value(), this.frm);
+				});
+		}
+	}
+});
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/regional/doctype/e_invoice_request_log/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/regional/doctype/e_invoice_request_log/__init__.py
diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js
new file mode 100644
index 0000000..7b7ba96
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('E Invoice Request Log', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json
new file mode 100644
index 0000000..5c1c79d
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json
@@ -0,0 +1,103 @@
+{
+ "actions": [],
+ "autoname": "EINV-REQ-.#####",
+ "creation": "2020-12-08 12:54:08.175992",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "user",
+  "url",
+  "headers",
+  "response",
+  "column_break_7",
+  "timestamp",
+  "reference_invoice",
+  "data"
+ ],
+ "fields": [
+  {
+   "fieldname": "user",
+   "fieldtype": "Link",
+   "label": "User",
+   "options": "User"
+  },
+  {
+   "fieldname": "reference_invoice",
+   "fieldtype": "Link",
+   "label": "Reference Invoice",
+   "options": "Sales Invoice"
+  },
+  {
+   "fieldname": "headers",
+   "fieldtype": "Code",
+   "label": "Headers",
+   "options": "JSON"
+  },
+  {
+   "fieldname": "data",
+   "fieldtype": "Code",
+   "label": "Data",
+   "options": "JSON"
+  },
+  {
+   "default": "Now",
+   "fieldname": "timestamp",
+   "fieldtype": "Datetime",
+   "label": "Timestamp"
+  },
+  {
+   "fieldname": "response",
+   "fieldtype": "Code",
+   "label": "Response",
+   "options": "JSON"
+  },
+  {
+   "fieldname": "url",
+   "fieldtype": "Data",
+   "label": "URL"
+  },
+  {
+   "fieldname": "column_break_7",
+   "fieldtype": "Column Break"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-12-24 21:09:38.882866",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "E Invoice Request Log",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User",
+   "share": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager",
+   "share": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py
new file mode 100644
index 0000000..9150bdd
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class EInvoiceRequestLog(Document):
+	pass
diff --git a/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py b/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py
new file mode 100644
index 0000000..c84e9a2
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestEInvoiceRequestLog(unittest.TestCase):
+	pass
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/regional/doctype/e_invoice_settings/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/regional/doctype/e_invoice_settings/__init__.py
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
new file mode 100644
index 0000000..cc2d9f0
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('E Invoice Settings', {
+	refresh(frm) {
+		const docs_link = 'https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing';
+		frm.dashboard.set_headline(
+			__("Read {0} for more information on E Invoicing features.", [`<a href='${docs_link}'>documentation</a>`])
+		);
+	}
+});
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
new file mode 100644
index 0000000..4dcb22a
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
@@ -0,0 +1,58 @@
+{
+ "actions": [],
+ "creation": "2020-09-24 16:23:16.235722",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "enable",
+  "section_break_2",
+  "credentials",
+  "auth_token",
+  "token_expiry"
+ ],
+ "fields": [
+  {
+   "default": "0",
+   "fieldname": "enable",
+   "fieldtype": "Check",
+   "label": "Enable"
+  },
+  {
+   "depends_on": "enable",
+   "fieldname": "section_break_2",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "auth_token",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "token_expiry",
+   "fieldtype": "Datetime",
+   "hidden": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "credentials",
+   "fieldtype": "Table",
+   "label": "Credentials",
+   "mandatory_depends_on": "enable",
+   "options": "E Invoice User"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2020-12-22 15:34:57.280044",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "E Invoice Settings",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py
new file mode 100644
index 0000000..c24ad88
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+from __future__ import unicode_literals
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+
+class EInvoiceSettings(Document):
+	def validate(self):
+		if self.enable and not self.credentials:
+			frappe.throw(_('You must add atleast one credentials to be able to use E Invoicing.'))
+
diff --git a/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py
new file mode 100644
index 0000000..a11ce63
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestEInvoiceSettings(unittest.TestCase):
+	pass
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/regional/doctype/e_invoice_user/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/regional/doctype/e_invoice_user/__init__.py
diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
new file mode 100644
index 0000000..dd9d997
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
@@ -0,0 +1,48 @@
+{
+ "actions": [],
+ "creation": "2020-12-22 15:02:46.229474",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "gstin",
+  "username",
+  "password"
+ ],
+ "fields": [
+  {
+   "fieldname": "gstin",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "GSTIN",
+   "reqd": 1
+  },
+  {
+   "fieldname": "username",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Username",
+   "reqd": 1
+  },
+  {
+   "fieldname": "password",
+   "fieldtype": "Password",
+   "in_list_view": 1,
+   "label": "Password",
+   "reqd": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2020-12-22 15:10:53.466205",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "E Invoice User",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py
new file mode 100644
index 0000000..056c54f
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class EInvoiceUser(Document):
+	pass
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
index 787d557..68c8a0d 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -192,19 +192,20 @@
 		for d in self.report_dict["itc_elg"]["itc_avl"]:
 
 			itc_type = itc_type_map.get(d["ty"])
-			gst_category = ["Registered Regular"]
 
 			if d["ty"] == 'ISRC':
-				reverse_charge = "Y"
+				reverse_charge = ["Y"]
 				itc_type = 'All Other ITC'
 				gst_category = ['Unregistered', 'Overseas']
 			else:
-				reverse_charge = "N"
+				gst_category = ['Unregistered', 'Overseas', 'Registered Regular']
+				reverse_charge = ["N", "Y"]
 
 			for account_head in self.account_heads:
 				for category in gst_category:
-					for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
-						d[key[0]] += flt(itc_details.get((category, itc_type, reverse_charge, account_head.get(key[1])), {}).get("amount"), 2)
+					for charge_type in reverse_charge:
+						for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
+							d[key[0]] += flt(itc_details.get((category, itc_type, charge_type, account_head.get(key[1])), {}).get("amount"), 2)
 
 			for key in ['iamt', 'camt', 'samt', 'csamt']:
 				net_itc[key] += flt(d[key], 2)
@@ -264,7 +265,8 @@
 
 	def get_itc_details(self):
 		itc_amount = frappe.db.sql("""
-			select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head, s.eligibility_for_itc, s.reverse_charge
+			select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount,
+			t.account_head, s.eligibility_for_itc, s.reverse_charge
 			from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t
 			where s.docstatus = 1 and t.parent = s.name
 			and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
@@ -387,7 +389,7 @@
 			tax_template = 'Purchase Taxes and Charges'
 
 		tax_amounts = frappe.db.sql("""
-			select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head
+			select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount, t.account_head
 			from `tab{doctype}` s , `tab{template}` t
 			where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s
 			and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
diff --git a/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json
index ce2c1d4..1ff5680 100644
--- a/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json
+++ b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json
@@ -29,25 +29,12 @@
  ],
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2020-09-30 20:08:18.764798",
+ "modified": "2020-12-25 20:20:22.342426",
  "modified_by": "Administrator",
  "module": "Regional",
  "name": "UAE VAT Settings",
  "owner": "Administrator",
- "permissions": [
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
-   "read": 1,
-   "report": 1,
-   "role": "System Manager",
-   "share": 1,
-   "write": 1
-  }
- ],
+ "permissions": [],
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/regional/india/e_invoice/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/regional/india/e_invoice/__init__.py
diff --git a/erpnext/regional/india/e_invoice/einv_item_template.json b/erpnext/regional/india/e_invoice/einv_item_template.json
new file mode 100644
index 0000000..78e5651
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/einv_item_template.json
@@ -0,0 +1,31 @@
+{{
+    "SlNo": "{item.sr_no}",
+    "PrdDesc": "{item.description}",
+    "IsServc": "{item.is_service_item}",
+    "HsnCd": "{item.gst_hsn_code}",
+    "Barcde": "{item.barcode}",
+    "Unit": "{item.uom}",
+    "Qty": "{item.qty}",
+    "FreeQty": "{item.free_qty}",
+    "UnitPrice": "{item.unit_rate}",
+    "TotAmt": "{item.gross_amount}",
+    "Discount": "{item.discount_amount}",
+    "AssAmt": "{item.taxable_value}",
+    "PrdSlNo": "{item.serial_no}",
+    "GstRt": "{item.tax_rate}",
+    "IgstAmt": "{item.igst_amount}",
+    "CgstAmt": "{item.cgst_amount}",
+    "SgstAmt": "{item.sgst_amount}",
+    "CesRt": "{item.cess_rate}",
+    "CesAmt": "{item.cess_amount}",
+    "CesNonAdvlAmt": "{item.cess_nadv_amount}",
+    "StateCesRt": "{item.state_cess_rate}",
+    "StateCesAmt": "{item.state_cess_amount}",
+    "StateCesNonAdvlAmt": "{item.state_cess_nadv_amount}",
+    "OthChrg": "{item.other_charges}",
+    "TotItemVal": "{item.total_value}",
+    "BchDtls": {{
+        "Nm": "{item.batch_no}",
+        "ExpDt": "{item.batch_expiry_date}"
+    }}
+}}
\ No newline at end of file
diff --git a/erpnext/regional/india/e_invoice/einv_template.json b/erpnext/regional/india/e_invoice/einv_template.json
new file mode 100644
index 0000000..60f490d
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/einv_template.json
@@ -0,0 +1,110 @@
+{{
+    "Version": "1.1",
+    "TranDtls": {{
+        "TaxSch": "{transaction_details.tax_scheme}",
+        "SupTyp": "{transaction_details.supply_type}",
+        "RegRev": "{transaction_details.reverse_charge}",
+        "EcmGstin": "{transaction_details.ecom_gstin}",
+        "IgstOnIntra": "{transaction_details.igst_on_intra}"
+    }},
+    "DocDtls": {{
+        "Typ": "{doc_details.invoice_type}",
+        "No": "{doc_details.invoice_name}",
+        "Dt": "{doc_details.invoice_date}"
+    }},
+    "SellerDtls": {{
+        "Gstin": "{seller_details.gstin}",
+        "LglNm": "{seller_details.legal_name}",
+        "TrdNm": "{seller_details.trade_name}",
+        "Loc": "{seller_details.location}",
+        "Pin": "{seller_details.pincode}",
+        "Stcd": "{seller_details.state_code}",
+        "Addr1": "{seller_details.address_line1}",
+        "Addr2": "{seller_details.address_line2}",
+        "Ph": "{seller_details.phone}",
+        "Em": "{seller_details.email}"
+    }},
+    "BuyerDtls": {{
+        "Gstin": "{buyer_details.gstin}",
+        "LglNm": "{buyer_details.legal_name}",
+        "TrdNm": "{buyer_details.trade_name}",
+        "Addr1": "{buyer_details.address_line1}",
+        "Addr2": "{buyer_details.address_line2}",
+        "Loc": "{buyer_details.location}",
+        "Pin": "{buyer_details.pincode}",
+        "Stcd": "{buyer_details.state_code}",
+        "Ph": "{buyer_details.phone}",
+        "Em": "{buyer_details.email}",
+        "Pos": "{buyer_details.place_of_supply}"
+    }},
+    "DispDtls": {{
+        "Nm": "{dispatch_details.company_name}",
+        "Addr1": "{dispatch_details.address_line1}",
+        "Addr2": "{dispatch_details.address_line2}",
+        "Loc": "{dispatch_details.location}",
+        "Pin": "{dispatch_details.pincode}",
+        "Stcd": "{dispatch_details.state_code}"
+    }},
+    "ShipDtls": {{
+        "Gstin": "{shipping_details.gstin}",
+        "LglNm": "{shipping_details.legal_name}",
+        "TrdNm": "{shipping_details.trader_name}",
+        "Addr1": "{shipping_details.address_line1}",
+        "Addr2": "{shipping_details.address_line2}",
+        "Loc": "{shipping_details.location}",
+        "Pin": "{shipping_details.pincode}",
+        "Stcd": "{shipping_details.state_code}"
+    }},
+    "ItemList": [
+        {item_list}
+    ],
+    "ValDtls": {{
+        "AssVal": "{invoice_value_details.base_total}",
+        "CgstVal": "{invoice_value_details.total_cgst_amt}",
+        "SgstVal": "{invoice_value_details.total_sgst_amt}",
+        "IgstVal": "{invoice_value_details.total_igst_amt}",
+        "CesVal": "{invoice_value_details.total_cess_amt}",
+        "Discount": "{invoice_value_details.invoice_discount_amt}",
+        "RndOffAmt": "{invoice_value_details.round_off}",
+        "OthChrg": "{invoice_value_details.total_other_charges}",
+        "TotInvVal": "{invoice_value_details.base_grand_total}",
+        "TotInvValFc": "{invoice_value_details.grand_total}"
+    }},
+    "PayDtls": {{
+        "Nm": "{payment_details.payee_name}",
+        "AccDet": "{payment_details.account_no}",
+        "Mode": "{payment_details.mode_of_payment}",
+        "FinInsBr": "{payment_details.ifsc_code}",
+        "PayTerm": "{payment_details.terms}",
+        "PaidAmt": "{payment_details.paid_amount}",
+        "PaymtDue": "{payment_details.outstanding_amount}"
+    }},
+    "RefDtls": {{
+        "DocPerdDtls": {{
+            "InvStDt": "{period_details.start_date}",
+            "InvEndDt": "{period_details.end_date}"
+        }},
+        "PrecDocDtls": [{{
+            "InvNo": "{prev_doc_details.invoice_name}",
+            "InvDt": "{prev_doc_details.invoice_date}"
+        }}]
+    }},
+    "ExpDtls": {{
+        "ShipBNo": "{export_details.bill_no}",
+        "ShipBDt": "{export_details.bill_date}",
+        "Port": "{export_details.port}",
+        "ForCur": "{export_details.foreign_curr_code}",
+        "CntCode": "{export_details.country_code}",
+        "ExpDuty": "{export_details.export_duty}"
+    }},
+    "EwbDtls": {{
+        "TransId": "{eway_bill_details.gstin}",
+        "TransName": "{eway_bill_details.name}",
+        "TransMode": "{eway_bill_details.mode_of_transport}",
+        "Distance": "{eway_bill_details.distance}",
+        "TransDocNo": "{eway_bill_details.document_name}",
+        "TransDocDt": "{eway_bill_details.document_date}",
+        "VehNo": "{eway_bill_details.vehicle_no}",
+        "VehType": "{eway_bill_details.vehicle_type}"
+    }}
+}}
\ No newline at end of file
diff --git a/erpnext/regional/india/e_invoice/einv_validation.json b/erpnext/regional/india/e_invoice/einv_validation.json
new file mode 100644
index 0000000..86290cf
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/einv_validation.json
@@ -0,0 +1,956 @@
+{
+  "Version": {
+    "type": "string",
+    "minLength": 1,
+    "maxLength": 6,
+    "description": "Version of the schema"
+  },
+  "Irn": {
+    "type": "string",
+    "minLength": 64,
+    "maxLength": 64,
+    "description": "Invoice Reference Number"
+  },
+  "TranDtls": {
+    "type": "object",
+    "properties": {
+      "TaxSch": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 10,
+        "enum": ["GST"],
+        "description": "GST- Goods and Services Tax Scheme"
+      },
+      "SupTyp": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 10,
+        "enum": ["B2B", "SEZWP", "SEZWOP", "EXPWP", "EXPWOP", "DEXP"],
+        "description": "Type of Supply: B2B-Business to Business, SEZWP - SEZ with payment, SEZWOP - SEZ without payment, EXPWP - Export with Payment, EXPWOP - Export without payment,DEXP - Deemed Export"
+      },
+      "RegRev": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 1,
+        "enum": ["Y", "N"],
+        "description": "Y- whether the tax liability is payable under reverse charge"
+      },
+      "EcmGstin": {
+        "type": "string",
+        "minLength": 15,
+        "maxLength": 15,
+        "pattern": "([0-9]{2}[0-9A-Z]{13})",
+        "description": "E-Commerce GSTIN",
+        "validationMsg": "E-Commerce GSTIN is invalid"
+      },
+      "IgstOnIntra": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 1,
+        "enum": ["Y", "N"],
+        "description": "Y- indicates the supply is intra state but chargeable to IGST"
+      }
+    },
+    "required": ["TaxSch", "SupTyp"]
+  },
+  "DocDtls": {
+    "type": "object",
+    "properties": {
+      "Typ": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 3,
+        "enum": ["INV", "CRN", "DBN"],
+        "description": "Document Type"
+      },
+      "No": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 16,
+        "pattern": "^([A-Z1-9]{1}[A-Z0-9/-]{0,15})$",
+        "description": "Document Number",
+        "validationMsg": "Document Number should not be starting with 0, / and -"
+      },
+      "Dt": {
+        "type": "string",
+        "minLength": 10,
+        "maxLength": 10,
+        "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+        "description": "Document Date"
+      }
+    },
+    "required": ["Typ", "No", "Dt"]
+  },
+  "SellerDtls": {
+    "type": "object",
+    "properties": {
+      "Gstin": {
+        "type": "string",
+        "minLength": 15,
+        "maxLength": 15,
+        "pattern": "([0-9]{2}[0-9A-Z]{13})",
+        "description": "Supplier GSTIN",
+        "validationMsg": "Company GSTIN is invalid"
+      },
+      "LglNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Legal Name"
+      },
+      "TrdNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Tradename"
+      },
+      "Addr1": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Address Line 1"
+      },
+      "Addr2": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Address Line 2"
+      },
+      "Loc": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 50,
+        "description": "Location"
+      },
+      "Pin": {
+        "type": "number",
+        "minimum": 100000,
+        "maximum": 999999,
+        "description": "Pincode"
+      },
+      "Stcd": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 2,
+        "description": "Supplier State Code"
+      },
+      "Ph": {
+        "type": "string",
+        "minLength": 6,
+        "maxLength": 12,
+        "description": "Phone"
+      },
+      "Em": {
+        "type": "string",
+        "minLength": 6,
+        "maxLength": 100,
+        "description": "Email-Id"
+      }
+    },
+    "required": ["Gstin", "LglNm", "Addr1", "Loc", "Pin", "Stcd"]
+  },
+  "BuyerDtls": {
+    "type": "object",
+    "properties": {
+      "Gstin": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 15,
+        "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$",
+        "description": "Buyer GSTIN",
+        "validationMsg": "Customer GSTIN is invalid"
+      },
+      "LglNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Legal Name"
+      },
+      "TrdNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Trade Name"
+      },
+      "Pos": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 2,
+        "description": "Place of Supply State code"
+      },
+      "Addr1": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Address Line 1"
+      },
+      "Addr2": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Address Line 2"
+      },
+      "Loc": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Location"
+      },
+      "Pin": {
+        "type": "number",
+        "minimum": 100000,
+        "maximum": 999999,
+        "description": "Pincode"
+      },
+      "Stcd": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 2,
+        "description": "Buyer State Code"
+      },
+      "Ph": {
+        "type": "string",
+        "minLength": 6,
+        "maxLength": 12,
+        "description": "Phone"
+      },
+      "Em": {
+        "type": "string",
+        "minLength": 6,
+        "maxLength": 100,
+        "description": "Email-Id"
+      }
+    },
+    "required": ["Gstin", "LglNm", "Pos", "Addr1", "Loc", "Stcd"]
+  },
+  "DispDtls": {
+    "type": "object",
+    "properties": {
+      "Nm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Dispatch Address Name"
+      },
+      "Addr1": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Address Line 1"
+      },
+      "Addr2": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Address Line 2"
+      },
+      "Loc": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Location"
+      },
+      "Pin": {
+        "type": "number",
+        "minimum": 100000,
+        "maximum": 999999,
+        "description": "Pincode"
+      },
+      "Stcd": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 2,
+        "description": "State Code"
+      }
+    },
+    "required": ["Nm", "Addr1", "Loc", "Pin", "Stcd"]
+  },
+  "ShipDtls": {
+    "type": "object",
+    "properties": {
+      "Gstin": {
+        "type": "string",
+        "maxLength": 15,
+        "minLength": 3,
+        "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$",
+        "description": "Shipping Address GSTIN",
+        "validationMsg": "Shipping Address GSTIN is invalid"
+      },
+      "LglNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Legal Name"
+      },
+      "TrdNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Trade Name"
+      },
+      "Addr1": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Address Line 1"
+      },
+      "Addr2": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Address Line 2"
+      },
+      "Loc": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Location"
+      },
+      "Pin": {
+        "type": "number",
+        "minimum": 100000,
+        "maximum": 999999,
+        "description": "Pincode"
+      },
+      "Stcd": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 2,
+        "description": "State Code"
+      }
+    },
+    "required": ["LglNm", "Addr1", "Loc", "Pin", "Stcd"]
+  },
+  "ItemList": {
+    "type": "Array",
+    "properties": {
+      "SlNo": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 6,
+        "description": "Serial No. of Item"
+      },
+      "PrdDesc": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 300,
+        "description": "Item Name"
+      },
+      "IsServc": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 1,
+        "enum": ["Y", "N"],
+        "description": "Is Service Item"
+      },
+      "HsnCd": {
+        "type": "string",
+        "minLength": 4,
+        "maxLength": 8,
+        "description": "HSN Code"
+      },
+      "Barcde": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 30,
+        "description": "Barcode"
+      },
+      "Qty": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 9999999999.999,
+        "description": "Quantity"
+      },
+      "FreeQty": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 9999999999.999,
+        "description": "Free Quantity"
+      },
+      "Unit": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 8,
+        "description": "UOM"
+      },
+      "UnitPrice": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.999,
+        "description": "Rate"
+      },
+      "TotAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Gross Amount"
+      },
+      "Discount": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Discount"
+      },
+      "PreTaxVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Pre tax value"
+      },
+      "AssAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Taxable Value"
+      },
+      "GstRt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999.999,
+        "description": "GST Rate"
+      },
+      "IgstAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "IGST Amount"
+      },
+      "CgstAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "CGST Amount"
+      },
+      "SgstAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "SGST Amount"
+      },
+      "CesRt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999.999,
+        "description": "Cess Rate"
+      },
+      "CesAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Cess Amount (Advalorem)"
+      },
+      "CesNonAdvlAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Cess Amount (Non-Advalorem)"
+      },
+      "StateCesRt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999.999,
+        "description": "State CESS Rate"
+      },
+      "StateCesAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "State CESS Amount"
+      },
+      "StateCesNonAdvlAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "State CESS Amount (Non Advalorem)"
+      },
+      "OthChrg": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Other Charges"
+      },
+      "TotItemVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Total Item Value"
+      },
+      "OrdLineRef": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 50,
+        "description": "Order line reference"
+      },
+      "OrgCntry": {
+        "type": "string",
+        "minLength": 2,
+        "maxLength": 2,
+        "description": "Origin Country"
+      },
+      "PrdSlNo": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 20,
+        "description": "Serial number"
+      },
+      "BchDtls": {
+        "type": "object",
+        "properties": {
+          "Nm": {
+            "type": "string",
+            "minLength": 3,
+            "maxLength": 20,
+            "description": "Batch number"
+          },
+          "ExpDt": {
+            "type": "string",
+            "maxLength": 10,
+            "minLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Batch Expiry Date"
+          },
+          "WrDt": {
+            "type": "string",
+            "maxLength": 10,
+            "minLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Warranty Date"
+          }
+        },
+        "required": ["Nm"]
+      },
+      "AttribDtls": {
+        "type": "Array",
+        "Attribute": {
+          "type": "object",
+          "properties": {
+            "Nm": {
+              "type": "string",
+              "minLength": 1,
+              "maxLength": 100,
+              "description": "Attribute name of the item"
+            },
+            "Val": {
+              "type": "string",
+              "minLength": 1,
+              "maxLength": 100,
+              "description": "Attribute value of the item"
+            }
+          }
+        }
+      }
+    },
+    "required": [
+      "SlNo",
+      "IsServc",
+      "HsnCd",
+      "UnitPrice",
+      "TotAmt",
+      "AssAmt",
+      "GstRt",
+      "TotItemVal"
+    ]
+  },
+  "ValDtls": {
+    "type": "object",
+    "properties": {
+      "AssVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Total Assessable value of all items"
+      },
+      "CgstVal": {
+        "type": "number",
+        "maximum": 99999999999999.99,
+        "minimum": 0,
+        "description": "Total CGST value of all items"
+      },
+      "SgstVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Total SGST value of all items"
+      },
+      "IgstVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Total IGST value of all items"
+      },
+      "CesVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Total CESS value of all items"
+      },
+      "StCesVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Total State CESS value of all items"
+      },
+      "Discount": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Invoice Discount"
+      },
+      "OthChrg": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Other Charges"
+      },
+      "RndOffAmt": {
+        "type": "number",
+        "minimum": -99.99,
+        "maximum": 99.99,
+        "description": "Rounded off Amount"
+      },
+      "TotInvVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Final Invoice Value "
+      },
+      "TotInvValFc": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Final Invoice value in Foreign Currency"
+      }
+    },
+    "required": ["AssVal", "TotInvVal"]
+  },
+  "PayDtls": {
+    "type": "object",
+    "properties": {
+      "Nm": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Payee Name"
+      },
+      "AccDet": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 18,
+        "description": "Bank Account Number of Payee"
+      },
+      "Mode": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 18,
+        "description": "Mode of Payment"
+      },
+      "FinInsBr": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 11,
+        "description": "Branch or IFSC code"
+      },
+      "PayTerm": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Terms of Payment"
+      },
+      "PayInstr": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Payment Instruction"
+      },
+      "CrTrn": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Credit Transfer"
+      },
+      "DirDr": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Direct Debit"
+      },
+      "CrDay": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 9999,
+        "description": "Credit Days"
+      },
+      "PaidAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Advance Amount"
+      },
+      "PaymtDue": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Outstanding Amount"
+      }
+    }
+  },
+  "RefDtls": {
+    "type": "object",
+    "properties": {
+      "InvRm": {
+        "type": "string",
+        "maxLength": 100,
+        "minLength": 3,
+        "pattern": "^[0-9A-Za-z/-]{3,100}$",
+        "description": "Remarks/Note"
+      },
+      "DocPerdDtls": {
+        "type": "object",
+        "properties": {
+          "InvStDt": {
+            "type": "string",
+            "maxLength": 10,
+            "minLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Invoice Period Start Date"
+          },
+          "InvEndDt": {
+            "type": "string",
+            "maxLength": 10,
+            "minLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Invoice Period End Date"
+          }
+        },
+        "required": ["InvStDt ", "InvEndDt "]
+      },
+      "PrecDocDtls": {
+        "type": "object",
+        "properties": {
+          "InvNo": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 16,
+            "pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$",
+            "description": "Reference of Original Invoice"
+          },
+          "InvDt": {
+            "type": "string",
+            "maxLength": 10,
+            "minLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Date of Orginal Invoice"
+          },
+          "OthRefNo": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "description": "Other Reference"
+          }
+        }
+      },
+      "required": ["InvNo", "InvDt"],
+      "ContrDtls": {
+        "type": "object",
+        "properties": {
+          "RecAdvRefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "pattern": "^([0-9A-Za-z/-]){1,20}$",
+            "description": "Receipt Advice No."
+          },
+          "RecAdvDt": {
+            "type": "string",
+            "minLength": 10,
+            "maxLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Date of receipt advice"
+          },
+          "TendRefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "pattern": "^([0-9A-Za-z/-]){1,20}$",
+            "description": "Lot/Batch Reference No."
+          },
+          "ContrRefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "pattern": "^([0-9A-Za-z/-]){1,20}$",
+            "description": "Contract Reference Number"
+          },
+          "ExtRefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "pattern": "^([0-9A-Za-z/-]){1,20}$",
+            "description": "Any other reference"
+          },
+          "ProjRefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "pattern": "^([0-9A-Za-z/-]){1,20}$",
+            "description": "Project Reference Number"
+          },
+          "PORefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 16,
+            "pattern": "^([0-9A-Za-z/-]){1,16}$",
+            "description": "PO Reference Number"
+          },
+          "PORefDt": {
+            "type": "string",
+            "minLength": 10,
+            "maxLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "PO Reference date"
+          }
+        }
+      }
+    }
+  },
+  "AddlDocDtls": {
+    "type": "Array",
+    "properties": {
+      "Url": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Supporting document URL"
+      },
+      "Docs": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 1000,
+        "description": "Supporting document in Base64 Format"
+      },
+      "Info": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 1000,
+        "description": "Any additional information"
+      }
+    }
+  },
+
+  "ExpDtls": {
+    "type": "object",
+    "properties": {
+      "ShipBNo": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 20,
+        "description": "Shipping Bill No."
+      },
+      "ShipBDt": {
+        "type": "string",
+        "minLength": 10,
+        "maxLength": 10,
+        "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+        "description": "Shipping Bill Date"
+      },
+      "Port": {
+        "type": "string",
+        "minLength": 2,
+        "maxLength": 10,
+        "pattern": "^[0-9A-Za-z]{2,10}$",
+        "description": "Port Code. Refer the master"
+      },
+      "RefClm": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 1,
+        "description": "Claiming Refund. Y/N"
+      },
+      "ForCur": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 16,
+        "description": "Additional Currency Code. Refer the master"
+      },
+      "CntCode": {
+        "type": "string",
+        "minLength": 2,
+        "maxLength": 2,
+        "description": "Country Code. Refer the master"
+      },
+      "ExpDuty": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Export Duty"
+      }
+    }
+  },
+  "EwbDtls": {
+    "type": "object",
+    "properties": {
+      "TransId": {
+        "type": "string",
+        "minLength": 15,
+        "maxLength": 15,
+        "description": "Transporter GSTIN"
+      },
+      "TransName": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Transporter Name"
+      },
+      "TransMode": {
+        "type": "string",
+        "maxLength": 1,
+        "minLength": 1,
+        "enum": ["1", "2", "3", "4"],
+        "description": "Mode of Transport"
+      },
+      "Distance": {
+        "type": "number",
+        "minimum": 1,
+        "maximum": 9999,
+        "description": "Distance"
+      },
+      "TransDocNo": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 15,
+        "pattern": "^([0-9A-Z/-]){1,15}$",
+        "description": "Tranport Document Number"
+      },
+      "TransDocDt": {
+        "type": "string",
+        "minLength": 10,
+        "maxLength": 10,
+        "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+        "description": "Transport Document Date"
+      },
+      "VehNo": {
+        "type": "string",
+        "minLength": 4,
+        "maxLength": 20,
+        "description": "Vehicle Number"
+      },
+      "VehType": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 1,
+        "enum": ["O", "R"],
+        "description": "Vehicle Type"
+      }
+    },
+    "required": ["Distance"]
+  },
+  "required": [
+    "Version",
+    "TranDtls",
+    "DocDtls",
+    "SellerDtls",
+    "BuyerDtls",
+    "ItemList",
+    "ValDtls"
+  ]
+}
diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js
new file mode 100644
index 0000000..9c86cc8
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/einvoice.js
@@ -0,0 +1,305 @@
+erpnext.setup_einvoice_actions = (doctype) => {
+	frappe.ui.form.on(doctype, {
+		refresh(frm) {
+			const einvoicing_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable");
+			const supply_type = frm.doc.gst_category;
+			const valid_supply_type = ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type);
+			const company_transaction = frm.doc.billing_address_gstin == frm.doc.company_gstin;
+
+			if (!einvoicing_enabled || !valid_supply_type || company_transaction) return;
+
+			const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc;
+
+			const add_custom_button = (label, action) => {
+				if (!frm.custom_buttons[label]) {
+					frm.add_custom_button(label, action, __('E Invoicing'));
+				}
+			};
+
+			if (!irn && !__unsaved) {
+				const action = () => {
+					frappe.call({
+						method: 'erpnext.regional.india.e_invoice.utils.get_einvoice',
+						args: { doctype, docname: name },
+						freeze: true,
+						callback: (res) => {
+							const einvoice = res.message;
+							show_einvoice_preview(frm, einvoice);
+						}
+					});
+				};
+
+				add_custom_button(__("Generate IRN"), action);
+			}
+
+			if (irn && !irn_cancelled && !ewaybill) {
+				const fields = [
+					{
+						"label": "Reason",
+						"fieldname": "reason",
+						"fieldtype": "Select",
+						"reqd": 1,
+						"default": "1-Duplicate",
+						"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
+					},
+					{ 
+						"label": "Remark",
+						"fieldname": "remark",
+						"fieldtype": "Data",
+						"reqd": 1
+					}
+				];
+				const action = () => {
+					const d = new frappe.ui.Dialog({
+						title: __("Cancel IRN"),
+						fields: fields,
+						primary_action: function() {
+							const data = d.get_values();
+							frappe.call({
+								method: 'erpnext.regional.india.e_invoice.utils.cancel_irn',
+								args: { 
+									doctype,
+									docname: name,
+									irn: irn,
+									reason: data.reason.split('-')[0],
+									remark: data.remark
+								},
+								freeze: true,
+								callback: () => frm.reload_doc() || d.hide(),
+								error: () => d.hide()
+							});
+						},
+						primary_action_label: __('Submit')
+					});
+					d.show();
+				};
+				add_custom_button(__("Cancel IRN"), action);
+			}
+
+			if (irn && !irn_cancelled && !ewaybill) {
+				const action = () => {
+					const d = new frappe.ui.Dialog({
+						title: __('Generate E-Way Bill'),
+						wide: 1,
+						fields: get_ewaybill_fields(frm),
+						primary_action: function() {
+							const data = d.get_values();
+							frappe.call({
+								method: 'erpnext.regional.india.e_invoice.utils.generate_eway_bill',
+								args: {
+									doctype,
+									docname: name,
+									irn,
+									...data
+								},
+								freeze: true,
+								callback: () => frm.reload_doc() || d.hide(),
+								error: () => d.hide()
+							});
+						},
+						primary_action_label: __('Submit')
+					});
+					d.show();
+				};
+
+				add_custom_button(__("Generate E-Way Bill"), action);
+			}
+
+			if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) {
+				const fields = [
+					{
+						"label": "Reason",
+						"fieldname": "reason",
+						"fieldtype": "Select",
+						"reqd": 1,
+						"default": "1-Duplicate",
+						"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
+					},
+					{
+						"label": "Remark",
+						"fieldname": "remark",
+						"fieldtype": "Data",
+						"reqd": 1
+					}
+				];
+				const action = () => {
+					const d = new frappe.ui.Dialog({
+						title: __('Cancel E-Way Bill'),
+						fields: fields,
+						primary_action: function() {
+							const data = d.get_values();
+							frappe.call({
+								method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
+								args: {
+									doctype,
+									docname: name,
+									eway_bill: ewaybill,
+									reason: data.reason.split('-')[0],
+									remark: data.remark
+								},
+								freeze: true,
+								callback: () => frm.reload_doc() || d.hide(),
+								error: () => d.hide()
+							});
+						},
+						primary_action_label: __('Submit')
+					});
+					d.show();
+				};
+				add_custom_button(__("Cancel E-Way Bill"), action);
+			}
+		}
+	});
+};
+
+const get_ewaybill_fields = (frm) => {
+	return [
+		{
+			'fieldname': 'transporter',
+			'label': 'Transporter',
+			'fieldtype': 'Link',
+			'options': 'Supplier',
+			'default': frm.doc.transporter
+		},
+		{
+			'fieldname': 'gst_transporter_id',
+			'label': 'GST Transporter ID',
+			'fieldtype': 'Data',
+			'fetch_from': 'transporter.gst_transporter_id',
+			'default': frm.doc.gst_transporter_id
+		},
+		{
+			'fieldname': 'driver',
+			'label': 'Driver',
+			'fieldtype': 'Link',
+			'options': 'Driver',
+			'default': frm.doc.driver
+		},
+		{
+			'fieldname': 'lr_no',
+			'label': 'Transport Receipt No',
+			'fieldtype': 'Data',
+			'default': frm.doc.lr_no
+		},
+		{
+			'fieldname': 'vehicle_no',
+			'label': 'Vehicle No',
+			'fieldtype': 'Data',
+			'depends_on': 'eval:(doc.mode_of_transport === "Road")',
+			'default': frm.doc.vehicle_no
+		},
+		{
+			'fieldname': 'distance',
+			'label': 'Distance (in km)',
+			'fieldtype': 'Float',
+			'default': frm.doc.distance
+		},
+		{
+			'fieldname': 'transporter_col_break',
+			'fieldtype': 'Column Break',
+		},
+		{
+			'fieldname': 'transporter_name',
+			'label': 'Transporter Name',
+			'fieldtype': 'Data',
+			'fetch_from': 'transporter.name',
+			'read_only': 1,
+			'default': frm.doc.transporter_name
+		},
+		{
+			'fieldname': 'mode_of_transport',
+			'label': 'Mode of Transport',
+			'fieldtype': 'Select',
+			'options': `\nRoad\nAir\nRail\nShip`,
+			'default': frm.doc.mode_of_transport
+		},
+		{
+			'fieldname': 'driver_name',
+			'label': 'Driver Name',
+			'fieldtype': 'Data',
+			'fetch_from': 'driver.full_name',
+			'read_only': 1,
+			'default': frm.doc.driver_name
+		},
+		{
+			'fieldname': 'lr_date',
+			'label': 'Transport Receipt Date',
+			'fieldtype': 'Date',
+			'default': frm.doc.lr_date
+		},
+		{
+			'fieldname': 'gst_vehicle_type',
+			'label': 'GST Vehicle Type',
+			'fieldtype': 'Select',
+			'options': `Regular\nOver Dimensional Cargo (ODC)`,
+			'depends_on': 'eval:(doc.mode_of_transport === "Road")',
+			'default': frm.doc.gst_vehicle_type
+		}
+	];
+};
+
+const request_irn_generation = (frm) => {
+	frappe.call({
+		method: 'erpnext.regional.india.e_invoice.utils.generate_irn',
+		args: { doctype: frm.doc.doctype, docname: frm.doc.name },
+		freeze: true,
+		callback: () => frm.reload_doc()
+	});
+};
+
+const get_preview_dialog = (frm, action) => {
+	const dialog = new frappe.ui.Dialog({
+		title: __("Preview"),
+		wide: 1,
+		fields: [
+			{ 
+				"label": "Preview",
+				"fieldname": "preview_html",
+				"fieldtype": "HTML"
+			}
+		],
+		primary_action: () => action(frm) || dialog.hide(),
+		primary_action_label: __('Generate IRN')
+	});
+	return dialog;
+};
+
+const show_einvoice_preview = (frm, einvoice) => {
+	const preview_dialog = get_preview_dialog(frm, request_irn_generation);
+
+	// initialize e-invoice fields
+	einvoice["Irn"] = einvoice["AckNo"] = ''; einvoice["AckDt"] = frappe.datetime.nowdate();
+	frm.doc.signed_einvoice = JSON.stringify(einvoice);
+
+	// initialize preview wrapper
+	const $preview_wrapper = preview_dialog.get_field("preview_html").$wrapper;
+	$preview_wrapper.html(
+		`<div>
+			<div class="print-preview">
+				<div class="print-format"></div>
+			</div>
+			<div class="page-break-message text-muted text-center text-medium margin-top"></div>
+		</div>`
+	);
+
+	frappe.call({
+		method: "frappe.www.printview.get_html_and_style",
+		args: {
+			doc: frm.doc,
+			print_format: "GST E-Invoice",
+			no_letterhead: 1
+		},
+		callback: function (r) {
+			if (!r.exc) {
+				$preview_wrapper.find(".print-format").html(r.message.html);
+				const style = `
+					.print-format { box-shadow: 0px 0px 5px rgba(0,0,0,0.2); padding: 0.30in; min-height: 80vh; }
+					.print-preview { min-height: 0px; }
+					.modal-dialog { width: 720px; }`;
+
+				frappe.dom.set_style(style, "custom-print-style");
+				preview_dialog.show();
+			}
+		}
+	});
+};
\ No newline at end of file
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
new file mode 100644
index 0000000..02ce6c1
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -0,0 +1,772 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import os
+import re
+import jwt
+import sys
+import json
+import base64
+import frappe
+import traceback
+from frappe import _, bold
+from pyqrcode import create as qrcreate
+from frappe.integrations.utils import make_post_request, make_get_request
+from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply
+from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date
+
+def validate_einvoice_fields(doc):
+	einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable'))
+	invalid_doctype = doc.doctype not in ['Sales Invoice']
+	invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export']
+	company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin')
+
+	if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction: return
+
+	if doc.docstatus == 0 and doc._action == 'save':
+		if doc.irn:
+			frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed'))
+		if len(doc.name) > 16:
+			raise_document_name_too_long_error()
+
+	elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn:
+		frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN'))
+
+	elif doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled:
+		frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed'))
+
+def raise_document_name_too_long_error():
+	title = _('Document ID Too Long')
+	msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice, ')
+	msg += _('document id {} exceed 16 letters. ').format(bold(_('should not')))
+	msg += '<br><br>'
+	msg += _('You must {} your {} in order to have document id of {} length 16. ').format(
+		bold(_('modify')), bold(_('naming series')), bold(_('maximum'))
+	)
+	msg += _('Please account for ammended documents too. ')
+	frappe.throw(msg, title=title)
+
+def read_json(name):
+	file_path = os.path.join(os.path.dirname(__file__), '{name}.json'.format(name=name))
+	with open(file_path, 'r') as f:
+		return cstr(f.read())
+
+def get_transaction_details(invoice):
+	supply_type = ''
+	if invoice.gst_category == 'Registered Regular': supply_type = 'B2B'
+	elif invoice.gst_category == 'SEZ': supply_type = 'SEZWOP'
+	elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP'
+	elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP'
+
+	if not supply_type: 
+		rr, sez, overseas, export = bold('Registered Regular'), bold('SEZ'), bold('Overseas'), bold('Deemed Export')
+		frappe.throw(_('GST category should be one of {}, {}, {}, {}').format(rr, sez, overseas, export),
+			title=_('Invalid Supply Type'))
+
+	return frappe._dict(dict(
+		tax_scheme='GST',
+		supply_type=supply_type,
+		reverse_charge=invoice.reverse_charge
+	))
+
+def get_doc_details(invoice):
+	invoice_type = 'CRN' if invoice.is_return else 'INV'
+
+	invoice_name = invoice.name
+	invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy')
+
+	return frappe._dict(dict(
+		invoice_type=invoice_type,
+		invoice_name=invoice_name,
+		invoice_date=invoice_date
+	))
+
+def get_party_details(address_name):
+	address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
+	gstin = address.get('gstin')
+
+	gstin_details = get_gstin_details(gstin)
+	legal_name = gstin_details.get('LegalName') or gstin_details.get('TradeName')
+	location = gstin_details.get('AddrLoc') or address.get('city')
+	state_code = gstin_details.get('StateCode')
+	pincode = gstin_details.get('AddrPncd')
+	address_line1 = '{} {}'.format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno'))
+	address_line2 = '{} {}'.format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt'))
+	email_id = address.get('email_id')
+	phone = address.get('phone')
+	# get last 10 digit 
+	phone = phone.replace(" ", "")[-10:] if phone else ''
+
+	if state_code == 97:
+		# according to einvoice standard
+		pincode = 999999
+
+	return frappe._dict(dict(
+		gstin=gstin, legal_name=legal_name, location=location,
+		pincode=pincode, state_code=state_code, address_line1=address_line1,
+		address_line2=address_line2, email=email_id, phone=phone
+	))
+
+def get_gstin_details(gstin):
+	if not hasattr(frappe.local, 'gstin_cache'):
+		frappe.local.gstin_cache = {}
+
+	key = gstin
+	details = frappe.local.gstin_cache.get(key)
+	if details:
+		return details
+
+	details = frappe.cache().hget('gstin_cache', key)
+	if details:
+		frappe.local.gstin_cache[key] = details
+		return details
+	
+	if not details:
+		return GSPConnector.get_gstin_details(gstin)
+
+def get_overseas_address_details(address_name):
+	address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value(
+		'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city', 'phone', 'email_id']
+	)
+
+	return frappe._dict(dict(
+		gstin='URP', legal_name=address_title, address_line1=address_line1,
+		address_line2=address_line2, email=email_id, phone=phone,
+		pincode=999999, state_code=96, place_of_supply=96, location=city
+	))
+
+def get_item_list(invoice):
+	item_list = []
+
+	for d in invoice.items:
+		einvoice_item_schema = read_json('einv_item_template')
+		item = frappe._dict({})
+		item.update(d.as_dict())
+
+		item.sr_no = d.idx
+		item.discount_amount = abs(item.discount_amount * item.qty)
+		item.description = d.item_name
+		item.qty = abs(item.qty)
+		item.unit_rate = abs(item.base_amount / item.qty)
+		item.gross_amount = abs(item.base_amount)
+		item.taxable_value = abs(item.base_amount)
+
+		item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
+		item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
+		item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y'
+
+		item = update_item_taxes(invoice, item)
+		
+		item.total_value = abs(
+			item.taxable_value + item.igst_amount + item.sgst_amount +
+			item.cgst_amount + item.cess_amount + item.cess_nadv_amount + item.other_charges
+		)
+		einv_item = einvoice_item_schema.format(item=item)
+		item_list.append(einv_item)
+
+	return ', '.join(item_list)
+
+def update_item_taxes(invoice, item):
+	gst_accounts = get_gst_accounts(invoice.company)
+	gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d]
+
+	for attr in [
+		'tax_rate', 'cess_rate', 'cess_nadv_amount',
+		'cgst_amount',  'sgst_amount', 'igst_amount',
+		'cess_amount', 'cess_nadv_amount', 'other_charges'
+		]:
+		item[attr] = 0
+
+	for t in invoice.taxes:
+		# this contains item wise tax rate & tax amount (incl. discount)
+		item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code)
+		if t.account_head in gst_accounts_list:
+			item_tax_rate = item_tax_detail[0]
+			# item tax amount excluding discount amount
+			item_tax_amount = (item_tax_rate / 100) * item.base_amount
+
+			if t.account_head in gst_accounts.cess_account:
+				item_tax_amount_after_discount = item_tax_detail[1]
+				if t.charge_type == 'On Item Quantity':
+					item.cess_nadv_amount += abs(item_tax_amount_after_discount)
+				else:
+					item.cess_rate += item_tax_rate
+					item.cess_amount += abs(item_tax_amount_after_discount)
+
+			for tax_type in ['igst', 'cgst', 'sgst']:
+				if t.account_head in gst_accounts[f'{tax_type}_account']:
+					item.tax_rate += item_tax_rate
+					item[f'{tax_type}_amount'] += abs(item_tax_amount)
+
+	return item
+
+def get_invoice_value_details(invoice):
+	invoice_value_details = frappe._dict(dict())
+	invoice_value_details.base_total = abs(invoice.base_total)
+	invoice_value_details.invoice_discount_amt = invoice.discount_amount
+	invoice_value_details.round_off = invoice.rounding_adjustment
+	invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total)
+	invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total)
+	
+	invoice_value_details = update_invoice_taxes(invoice, invoice_value_details)
+	
+	return invoice_value_details
+
+def update_invoice_taxes(invoice, invoice_value_details):
+	gst_accounts = get_gst_accounts(invoice.company)
+	gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d]
+
+	invoice_value_details.total_cgst_amt = 0
+	invoice_value_details.total_sgst_amt = 0
+	invoice_value_details.total_igst_amt = 0
+	invoice_value_details.total_cess_amt = 0
+	invoice_value_details.total_other_charges = 0
+	for t in invoice.taxes:
+		if t.account_head in gst_accounts_list:
+			if t.account_head in gst_accounts.cess_account:
+				# using after discount amt since item also uses after discount amt for cess calc
+				invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount)
+			
+			for tax_type in ['igst', 'cgst', 'sgst']:
+				if t.account_head in gst_accounts[f'{tax_type}_account']:
+					invoice_value_details[f'total_{tax_type}_amt'] += abs(t.base_tax_amount)
+		else:
+			invoice_value_details.total_other_charges += abs(t.base_tax_amount)
+	
+	return invoice_value_details
+
+def get_payment_details(invoice):
+	payee_name = invoice.company
+	mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments])
+	paid_amount = invoice.base_paid_amount
+	outstanding_amount = invoice.outstanding_amount
+
+	return frappe._dict(dict(
+		payee_name=payee_name, mode_of_payment=mode_of_payment,
+		paid_amount=paid_amount, outstanding_amount=outstanding_amount
+	))
+
+def get_return_doc_reference(invoice):
+	invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date')
+	return frappe._dict(dict(
+		invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy')
+	))
+
+def get_eway_bill_details(invoice):
+	if invoice.is_return:
+		frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes'), title=_('E Invoice Validation Failed'))
+
+	mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' }
+	vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' }
+
+	return frappe._dict(dict(
+		gstin=invoice.gst_transporter_id,
+		name=invoice.transporter_name,
+		mode_of_transport=mode_of_transport[invoice.mode_of_transport],
+		distance=invoice.distance or 0,
+		document_name=invoice.lr_no,
+		document_date=format_date(invoice.lr_date, 'dd/mm/yyyy'),
+		vehicle_no=invoice.vehicle_no,
+		vehicle_type=vehicle_type[invoice.gst_vehicle_type]
+	))
+
+def make_einvoice(invoice):
+	schema = read_json('einv_template')
+
+	transaction_details = get_transaction_details(invoice)
+	item_list = get_item_list(invoice)
+	doc_details = get_doc_details(invoice)
+	invoice_value_details = get_invoice_value_details(invoice)
+	seller_details = get_party_details(invoice.company_address)
+
+	if invoice.gst_category == 'Overseas':
+		buyer_details = get_overseas_address_details(invoice.customer_address)
+	else:
+		buyer_details = get_party_details(invoice.customer_address)
+		place_of_supply = get_place_of_supply(invoice, invoice.doctype) or invoice.billing_address_gstin
+		place_of_supply = place_of_supply[:2]
+		buyer_details.update(dict(place_of_supply=place_of_supply))
+	
+	shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({})
+	if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name:
+		shipping_details = get_party_details(invoice.shipping_address_name)
+	
+	if invoice.is_pos and invoice.base_paid_amount:
+		payment_details = get_payment_details(invoice)
+	
+	if invoice.is_return and invoice.return_against:
+		prev_doc_details = get_return_doc_reference(invoice)
+	
+	if invoice.transporter:
+		eway_bill_details = get_eway_bill_details(invoice)
+	
+	# not yet implemented
+	dispatch_details = period_details = export_details = frappe._dict({})
+
+	einvoice = schema.format(
+		transaction_details=transaction_details, doc_details=doc_details, dispatch_details=dispatch_details,
+		seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details,
+		item_list=item_list, invoice_value_details=invoice_value_details, payment_details=payment_details,
+		period_details=period_details, prev_doc_details=prev_doc_details,
+		export_details=export_details, eway_bill_details=eway_bill_details
+	)
+	einvoice = json.loads(einvoice)
+	
+	validations = json.loads(read_json('einv_validation'))
+	errors = validate_einvoice(validations, einvoice)
+	if errors:
+		message = "\n".join([
+			"E Invoice: ", json.dumps(einvoice, indent=4),
+			"-" * 50,
+			"Errors: ", json.dumps(errors, indent=4)
+		])
+		frappe.log_error(title="E Invoice Validation Failed", message=message)
+		frappe.throw(errors, title=_('E Invoice Validation Failed'), as_list=1)
+
+	return einvoice
+
+def validate_einvoice(validations, einvoice, errors=[]):
+	for fieldname, field_validation in validations.items():
+		value = einvoice.get(fieldname, None)
+		if not value or value == "None":
+			# remove keys with empty values
+			einvoice.pop(fieldname, None)
+			continue
+
+		value_type = field_validation.get("type").lower()
+		if value_type in ['object', 'array']:
+			child_validations = field_validation.get('properties')
+
+			if isinstance(value, list):
+				for d in value:
+					validate_einvoice(child_validations, d, errors)
+					if not d:
+						# remove empty dicts
+						einvoice.pop(fieldname, None)
+			else:
+				validate_einvoice(child_validations, value, errors)
+				if not value:
+					# remove empty dicts
+					einvoice.pop(fieldname, None)
+			continue
+		
+		# convert to int or str
+		if value_type == 'string':
+			einvoice[fieldname] = str(value)
+		elif value_type == 'number':
+			is_integer = '.' not in str(field_validation.get('maximum'))
+			precision = 3 if '.999' in str(field_validation.get('maximum')) else 2
+			einvoice[fieldname] = flt(value, precision) if not is_integer else cint(value)
+			value = einvoice[fieldname]
+
+		max_length = field_validation.get('maxLength')
+		minimum = flt(field_validation.get('minimum'))
+		maximum = flt(field_validation.get('maximum'))
+		pattern_str = field_validation.get('pattern')
+		pattern = re.compile(pattern_str or '')
+
+		label = field_validation.get('description') or fieldname
+
+		if value_type == 'string' and len(value) > max_length:
+			errors.append(_('{} should not exceed {} characters').format(label, max_length))
+		if value_type == 'number' and (value > maximum or value < minimum):
+			errors.append(_('{} {} should be between {} and {}').format(label, value, minimum, maximum))
+		if pattern_str and not pattern.match(value):
+			errors.append(field_validation.get('validationMsg'))
+	
+	return errors
+
+class RequestFailed(Exception): pass
+
+class GSPConnector():
+	def __init__(self, doctype=None, docname=None):
+		self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings')
+		self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None
+		self.credentials = self.get_credentials()
+
+		self.base_url = 'https://gsp.adaequare.com'
+		self.authenticate_url = self.base_url + '/gsp/authenticate?grant_type=token'
+		self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin'
+		self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
+		self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
+		self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel'
+		self.cancel_ewaybill_url = self.base_url + '/enriched/ei/api/ewayapi'
+		self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill'
+
+	def get_credentials(self):
+		if self.invoice:
+			gstin = self.get_seller_gstin()
+			credentials = next(d for d in self.e_invoice_settings.credentials if d.gstin == gstin)
+		else:
+			credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
+		return credentials
+	
+	def get_seller_gstin(self):
+		gstin = self.invoice.company_gstin or frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
+		if not gstin:
+			frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.'))
+		return gstin
+	
+	def get_auth_token(self):
+		if time_diff_in_seconds(self.e_invoice_settings.token_expiry, now_datetime()) < 150.0:
+			self.fetch_auth_token()
+		
+		return self.e_invoice_settings.auth_token
+	
+	def make_request(self, request_type, url, headers=None, data=None):
+		if request_type == 'post':
+			res = make_post_request(url, headers=headers, data=data)
+		else:
+			res = make_get_request(url, headers=headers, data=data)
+
+		self.log_request(url, headers, data, res)
+		return res
+	
+	def log_request(self, url, headers, data, res):
+		headers.update({ 'password': self.credentials.password })
+		request_log = frappe.get_doc({
+			"doctype": "E Invoice Request Log",
+			"user": frappe.session.user,
+			"reference_invoice": self.invoice.name if self.invoice else None,
+			"url": url,
+			"headers": json.dumps(headers, indent=4) if headers else None,
+			"data": json.dumps(data, indent=4) if isinstance(data, dict) else data,
+			"response": json.dumps(res, indent=4) if res else None
+		})
+		request_log.insert(ignore_permissions=True)
+		frappe.db.commit()
+
+	def fetch_auth_token(self):
+		headers = {
+			'gspappid': frappe.conf.einvoice_client_id,
+			'gspappsecret': frappe.conf.einvoice_client_secret
+		}
+		res = {}
+		try:
+			res = self.make_request('post', self.authenticate_url, headers)
+			self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token'))
+			self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in'))
+			self.e_invoice_settings.save()
+
+		except Exception:
+			self.log_error(res)
+			self.raise_error(True)
+	
+	def get_headers(self):
+		return {
+			'content-type': 'application/json',
+			'user_name': self.credentials.username,
+			'password': self.credentials.get_password(),
+			'gstin': self.credentials.gstin,
+			'authorization': self.get_auth_token(),
+			'requestid': str(base64.b64encode(os.urandom(18))),
+		}
+
+	def fetch_gstin_details(self, gstin):
+		headers = self.get_headers()
+
+		try:
+			params = '?gstin={gstin}'.format(gstin=gstin)
+			res = self.make_request('get', self.gstin_details_url + params, headers)
+			if res.get('success'):
+				return res.get('result')
+			else:
+				self.log_error(res)
+				raise RequestFailed
+		
+		except RequestFailed:
+			self.raise_error()
+
+		except Exception:
+			self.log_error()
+			self.raise_error(True)
+	
+	@staticmethod
+	def get_gstin_details(gstin):
+		'''fetch and cache GSTIN details'''
+		if not hasattr(frappe.local, 'gstin_cache'):
+			frappe.local.gstin_cache = {}
+
+		key = gstin
+		gsp_connector = GSPConnector()
+		details = gsp_connector.fetch_gstin_details(gstin)
+
+		frappe.local.gstin_cache[key] = details
+		frappe.cache().hset('gstin_cache', key, details)
+		return details
+
+	def generate_irn(self):
+		headers = self.get_headers()
+		einvoice = make_einvoice(self.invoice)
+		data = json.dumps(einvoice, indent=4)
+
+		try:
+			res = self.make_request('post', self.generate_irn_url, headers, data)
+			if res.get('success'):
+				self.set_einvoice_data(res.get('result'))
+
+			elif '2150' in res.get('message'):
+				# IRN already generated but not updated in invoice
+				# Extract the IRN from the response description and fetch irn details
+				irn = res.get('result')[0].get('Desc').get('Irn')
+				irn_details = self.get_irn_details(irn)
+				if irn_details:
+					self.set_einvoice_data(irn_details)
+				else:
+					raise RequestFailed('IRN has already been generated for the invoice but cannot fetch details for the it. \
+						Contact ERPNext support to resolve the issue.')
+
+			else:
+				raise RequestFailed
+		
+		except RequestFailed:
+			errors = self.sanitize_error_message(res.get('message'))
+			self.raise_error(errors=errors)
+
+		except Exception:
+			self.log_error(data)
+			self.raise_error(True)
+	
+	def get_irn_details(self, irn):
+		headers = self.get_headers()
+
+		try:
+			params = '?irn={irn}'.format(irn=irn)
+			res = self.make_request('get', self.irn_details_url + params, headers)
+			if res.get('success'):
+				return res.get('result')
+			else:
+				raise RequestFailed
+		
+		except RequestFailed:
+			errors = self.sanitize_error_message(res.get('message'))
+			self.raise_error(errors=errors)
+
+		except Exception:
+			self.log_error()
+			self.raise_error(True)
+	
+	def cancel_irn(self, irn, reason, remark):
+		headers = self.get_headers()
+		data = json.dumps({
+			'Irn': irn,
+			'Cnlrsn': reason,
+			'Cnlrem': remark
+		}, indent=4)
+
+		try:
+			res = self.make_request('post', self.cancel_irn_url, headers, data)
+			if res.get('success'):
+				self.invoice.irn_cancelled = 1
+				self.invoice.flags.updater_reference = {
+					'doctype': self.invoice.doctype,
+					'docname': self.invoice.name,
+					'label': _('IRN Cancelled - {}').format(remark)
+				}
+				self.update_invoice()
+
+			else:
+				raise RequestFailed
+		
+		except RequestFailed:
+			errors = self.sanitize_error_message(res.get('message'))
+			self.raise_error(errors=errors)
+
+		except Exception:
+			self.log_error(data)
+			self.raise_error(True)
+	
+	def generate_eway_bill(self, **kwargs):
+		args = frappe._dict(kwargs)
+
+		headers = self.get_headers()
+		eway_bill_details = get_eway_bill_details(args)
+		data = json.dumps({
+			'Irn': args.irn,
+			'Distance': cint(eway_bill_details.distance),
+			'TransMode': eway_bill_details.mode_of_transport,
+			'TransId': eway_bill_details.gstin,
+			'TransName': eway_bill_details.transporter,
+			'TrnDocDt': eway_bill_details.document_date,
+			'TrnDocNo': eway_bill_details.document_name,
+			'VehNo': eway_bill_details.vehicle_no,
+			'VehType': eway_bill_details.vehicle_type
+		}, indent=4)
+
+		try:
+			res = self.make_request('post', self.generate_ewaybill_url, headers, data)
+			if res.get('success'):
+				self.invoice.ewaybill = res.get('result').get('EwbNo')
+				self.invoice.eway_bill_cancelled = 0
+				self.invoice.update(args)
+				self.invoice.flags.updater_reference = {
+					'doctype': self.invoice.doctype,
+					'docname': self.invoice.name,
+					'label': _('E-Way Bill Generated')
+				}
+				self.update_invoice()
+
+			else:
+				raise RequestFailed
+
+		except RequestFailed:
+			errors = self.sanitize_error_message(res.get('message'))
+			self.raise_error(errors=errors)
+
+		except Exception:
+			self.log_error(data)
+			self.raise_error(True)
+	
+	def cancel_eway_bill(self, eway_bill, reason, remark):
+		headers = self.get_headers()
+		data = json.dumps({
+			'ewbNo': eway_bill,
+			'cancelRsnCode': reason,
+			'cancelRmrk': remark
+		}, indent=4)
+
+		try:
+			res = self.make_request('post', self.cancel_ewaybill_url, headers, data)
+			if res.get('success'):
+				self.invoice.ewaybill = ''
+				self.invoice.eway_bill_cancelled = 1
+				self.invoice.flags.updater_reference = {
+					'doctype': self.invoice.doctype,
+					'docname': self.invoice.name,
+					'label': _('E-Way Bill Cancelled - {}').format(remark)
+				}
+				self.update_invoice()
+
+			else:
+				raise RequestFailed
+
+		except RequestFailed:
+			errors = self.sanitize_error_message(res.get('message'))
+			self.raise_error(errors=errors)
+
+		except Exception:
+			self.log_error(data)
+			self.raise_error(True)
+	
+	def sanitize_error_message(self, message):
+		'''
+			On validation errors, response message looks something like this:
+			message = '2174 : For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable,
+						3095 : Supplier GSTIN is inactive'
+			we search for string between ':' to extract the error messages
+			errors = [
+				': For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, 3095 ',
+				': Test'
+			]
+			then we trim down the message by looping over errors
+		'''
+		errors = re.findall(': [^:]+', message)
+		for idx, e in enumerate(errors):
+			# remove colons
+			errors[idx] = errors[idx].replace(':', '').strip()
+			# if not last
+			if idx != len(errors) - 1:
+				# remove last 7 chars eg: ', 3095 '
+				errors[idx] = errors[idx][:-6]
+
+		return errors
+
+	def log_error(self, data={}):
+		if not isinstance(data, dict):
+			data = json.loads(data)
+
+		seperator = "--" * 50
+		err_tb = traceback.format_exc()
+		err_msg = str(sys.exc_info()[1])
+		data = json.dumps(data, indent=4)
+
+		message = "\n".join([
+			"Error", err_msg, seperator,
+			"Data:", data, seperator,
+			"Exception:", err_tb
+		])
+		frappe.log_error(title=_('E Invoice Request Failed'), message=message)
+	
+	def raise_error(self, raise_exception=False, errors=[]):
+		title = _('E Invoice Request Failed')
+		if errors:
+			frappe.throw(errors, title=title, as_list=1)
+		else:
+			link_to_error_list = '<a href="desk#List/Error Log/List?method=E Invoice Request Failed">Error Log</a>'
+			frappe.msgprint(
+				_('An error occurred while making e-invoicing request. Please check {} for more information.').format(link_to_error_list),
+				title=title,
+				raise_exception=raise_exception,
+				indicator='red'
+			)
+	
+	def set_einvoice_data(self, res):
+		enc_signed_invoice = res.get('SignedInvoice')
+		dec_signed_invoice = jwt.decode(enc_signed_invoice, verify=False)['data']
+
+		self.invoice.irn = res.get('Irn')
+		self.invoice.ewaybill = res.get('EwbNo')
+		self.invoice.signed_einvoice = dec_signed_invoice
+		self.invoice.signed_qr_code = res.get('SignedQRCode')
+
+		self.attach_qrcode_image()
+
+		self.invoice.flags.updater_reference = {
+			'doctype': self.invoice.doctype,
+			'docname': self.invoice.name,
+			'label': _('IRN Generated')
+		}
+		self.update_invoice()
+	
+	def attach_qrcode_image(self):
+		qrcode = self.invoice.signed_qr_code
+		doctype = self.invoice.doctype
+		docname = self.invoice.name
+
+		_file = frappe.new_doc('File')
+		_file.update({
+			'file_name': f'QRCode_{docname}.png',
+			'attached_to_doctype': doctype,
+			'attached_to_name': docname,
+			'content': 'qrcode',
+			'is_private': 1
+		})
+		_file.insert()
+		frappe.db.commit()
+		url = qrcreate(qrcode, error='L')
+		abs_file_path = os.path.abspath(_file.get_full_path())
+		url.png(abs_file_path, scale=2, quiet_zone=1)
+
+		self.invoice.qrcode_image = _file.file_url
+	
+	def update_invoice(self):
+		self.invoice.flags.ignore_validate_update_after_submit = True
+		self.invoice.flags.ignore_validate = True
+		self.invoice.save()
+
+@frappe.whitelist()
+def get_einvoice(doctype, docname):
+	invoice = frappe.get_doc(doctype, docname)
+	return make_einvoice(invoice)
+
+@frappe.whitelist()
+def generate_irn(doctype, docname):
+	gsp_connector = GSPConnector(doctype, docname)
+	gsp_connector.generate_irn()
+
+@frappe.whitelist()
+def cancel_irn(doctype, docname, irn, reason, remark):
+	gsp_connector = GSPConnector(doctype, docname)
+	gsp_connector.cancel_irn(irn, reason, remark)
+
+@frappe.whitelist()
+def generate_eway_bill(doctype, docname, **kwargs):
+	gsp_connector = GSPConnector(doctype, docname)
+	gsp_connector.generate_eway_bill(**kwargs)
+
+@frappe.whitelist()
+def cancel_eway_bill(doctype, docname, eway_bill, reason, remark):
+	gsp_connector = GSPConnector(doctype, docname)
+	gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
\ No newline at end of file
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index cbcd6e3..5321a9a 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -87,7 +87,7 @@
 			)).insert()
 
 def add_permissions():
-	for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate'):
+	for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate', 'E Invoice Settings'):
 		add_permission(doctype, 'All', 0)
 		for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
 			add_permission(doctype, role, 0)
@@ -103,9 +103,10 @@
 def add_print_formats():
 	frappe.reload_doc("regional", "print_format", "gst_tax_invoice")
 	frappe.reload_doc("accounts", "print_format", "gst_pos_invoice")
+	frappe.reload_doc("accounts", "print_format", "GST E-Invoice")
 
 	frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where
-		name in('GST POS Invoice', 'GST Tax Invoice') """)
+		name in('GST POS Invoice', 'GST Tax Invoice', 'GST E-Invoice') """)
 
 def make_custom_fields(update=True):
 	hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
@@ -351,7 +352,6 @@
 			'label': 'Mode of Transport',
 			'fieldtype': 'Select',
 			'options': '\nRoad\nAir\nRail\nShip',
-			'default': 'Road',
 			'insert_after': 'transporter_name',
 			'print_hide': 1,
 			'translatable': 0
@@ -388,13 +388,34 @@
 			'fieldname': 'ewaybill',
 			'label': 'E-Way Bill No.',
 			'fieldtype': 'Data',
-			'depends_on': 'eval:(doc.docstatus === 1)',
+			'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)',
 			'allow_on_submit': 1,
 			'insert_after': 'tax_id',
 			'translatable': 0
 		}
 	]
 
+	si_einvoice_fields = [
+		dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1,
+			depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'),
+		
+		dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1),
+		
+		dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
+
+		dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+			depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+
+		dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+			depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+
+		dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+		dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+		dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1)
+	]
+
 	custom_fields = {
 		'Address': [
 			dict(fieldname='gstin', label='Party GSTIN', fieldtype='Data',
@@ -407,7 +428,7 @@
 		'Purchase Invoice': purchase_invoice_gst_category + invoice_gst_fields + purchase_invoice_itc_fields + purchase_invoice_gst_fields,
 		'Purchase Order': purchase_invoice_gst_fields,
 		'Purchase Receipt': purchase_invoice_gst_fields,
-		'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields,
+		'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields + si_einvoice_fields,
 		'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields,
 		'Sales Order': sales_invoice_gst_fields,
 		'Tax Category': inter_state_gst_field,
diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js
index 3b6a28f..87baece 100644
--- a/erpnext/regional/india/taxes.js
+++ b/erpnext/regional/india/taxes.js
@@ -12,6 +12,9 @@
 		tax_category: function(frm) {
 			frm.trigger('get_tax_template');
 		},
+		customer_address: function(frm) {
+			frm.trigger('get_tax_template');
+		},
 		get_tax_template: function(frm) {
 			if (!frm.doc.company) return;
 
@@ -19,6 +22,7 @@
 				'shipping_address': frm.doc.shipping_address || '',
 				'shipping_address_name': frm.doc.shipping_address_name || '',
 				'customer_address': frm.doc.customer_address || '',
+				'supplier_address': frm.doc.supplier_address,
 				'customer': frm.doc.customer,
 				'supplier': frm.doc.supplier,
 				'supplier_gstin': frm.doc.supplier_gstin,
@@ -31,12 +35,12 @@
 				args: {
 					party_details: JSON.stringify(party_details),
 					doctype: frm.doc.doctype,
-					company: frm.doc.company,
-					return_taxes: 1
+					company: frm.doc.company
 				},
 				callback: function(r) {
 					if(r.message) {
 						frm.set_value('taxes_and_charges', r.message.taxes_and_charges);
+						frm.set_value('place_of_supply', r.message.place_of_supply);
 					} else if (frm.doc.is_internal_supplier || frm.doc.is_internal_customer) {
 						frm.set_value('taxes_and_charges', '');
 						frm.set_value('taxes', []);
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 6164e06..f256a66 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -12,6 +12,7 @@
 from six import string_types
 from erpnext.accounts.general_ledger import make_gl_entries
 from erpnext.accounts.utils import get_account_currency
+from frappe.model.utils import get_fetch_values
 
 def validate_gstin_for_india(doc, method):
 	if hasattr(doc, 'gst_state') and doc.gst_state:
@@ -51,6 +52,13 @@
 			frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.")
 				.format(doc.gst_state_number))
 
+def validate_tax_category(doc, method):
+	if doc.get('gst_state') and frappe.db.get_value('Tax Category', {'gst_state': doc.gst_state, 'is_inter_state': doc.is_inter_state}):
+		if doc.is_inter_state:
+			frappe.throw(_("Inter State tax category for GST State {0} already exists").format(doc.gst_state))
+		else:
+			frappe.throw(_("Intra State tax category for GST State {0} already exists").format(doc.gst_state))
+
 def update_gst_category(doc, method):
 	for link in doc.links:
 		if link.link_doctype in ['Customer', 'Supplier']:
@@ -85,8 +93,7 @@
 		total += digit
 		factor = 2 if factor == 1 else 1
 	if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]:
-		frappe.throw(_("""Invalid {0}! The check digit validation has failed.
-			Please ensure you've typed the {0} correctly.""".format(label)))
+		frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label))
 
 def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
 	if frappe.get_meta(item_doctype).has_field('gst_hsn_code'):
@@ -150,17 +157,19 @@
 			return cstr(address.gst_state_number) + "-" + cstr(address.gst_state)
 
 @frappe.whitelist()
-def get_regional_address_details(party_details, doctype, company, return_taxes=None):
+def get_regional_address_details(party_details, doctype, company):
 	if isinstance(party_details, string_types):
 		party_details = json.loads(party_details)
 		party_details = frappe._dict(party_details)
 
+	update_party_details(party_details, doctype)
+
 	party_details.place_of_supply = get_place_of_supply(party_details, doctype)
 
 	if is_internal_transfer(party_details, doctype):
 		party_details.taxes_and_charges = ''
 		party_details.taxes = ''
-		return
+		return party_details
 
 	if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
 		master_doctype = "Sales Taxes and Charges Template"
@@ -168,26 +177,26 @@
 		get_tax_template_for_sez(party_details, master_doctype, company, 'Customer')
 		get_tax_template_based_on_category(master_doctype, company, party_details)
 
-		if party_details.get('taxes_and_charges') and return_taxes:
+		if party_details.get('taxes_and_charges'):
 			return party_details
 
 		if not party_details.company_gstin:
-			return
+			return party_details
 
 	elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
 		master_doctype = "Purchase Taxes and Charges Template"
 		get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier')
 		get_tax_template_based_on_category(master_doctype, company, party_details)
 
-		if party_details.get('taxes_and_charges') and return_taxes:
+		if party_details.get('taxes_and_charges'):
 			return party_details
 
 		if not party_details.supplier_gstin:
-			return
+			return party_details
 
-	if not party_details.place_of_supply: return
+	if not party_details.place_of_supply: return party_details
 
-	if not party_details.company_gstin: return
+	if not party_details.company_gstin: return party_details
 
 	if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin
 		and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice",
@@ -197,12 +206,16 @@
 		default_tax = get_tax_template(master_doctype, company, 0, party_details.company_gstin[:2])
 
 	if not default_tax:
-		return
+		return party_details
 	party_details["taxes_and_charges"] = default_tax
 	party_details.taxes = get_taxes_and_charges(master_doctype, default_tax)
 
-	if return_taxes:
-		return party_details
+	return party_details
+
+def update_party_details(party_details, doctype):
+	for address_field in ['shipping_address', 'company_address', 'supplier_address', 'shipping_address_name', 'customer_address']:
+		if party_details.get(address_field):
+			party_details.update(get_fetch_values(doctype, address_field, party_details.get(address_field)))
 
 def is_internal_transfer(party_details, doctype):
 	if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
@@ -517,6 +530,9 @@
 		data.actualToStateCode = data.toStateCode
 		shipping_address = billing_address
 
+	if doc.gst_category == 'SEZ':
+		data.toStateCode = 99
+
 	return data
 
 def get_item_list(data, doc):
@@ -752,4 +768,4 @@
 					}, account_currency, item=tax)
 				)
 
-	return gl_entries
\ No newline at end of file
+	return gl_entries
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 282efe4..ad3de5f 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -78,7 +78,7 @@
 				place_of_supply = invoice_details.get("place_of_supply")
 				ecommerce_gstin =  invoice_details.get("ecommerce_gstin")
 
-				b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin),{
+				b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin, inv),{
 					"place_of_supply": "",
 					"ecommerce_gstin": "",
 					"rate": "",
@@ -90,7 +90,7 @@
 					"invoice_value": invoice_details.get("base_grand_total"),
 				})
 
-				row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin))
+				row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin, inv))
 				row["place_of_supply"] = place_of_supply
 				row["ecommerce_gstin"] = ecommerce_gstin
 				row["rate"] = rate
@@ -151,6 +151,7 @@
 				{select_columns}
 			from `tab{doctype}`
 			where docstatus = 1 {where_conditions}
+			and is_opening = 'No'
 			order by posting_date desc
 			""".format(select_columns=self.select_columns, doctype=self.doctype,
 				where_conditions=conditions), self.filters, as_dict=1)
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 0172d9c..29214ee 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -58,6 +58,7 @@
 		self.set_loyalty_program()
 		self.check_customer_group_change()
 		self.validate_default_bank_account()
+		self.validate_internal_customer()
 
 		# set loyalty program tier
 		if frappe.db.exists('Customer', self.name):
@@ -82,6 +83,11 @@
 			if not is_company_account:
 				frappe.throw(_("{0} is not a company bank account").format(frappe.bold(self.default_bank_account)))
 
+	def validate_internal_customer(self):
+		if self.is_internal_customer and frappe.db.get_value('Customer', {"represents_company": self.represents_company}, "name"):
+			frappe.throw(_("Internal Customer for company {0} already exists").format(
+				frappe.bold(self.represents_company)))
+
 	def on_update(self):
 		self.validate_name_with_customer_group()
 		self.create_primary_contact()
@@ -398,7 +404,7 @@
 			# form a list of emails and names to show to the user
 			credit_controller_users_formatted = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users]
 			if not credit_controller_users_formatted:
-				frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.".format(customer)))
+				frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.").format(customer))
 
 			message = """Please contact any of the following users to extend the credit limits for {0}:
 				<br><br><ul><li>{1}</li></ul>""".format(customer, '<li>'.join(credit_controller_users_formatted))
diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json
index 5b85187..3eba62b 100644
--- a/erpnext/selling/doctype/quotation/quotation.json
+++ b/erpnext/selling/doctype/quotation/quotation.json
@@ -1,5 +1,6 @@
 {
  "actions": [],
+ "allow_auto_repeat": 1,
  "allow_import": 1,
  "autoname": "naming_series:",
  "creation": "2013-05-24 19:29:08",
@@ -932,7 +933,7 @@
  "is_submittable": 1,
  "links": [],
  "max_attachments": 1,
- "modified": "2020-07-26 17:46:19.951223",
+ "modified": "2020-10-30 13:58:59.212060",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Quotation",
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 73cc0b8..d4fb07c 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -8,7 +8,7 @@
 		frm.custom_make_buttons = {
 			'Delivery Note': 'Delivery Note',
 			'Pick List': 'Pick List',
-			'Sales Invoice': 'Invoice',
+			'Sales Invoice': 'Sales Invoice',
 			'Material Request': 'Material Request',
 			'Purchase Order': 'Purchase Order',
 			'Project': 'Project',
@@ -326,8 +326,7 @@
 								callback: function(r) {
 									if(r.message) {
 										frappe.msgprint({
-											message: __('Work Orders Created: {0}',
-												[r.message.map(function(d) {
+											message: __('Work Orders Created: {0}', [r.message.map(function(d) {
 													return repl('<a href="#Form/Work Order/%(name)s">%(name)s</a>', {name:d})
 												}).join(', ')]),
 											indicator: 'green'
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index 77c1787..3d64ac3 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -1,5 +1,6 @@
 {
  "actions": [],
+ "allow_auto_repeat": 1,
  "allow_import": 1,
  "autoname": "naming_series:",
  "creation": "2013-06-18 12:39:59",
@@ -1460,7 +1461,7 @@
  "idx": 105,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-07 14:30:01.782617",
+ "modified": "2020-10-30 13:59:18.628077",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Sales Order",
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 04d85e5..9388e09 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -14,7 +14,6 @@
 from frappe.desk.notifications import clear_doctype_notifications
 from frappe.contacts.doctype.address.address import get_company_address
 from erpnext.controllers.selling_controller import SellingController
-from frappe.automation.doctype.auto_repeat.auto_repeat import get_next_schedule_date
 from erpnext.selling.doctype.customer.customer import check_credit_limit
 from erpnext.stock.doctype.item.item import get_item_defaults
 from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
@@ -418,8 +417,7 @@
 	def on_recurring(self, reference_doc, auto_repeat_doc):
 
 		def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date):
-			delivery_date = get_next_schedule_date(ref_doc_delivery_date,
-				auto_repeat_doc.frequency, auto_repeat_doc.start_date, cint(auto_repeat_doc.repeat_on_day))
+			delivery_date = auto_repeat_doc.get_next_schedule_date(schedule_date=ref_doc_delivery_date)
 
 			if delivery_date <= transaction_date:
 				delivery_date_diff = frappe.utils.date_diff(ref_doc_delivery_date, red_doc_transaction_date)
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 eff17f8..159655b 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -785,7 +785,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-05-29 20:54:32.309460",
+ "modified": "2020-012-07 20:54:32.309460",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Sales Order Item",
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index 970d840..d4cde43 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -111,24 +111,24 @@
 		dialog.show();
 	}
 
-	prepare_app_defaults(data) {
+	async prepare_app_defaults(data) {
 		this.pos_opening = data.name;
 		this.company = data.company;
 		this.pos_profile = data.pos_profile;
 		this.pos_opening_time = data.period_start_date;
+		this.item_stock_map = {};
+		this.settings = {};
 
 		frappe.db.get_value('Stock Settings', undefined, 'allow_negative_stock').then(({ message }) => {
 			this.allow_negative_stock = flt(message.allow_negative_stock) || false;
 		});
 
 		frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => {
-			this.customer_groups = profile.customer_groups.map(group => group.customer_group);
-			this.cart.make_customer_selector();
+			this.settings.customer_groups = profile.customer_groups.map(group => group.customer_group);
+			this.settings.hide_images = profile.hide_images;
+			this.settings.auto_add_item_to_cart = profile.auto_add_item_to_cart;
+			this.make_app();
 		});
-
-		this.item_stock_map = {};
-
-		this.make_app();
 	}
 
 	set_opening_entry_status() {
@@ -238,12 +238,11 @@
 		this.item_selector = new erpnext.PointOfSale.ItemSelector({
 			wrapper: this.$components_wrapper,
 			pos_profile: this.pos_profile,
+			settings: this.settings,
 			events: {
 				item_selected: args => this.on_cart_update(args),
 
-				get_frm: () => this.frm || {},
-
-				get_allowed_item_group: () => this.item_groups
+				get_frm: () => this.frm || {}
 			}
 		})
 	}
@@ -251,6 +250,7 @@
 	init_item_cart() {
 		this.cart = new erpnext.PointOfSale.ItemCart({
 			wrapper: this.$components_wrapper,
+			settings: this.settings,
 			events: {
 				get_frm: () => this.frm,
 
@@ -273,9 +273,7 @@
 					this.customer_details = details;
 					// will add/remove LP payment method
 					this.payment.render_loyalty_points_payment_mode();
-				},
-
-				get_allowed_customer_group: () => this.customer_groups
+				}
 			}
 		})
 	}
@@ -644,8 +642,7 @@
 			})
 		} else if (available_qty < qty_needed) {
 			frappe.show_alert({
-				message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.',
-					[bold_item_code, bold_warehouse, bold_available_qty]),
+				message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]),
 				indicator: 'orange'
 			});
 			frappe.utils.play_sound("error");
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index 7799dac..3938300 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -1,8 +1,10 @@
 erpnext.PointOfSale.ItemCart = class {
-	constructor({ wrapper, events }) {
+	constructor({ wrapper, events, settings }) {
 		this.wrapper = wrapper;
 		this.events = events;
 		this.customer_info = undefined;
+		this.hide_images = settings.hide_images;
+		this.allowed_customer_groups = settings.customer_groups;
 		
 		this.init_component();
 	}
@@ -32,6 +34,7 @@
 			`<div class="customer-section rounded flex flex-col m-8 mb-0"></div>`
 		)
 		this.$customer_section = this.$component.find('.customer-section');
+		this.make_customer_selector();
 	}
 	
 	reset_customer_selector() {
@@ -302,7 +305,7 @@
 		this.$customer_section.html(`<div class="customer-search-field flex flex-1 items-center"></div>`);
 		const me = this;
 		const query = { query: 'erpnext.controllers.queries.customer_query' };
-		const allowed_customer_group = this.events.get_allowed_customer_group() || [];
+		const allowed_customer_group = this.allowed_customer_groups || [];
 		if (allowed_customer_group.length) {
 			query.filters = {
 				customer_group: ['in', allowed_customer_group]
@@ -423,6 +426,7 @@
 	}
 	
 	update_customer_section() {
+		const me = this;
 		const { customer, email_id='', mobile_no='', image } = this.customer_info || {};
 
 		if (customer) {
@@ -460,7 +464,7 @@
 		}
 
 		function get_customer_image() {
-			if (image) {
+			if (!me.hide_images && image) {
 				return `<div class="icon flex items-center justify-center w-12 h-12 rounded bg-light-grey mr-4 text-grey-200">
 							<img class="h-full" src="${image}" alt="${image}" style="object-fit: cover;">
 						</div>`
diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js
index 49d4281..a06b394 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_selector.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -1,8 +1,10 @@
 erpnext.PointOfSale.ItemSelector = class {
-	constructor({ frm, wrapper, events, pos_profile }) {
+	constructor({ frm, wrapper, events, pos_profile, settings }) {
 		this.wrapper = wrapper;
 		this.events = events;
 		this.pos_profile = pos_profile;
+		this.hide_images = settings.hide_images;
+		this.auto_add_item = settings.auto_add_item_to_cart;
 		
 		this.inti_component();
 	}
@@ -26,13 +28,14 @@
 					<div class="flex flex-1 flex-col p-8 pt-2">
 						<div class="text-grey mb-6">ALL ITEMS</div>
 						<div class="items-container grid grid-cols-4 gap-8">
-						</div>					
+						</div>
 					</div>
 				</div>
 			</section>`
 		);
 		
 		this.$component = this.wrapper.find('.items-selector');
+		this.$items_container = this.$component.find('.items-container');
 	}
 
 	async load_items_data() {
@@ -65,7 +68,6 @@
 
 
 	render_item_list(items) {
-		this.$items_container = this.$component.find('.items-container');
 		this.$items_container.html('');
 
 		items.forEach(item => {
@@ -75,11 +77,12 @@
 	}
 
 	get_item_html(item) {
+		const me = this;
 		const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item;
 		const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange";
 
 		function get_item_image_html() {
-			if (item_image) {
+			if (!me.hide_images && item_image) {
 				return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
 							<img class="h-full" src="${item_image}" alt="${frappe.get_abbr(item.item_name)}" style="object-fit: cover;">
 						</div>`
@@ -203,6 +206,7 @@
 			ignore_inputs: true,
 			page: cur_page.page.page
 		});
+
 		// for selecting the last filtered item on search
 		frappe.ui.keys.on("enter", () => {
 			const selector_is_visible = this.$component.is(':visible');
@@ -235,6 +239,7 @@
 				const items = this.search_index[search_term];
 				this.items = items;
 				this.render_item_list(items);
+				this.auto_add_item && this.items.length == 1 && this.add_filtered_item_to_cart();
 				return;
 			}
 		}
@@ -247,8 +252,13 @@
 				}
 				this.items = items;
 				this.render_item_list(items);
+				this.auto_add_item && this.items.length == 1 && this.add_filtered_item_to_cart();
 			});
 	}
+
+	add_filtered_item_to_cart() {
+		this.$items_container.find(".item-wrapper").click();
+	}
 	
 	resize_selector(minimize) {
 		minimize ? 
diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
index c716aa9..8473276 100644
--- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
+++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
@@ -10,8 +10,8 @@
 def execute(filters=None):
 	filters = frappe._dict(filters or {})
 	if filters.from_date > filters.to_date:
-		frappe.throw(_('From Date cannot be greater than To Date'))
-	
+		frappe.throw(_("From Date cannot be greater than To Date"))
+
 	columns = get_columns(filters)
 	data = get_data(filters)
 
@@ -148,14 +148,16 @@
 	company_list.append(filters.get("company"))
 
 	customer_details = get_customer_details()
+	item_details = get_item_details()
 	sales_order_records = get_sales_order_details(company_list, filters)
 
 	for record in sales_order_records:
 		customer_record = customer_details.get(record.customer)
+		item_record = item_details.get(record.item_code)
 		row = {
 			"item_code": record.item_code,
-			"item_name": record.item_name,
-			"item_group": record.item_group,
+			"item_name": item_record.item_name,
+			"item_group": item_record.item_group,
 			"description": record.description,
 			"quantity": record.qty,
 			"uom": record.uom,
@@ -196,8 +198,8 @@
 	return conditions
 
 def get_customer_details():
-	details = frappe.get_all('Customer',
-		fields=['name', 'customer_name', "customer_group"])
+	details = frappe.get_all("Customer",
+		fields=["name", "customer_name", "customer_group"])
 	customer_details = {}
 	for d in details:
 		customer_details.setdefault(d.name, frappe._dict({
@@ -206,15 +208,25 @@
 		}))
 	return customer_details
 
+def get_item_details():
+	details = frappe.db.get_all("Item",
+		fields=["item_code", "item_name", "item_group"])
+	item_details = {}
+	for d in details:
+		item_details.setdefault(d.item_code, frappe._dict({
+			"item_name": d.item_name,
+			"item_group": d.item_group
+		}))
+	return item_details
+
 def get_sales_order_details(company_list, filters):
 	conditions = get_conditions(filters)
 
 	return frappe.db.sql("""
 		SELECT
-			so_item.item_code, so_item.item_name, so_item.item_group,
-			so_item.description, so_item.qty, so_item.uom,
-			so_item.base_rate, so_item.base_amount, so.name,
-			so.transaction_date, so.customer, so.territory,
+			so_item.item_code, so_item.description, so_item.qty,
+			so_item.uom, so_item.base_rate, so_item.base_amount,
+			so.name, so.transaction_date, so.customer,so.territory,
 			so.project, so_item.delivered_qty,
 			so_item.billed_amt, so.company
 		FROM
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 002cfe4..7f00fca 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -42,16 +42,6 @@
 		me.frm.set_query('customer_address', erpnext.queries.address_query);
 		me.frm.set_query('shipping_address_name', erpnext.queries.address_query);
 
-		if(this.frm.fields_dict.taxes_and_charges) {
-			this.frm.set_query("taxes_and_charges", function() {
-				return {
-					filters: [
-						['Sales Taxes and Charges Template', 'company', '=', me.frm.doc.company],
-						['Sales Taxes and Charges Template', 'docstatus', '!=', 2]
-					]
-				}
-			});
-		}
 
 		if(this.frm.fields_dict.selling_price_list) {
 			this.frm.set_query("selling_price_list", function() {
@@ -479,7 +469,7 @@
 						$.each(frm.doc["items"] || [], function(i, row) {
 							if(r.message) {
 								frappe.model.set_value(row.doctype, row.name, "cost_center", r.message);
-								frappe.msgprint(__("Cost Center For Item with Item Code '"+row.item_name+"' has been Changed to "+ r.message));
+								frappe.msgprint(__("Cost Center For Item with Item Code {0} has been Changed to {1}", [row.item_name, r.message]));
 							}
 						})
 					}
diff --git a/erpnext/setup/desk_page/home/home.json b/erpnext/setup/desk_page/home/home.json
index 9cf9f41..0fbd0ec 100644
--- a/erpnext/setup/desk_page/home/home.json
+++ b/erpnext/setup/desk_page/home/home.json
@@ -2,26 +2,6 @@
  "cards": [
   {
    "hidden": 0,
-   "label": "Healthcare",
-   "links": "[\n    {\n        \"label\": \"Patient\",\n        \"name\": \"Patient\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Diagnosis\",\n        \"name\": \"Diagnosis\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    }\n]"
-  },
-  {
-   "hidden": 0,
-   "label": "Agriculture",
-   "links": "[\n    {\n        \"label\": \"Crop\",\n        \"name\": \"Crop\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Crop Cycle\",\n        \"name\": \"Crop Cycle\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Location\",\n        \"name\": \"Location\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Fertilizer\",\n        \"name\": \"Fertilizer\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    }\n]"
-  },
-  {
-   "hidden": 0,
-   "label": "Education",
-   "links": "[\n    {\n        \"label\": \"Student\",\n        \"name\": \"Student\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Course\",\n        \"name\": \"Course\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Instructor\",\n        \"name\": \"Instructor\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Room\",\n        \"name\": \"Room\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    }\n]"
-  },
-  {
-   "hidden": 0,
-   "label": "Non Profit",
-   "links": "[\n    {\n        \"description\": \"Member information.\",\n        \"label\": \"Member\",\n        \"name\": \"Member\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"description\": \"Volunteer information.\",\n        \"label\": \"Volunteer\",\n        \"name\": \"Volunteer\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"description\": \"Chapter information.\",\n        \"label\": \"Chapter\",\n        \"name\": \"Chapter\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"description\": \"Donor information.\",\n        \"label\": \"Donor\",\n        \"name\": \"Donor\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    }\n]"
-  },
-  {
-   "hidden": 0,
    "label": "Stock",
    "links": "[\n    {\n        \"label\": \"Warehouse\",\n        \"name\": \"Warehouse\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Brand\",\n        \"name\": \"Brand\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Unit of Measure (UOM)\",\n        \"name\": \"UOM\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Stock Reconciliation\",\n        \"name\": \"Stock Reconciliation\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    }\n]"
   },
@@ -54,10 +34,11 @@
  "docstatus": 0,
  "doctype": "Desk Page",
  "extends_another_page": 0,
+ "hide_custom": 0,
  "idx": 0,
  "is_standard": 1,
  "label": "Home",
- "modified": "2020-05-11 10:20:37.358701",
+ "modified": "2020-12-07 14:22:38.667767",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Home",
@@ -96,4 +77,4 @@
    "type": "Page"
   }
  ]
-}
\ No newline at end of file
+}
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index cbf67b4..36033d9 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -274,7 +274,8 @@
 		["default_employee_advance_account", {"root_type": "Asset"}],
 		["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}],
 		["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}],
-		["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}]
+		["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}],
+		["unrealized_profit_loss_account", {"root_type": "Liability"}]
 	], function(i, v) {
 		erpnext.company.set_custom_query(frm, v);
 	});
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 40938ea..d49ae7c 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -46,10 +46,9 @@
   "round_off_account",
   "round_off_cost_center",
   "write_off_account",
-  "discount_allowed_account",
-  "discount_received_account",
   "exchange_gain_loss_account",
   "unrealized_exchange_gain_loss_account",
+  "unrealized_profit_loss_account",
   "column_break0",
   "allow_account_creation_against_child_company",
   "default_payable_account",
@@ -261,14 +260,14 @@
   {
    "fieldname": "create_chart_of_accounts_based_on",
    "fieldtype": "Select",
-   "label": "Create Chart of Accounts Based on",
+   "label": "Create Chart Of Accounts Based On",
    "options": "\nStandard Template\nExisting Company"
   },
   {
    "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"",
    "fieldname": "chart_of_accounts",
    "fieldtype": "Select",
-   "label": "Chart of Accounts Template",
+   "label": "Chart Of Accounts Template",
    "no_copy": 1
   },
   {
@@ -346,18 +345,6 @@
    "options": "Account"
   },
   {
-   "fieldname": "discount_allowed_account",
-   "fieldtype": "Link",
-   "label": "Discount Allowed Account",
-   "options": "Account"
-  },
-  {
-   "fieldname": "discount_received_account",
-   "fieldtype": "Link",
-   "label": "Discount Received Account",
-   "options": "Account"
-  },
-  {
    "fieldname": "exchange_gain_loss_account",
    "fieldtype": "Link",
    "label": "Exchange Gain / Loss Account",
@@ -740,6 +727,12 @@
    "fieldtype": "Link",
    "label": "Default In Transit Warehouse",
    "options": "Warehouse"
+  },
+  {
+   "fieldname": "unrealized_profit_loss_account",
+   "fieldtype": "Link",
+   "label": "Unrealized Profit / Loss Account",
+   "options": "Account"
   }
  ],
  "icon": "fa fa-building",
@@ -747,7 +740,7 @@
  "image_field": "company_logo",
  "is_tree": 1,
  "links": [],
- "modified": "2020-08-06 00:38:08.311216",
+ "modified": "2020-12-03 12:27:27.085094",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Company",
@@ -808,4 +801,4 @@
  "sort_field": "modified",
  "sort_order": "ASC",
  "track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py
index 566f20c..7a72fe3 100644
--- a/erpnext/setup/doctype/company/delete_company_transactions.py
+++ b/erpnext/setup/doctype/company/delete_company_transactions.py
@@ -28,7 +28,7 @@
 			"Party Account", "Employee", "Sales Taxes and Charges Template",
 			"Purchase Taxes and Charges Template", "POS Profile", "BOM",
 			"Company", "Bank Account", "Item Tax Template", "Mode Of Payment",
-			"Item Default"):
+			"Item Default", "Customer", "Supplier"):
 				delete_for_doctype(doctype, company_name)
 
 	# reset company values
diff --git a/erpnext/setup/doctype/company/test_records.json b/erpnext/setup/doctype/company/test_records.json
index 2130241..9e55702 100644
--- a/erpnext/setup/doctype/company/test_records.json
+++ b/erpnext/setup/doctype/company/test_records.json
@@ -7,7 +7,8 @@
 		"doctype": "Company",
 		"domain": "Manufacturing",
 		"chart_of_accounts": "Standard",
-		"default_holiday_list": "_Test Holiday List"
+		"default_holiday_list": "_Test Holiday List",
+		"enable_perpetual_inventory": 0
 	},
 	{
 		"abbr": "_TC1",
@@ -17,7 +18,8 @@
 		"doctype": "Company",
 		"domain": "Retail",
 		"chart_of_accounts": "Standard",
-		"default_holiday_list": "_Test Holiday List"
+		"default_holiday_list": "_Test Holiday List",
+		"enable_perpetual_inventory": 0
 	},
 	{
 		"abbr": "_TC2",
@@ -27,7 +29,8 @@
 		"doctype": "Company",
 		"domain": "Retail",
 		"chart_of_accounts": "Standard",
-		"default_holiday_list": "_Test Holiday List"
+		"default_holiday_list": "_Test Holiday List",
+		"enable_perpetual_inventory": 0
 	},
 	{
 		"abbr": "_TC3",
@@ -38,7 +41,8 @@
 		"doctype": "Company",
 		"domain": "Manufacturing",
 		"chart_of_accounts": "Standard",
-		"default_holiday_list": "_Test Holiday List"
+		"default_holiday_list": "_Test Holiday List",
+		"enable_perpetual_inventory": 0
 	},
 	{
 		"abbr": "_TC4",
@@ -50,7 +54,8 @@
 		"doctype": "Company",
 		"domain": "Manufacturing",
 		"chart_of_accounts": "Standard",
-		"default_holiday_list": "_Test Holiday List"
+		"default_holiday_list": "_Test Holiday List",
+		"enable_perpetual_inventory": 0
 	},
 	{
 		"abbr": "_TC5",
@@ -61,7 +66,8 @@
 		"doctype": "Company",
 		"domain": "Manufacturing",
 		"chart_of_accounts": "Standard",
-		"default_holiday_list": "_Test Holiday List"
+		"default_holiday_list": "_Test Holiday List",
+		"enable_perpetual_inventory": 0
 	},
 	{
 		"abbr": "TCP1",
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index b30bd78..cbb4c7c 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -48,12 +48,8 @@
 		recipients = list(filter(lambda r: r in valid_users,
 			self.recipient_list.split("\n")))
 
-		original_user = frappe.session.user
-
 		if recipients:
 			for user_id in recipients:
-				frappe.set_user(user_id)
-				frappe.set_user_lang(user_id)
 				msg_for_this_recipient = self.get_msg_html()
 				if msg_for_this_recipient:
 					frappe.sendmail(
@@ -64,9 +60,6 @@
 						reference_name = self.name,
 						unsubscribe_message = _("Unsubscribe from this Email Digest"))
 
-		frappe.set_user(original_user)
-		frappe.set_user_lang(original_user)
-
 	def get_msg_html(self):
 		"""Build email digest content"""
 		frappe.flags.ignore_account_permission = True
diff --git a/erpnext/setup/doctype/sales_person/sales_person.js b/erpnext/setup/doctype/sales_person/sales_person.js
index 8f7593d..b71a92f 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.js
+++ b/erpnext/setup/doctype/sales_person/sales_person.js
@@ -5,8 +5,7 @@
 	refresh: function(frm) {
 		if(frm.doc.__onload && frm.doc.__onload.dashboard_info) {
 			var info = frm.doc.__onload.dashboard_info;
-			frm.dashboard.add_indicator(__('Total Contribution Amount: {0}',
-				[format_currency(info.allocated_amount, info.currency)]), 'blue');
+			frm.dashboard.add_indicator(__('Total Contribution Amount: {0}', [format_currency(info.allocated_amount, info.currency)]), 'blue');
 		}
 	},
 
diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py
index 0ccc025..c2549fe 100644
--- a/erpnext/shopping_cart/cart.py
+++ b/erpnext/shopping_cart/cart.py
@@ -345,7 +345,7 @@
 	selling_price_list = None
 
 	# check if default customer price list exists
-	if party_name:
+	if party_name and frappe.db.exists("Customer", party_name):
 		selling_price_list = get_default_price_list(frappe.get_doc("Customer", party_name))
 
 	# check default price list in shopping cart
diff --git a/erpnext/stock/desk_page/stock/stock.json b/erpnext/stock/desk_page/stock/stock.json
index 390fcd9..9068e33 100644
--- a/erpnext/stock/desk_page/stock/stock.json
+++ b/erpnext/stock/desk_page/stock/stock.json
@@ -8,7 +8,7 @@
   {
    "hidden": 0,
    "label": "Stock Transactions",
-   "links": "[\n     {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"label\": \"Material Request\",\n        \"name\": \"Material Request\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"label\": \"Stock Entry\",\n        \"name\": \"Stock Entry\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\",\n            \"Customer\"\n        ],\n        \"label\": \"Delivery Note\",\n        \"name\": \"Delivery Note\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\",\n            \"Supplier\"\n        ],\n        \"label\": \"Purchase Receipt\",\n        \"name\": \"Purchase Receipt\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"label\": \"Pick List\",\n        \"name\": \"Pick List\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Delivery Trip\",\n        \"name\": \"Delivery Trip\",\n        \"type\": \"doctype\"\n    }\n]"
+   "links": "[\n     {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"label\": \"Material Request\",\n        \"name\": \"Material Request\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"label\": \"Stock Entry\",\n        \"name\": \"Stock Entry\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\",\n            \"Customer\"\n        ],\n        \"label\": \"Delivery Note\",\n        \"name\": \"Delivery Note\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\",\n            \"Supplier\"\n        ],\n        \"label\": \"Purchase Receipt\",\n        \"name\": \"Purchase Receipt\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"label\": \"Pick List\",\n        \"name\": \"Pick List\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Shipment\",\n        \"name\": \"Shipment\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Delivery Trip\",\n        \"name\": \"Delivery Trip\",\n        \"type\": \"doctype\"\n    }\n]"
   },
   {
    "hidden": 0,
@@ -58,7 +58,7 @@
  "idx": 0,
  "is_standard": 1,
  "label": "Stock",
- "modified": "2020-10-07 18:40:17.130207",
+ "modified": "2020-12-02 15:47:41.532942",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock",
diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py
index c2a3d3c..e41f1a8 100644
--- a/erpnext/stock/doctype/batch/test_batch.py
+++ b/erpnext/stock/doctype/batch/test_batch.py
@@ -8,13 +8,8 @@
 
 from erpnext.stock.doctype.batch.batch import get_batch_qty, UnableToSelectBatchError, get_batch_no
 from frappe.utils import cint, flt
-from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
 
 class TestBatch(unittest.TestCase):
-
-	def setUp(self):
-		set_perpetual_inventory(0)
-
 	def test_item_has_batch_enabled(self):
 		self.assertRaises(ValidationError, frappe.get_doc({
 			"doctype": "Batch",
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index 7acdec7..ab19b77 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -16,22 +16,30 @@
 	def update_stock(self, args, allow_negative_stock=False, via_landed_cost_voucher=False):
 		'''Called from erpnext.stock.utils.update_bin'''
 		self.update_qty(args)
-
 		if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation":
-			from erpnext.stock.stock_ledger import update_entries_after
+			from erpnext.stock.stock_ledger import update_entries_after, update_qty_in_future_sle
 
 			if not args.get("posting_date"):
 				args["posting_date"] = nowdate()
 
+			if args.get("is_cancelled") and via_landed_cost_voucher:
+				return
+
+			# Reposts only current voucher SL Entries
+			# Updates valuation rate, stock value, stock queue for current transaction
 			update_entries_after({
 				"item_code": self.item_code,
 				"warehouse": self.warehouse,
 				"posting_date": args.get("posting_date"),
 				"posting_time": args.get("posting_time"),
+				"voucher_type": args.get("voucher_type"),
 				"voucher_no": args.get("voucher_no"),
-				"sle_id": args.sle_id
+				"sle_id": args.name
 			}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
 
+			# Update qty_after_transaction in future SLEs of this item and warehouse
+			update_qty_in_future_sle(args)
+
 	def update_qty(self, args):
 		# update the stock values (for current quantities)
 		if args.get("voucher_type")=="Stock Reconciliation":
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 251a26a..5f2658c 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -15,6 +15,7 @@
 			'Installation Note': 'Installation Note',
 			'Sales Invoice': 'Invoice',
 			'Stock Entry': 'Return',
+			'Shipment': 'Shipment'
 		},
 		frm.set_indicator_formatter('item_code',
 			function(doc) {
@@ -156,6 +157,11 @@
 		}
 
 		if (!doc.is_return && doc.status!="Closed") {
+			if(doc.docstatus == 1) {
+				this.frm.add_custom_button(__('Shipment'), function() {
+					me.make_shipment() }, __('Create'));
+			}
+
 			if(flt(doc.per_installed, 2) < 100 && doc.docstatus==1)
 				this.frm.add_custom_button(__('Installation Note'), function() {
 					me.make_installation_note() }, __('Create'));
@@ -220,6 +226,13 @@
 		}
 	},
 
+	make_shipment: function() {
+		frappe.model.open_mapped_doc({
+			method: "erpnext.stock.doctype.delivery_note.delivery_note.make_shipment",
+			frm: this.frm
+		})
+	},
+
 	make_sales_invoice: function() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index 3c5129b..c9f8d08 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -1,5 +1,6 @@
 {
  "actions": [],
+ "allow_auto_repeat": 1,
  "allow_import": 1,
  "autoname": "naming_series:",
  "creation": "2013-05-24 19:29:09",
@@ -132,6 +133,7 @@
   "per_installed",
   "installation_status",
   "column_break_89",
+  "per_returned",
   "excise_page",
   "instructions",
   "subscription_section",
@@ -1098,7 +1100,7 @@
    "no_copy": 1,
    "oldfieldname": "status",
    "oldfieldtype": "Select",
-   "options": "\nDraft\nTo Bill\nCompleted\nCancelled\nClosed",
+   "options": "\nDraft\nTo Bill\nCompleted\nReturn Issued\nCancelled\nClosed",
    "print_hide": 1,
    "print_width": "150px",
    "read_only": 1,
@@ -1250,13 +1252,22 @@
    "fieldtype": "Link",
    "label": "Inter Company Reference",
    "options": "Purchase Receipt"
+  },
+  {
+   "depends_on": "eval:!doc.__islocal",
+   "fieldname": "per_returned",
+   "fieldtype": "Percent",
+   "label": "% Returned",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "icon": "fa fa-truck",
  "idx": 146,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-11 14:57:16.388139",
+ "modified": "2020-11-30 12:54:45.407289",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index d04cf78..a30cadf 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -55,7 +55,7 @@
 			'no_allowance': 1
 		}]
 		if cint(self.is_return):
-			self.status_updater.append({
+			self.status_updater.extend([{
 				'source_dt': 'Delivery Note Item',
 				'target_dt': 'Sales Order Item',
 				'join_field': 'so_detail',
@@ -69,7 +69,19 @@
 					where name=`tabDelivery Note Item`.parent and is_return=1)""",
 				'second_source_extra_cond': """ and exists (select name from `tabSales Invoice`
 					where name=`tabSales Invoice Item`.parent and is_return=1 and update_stock=1)"""
-			})
+			},
+			{
+				'source_dt': 'Delivery Note Item',
+				'target_dt': 'Delivery Note Item',
+				'join_field': 'dn_detail',
+				'target_field': 'returned_qty',
+				'target_parent_dt': 'Delivery Note',
+				'target_parent_field': 'per_returned',
+				'target_ref_field': 'stock_qty',
+				'source_field': '-1 * stock_qty',
+				'percent_join_field_parent': 'return_against'
+			}
+		])
 
 	def before_print(self):
 		def toggle_print_hide(meta, fieldname):
@@ -205,6 +217,7 @@
 		# because updating reserved qty in bin depends upon updated delivered qty in SO
 		self.update_stock_ledger()
 		self.make_gl_entries()
+		self.repost_future_sle_and_gle()
 
 	def on_cancel(self):
 		super(DeliveryNote, self).on_cancel()
@@ -222,7 +235,8 @@
 		self.cancel_packing_slips()
 
 		self.make_gl_entries_on_cancel()
-		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
+		self.repost_future_sle_and_gle()
+		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
 
 	def check_credit_limit(self):
 		from erpnext.selling.doctype.customer.customer import check_credit_limit
@@ -569,6 +583,70 @@
 
 	return doclist
 
+@frappe.whitelist()
+def make_shipment(source_name, target_doc=None):
+	def postprocess(source, target):
+		user = frappe.db.get_value("User", frappe.session.user, ['email', 'full_name', 'phone', 'mobile_no'], as_dict=1)
+		target.pickup_contact_email = user.email
+		pickup_contact_display = '{}'.format(user.full_name)
+		if user:
+			if user.email:
+				pickup_contact_display += '<br>' + user.email
+			if user.phone:
+				pickup_contact_display += '<br>' + user.phone
+			if user.mobile_no and not user.phone:
+				pickup_contact_display += '<br>' + user.mobile_no
+		target.pickup_contact = pickup_contact_display
+
+		# As we are using session user details in the pickup_contact then pickup_contact_person will be session user
+		target.pickup_contact_person = frappe.session.user
+
+		contact = frappe.db.get_value("Contact", source.contact_person, ['email_id', 'phone', 'mobile_no'], as_dict=1)
+		delivery_contact_display = '{}'.format(source.contact_display)
+		if contact:
+			if contact.email_id:
+				delivery_contact_display += '<br>' + contact.email_id
+			if contact.phone:
+				delivery_contact_display += '<br>' + contact.phone
+			if contact.mobile_no and not contact.phone:
+				delivery_contact_display += '<br>' + contact.mobile_no
+		target.delivery_contact = delivery_contact_display
+
+		if source.shipping_address_name:
+			target.delivery_address_name = source.shipping_address_name
+			target.delivery_address = source.shipping_address
+		elif source.customer_address:
+			target.delivery_address_name = source.customer_address
+			target.delivery_address = source.address_display
+
+	doclist = get_mapped_doc("Delivery Note", source_name, 	{
+		"Delivery Note": {
+			"doctype": "Shipment",
+			"field_map": {
+				"grand_total": "value_of_goods",
+				"company": "pickup_company",
+				"company_address": "pickup_address_name",
+				"company_address_display": "pickup_address",
+				"customer": "delivery_customer",
+				"contact_person": "delivery_contact_name",
+				"contact_email": "delivery_contact_email"
+			},
+			"validation": {
+				"docstatus": ["=", 1]
+			}
+		},
+		"Delivery Note Item": {
+			"doctype": "Shipment Delivery Note",
+			"field_map": {
+				"name": "prevdoc_detail_docname",
+				"parent": "prevdoc_docname",
+				"parenttype": "prevdoc_doctype",
+				"base_amount": "grand_total"
+			}
+		}
+	}, target_doc, postprocess)
+
+	return doclist
 
 @frappe.whitelist()
 def make_sales_return(source_name, target_doc=None):
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py
index beeb9eb..47684d5 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py
@@ -19,7 +19,7 @@
 			},
 			{
 				'label': _('Reference'),
-				'items': ['Sales Order', 'Quality Inspection']
+				'items': ['Sales Order', 'Shipment', 'Quality Inspection']
 			},
 			{
 				'label': _('Returns'),
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_list.js b/erpnext/stock/doctype/delivery_note/delivery_note_list.js
index 0ae7c37..4a6500c 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note_list.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note_list.js
@@ -6,9 +6,11 @@
 			return [__("Return"), "darkgrey", "is_return,=,Yes"];
 		} else if (doc.status === "Closed") {
 			return [__("Closed"), "green", "status,=,Closed"];
+		} else if (flt(doc.per_returned, 2) === 100) {
+			return [__("Return Issued"), "grey", "per_returned,=,100"];
 		} else if (flt(doc.per_billed, 2) < 100) {
 			return [__("To Bill"), "orange", "per_billed,<,100"];
-		} else if (flt(doc.per_billed, 2) == 100) {
+		} else if (flt(doc.per_billed, 2) === 100) {
 			return [__("Completed"), "green", "per_billed,=,100"];
 		}
 	},
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 9566af7..559f8be 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -10,8 +10,7 @@
 from frappe.utils import cint, nowdate, nowtime, cstr, add_days, flt, today
 from erpnext.stock.stock_ledger import get_previous_sle
 from erpnext.accounts.utils import get_balance_on
-from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \
-	import get_gl_entries, set_perpetual_inventory
+from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries
 from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice, make_delivery_trip
 from erpnext.stock.doctype.stock_entry.test_stock_entry \
 	import make_stock_entry, make_serialized_item, get_qty_after_transaction
@@ -24,9 +23,6 @@
 from erpnext.stock.doctype.item.test_item import create_item
 
 class TestDeliveryNote(unittest.TestCase):
-	def setUp(self):
-		set_perpetual_inventory(0)
-
 	def test_over_billing_against_dn(self):
 		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
 
@@ -43,7 +39,6 @@
 
 	def test_delivery_note_no_gl_entry(self):
 		company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company')
-		set_perpetual_inventory(0, company)
 		make_stock_entry(target="_Test Warehouse - _TC", qty=5, basic_rate=100)
 
 		stock_queue = json.loads(get_previous_sle({
@@ -206,7 +201,7 @@
 		for field, value in field_values.items():
 			self.assertEqual(cstr(serial_no.get(field)), value)
 
-	def test_sales_return_for_non_bundled_items(self):
+	def test_sales_return_for_non_bundled_items_partial(self):
 		company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
 
 		make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=50, basic_rate=100)
@@ -225,7 +220,10 @@
 
 		# return entry
 		dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-2, rate=500,
-			company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1")
+			company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1",
+			cost_center="Main - TCP1", do_not_submit=1)
+		dn1.items[0].dn_detail = dn.items[0].name
+		dn1.submit()
 
 		actual_qty_2 = get_qty_after_transaction(warehouse="Stores - TCP1")
 
@@ -243,6 +241,70 @@
 
 		self.assertEqual(gle_warehouse_amount, stock_value_difference)
 
+		# hack because new_doc isn't considering is_return portion of status_updater
+		returned = frappe.get_doc("Delivery Note", dn1.name)
+		returned.update_prevdoc_status()
+		dn.load_from_db()
+
+		# Check if Original DN updated
+		self.assertEqual(dn.items[0].returned_qty, 2)
+		self.assertEqual(dn.per_returned, 40)
+
+		from erpnext.controllers.sales_and_purchase_return import make_return_doc
+		return_dn_2 = make_return_doc("Delivery Note", dn.name)
+
+		# Check if unreturned amount is mapped in 2nd return
+		self.assertEqual(return_dn_2.items[0].qty, -3)
+
+		si = make_sales_invoice(dn.name)
+		si.submit()
+
+		self.assertEqual(si.items[0].qty, 3)
+
+		dn.load_from_db()
+		# DN should be completed on billing all unreturned amount
+		self.assertEqual(dn.items[0].billed_amt, 1500)
+		self.assertEqual(dn.per_billed, 100)
+		self.assertEqual(dn.status, 'Completed')
+
+		si.load_from_db()
+		si.cancel()
+
+		dn.load_from_db()
+		self.assertEqual(dn.per_billed, 0)
+
+		dn1.cancel()
+		dn.cancel()
+
+	def test_sales_return_for_non_bundled_items_full(self):
+		from erpnext.stock.doctype.item.test_item import make_item
+
+		company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
+
+		make_item("Box", {'is_stock_item': 1})
+
+		make_stock_entry(item_code="Box", target="Stores - TCP1", qty=10, basic_rate=100)
+
+		dn = create_delivery_note(item_code="Box", qty=5, rate=500, warehouse="Stores - TCP1", company=company,
+			expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1")
+
+		#return entry
+		dn1 = create_delivery_note(item_code="Box", is_return=1, return_against=dn.name, qty=-5, rate=500,
+			company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1",
+			cost_center="Main - TCP1", do_not_submit=1)
+		dn1.items[0].dn_detail = dn.items[0].name
+		dn1.submit()
+
+		# hack because new_doc isn't considering is_return portion of status_updater
+		returned = frappe.get_doc("Delivery Note", dn1.name)
+		returned.update_prevdoc_status()
+		dn.load_from_db()
+
+		# Check if Original DN updated
+		self.assertEqual(dn.items[0].returned_qty, 5)
+		self.assertEqual(dn.per_returned, 100)
+		self.assertEqual(dn.status, 'Return Issued')
+
 	def test_return_single_item_from_bundled_items(self):
 		company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
 
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 3d57f47..4bbf3de 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "autoname": "hash",
  "creation": "2013-04-22 13:15:44",
  "doctype": "DocType",
@@ -24,7 +25,10 @@
   "col_break2",
   "uom",
   "conversion_factor",
+  "stock_qty_sec_break",
   "stock_qty",
+  "stock_qty_col_break",
+  "returned_qty",
   "section_break_17",
   "price_list_rate",
   "base_price_list_rate",
@@ -52,6 +56,7 @@
   "base_net_rate",
   "base_net_amount",
   "billed_amt",
+  "incoming_rate",
   "item_weight_details",
   "weight_per_unit",
   "total_weight",
@@ -211,7 +216,7 @@
   {
    "fieldname": "stock_qty",
    "fieldtype": "Float",
-   "label": "Qty as per Stock UOM",
+   "label": "Qty in Stock UOM",
    "no_copy": 1,
    "print_hide": 1,
    "read_only": 1
@@ -715,12 +720,35 @@
    "no_copy": 1,
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "fieldname": "stock_qty_sec_break",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "stock_qty_col_break",
+   "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "returned_qty",
+   "fieldname": "returned_qty",
+   "fieldtype": "Float",
+   "label": "Returned Qty in Stock UOM"
+  },
+  {
+   "fieldname": "incoming_rate",
+   "fieldtype": "Currency",
+   "label": "Incoming Rate",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
+ "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-07-20 12:25:06.177894",
+ "modified": "2020-12-07 19:59:27.119856",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 3b62c38..be845d9 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -977,15 +977,20 @@
 		# For "Is Stock Item", following doctypes is important
 		# because reserved_qty, ordered_qty and requested_qty updated from these doctypes
 		if field == "is_stock_item":
-			linked_doctypes += ["Sales Order Item", "Purchase Order Item", "Material Request Item"]
+			linked_doctypes += ["Sales Order Item", "Purchase Order Item", "Material Request Item", "Product Bundle"]
 
 		for doctype in linked_doctypes:
+			filters={"item_code": self.name, "docstatus": 1}
+
+			if doctype == "Product Bundle":
+				filters={"new_item_code": self.name}
+
 			if doctype in ("Purchase Invoice Item", "Sales Invoice Item",):
 				# If Invoice has Stock impact, only then consider it.
 				if self.stock_ledger_created():
 					return True
 
-			elif frappe.db.get_value(doctype, filters={"item_code": self.name, "docstatus": 1}):
+			elif frappe.db.get_value(doctype, filters):
 				return True
 
 	def validate_auto_reorder_enabled_in_stock_settings(self):
diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json
index 9ca887c..8f437b1 100644
--- a/erpnext/stock/doctype/item/test_records.json
+++ b/erpnext/stock/doctype/item/test_records.json
@@ -458,5 +458,15 @@
     "item_tax_template": "_Test Item Tax Template 1"
    }
   ]
+ },
+ {
+  "description": "_Test",
+  "doctype": "Item",
+  "is_stock_item": 1,
+  "item_code": "138-CMS Shoe",
+  "item_group": "_Test Item Group",
+  "item_name": "138-CMS Shoe",
+  "stock_uom": "_Test UOM",
+  "gst_hsn_code": "999800"
  }
 ]
diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py
index f045e4f..d5700fe 100644
--- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py
+++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py
@@ -12,11 +12,9 @@
 from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
 from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt, make_rm_stock_entry
 import unittest
-from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
 
 class TestItemAlternative(unittest.TestCase):
 	def setUp(self):
-		set_perpetual_inventory(0)
 		make_items()
 
 	def test_alternative_item_for_subcontract_rm(self):
diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json
index f1e1fd3..888bc2d 100644
--- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json
+++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json
@@ -1,88 +1,57 @@
 {
- "allow_copy": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "hash", 
- "beta": 0, 
- "creation": "2013-02-22 01:28:01", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "editable_grid": 1, 
+ "actions": [],
+ "autoname": "hash",
+ "creation": "2013-02-22 01:28:01",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "specification",
+  "value",
+  "column_break_3",
+  "acceptance_formula"
+ ],
  "fields": [
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "fieldname": "specification", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Parameter", 
-   "length": 0, 
-   "no_copy": 0, 
-   "oldfieldname": "specification", 
-   "oldfieldtype": "Data", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "print_width": "200px", 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0, 
+   "fieldname": "specification",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Parameter",
+   "oldfieldname": "specification",
+   "oldfieldtype": "Data",
+   "print_width": "200px",
+   "reqd": 1,
    "width": "200px"
-  }, 
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "fieldname": "value", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Acceptance Criteria", 
-   "length": 0, 
-   "no_copy": 0, 
-   "oldfieldname": "value", 
-   "oldfieldtype": "Data", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
+   "fieldname": "value",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Acceptance Criteria",
+   "oldfieldname": "value",
+   "oldfieldtype": "Data"
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  },
+  {
+   "description": "Simple Python formula based on numeric Readings.<br> Example 1: <b>reading_1 &gt; 0.2 and reading_1 &lt; 0.5</b><br>\nExample 2: <b>(reading_1 + reading_2) / 2 &lt; 10</b>",
+   "fieldname": "acceptance_formula",
+   "fieldtype": "Code",
+   "in_list_view": 1,
+   "label": "Acceptance Criteria Formula"
   }
- ], 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 1, 
- "image_view": 0, 
- "in_create": 0, 
-
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "modified": "2016-07-11 03:28:01.074316", 
- "modified_by": "Administrator", 
- "module": "Stock", 
- "name": "Item Quality Inspection Parameter", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "track_seen": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2020-11-16 16:33:42.421842",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Item Quality Inspection Parameter",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC"
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json
index 0cc243d..64331c7 100644
--- a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json
+++ b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "creation": "2014-07-11 11:51:00.453717",
  "doctype": "DocType",
  "editable_grid": 1,
@@ -31,16 +32,19 @@
    "reqd": 1
   },
   {
+   "depends_on": "eval:cint(erpnext.is_perpetual_inventory_enabled(parent.company))",
    "fieldname": "expense_account",
    "fieldtype": "Link",
    "in_list_view": 1,
    "label": "Expense Account",
+   "mandatory_depends_on": "eval:cint(erpnext.is_perpetual_inventory_enabled(parent.company))",
    "options": "Account",
-   "reqd": 1
+   "print_hide": 1
   }
  ],
  "istable": 1,
- "modified": "2019-09-30 18:28:32.070655",
+ "links": [],
+ "modified": "2020-12-04 00:22:14.373312",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Landed Cost Taxes and Charges",
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
index bc3d326..9ec6b89 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -77,9 +77,9 @@
 		company_currency = erpnext.get_company_currency(self.company)
 		for account in self.taxes:
 			if get_account_currency(account.expense_account) != company_currency:
-				frappe.throw(msg=_(""" Row {0}: Expense account currency should be same as company's default currency.
-					Please select expense account with account currency as {1}""")
-					.format(account.idx, frappe.bold(company_currency)), title=_("Invalid Account Currency"))
+				frappe.throw(_("Row {}: Expense account currency should be same as company's default currency.").format(account.idx)
+					+ _("Please select expense account with account currency as {}.").format(frappe.bold(company_currency)),
+					title=_("Invalid Account Currency"))
 
 	def set_total_taxes_and_charges(self):
 		self.total_taxes_and_charges = sum([flt(d.amount) for d in self.get("taxes")])
@@ -121,7 +121,7 @@
 			doc.set_landed_cost_voucher_amount()
 
 			# set valuation amount in pr item
-			doc.update_valuation_rate("items")
+			doc.update_valuation_rate(reset_outgoing_rate=False)
 
 			# db_update will update and save landed_cost_voucher_amount and voucher_amount in PR
 			for item in doc.get("items"):
@@ -143,6 +143,7 @@
 			doc.docstatus = 1
 			doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True)
 			doc.make_gl_entries()
+			doc.repost_future_sle_and_gle()
 
 	def validate_asset_qty_and_status(self, receipt_document_type, receipt_document):
 		for item in self.get('items'):
@@ -152,14 +153,13 @@
 				docs = frappe.db.get_all('Asset', filters={ receipt_document_type: item.receipt_document,
 					'item_code': item.item_code }, fields=['name', 'docstatus'])
 				if not docs or len(docs) != item.qty:
-					frappe.throw(_('There are not enough asset created or linked to {0}. \
-						Please create or link {1} Assets with respective document.').format(item.receipt_document, item.qty))
+					frappe.throw(_('There are not enough asset created or linked to {0}.').format(item.receipt_document)
+						+ _('Please create or link {0} Assets with respective document.').format(item.qty))
 				if docs:
 					for d in docs:
 						if d.docstatus == 1:
-							frappe.throw(_('{2} <b>{0}</b> has submitted Assets.\
-								Remove Item <b>{1}</b> from table to continue.').format(
-									item.receipt_document, item.item_code, item.receipt_document_type))
+							frappe.throw(_('{0} {1} has submitted Assets. Remove Item {2} from table to continue.')
+								.format(item.receipt_document_type, frappe.bold(item.receipt_document), frappe.bold(item.item_code)))
 
 	def update_rate_in_serial_no_for_non_asset_items(self, receipt_document):
 		for item in receipt_document.get("items"):
diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
index 3f2c5da..b97213e 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
@@ -7,7 +7,7 @@
 import frappe
 from frappe.utils import flt
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \
-	import set_perpetual_inventory, get_gl_entries, test_records as pr_test_records, make_purchase_receipt
+	import get_gl_entries, test_records as pr_test_records, make_purchase_receipt
 from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
 from erpnext.accounts.doctype.account.test_account import get_inventory_account
 
@@ -27,7 +27,7 @@
 			},
 			fieldname=["qty_after_transaction", "stock_value"], as_dict=1)
 
-		submit_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
+		create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
 
 		pr_lc_value = frappe.db.get_value("Purchase Receipt Item", {"parent": pr.name}, "landed_cost_voucher_amount")
 		self.assertEqual(pr_lc_value, 25.0)
@@ -89,7 +89,7 @@
 			},
 			fieldname=["qty_after_transaction", "stock_value"], as_dict=1)
 
-		submit_landed_cost_voucher("Purchase Invoice", pi.name, pi.company)
+		create_landed_cost_voucher("Purchase Invoice", pi.name, pi.company)
 
 		pi_lc_value = frappe.db.get_value("Purchase Invoice Item", {"parent": pi.name},
 			"landed_cost_voucher_amount")
@@ -137,7 +137,7 @@
 
 		serial_no_rate = frappe.db.get_value("Serial No", "SN001", "purchase_rate")
 
-		submit_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
+		create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
 
 		serial_no = frappe.db.get_value("Serial No", "SN001",
 			["warehouse", "purchase_rate"], as_dict=1)
@@ -160,7 +160,7 @@
 			})
 		pr.submit()
 
-		lcv = submit_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, 123.22)
+		lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, 123.22)
 
 		self.assertEqual(lcv.items[0].applicable_charges, 41.07)
 		self.assertEqual(lcv.items[2].applicable_charges, 41.08)
@@ -236,7 +236,7 @@
 	return lcv
 
 
-def submit_landed_cost_voucher(receipt_document_type, receipt_document, company, charges=50):
+def create_landed_cost_voucher(receipt_document_type, receipt_document, company, charges=50):
 	ref_doc = frappe.get_doc(receipt_document_type, receipt_document)
 
 	lcv = frappe.new_doc("Landed Cost Voucher")
diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py
index 19924b1..0a29fa0 100644
--- a/erpnext/stock/doctype/material_request/test_material_request.py
+++ b/erpnext/stock/doctype/material_request/test_material_request.py
@@ -12,9 +12,6 @@
 from erpnext.stock.doctype.item.test_item import create_item
 
 class TestMaterialRequest(unittest.TestCase):
-	def setUp(self):
-		erpnext.set_perpetual_inventory(0)
-
 	def test_make_purchase_order(self):
 		mr = frappe.copy_doc(test_records[0]).insert()
 
diff --git a/erpnext/stock/doctype/packed_item/packed_item.json b/erpnext/stock/doctype/packed_item/packed_item.json
index 2ac5c42..f1d7f8c 100644
--- a/erpnext/stock/doctype/packed_item/packed_item.json
+++ b/erpnext/stock/doctype/packed_item/packed_item.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "creation": "2013-02-22 01:28:00",
  "doctype": "DocType",
  "editable_grid": 1,
@@ -14,6 +15,7 @@
   "target_warehouse",
   "column_break_9",
   "qty",
+  "uom",
   "section_break_9",
   "serial_no",
   "column_break_11",
@@ -23,7 +25,7 @@
   "actual_qty",
   "projected_qty",
   "column_break_16",
-  "uom",
+  "incoming_rate",
   "page_break",
   "prevdoc_doctype",
   "parent_detail_docname"
@@ -199,11 +201,21 @@
    "no_copy": 1,
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "fieldname": "incoming_rate",
+   "fieldtype": "Currency",
+   "label": "Incoming Rate",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
+ "index_web_pages_for_search": 1,
  "istable": 1,
- "modified": "2019-11-26 20:09:59.400960",
+ "links": [],
+ "modified": "2020-09-24 09:25:13.050151",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Packed Item",
@@ -212,4 +224,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index ce54fc8..5bb3095 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -1,5 +1,6 @@
 {
  "actions": [],
+ "allow_auto_repeat": 1,
  "allow_import": 1,
  "autoname": "naming_series:",
  "creation": "2013-05-21 16:16:39",
@@ -110,6 +111,7 @@
   "range",
   "column_break4",
   "per_billed",
+  "per_returned",
   "is_internal_supplier",
   "inter_company_reference",
   "subscription_detail",
@@ -894,7 +896,7 @@
    "no_copy": 1,
    "oldfieldname": "status",
    "oldfieldtype": "Select",
-   "options": "\nDraft\nTo Bill\nCompleted\nCancelled\nClosed",
+   "options": "\nDraft\nTo Bill\nCompleted\nReturn Issued\nCancelled\nClosed",
    "print_hide": 1,
    "print_width": "150px",
    "read_only": 1,
@@ -1103,13 +1105,22 @@
    "fieldtype": "Small Text",
    "label": "Billing Address",
    "read_only": 1
+  },
+  {
+   "depends_on": "eval:!doc.__islocal",
+   "fieldname": "per_returned",
+   "fieldtype": "Percent",
+   "label": "% Returned",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "icon": "fa fa-truck",
  "idx": 261,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-08-03 23:20:26.381024",
+ "modified": "2020-11-30 12:54:23.278500",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index d964669..f833fc7 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -55,20 +55,33 @@
 			'percent_join_field': 'material_request'
 		}]
 		if cint(self.is_return):
-			self.status_updater.append({
-				'source_dt': 'Purchase Receipt Item',
-				'target_dt': 'Purchase Order Item',
-				'join_field': 'purchase_order_item',
-				'target_field': 'returned_qty',
-				'source_field': '-1 * qty',
-				'second_source_dt': 'Purchase Invoice Item',
-				'second_source_field': '-1 * qty',
-				'second_join_field': 'po_detail',
-				'extra_cond': """ and exists (select name from `tabPurchase Receipt`
-					where name=`tabPurchase Receipt Item`.parent and is_return=1)""",
-				'second_source_extra_cond': """ and exists (select name from `tabPurchase Invoice`
-					where name=`tabPurchase Invoice Item`.parent and is_return=1 and update_stock=1)"""
-			})
+			self.status_updater.extend([
+				{
+					'source_dt': 'Purchase Receipt Item',
+					'target_dt': 'Purchase Order Item',
+					'join_field': 'purchase_order_item',
+					'target_field': 'returned_qty',
+					'source_field': '-1 * qty',
+					'second_source_dt': 'Purchase Invoice Item',
+					'second_source_field': '-1 * qty',
+					'second_join_field': 'po_detail',
+					'extra_cond': """ and exists (select name from `tabPurchase Receipt`
+						where name=`tabPurchase Receipt Item`.parent and is_return=1)""",
+					'second_source_extra_cond': """ and exists (select name from `tabPurchase Invoice`
+						where name=`tabPurchase Invoice Item`.parent and is_return=1 and update_stock=1)"""
+				},
+				{
+					'source_dt': 'Purchase Receipt Item',
+					'target_dt': 'Purchase Receipt Item',
+					'join_field': 'purchase_receipt_item',
+					'target_field': 'returned_qty',
+					'target_parent_dt': 'Purchase Receipt',
+					'target_parent_field': 'per_returned',
+					'target_ref_field': 'received_stock_qty',
+					'source_field': '-1 * received_stock_qty',
+					'percent_join_field_parent': 'return_against'
+				}
+			])
 
 	def validate(self):
 		self.validate_posting_time()
@@ -168,6 +181,7 @@
 		update_serial_nos_after_submit(self, "items")
 
 		self.make_gl_entries()
+		self.repost_future_sle_and_gle()
 
 	def check_next_docstatus(self):
 		submit_rv = frappe.db.sql("""select t1.name
@@ -196,7 +210,8 @@
 		# because updating ordered qty in bin depends upon updated ordered qty in PO
 		self.update_stock_ledger()
 		self.make_gl_entries_on_cancel()
-		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
+		self.repost_future_sle_and_gle()
+		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
 		self.delete_auto_created_batches()
 
 	def get_current_stock(self):
@@ -310,7 +325,7 @@
 				elif d.warehouse not in warehouse_with_no_account or \
 					d.rejected_warehouse not in warehouse_with_no_account:
 						warehouse_with_no_account.append(d.warehouse)
-			elif d.item_code not in stock_items and flt(d.qty) and auto_accounting_for_non_stock_items:
+			elif d.item_code not in stock_items and not d.is_fixed_asset and flt(d.qty) and auto_accounting_for_non_stock_items:
 
 				service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed")
 				credit_currency = get_account_currency(service_received_but_not_billed_account)
@@ -393,7 +408,7 @@
 		if warehouse_with_no_account:
 			frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
 				"\n".join(warehouse_with_no_account))
-
+		
 		return process_gl_map(gl_entries)
 
 	def get_asset_gl_entry(self, gl_entries):
@@ -478,7 +493,7 @@
 			frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate))
 
 	def update_status(self, status):
-		self.set_status(update=True, status = status)
+		self.set_status(update=True, status=status)
 		self.notify_update()
 		clear_doctype_notifications(self)
 
@@ -490,7 +505,7 @@
 
 		for pr in set(updated_pr):
 			pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr)
-			pr_doc.update_billing_percentage(update_modified=update_modified)
+			update_billing_percentage(pr_doc, update_modified=update_modified)
 
 		self.load_from_db()
 
@@ -500,7 +515,7 @@
 		where po_detail=%s and (pr_detail is null or pr_detail = '') and docstatus=1""", po_detail)
 	billed_against_po = billed_against_po and billed_against_po[0][0] or 0
 
-	# Get all Delivery Note Item rows against the Sales Order Item row
+	# Get all Purchase Receipt Item rows against the Purchase Order Item row
 	pr_details = frappe.db.sql("""select pr_item.name, pr_item.amount, pr_item.parent
 		from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
 		where pr.name=pr_item.parent and pr_item.purchase_order_item=%s
@@ -530,6 +545,39 @@
 
 	return updated_pr
 
+def update_billing_percentage(pr_doc, update_modified=True):
+	# Reload as billed amount was set in db directly
+	pr_doc.load_from_db()
+
+	# Update Billing % based on pending accepted qty
+	total_amount, total_billed_amount = 0, 0
+	for item in pr_doc.items:
+		return_data = frappe.db.get_list("Purchase Receipt",
+			fields = [
+				"sum(abs(`tabPurchase Receipt Item`.qty)) as qty"
+			],
+			filters = [
+				["Purchase Receipt", "docstatus", "=", 1],
+				["Purchase Receipt", "is_return", "=", 1],
+				["Purchase Receipt Item", "purchase_receipt_item", "=", item.name]
+		])
+
+		returned_qty = return_data[0].qty if return_data else 0
+		returned_amount = flt(returned_qty) * flt(item.rate)
+		pending_amount = flt(item.amount) - returned_amount
+		total_billable_amount = pending_amount if item.billed_amt <= pending_amount else item.billed_amt
+
+		total_amount += total_billable_amount
+		total_billed_amount += flt(item.billed_amt)
+
+	percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6)
+	pr_doc.db_set("per_billed", percent_billed)
+	pr_doc.load_from_db()
+
+	if update_modified:
+		pr_doc.set_status(update=True)
+		pr_doc.notify_update()
+
 @frappe.whitelist()
 def make_purchase_invoice(source_name, target_doc=None):
 	from frappe.model.mapper import get_mapped_doc
@@ -552,6 +600,7 @@
 
 	def update_item(source_doc, target_doc, source_parent):
 		target_doc.qty, returned_qty = get_pending_qty(source_doc)
+		target_doc.stock_qty = flt(target_doc.qty) * flt(target_doc.conversion_factor, target_doc.precision("conversion_factor"))
 		returned_qty_map[source_doc.name] = returned_qty
 
 	def get_pending_qty(item_row):
@@ -572,7 +621,8 @@
 			"doctype": "Purchase Invoice",
 			"field_map": {
 				"supplier_warehouse":"supplier_warehouse",
-				"is_return": "is_return"
+				"is_return": "is_return",
+				"bill_date": "bill_date"
 			},
 			"validation": {
 				"docstatus": ["=", 1],
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
index e81f323..c9501a4 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
@@ -6,9 +6,11 @@
 			return [__("Return"), "darkgrey", "is_return,=,Yes"];
 		} else if (doc.status === "Closed") {
 			return [__("Closed"), "green", "status,=,Closed"];
+		} else if (flt(doc.per_returned, 2) === 100) {
+			return [__("Return Issued"), "grey", "per_returned,=,100"];
 		} else if (flt(doc.grand_total) !== 0 && flt(doc.per_billed, 2) < 100) {
 			return [__("To Bill"), "orange", "per_billed,<,100"];
-		} else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) == 100) {
+		} else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) === 100) {
 			return [__("Completed"), "green", "per_billed,=,100"];
 		}
 	}
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 253edb0..f99ca89 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -9,14 +9,15 @@
 from frappe.utils import cint, flt, cstr, today, random_string, add_days
 from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
 from erpnext.stock.doctype.item.test_item import create_item
-from erpnext import set_perpetual_inventory
 from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError
 from erpnext.accounts.doctype.account.test_account import get_inventory_account
 from erpnext.stock.doctype.item.test_item import make_item
 from six import iteritems
+from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+
+
 class TestPurchaseReceipt(unittest.TestCase):
 	def setUp(self):
-		set_perpetual_inventory(0)
 		frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)
 
 	def test_reverse_purchase_receipt_sle(self):
@@ -112,6 +113,8 @@
 
 		self.assertFalse(get_gl_entries("Purchase Receipt", pr.name))
 
+		pr.cancel()
+
 	def test_batched_serial_no_purchase(self):
 		item = frappe.db.exists("Item", {'item_name': 'Batched Serialized Item'})
 		if not item:
@@ -137,7 +140,10 @@
 		self.assertFalse(frappe.db.get_all('Serial No', {'batch_no': batch_no}))
 
 	def test_purchase_receipt_gl_entry(self):
-		pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", get_multiple_items = True, get_taxes_and_charges = True)
+		pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
+			warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1",
+			get_multiple_items = True, get_taxes_and_charges = True)
+
 		self.assertEqual(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1)
 
 		gl_entries = get_gl_entries("Purchase Receipt", pr.name)
@@ -180,22 +186,30 @@
 
 		rm_supp_cost = sum([d.amount for d in pr.get("supplied_items")])
 		self.assertEqual(pr.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2))
+		
+		pr.cancel()
 
 	def test_subcontracting_gle_fg_item_rate_zero(self):
 		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
-		set_perpetual_inventory()
 		frappe.db.set_value("Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM")
-		make_stock_entry(item_code="_Test Item", target="Work In Progress - TCP1", qty=100, basic_rate=100, company="_Test Company with perpetual inventory")
-		make_stock_entry(item_code="_Test Item Home Desktop 100", target="Work In Progress - TCP1",
+
+		se1 = make_stock_entry(item_code="_Test Item", target="Work In Progress - TCP1",
 			qty=100, basic_rate=100, company="_Test Company with perpetual inventory")
+
+		se2 = make_stock_entry(item_code="_Test Item Home Desktop 100", target="Work In Progress - TCP1",
+			qty=100, basic_rate=100, company="_Test Company with perpetual inventory")
+
 		pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=0, is_subcontracted="Yes",
-			company="_Test Company with perpetual inventory", warehouse='Stores - TCP1', supplier_warehouse='Work In Progress - TCP1')
+			company="_Test Company with perpetual inventory", warehouse='Stores - TCP1',
+			supplier_warehouse='Work In Progress - TCP1')
 
 		gl_entries = get_gl_entries("Purchase Receipt", pr.name)
 
 		self.assertFalse(gl_entries)
 
-		set_perpetual_inventory(0)
+		pr.cancel()
+		se1.cancel()
+		se2.cancel()
 
 	def test_subcontracting_over_receipt(self):
 		"""
@@ -213,13 +227,13 @@
 		item_code = "_Test Subcontracted FG Item 1"
 		make_subcontracted_item(item_code=item_code)
 
-		po = create_purchase_order(item_code=item_code, qty=1,
+		po = create_purchase_order(item_code=item_code, qty=1, include_exploded_items=0,
 			is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
 
 		#stock raw materials in a warehouse before transfer
-		make_stock_entry(target="_Test Warehouse - _TC",
-			item_code = "Test Extra Item 1", qty=1, basic_rate=100)
-		make_stock_entry(target="_Test Warehouse - _TC",
+		se1 = make_stock_entry(target="_Test Warehouse - _TC",
+			item_code = "Test Extra Item 1", qty=10, basic_rate=100)
+		se2 = make_stock_entry(target="_Test Warehouse - _TC",
 			item_code = "_Test FG Item", qty=1, basic_rate=100)
 		rm_items = [
 			{
@@ -251,6 +265,13 @@
 		pr1.submit()
 		self.assertRaises(frappe.ValidationError, pr2.submit)
 
+		pr1.cancel()
+		se.cancel()
+		se1.cancel()
+		se2.cancel()
+		po.reload()
+		po.cancel()
+
 	def test_serial_no_supplier(self):
 		pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)
 		self.assertEqual(frappe.db.get_value("Serial No", pr.get("items")[0].serial_no, "supplier"),
@@ -281,11 +302,17 @@
 			self.assertEqual(frappe.db.get_value("Serial No", serial_no, "warehouse"),
 				pr.get("items")[0].rejected_warehouse)
 
-	def test_purchase_return(self):
+		pr.cancel()
 
-		pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1")
+	def test_purchase_return_partial(self):
+		pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
+			warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1")
 
-		return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-2)
+		return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
+			warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1",
+			is_return=1, return_against=pr.name, qty=-2, do_not_submit=1)
+		return_pr.items[0].purchase_receipt_item = pr.items[0].name
+		return_pr.submit()
 
 		# check sle
 		outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
@@ -309,6 +336,63 @@
 			self.assertEqual(expected_values[gle.account][0], gle.debit)
 			self.assertEqual(expected_values[gle.account][1], gle.credit)
 
+		# hack because new_doc isn't considering is_return portion of status_updater
+		returned = frappe.get_doc("Purchase Receipt", return_pr.name)
+		returned.update_prevdoc_status()
+		pr.load_from_db()
+
+		# Check if Original PR updated
+		self.assertEqual(pr.items[0].returned_qty, 2)
+		self.assertEqual(pr.per_returned, 40)
+
+		from erpnext.controllers.sales_and_purchase_return import make_return_doc
+		return_pr_2 = make_return_doc("Purchase Receipt", pr.name)
+
+		# Check if unreturned amount is mapped in 2nd return
+		self.assertEqual(return_pr_2.items[0].qty, -3)
+
+		# Make PI against unreturned amount
+		pi = make_purchase_invoice(pr.name)
+		pi.submit()
+
+		self.assertEqual(pi.items[0].qty, 3)
+
+		pr.load_from_db()
+		# PR should be completed on billing all unreturned amount
+		self.assertEqual(pr.items[0].billed_amt, 150)
+		self.assertEqual(pr.per_billed, 100)
+		self.assertEqual(pr.status, 'Completed')
+
+		pi.load_from_db()
+		pi.cancel()
+
+		pr.load_from_db()
+		self.assertEqual(pr.per_billed, 0)
+
+		return_pr.cancel()
+		pr.cancel()
+
+	def test_purchase_return_full(self):
+		pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
+			supplier_warehouse = "Work in Progress - TCP1")
+
+		return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
+			supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-5, do_not_submit=1)
+		return_pr.items[0].purchase_receipt_item = pr.items[0].name
+		return_pr.submit()
+
+		# hack because new_doc isn't considering is_return portion of status_updater
+		returned = frappe.get_doc("Purchase Receipt", return_pr.name)
+		returned.update_prevdoc_status()
+		pr.load_from_db()
+
+		# Check if Original PR updated
+		self.assertEqual(pr.items[0].returned_qty, 5)
+		self.assertEqual(pr.per_returned, 100)
+		self.assertEqual(pr.status, 'Return Issued')
+
+		return_pr.cancel()
+		pr.cancel()
 
 	def test_purchase_return_for_rejected_qty(self):
 		from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse
@@ -327,6 +411,9 @@
 
 		self.assertEqual(actual_qty, -2)
 
+		return_pr.cancel()
+		pr.cancel()
+
 
 	def test_purchase_return_for_serialized_items(self):
 		def _check_serial_no_values(serial_no, field_values):
@@ -354,6 +441,10 @@
 			"delivery_document_no": return_pr.name
 		})
 
+		return_pr.cancel()
+		pr.reload()
+		pr.cancel()
+
 	def test_purchase_return_for_multi_uom(self):
 		item_code = "_Test Purchase Return For Multi-UOM"
 		if not frappe.db.exists('Item', item_code):
@@ -370,6 +461,9 @@
 
 		self.assertEqual(abs(return_pr.items[0].stock_qty), 1.0)
 
+		return_pr.cancel()
+		pr.cancel()
+
 	def test_closed_purchase_receipt(self):
 		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_purchase_receipt_status
 
@@ -379,6 +473,9 @@
 		update_purchase_receipt_status(pr.name, "Closed")
 		self.assertEqual(frappe.db.get_value("Purchase Receipt", pr.name, "status"), "Closed")
 
+		pr.reload()
+		pr.cancel()
+
 	def test_pr_billing_status(self):
 		# PO -> PR1 -> PI and PO -> PI and PO -> PR2
 		from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
@@ -416,10 +513,21 @@
 		self.assertEqual(pr1.per_billed, 100)
 		self.assertEqual(pr1.status, "Completed")
 
+		pr2.load_from_db()
 		self.assertEqual(pr2.get("items")[0].billed_amt, 2000)
 		self.assertEqual(pr2.per_billed, 80)
 		self.assertEqual(pr2.status, "To Bill")
 
+		pr2.cancel()
+		pi2.reload()
+		pi2.cancel()
+		pi1.reload()
+		pi1.cancel()
+		pr1.reload()
+		pr1.cancel()
+		po.reload()
+		po.cancel()
+
 	def test_serial_no_against_purchase_receipt(self):
 		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 
@@ -447,6 +555,8 @@
 		self.assertEqual(serial_no, frappe.db.get_value("Serial No",
 			{"purchase_document_type": "Purchase Receipt", "purchase_document_no": new_pr_doc.name}, "name"))
 
+		new_pr_doc.cancel()
+
 	def test_not_accept_duplicate_serial_no(self):
 		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
 		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
@@ -457,15 +567,18 @@
 			item_code = item.name
 
 		serial_no = random_string(5)
-		make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no)
-		create_delivery_note(item_code=item_code, qty=1, serial_no=serial_no)
+		pr1 = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no)
+		dn = create_delivery_note(item_code=item_code, qty=1, serial_no=serial_no)
 
-		pr = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no, do_not_submit=True)
-		self.assertRaises(SerialNoDuplicateError, pr.submit)
+		pr2 = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no, do_not_submit=True)
+		self.assertRaises(SerialNoDuplicateError, pr2.submit)
 
 		se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1,
 			serial_no=serial_no, basic_rate=100, do_not_submit=True)
-		self.assertRaises(SerialNoDuplicateError, se.submit)
+		se.submit()
+
+		dn.cancel()
+		pr1.cancel()
 
 	def test_auto_asset_creation(self):
 		asset_item = "Test Asset Item"
@@ -487,7 +600,7 @@
 						'company_name': '_Test Company',
 						'fixed_asset_account': '_Test Fixed Asset - _TC',
 						'accumulated_depreciation_account': '_Test Accumulated Depreciations - _TC',
-						'depreciation_expense_account': '_Test Depreciation - _TC'
+						'depreciation_expense_account': '_Test Depreciations - _TC'
 					}]
 				}).insert()
 
@@ -506,6 +619,8 @@
 		location = frappe.db.get_value('Asset', assets[0].name, 'location')
 		self.assertEquals(location, "Test Location")
 
+		pr.cancel()
+
 	def test_purchase_return_with_submitted_asset(self):
 		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return
 
@@ -532,6 +647,9 @@
 
 		pr_return.submit()
 
+		pr_return.cancel()
+		pr.cancel()
+
 	def test_purchase_receipt_cost_center(self):
 		from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
 		cost_center = "_Test Cost Center for BS Account - TCP1"
@@ -543,7 +661,8 @@
 				'location_name': 'Test Location'
 			}).insert()
 
-		pr = make_purchase_receipt(cost_center=cost_center, company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1")
+		pr = make_purchase_receipt(cost_center=cost_center, company="_Test Company with perpetual inventory",
+			warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1")
 
 		stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse)
 		gl_entries = get_gl_entries("Purchase Receipt", pr.name)
@@ -561,6 +680,8 @@
 		for i, gle in enumerate(gl_entries):
 			self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
 
+		pr.cancel()
+
 	def test_purchase_receipt_cost_center_with_balance_sheet_account(self):
 		if not frappe.db.exists('Location', 'Test Location'):
 			frappe.get_doc({
@@ -586,6 +707,8 @@
 		for i, gle in enumerate(gl_entries):
 			self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
 
+		pr.cancel()
+
 	def test_make_purchase_invoice_from_pr_for_returned_qty(self):
 		from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order, create_pr_against_po
 
@@ -601,6 +724,12 @@
 		pi = make_purchase_invoice(pr.name)
 		self.assertEquals(pi.items[0].qty, 3)
 
+		pr1.cancel()
+		pr.reload()
+		pr.cancel()
+		po.reload()
+		po.cancel()
+
 	def test_make_purchase_invoice_from_pr_with_returned_qty_duplicate_items(self):
 		pr1 = make_purchase_receipt(qty=8, do_not_submit=True)
 		pr1.append("items", {
@@ -627,8 +756,14 @@
 		self.assertEquals(pi2.items[0].qty, 2)
 		self.assertEquals(pi2.items[1].qty, 1)
 
+		pr2.cancel()
+		pi1.cancel()
+		pr1.reload()
+		pr1.cancel()
+
 	def test_stock_transfer_from_purchase_receipt(self):
-		pr1 = make_purchase_receipt(warehouse = 'Work In Progress - TCP1', company="_Test Company with perpetual inventory")
+		pr1 = make_purchase_receipt(warehouse = 'Work In Progress - TCP1',
+			company="_Test Company with perpetual inventory")
 
 		pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
 			warehouse = "Stores - TCP1", do_not_save=1)
@@ -651,18 +786,20 @@
 		for sle in sl_entries:
 			self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty)
 
-	def test_stock_transfer_from_purchase_receipt_with_valuation(self):
-		warehouse = frappe.get_doc('Warehouse', 'Work In Progress - TCP1')
-		warehouse.account = '_Test Account Stock In Hand - TCP1'
-		warehouse.save()
+		pr.cancel()
+		pr1.cancel()
 
-		pr1 = make_purchase_receipt(warehouse = 'Work In Progress - TCP1',
+	def test_stock_transfer_from_purchase_receipt_with_valuation(self):
+		create_warehouse("_Test Warehouse for Valuation", company="_Test Company with perpetual inventory",
+			properties={"account": '_Test Account Stock In Hand - TCP1'})
+
+		pr1 = make_purchase_receipt(warehouse = '_Test Warehouse for Valuation - TCP1',
 			company="_Test Company with perpetual inventory")
 
 		pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
 			warehouse = "Stores - TCP1", do_not_save=1)
 
-		pr.items[0].from_warehouse = 'Work In Progress - TCP1'
+		pr.items[0].from_warehouse = '_Test Warehouse for Valuation - TCP1'
 		pr.supplier_warehouse = ''
 
 
@@ -687,7 +824,7 @@
 		]
 
 		expected_sle = {
-			'Work In Progress - TCP1': -5,
+			'_Test Warehouse for Valuation - TCP1': -5,
 			'Stores - TCP1': 5
 		}
 
@@ -699,60 +836,9 @@
 			self.assertEqual(gle.debit, expected_gle[i][1])
 			self.assertEqual(gle.credit, expected_gle[i][2])
 
-		warehouse.account = ''
-		warehouse.save()
+		pr.cancel()
+		pr1.cancel()
 
-	def test_backdated_purchase_receipt(self):
-		# make purchase receipt for default company
-		make_purchase_receipt(company="_Test Company 4", warehouse="Stores - _TC4")
-
-		# try to make another backdated PR
-		posting_date = add_days(today(), -1)
-		pr = make_purchase_receipt(company="_Test Company 4", warehouse="Stores - _TC4",
-			do_not_submit=True)
-
-		pr.set_posting_time = 1
-		pr.posting_date = posting_date
-		pr.save()
-
-		self.assertRaises(frappe.ValidationError, pr.submit)
-
-		# make purchase receipt for other company backdated
-		pr = make_purchase_receipt(company="_Test Company 5", warehouse="Stores - _TC5",
-			do_not_submit=True)
-
-		pr.set_posting_time = 1
-		pr.posting_date = posting_date
-		pr.submit()
-
-		# Allowed to submit for other company's PR
-		self.assertEqual(pr.docstatus, 1)
-
-	def test_backdated_purchase_receipt_for_same_company_different_warehouse(self):
-			# make purchase receipt for default company
-		make_purchase_receipt(company="_Test Company 4", warehouse="Stores - _TC4")
-
-		# try to make another backdated PR
-		posting_date = add_days(today(), -1)
-		pr = make_purchase_receipt(company="_Test Company 4", warehouse="Stores - _TC4",
-			do_not_submit=True)
-
-		pr.set_posting_time = 1
-		pr.posting_date = posting_date
-		pr.save()
-
-		self.assertRaises(frappe.ValidationError, pr.submit)
-
-		# make purchase receipt for other company backdated
-		pr = make_purchase_receipt(company="_Test Company 4", warehouse="Finished Goods - _TC4",
-			do_not_submit=True)
-
-		pr.set_posting_time = 1
-		pr.posting_date = posting_date
-		pr.submit()
-
-		# Allowed to submit for other company's PR
-		self.assertEqual(pr.docstatus, 1)
 
 	def test_subcontracted_pr_for_multi_transfer_batches(self):
 		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
@@ -815,6 +901,12 @@
 
 		update_backflush_based_on("BOM")
 
+		pr.delete()
+		se.cancel()
+		ste2.cancel()
+		ste1.cancel()
+		po.cancel()
+
 def get_sl_entries(voucher_type, voucher_no):
 	return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference
 		from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s
@@ -910,6 +1002,8 @@
 	pr.posting_date = args.posting_date or today()
 	if args.posting_time:
 		pr.posting_time = args.posting_time
+	if args.posting_date or args.posting_time:
+		pr.set_posting_time = 1
 	pr.company = args.company or "_Test Company"
 	pr.supplier = args.supplier or "_Test Supplier"
 	pr.is_subcontracted = args.is_subcontracted or "No"
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 c1e1f90..871b255 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -28,9 +28,13 @@
   "uom",
   "stock_uom",
   "conversion_factor",
-  "stock_qty",
   "retain_sample",
   "sample_quantity",
+  "tracking_section",
+  "received_stock_qty",
+  "stock_qty",
+  "col_break_tracking_section",
+  "returned_qty",
   "rate_and_amount",
   "price_list_rate",
   "discount_percentage",
@@ -526,7 +530,7 @@
   {
    "fieldname": "stock_qty",
    "fieldtype": "Float",
-   "label": "Accepted Qty as per Stock UOM",
+   "label": "Accepted Qty in Stock UOM",
    "oldfieldname": "stock_qty",
    "oldfieldtype": "Currency",
    "print_hide": 1,
@@ -834,12 +838,35 @@
    "collapsible": 1,
    "fieldname": "image_column",
    "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "tracking_section",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "col_break_tracking_section",
+   "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "returned_qty",
+   "fieldname": "returned_qty",
+   "fieldtype": "Float",
+   "label": "Returned Qty in Stock UOM",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "received_stock_qty",
+   "fieldtype": "Float",
+   "label": "Received Qty in Stock UOM",
+   "print_hide": 1
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-04-28 19:01:21.154963",
+ "modified": "2020-12-07 10:00:38.204294",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js
index 22f29e0..03e3de1 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js
@@ -4,6 +4,11 @@
 cur_frm.cscript.refresh = cur_frm.cscript.inspection_type;
 
 frappe.ui.form.on("Quality Inspection", {
+	refresh: function(frm) {
+		// Ignore cancellation of reference doctype on cancel all.
+		frm.ignore_doctypes_on_cancel_all = [frm.doc.reference_type];
+	},
+
 	item_code: function(frm) {
 		if (frm.doc.item_code) {
 			return frm.call({
@@ -31,17 +36,27 @@
 
 // item code based on GRN/DN
 cur_frm.fields_dict['item_code'].get_query = function(doc, cdt, cdn) {
-	const doctype = (doc.reference_type == "Stock Entry") ?
-		"Stock Entry Detail" : doc.reference_type + " Item";
+	let doctype = doc.reference_type;
+
+	if (doc.reference_type !== "Job Card") {
+		doctype = (doc.reference_type == "Stock Entry") ?
+			"Stock Entry Detail" : doc.reference_type + " Item";
+	}
 
 	if (doc.reference_type && doc.reference_name) {
+		let filters = {
+			"from": doctype,
+			"inspection_type": doc.inspection_type
+		};
+
+		if (doc.reference_type == doctype)
+			filters["reference_name"] = doc.reference_name;
+		else
+			filters["parent"] = doc.reference_name;
+
 		return {
 			query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query",
-			filters: {
-				"from": doctype,
-				"parent": doc.reference_name,
-				"inspection_type": doc.inspection_type
-			}
+			filters: filters
 		};
 	}
 },
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
index dd95075..f6d7619 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
@@ -73,7 +73,7 @@
    "fieldname": "reference_type",
    "fieldtype": "Select",
    "label": "Reference Type",
-   "options": "\nPurchase Receipt\nPurchase Invoice\nDelivery Note\nSales Invoice\nStock Entry",
+   "options": "\nPurchase Receipt\nPurchase Invoice\nDelivery Note\nSales Invoice\nStock Entry\nJob Card",
    "reqd": 1
   },
   {
@@ -236,7 +236,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-21 13:03:11.938072",
+ "modified": "2020-11-19 17:06:05.409963",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Quality Inspection",
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index c3bb514..ae4eb9b 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -4,15 +4,20 @@
 from __future__ import unicode_literals
 import frappe
 from frappe.model.document import Document
+from frappe.model.mapper import get_mapped_doc
+from frappe import _
+from frappe.utils import flt
 from erpnext.stock.doctype.quality_inspection_template.quality_inspection_template \
 	import get_template_details
-from frappe.model.mapper import get_mapped_doc
 
 class QualityInspection(Document):
 	def validate(self):
 		if not self.readings and self.item_code:
 			self.get_item_specification_details()
 
+		if self.readings:
+			self.set_status_based_on_acceptance_formula()
+
 	def get_item_specification_details(self):
 		if not self.quality_inspection_template:
 			self.quality_inspection_template = frappe.db.get_value('Item',
@@ -26,6 +31,7 @@
 			child = self.append('readings', {})
 			child.specification = d.specification
 			child.value = d.value
+			child.acceptance_formula = d.acceptance_formula
 			child.status = "Accepted"
 
 	def get_quality_inspection_template(self):
@@ -47,16 +53,51 @@
 
 	def update_qc_reference(self):
 		quality_inspection = self.name if self.docstatus == 1 else ""
-		doctype = self.reference_type + ' Item'
-		if self.reference_type == 'Stock Entry':
-			doctype = 'Stock Entry Detail'
 
-		if self.reference_type and self.reference_name:
-			frappe.db.sql("""update `tab{child_doc}` t1, `tab{parent_doc}` t2
-				set t1.quality_inspection = %s, t2.modified = %s
-				where t1.parent = %s and t1.item_code = %s and t1.parent = t2.name"""
-				.format(parent_doc=self.reference_type, child_doc=doctype),
-				(quality_inspection, self.modified, self.reference_name, self.item_code))
+		if self.reference_type == 'Job Card':
+			if self.reference_name:
+				frappe.db.sql("""
+					UPDATE `tab{doctype}`
+					SET quality_inspection = %s, modified = %s
+					WHERE name = %s and production_item = %s
+				""".format(doctype=self.reference_type),
+					(quality_inspection, self.modified, self.reference_name, self.item_code))
+
+		else:
+			doctype = self.reference_type + ' Item'
+			if self.reference_type == 'Stock Entry':
+				doctype = 'Stock Entry Detail'
+
+			if self.reference_type and self.reference_name:
+				frappe.db.sql("""
+					UPDATE `tab{child_doc}` t1, `tab{parent_doc}` t2
+					SET t1.quality_inspection = %s, t2.modified = %s
+					WHERE t1.parent = %s and t1.item_code = %s and t1.parent = t2.name
+				""".format(parent_doc=self.reference_type, child_doc=doctype),
+					(quality_inspection, self.modified, self.reference_name, self.item_code))
+
+	def set_status_based_on_acceptance_formula(self):
+		for reading in self.readings:
+			if not reading.acceptance_formula: continue
+
+			condition = reading.acceptance_formula
+			data = {}
+			for i in range(1, 11):
+				field = "reading_" + str(i)
+				data[field] = flt(reading.get(field)) or 0
+
+			try:
+				result = frappe.safe_eval(condition, None, data)
+				reading.status = "Accepted" if result else "Rejected"
+			except SyntaxError:
+				frappe.throw(_("Row #{0}: Acceptance Criteria Formula is incorrect.").format(reading.idx),
+					title=_("Invalid Formula"))
+			except NameError as e:
+				field = frappe.bold(e.args[0].split()[1])
+				frappe.throw(_("Row #{0}: {1} is not a valid reading field. Please refer to the field description.")
+					.format(reading.idx, field),
+					title=_("Invalid Formula"))
+
 
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
@@ -66,27 +107,44 @@
 		mcond = get_match_cond(filters["from"])
 		cond, qi_condition = "", "and (quality_inspection is null or quality_inspection = '')"
 
-		if filters.get('from') in ['Purchase Invoice Item', 'Purchase Receipt Item']\
-				and filters.get("inspection_type") != "In Process":
-			cond = """and item_code in (select name from `tabItem` where
-				inspection_required_before_purchase = 1)"""
-		elif filters.get('from') in ['Sales Invoice Item', 'Delivery Note Item']\
-				and filters.get("inspection_type") != "In Process":
-			cond = """and item_code in (select name from `tabItem` where
-				inspection_required_before_delivery = 1)"""
-		elif filters.get('from') == 'Stock Entry Detail':
-			cond = """and s_warehouse is null"""
+		if filters.get("parent"):
+			if filters.get('from') in ['Purchase Invoice Item', 'Purchase Receipt Item']\
+					and filters.get("inspection_type") != "In Process":
+				cond = """and item_code in (select name from `tabItem` where
+					inspection_required_before_purchase = 1)"""
+			elif filters.get('from') in ['Sales Invoice Item', 'Delivery Note Item']\
+					and filters.get("inspection_type") != "In Process":
+				cond = """and item_code in (select name from `tabItem` where
+					inspection_required_before_delivery = 1)"""
+			elif filters.get('from') == 'Stock Entry Detail':
+				cond = """and s_warehouse is null"""
 
-		if filters.get('from') in ['Supplier Quotation Item']:
-			qi_condition = ""
+			if filters.get('from') in ['Supplier Quotation Item']:
+				qi_condition = ""
 
-		return frappe.db.sql(""" select item_code from `tab{doc}`
-			where parent=%(parent)s and docstatus < 2 and item_code like %(txt)s
-			{qi_condition} {cond} {mcond}
-			order by item_code limit {start}, {page_len}""".format(doc=filters.get('from'),
-			parent=filters.get('parent'), cond = cond, mcond = mcond, start = start,
-			page_len = page_len, qi_condition = qi_condition),
-			{'parent': filters.get('parent'), 'txt': "%%%s%%" % txt})
+			return frappe.db.sql("""
+					SELECT item_code
+					FROM `tab{doc}`
+					WHERE parent=%(parent)s and docstatus < 2 and item_code like %(txt)s
+					{qi_condition} {cond} {mcond}
+					ORDER BY item_code limit {start}, {page_len}
+				""".format(doc=filters.get('from'),
+					cond = cond, mcond = mcond, start = start,
+					page_len = page_len, qi_condition = qi_condition),
+					{'parent': filters.get('parent'), 'txt': "%%%s%%" % txt})
+
+		elif filters.get("reference_name"):
+			return frappe.db.sql("""
+					SELECT production_item
+					FROM `tab{doc}`
+					WHERE name = %(reference_name)s and docstatus < 2 and production_item like %(txt)s
+					{qi_condition} {cond} {mcond}
+					ORDER BY production_item
+					LIMIT {start}, {page_len}
+				""".format(doc=filters.get("from"),
+					cond = cond, mcond = mcond, start = start,
+					page_len = page_len, qi_condition = qi_condition),
+					{'reference_name': filters.get('reference_name'), 'txt': "%%%s%%" % txt})
 
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
index bb535c1..2c40009 100644
--- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
@@ -7,6 +7,7 @@
 from frappe.utils import nowdate
 from erpnext.stock.doctype.item.test_item import create_item
 from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
 from erpnext.controllers.stock_controller import QualityInspectionRejectedError, QualityInspectionRequiredError, QualityInspectionNotSubmittedError
 
 # test_records = frappe.get_test_records('Quality Inspection')
@@ -17,10 +18,12 @@
 		frappe.db.set_value("Item", "_Test Item with QA", "inspection_required_before_delivery", 1)
 
 	def test_qa_for_delivery(self):
+		make_stock_entry(item_code="_Test Item with QA", target="_Test Warehouse - _TC", qty=1, basic_rate=100)
 		dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
+
 		self.assertRaises(QualityInspectionRequiredError, dn.submit)
 
-		qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, status="Rejected", submit=True)
+		qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, status="Rejected")
 		dn.reload()
 		self.assertRaises(QualityInspectionRejectedError, dn.submit)
 
@@ -28,12 +31,51 @@
 		dn.reload()
 		dn.submit()
 
+		qa.cancel()
+		dn.reload()
+		dn.cancel()
+
 	def test_qa_not_submit(self):
 		dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
-		qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, submit = False)
+		qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, do_not_submit=True)
 		dn.items[0].quality_inspection = qa.name
 		self.assertRaises(QualityInspectionNotSubmittedError, dn.submit)
 
+		qa.delete()
+		dn.delete()
+
+	def test_formula_based_qi_readings(self):
+		dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
+		readings = [{
+			"specification": "Iron Content",
+			"acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50",
+			"reading_1": 0.4
+		},
+		{
+			"specification": "Calcium Content",
+			"acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50",
+			"reading_1": 0.7
+		},
+		{
+			"specification": "Mg Content",
+			"acceptance_formula": "(reading_1 + reading_2 + reading_3) / 3 < 0.9",
+			"reading_1": 0.5,
+			"reading_2": 0.7,
+			"reading_3": "random text" # check if random string input causes issues
+		}]
+
+		qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name,
+			readings=readings, do_not_save=True)
+		qa.save()
+
+		# status must be auto set as per formula
+		self.assertEqual(qa.readings[0].status, "Accepted")
+		self.assertEqual(qa.readings[1].status, "Rejected")
+		self.assertEqual(qa.readings[2].status, "Accepted")
+
+		qa.delete()
+		dn.delete()
+
 def create_quality_inspection(**args):
 	args = frappe._dict(args)
 	qa = frappe.new_doc("Quality Inspection")
@@ -44,12 +86,18 @@
 	qa.item_code = args.item_code or "_Test Item with QA"
 	qa.sample_size = 1
 	qa.inspected_by = frappe.session.user
-	qa.append("readings", {
-		"specification": "Size",
-		"status": args.status
-	})
-	qa.save()
-	if args.submit:
-		qa.submit()
+
+	readings = args.readings or {"specification": "Size", "status": args.status}
+
+	if isinstance(readings, list):
+		for entry in readings:
+			qa.append("readings", entry)
+	else:
+		qa.append("readings", readings)
+
+	if not args.do_not_save:
+		qa.save()
+		if not args.do_not_submit:
+			qa.submit()
 
 	return qa
diff --git a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json
index f9f8a71..c1976dd 100644
--- a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json
+++ b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json
@@ -1,22 +1,29 @@
 {
+ "actions": [],
  "autoname": "hash",
  "creation": "2013-02-22 01:27:43",
  "doctype": "DocType",
  "editable_grid": 1,
+ "engine": "InnoDB",
  "field_order": [
   "specification",
   "value",
+  "status",
+  "column_break_4",
+  "acceptance_formula",
+  "section_break_3",
   "reading_1",
   "reading_2",
   "reading_3",
+  "column_break_10",
   "reading_4",
   "reading_5",
   "reading_6",
+  "column_break_14",
   "reading_7",
   "reading_8",
   "reading_9",
-  "reading_10",
-  "status"
+  "reading_10"
  ],
  "fields": [
   {
@@ -124,15 +131,40 @@
    "oldfieldname": "status",
    "oldfieldtype": "Select",
    "options": "Accepted\nRejected"
+  },
+  {
+   "fieldname": "section_break_3",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
+  {
+   "description": "Simple Python formula based on numeric Readings.<br> Example 1: <b>reading_1 &gt; 0.2 and reading_1 &lt; 0.5</b><br>\nExample 2: <b>(reading_1 + reading_2) / 2 &lt; 10</b>",
+   "fieldname": "acceptance_formula",
+   "fieldtype": "Code",
+   "label": "Acceptance Criteria Formula"
+  },
+  {
+   "fieldname": "column_break_10",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "column_break_14",
+   "fieldtype": "Column Break"
   }
  ],
  "idx": 1,
  "istable": 1,
- "modified": "2019-07-11 18:48:12.667404",
+ "links": [],
+ "modified": "2020-11-16 16:34:29.947856",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Quality Inspection Reading",
  "owner": "Administrator",
  "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
index 0d9a903..e284846 100644
--- a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
+++ b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
@@ -12,5 +12,7 @@
 def get_template_details(template):
 	if not template: return []
 
-	return frappe.get_all('Item Quality Inspection Parameter', fields=["specification", "value"],
-		filters={'parenttype': 'Quality Inspection Template', 'parent': template}, order_by="idx")
\ No newline at end of file
+	return frappe.get_all('Item Quality Inspection Parameter',
+		fields=["specification", "value", "acceptance_formula"],
+		filters={'parenttype': 'Quality Inspection Template', 'parent': template},
+		order_by="idx")
\ No newline at end of file
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/stock/doctype/repost_item_valuation/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/stock/doctype/repost_item_valuation/__init__.py
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
new file mode 100644
index 0000000..e429cd5
--- /dev/null
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
@@ -0,0 +1,52 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Repost Item Valuation', {
+	setup: function(frm) {
+		frm.set_query("warehouse", () => {
+			let filters = {
+				'is_group': 0
+			};
+			if (frm.doc.company) filters['company'] = frm.doc.company;
+			return {filters: filters};
+		});
+
+		frm.set_query("voucher_type", () => {
+			return {
+				filters: {
+					name: ['in', ['Purchase Receipt', 'Purchase Invoice', 'Delivery Note',
+						'Sales Invoice', 'Stock Entry', 'Stock Reconciliation']]
+				}
+			};
+		});
+
+		if (frm.doc.company) {
+			frm.set_query("voucher_no", () => {
+				return {
+					filters: {
+						company: frm.doc.company
+					}
+				};
+			});
+		}
+	},
+	refresh: function(frm) {
+		if (frm.doc.status == "Failed") {
+			frm.add_custom_button(__('Restart'), function () {
+				frm.trigger("restart_reposting");
+			}).addClass("btn-primary");
+		}
+	},
+
+	restart_reposting: function(frm) {
+		frappe.call({
+			method: "restart_reposting",
+			doc: frm.doc,
+			callback: function(r) {
+				if (!r.exc) {
+					frm.refresh();
+				}
+			}
+		});
+	}
+});
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
new file mode 100644
index 0000000..071fc86
--- /dev/null
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
@@ -0,0 +1,215 @@
+{
+ "actions": [],
+ "autoname": "REPOST-ITEM-VAL-.######",
+ "creation": "2020-10-22 22:27:07.742161",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "based_on",
+  "voucher_type",
+  "voucher_no",
+  "item_code",
+  "warehouse",
+  "posting_date",
+  "posting_time",
+  "column_break_5",
+  "status",
+  "company",
+  "allow_negative_stock",
+  "via_landed_cost_voucher",
+  "allow_zero_rate",
+  "amended_from",
+  "error_section",
+  "error_log"
+ ],
+ "fields": [
+  {
+   "depends_on": "eval:doc.based_on=='Item and Warehouse'",
+   "fieldname": "item_code",
+   "fieldtype": "Link",
+   "label": "Item Code",
+   "mandatory_depends_on": "eval:doc.based_on=='Item and Warehouse'",
+   "options": "Item"
+  },
+  {
+   "depends_on": "eval:doc.based_on=='Item and Warehouse'",
+   "fieldname": "warehouse",
+   "fieldtype": "Link",
+   "label": "Warehouse",
+   "mandatory_depends_on": "eval:doc.based_on=='Item and Warehouse'",
+   "options": "Warehouse"
+  },
+  {
+   "fetch_from": "voucher_no.posting_date",
+   "fieldname": "posting_date",
+   "fieldtype": "Date",
+   "label": "Posting Date",
+   "reqd": 1
+  },
+  {
+   "fetch_from": "voucher_no.posting_time",
+   "fieldname": "posting_time",
+   "fieldtype": "Time",
+   "label": "Posting Time"
+  },
+  {
+   "default": "Queued",
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Status",
+   "no_copy": 1,
+   "options": "Queued\nIn Progress\nCompleted\nFailed",
+   "read_only": 1
+  },
+  {
+   "fieldname": "amended_from",
+   "fieldtype": "Link",
+   "label": "Amended From",
+   "no_copy": 1,
+   "options": "Repost Item Valuation",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_5",
+   "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "eval:doc.status=='Failed'",
+   "fieldname": "error_section",
+   "fieldtype": "Section Break",
+   "label": "Error"
+  },
+  {
+   "fieldname": "error_log",
+   "fieldtype": "Long Text",
+   "label": "Error Log",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "fetch_from": "warehouse.company",
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company"
+  },
+  {
+   "depends_on": "eval:doc.based_on=='Transaction'",
+   "fieldname": "voucher_type",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Voucher Type",
+   "mandatory_depends_on": "eval:doc.based_on=='Transaction'",
+   "options": "DocType"
+  },
+  {
+   "depends_on": "eval:doc.based_on=='Transaction'",
+   "fieldname": "voucher_no",
+   "fieldtype": "Dynamic Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Voucher No",
+   "mandatory_depends_on": "eval:doc.based_on=='Transaction'",
+   "options": "voucher_type"
+  },
+  {
+   "default": "Transaction",
+   "fieldname": "based_on",
+   "fieldtype": "Select",
+   "label": "Based On",
+   "options": "Transaction\nItem and Warehouse",
+   "reqd": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "allow_negative_stock",
+   "fieldtype": "Check",
+   "label": "Allow Negative Stock"
+  },
+  {
+   "default": "0",
+   "fieldname": "via_landed_cost_voucher",
+   "fieldtype": "Check",
+   "label": "Via Landed Cost Voucher"
+  },
+  {
+   "default": "0",
+   "fieldname": "allow_zero_rate",
+   "fieldtype": "Check",
+   "label": "Allow Zero Rate"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-12-10 07:52:12.476589",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Repost Item Valuation",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Stock User",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Stock Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
new file mode 100644
index 0000000..ba2c2c6
--- /dev/null
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -0,0 +1,112 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe, erpnext
+from frappe.model.document import Document
+from frappe.utils import cint, get_link_to_form
+from erpnext.stock.stock_ledger import repost_future_sle
+from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced
+from frappe.utils.user import get_users_with_role
+from frappe import _
+class RepostItemValuation(Document):
+	def validate(self):
+		self.set_status()
+		self.reset_field_values()
+		self.set_company()
+
+	def reset_field_values(self):
+		if self.based_on == 'Transaction':
+			self.item_code = None
+			self.warehouse = None
+		else:
+			self.voucher_type = None
+			self.voucher_no = None
+
+	def set_company(self):
+		if self.voucher_type and self.voucher_no:
+			self.company = frappe.get_cached_value(self.voucher_type, self.voucher_no, "company")
+		elif self.warehouse:
+			self.company = frappe.get_cached_value("Warehouse", self.warehouse, "company")
+	
+	def set_status(self, status=None):
+		if not status:
+			status = 'Queued'
+		self.db_set('status', status)
+
+	def on_submit(self):
+		frappe.enqueue(repost, timeout=1800, queue='long',
+			job_name='repost_sle', now=frappe.flags.in_test, doc=self)
+
+	def restart_reposting(self):
+		self.set_status('Queued')
+		frappe.enqueue(repost, timeout=1800, queue='long',
+			job_name='repost_sle', now=True, doc=self)
+
+def repost(doc):
+	try:
+		doc.set_status('In Progress')
+		frappe.db.commit()
+
+		repost_sl_entries(doc)
+		repost_gl_entries(doc)
+		check_if_stock_and_account_balance_synced(doc.posting_date, doc.company)
+
+		doc.set_status('Completed')
+	except Exception:
+		frappe.db.rollback()
+		traceback = frappe.get_traceback()
+		frappe.log_error(traceback)
+
+		message = frappe.message_log.pop()
+		if traceback:
+			message += "<br>" + "Traceback: <br>" + traceback
+		frappe.db.set_value(doc.doctype, doc.name, 'error_log', message)
+
+		notify_error_to_stock_managers(doc)
+		doc.set_status('Failed')
+		raise
+	finally:
+		frappe.db.commit()
+
+def repost_sl_entries(doc):
+	if doc.based_on == 'Transaction':
+		repost_future_sle(voucher_type=doc.voucher_type, voucher_no=doc.voucher_no,
+			allow_negative_stock=doc.allow_negative_stock, via_landed_cost_voucher=doc.via_landed_cost_voucher)
+	else:
+		repost_future_sle(args=[frappe._dict({
+			"item_code": doc.item_code,
+			"warehouse": doc.warehouse,
+			"posting_date": doc.posting_date,
+			"posting_time": doc.posting_time
+		})], allow_negative_stock=doc.allow_negative_stock, via_landed_cost_voucher=doc.via_landed_cost_voucher)
+
+def repost_gl_entries(doc):
+	if not cint(erpnext.is_perpetual_inventory_enabled(doc.company)):
+		return
+
+	if doc.based_on == 'Transaction':
+		ref_doc = frappe.get_doc(doc.voucher_type, doc.voucher_no)
+		items, warehouses = ref_doc.get_items_and_warehouses()
+	else:
+		items = [doc.item_code]
+		warehouses = [doc.warehouse]
+
+	update_gl_entries_after(doc.posting_date, doc.posting_time,
+		warehouses, items, company=doc.company)
+
+def notify_error_to_stock_managers(doc, traceback):
+	recipients = get_users_with_role("Stock Manager")
+	if not recipients:
+		get_users_with_role("System Manager")
+	
+	subject = _("Error while reposting item valuation")
+	message = (_("Hi,") + "<br>"
+		+ _("An error has been appeared while reposting item valuation via {0}")
+			.format(get_link_to_form(doc.doctype, doc.name)) + "<br>"
+		+ _("Please check the error message and take necessary actions to fix the error and then restart the reposting again.")
+	)
+	frappe.sendmail(recipients=recipients, subject=subject, message=message)
+
+
diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
new file mode 100644
index 0000000..13ceb68
--- /dev/null
+++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestRepostItemValuation(unittest.TestCase):
+	pass
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 295149e..6bacf1f 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -6,7 +6,7 @@
 import json
 
 from frappe.model.naming import make_autoname
-from frappe.utils import cint, cstr, flt, add_days, nowdate, getdate
+from frappe.utils import cint, cstr, flt, add_days, nowdate, getdate, get_link_to_form
 from erpnext.stock.get_item_details import get_reserved_qty_for_so
 
 from frappe import _, ValidationError
@@ -134,17 +134,13 @@
 		sle_dict = self.get_stock_ledger_entries(serial_no)
 		if sle_dict:
 			if sle_dict.get("incoming", []):
-				sle_list = [sle for sle in sle_dict["incoming"] if sle.is_cancelled == 0]
-				if sle_list:
-					entries["purchase_sle"] = sle_list[0]
+				entries["purchase_sle"] = sle_dict["incoming"][0]
 
 			if len(sle_dict.get("incoming", [])) - len(sle_dict.get("outgoing", [])) > 0:
 				entries["last_sle"] = sle_dict["incoming"][0]
 			else:
 				entries["last_sle"] = sle_dict["outgoing"][0]
-				sle_list = [sle for sle in sle_dict["outgoing"] if sle.is_cancelled == 0]
-				if sle_list:
-					entries["delivery_sle"] = sle_list[0]
+				entries["delivery_sle"] = sle_dict["outgoing"][0]
 
 		return entries
 
@@ -155,11 +151,12 @@
 
 		for sle in frappe.db.sql("""
 			SELECT voucher_type, voucher_no,
-				posting_date, posting_time, incoming_rate, actual_qty, serial_no, is_cancelled
+				posting_date, posting_time, incoming_rate, actual_qty, serial_no
 			FROM
 				`tabStock Ledger Entry`
 			WHERE
 				item_code=%s AND company = %s
+				AND is_cancelled = 0
 				AND (serial_no = %s
 					OR serial_no like %s
 					OR serial_no like %s
@@ -179,7 +176,7 @@
 
 	def on_trash(self):
 		sl_entries = frappe.db.sql("""select serial_no from `tabStock Ledger Entry`
-			where serial_no like %s and item_code=%s""",
+			where serial_no like %s and item_code=%s and is_cancelled=0""",
 			("%%%s%%" % self.name, self.item_code), as_dict=True)
 
 		# Find the exact match
@@ -229,7 +226,7 @@
 		if serial_nos:
 			frappe.throw(_("Item {0} is not setup for Serial Nos. Column must be blank").format(sle.item_code),
 				SerialNoNotRequiredError)
-	else:
+	elif not sle.is_cancelled:
 		if serial_nos:
 			if cint(sle.actual_qty) != flt(sle.actual_qty):
 				frappe.throw(_("Serial No {0} quantity {1} cannot be a fraction").format(sle.item_code, sle.actual_qty))
@@ -244,21 +241,18 @@
 			for serial_no in serial_nos:
 				if frappe.db.exists("Serial No", serial_no):
 					sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order",
-						"delivery_document_no", "delivery_document_type", "warehouse",
+						"delivery_document_no", "delivery_document_type", "warehouse", "purchase_document_type",
 						"purchase_document_no", "company"], as_dict=1)
 
-					if sr and cint(sle.actual_qty) < 0 and sr.warehouse != sle.warehouse:
-						frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}")
-							.format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse), SerialNoWarehouseError)
-
 					if sr.item_code!=sle.item_code:
 						if not allow_serial_nos_with_different_item(serial_no, sle):
 							frappe.throw(_("Serial No {0} does not belong to Item {1}").format(serial_no,
 								sle.item_code), SerialNoItemError)
 
-					if cint(sle.actual_qty) > 0 and has_duplicate_serial_no(sr, sle):
-						frappe.throw(_("Serial No {0} has already been received").format(serial_no),
-							SerialNoDuplicateError)
+					if cint(sle.actual_qty) > 0 and has_serial_no_exists(sr, sle):
+						doc_name = frappe.bold(get_link_to_form(sr.purchase_document_type, sr.purchase_document_no))
+						frappe.throw(_("Serial No {0} has already been received in the {1} #{2}")
+							.format(frappe.bold(serial_no), sr.purchase_document_type, doc_name), SerialNoDuplicateError)
 
 					if (sr.delivery_document_no and sle.voucher_type not in ['Stock Entry', 'Stock Reconciliation']
 						and sle.voucher_type == sr.delivery_document_type):
@@ -277,7 +271,7 @@
 								frappe.throw(_("Serial No {0} does not belong to Batch {1}").format(serial_no,
 									sle.batch_no), SerialNoBatchError)
 
-							if not sr.warehouse:
+							if not sle.is_cancelled and not sr.warehouse:
 								frappe.throw(_("Serial No {0} does not belong to any Warehouse")
 									.format(serial_no), SerialNoWarehouseError)
 
@@ -327,6 +321,12 @@
 		elif cint(sle.actual_qty) < 0 or not item_det.serial_no_series:
 			frappe.throw(_("Serial Nos Required for Serialized Item {0}").format(sle.item_code),
 				SerialNoRequiredError)
+	elif serial_nos:
+		for serial_no in serial_nos:
+			sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse"], as_dict=1)
+			if sr and cint(sle.actual_qty) < 0 and sr.warehouse != sle.warehouse:
+				frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}")
+					.format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse))
 
 def validate_material_transfer_entry(sle_doc):
 	sle_doc.update({
@@ -334,7 +334,7 @@
 		"skip_serial_no_validaiton": False
 	})
 
-	if (sle_doc.voucher_type == "Stock Entry" and
+	if (sle_doc.voucher_type == "Stock Entry" and not sle_doc.is_cancelled and
 		frappe.get_cached_value("Stock Entry", sle_doc.voucher_no, "purpose") == "Material Transfer"):
 		if sle_doc.actual_qty < 0:
 			sle_doc.skip_update_serial_no = True
@@ -349,7 +349,7 @@
 		frappe.throw(_("""{0} Serial No {1} cannot be delivered""")
 			.format(msg, sr.name))
 
-def has_duplicate_serial_no(sn, sle):
+def has_serial_no_exists(sn, sle):
 	if (sn.warehouse and not sle.skip_serial_no_validaiton
 		and sle.voucher_type != 'Stock Reconciliation'):
 		return True
@@ -359,12 +359,13 @@
 
 	status = False
 	if sn.purchase_document_no:
-		if sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and \
-			sn.delivery_document_type not in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"]:
+		if (sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and
+			sn.delivery_document_type not in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"]):
 			status = True
 
-		if status and sle.voucher_type == 'Stock Entry' and \
-			frappe.db.get_value('Stock Entry', sle.voucher_no, 'purpose') != 'Material Receipt':
+		# If status is receipt then system will allow to in-ward the delivered serial no
+		if (status and sle.voucher_type == "Stock Entry" and frappe.db.get_value("Stock Entry",
+			sle.voucher_no, "purpose") in ("Material Receipt", "Material Transfer")):
 			status = False
 
 	return status
@@ -379,7 +380,7 @@
 		stock_entry = frappe.get_cached_doc("Stock Entry", sle.voucher_no)
 		if stock_entry.purpose in ("Repack", "Manufacture"):
 			for d in stock_entry.get("items"):
-				if d.serial_no and (d.s_warehouse or d.t_warehouse):
+				if d.serial_no and (d.s_warehouse if not sle.is_cancelled else d.t_warehouse):
 					serial_nos = get_serial_nos(d.serial_no)
 					if sle_serial_no in serial_nos:
 						allow_serial_nos = True
@@ -388,7 +389,7 @@
 
 def update_serial_nos(sle, item_det):
 	if sle.skip_update_serial_no: return
-	if not sle.serial_no and cint(sle.actual_qty) > 0 \
+	if not sle.is_cancelled and not sle.serial_no and cint(sle.actual_qty) > 0 \
 			and item_det.has_serial_no == 1 and item_det.serial_no_series:
 		serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty)
 		frappe.db.set(sle, "serial_no", serial_nos)
@@ -420,7 +421,7 @@
 		if is_new:
 			created_numbers.append(sr.name)
 
-	form_links = list(map(lambda d: frappe.utils.get_link_to_form('Serial No', d), created_numbers))
+	form_links = list(map(lambda d: get_link_to_form('Serial No', d), created_numbers))
 
 	# Setting up tranlated title field for all cases
 	singular_title = _("Serial Number Created")
diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py
index ab06107..ed70790 100644
--- a/erpnext/stock/doctype/serial_no/test_serial_no.py
+++ b/erpnext/stock/doctype/serial_no/test_serial_no.py
@@ -12,7 +12,6 @@
 from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
-from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
 
 test_dependencies = ["Item"]
 test_records = frappe.get_test_records('Serial No')
@@ -38,8 +37,6 @@
 		self.assertTrue(SerialNoCannotCannotChangeError, sr.save)
 
 	def test_inter_company_transfer(self):
-		set_perpetual_inventory(0, "_Test Company 1")
-		set_perpetual_inventory(0)
 		se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
 		serial_nos = get_serial_nos(se.get("items")[0].serial_no)
 
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/stock/doctype/shipment/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/stock/doctype/shipment/__init__.py
diff --git a/erpnext/stock/doctype/shipment/shipment.js b/erpnext/stock/doctype/shipment/shipment.js
new file mode 100644
index 0000000..5ccb7d2
--- /dev/null
+++ b/erpnext/stock/doctype/shipment/shipment.js
@@ -0,0 +1,447 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Shipment', {
+	address_query: function(frm, link_doctype, link_name, is_your_company_address) {
+		return {
+			query: 'frappe.contacts.doctype.address.address.address_query',
+			filters: {
+				link_doctype: link_doctype,
+				link_name: link_name,
+				is_your_company_address: is_your_company_address
+			}
+		};
+	},
+	contact_query: function(frm, link_doctype, link_name) {
+		return {
+			query: 'frappe.contacts.doctype.contact.contact.contact_query',
+			filters: {
+				link_doctype: link_doctype,
+				link_name: link_name
+			}
+		};
+	},
+	onload: function(frm) {
+		frm.set_query("delivery_address_name", () => {
+			let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}`;
+			return frm.events.address_query(frm, frm.doc.delivery_to_type, frm.doc[delivery_to], frm.doc.delivery_to_type === 'Company' ? 1 : 0);
+		});
+		frm.set_query("pickup_address_name", () => {
+			let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}`;
+			return frm.events.address_query(frm, frm.doc.pickup_from_type, frm.doc[pickup_from], frm.doc.pickup_from_type === 'Company' ? 1 : 0);
+		});
+		frm.set_query("delivery_contact_name", () => {
+			let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}`;
+			return frm.events.contact_query(frm, frm.doc.delivery_to_type, frm.doc[delivery_to]);
+		});
+		frm.set_query("pickup_contact_name", () => {
+			let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}`;
+			return frm.events.contact_query(frm, frm.doc.pickup_from_type, frm.doc[pickup_from]);
+		});
+		frm.set_query("delivery_note", "shipment_delivery_note", function() {
+			let customer = '';
+			if (frm.doc.delivery_to_type == "Customer") {
+				customer = frm.doc.delivery_customer;
+			}
+			if (frm.doc.delivery_to_type == "Company") {
+				customer = frm.doc.delivery_company;
+			}
+			if (customer) {
+				return {
+					filters: {
+						customer: customer,
+						docstatus: 1,
+						status: ["not in", ["Cancelled"]]
+					}
+				};
+			}
+		});
+	},
+	refresh: function() {
+		$('div[data-fieldname=pickup_address] > div > .clearfix').hide();
+		$('div[data-fieldname=pickup_contact] > div > .clearfix').hide();
+		$('div[data-fieldname=delivery_address] > div > .clearfix').hide();
+		$('div[data-fieldname=delivery_contact] > div > .clearfix').hide();
+	},
+	before_save: function(frm) {
+		let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}`;
+		frm.set_value("delivery_to", frm.doc[delivery_to]);
+		let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}`;
+		frm.set_value("pickup", frm.doc[pickup_from]);
+	},
+	set_pickup_company_address: function(frm) {
+		frappe.db.get_value('Address', {
+			address_title: frm.doc.pickup_company,
+			is_your_company_address: 1
+		}, 'name', (r) => {
+			frm.set_value("pickup_address_name", r.name);
+		});
+	},
+	set_delivery_company_address: function(frm) {
+		frappe.db.get_value('Address', {
+			address_title: frm.doc.delivery_company,
+			is_your_company_address: 1
+		}, 'name', (r) => {
+			frm.set_value("delivery_address_name", r.name);
+		});
+	},
+	pickup_from_type: function(frm) {
+		if (frm.doc.pickup_from_type == 'Company') {
+			frm.set_value("pickup_company", frappe.defaults.get_default('company'));
+			frm.set_value("pickup_customer", '');
+			frm.set_value("pickup_supplier", '');
+		} else {
+			frm.trigger('clear_pickup_fields');
+		}
+		if (frm.doc.pickup_from_type == 'Customer') {
+			frm.set_value("pickup_company", '');
+			frm.set_value("pickup_supplier", '');
+		}
+		if (frm.doc.pickup_from_type == 'Supplier') {
+			frm.set_value("pickup_customer", '');
+			frm.set_value("pickup_company", '');
+		}
+	},
+	delivery_to_type: function(frm) {
+		if (frm.doc.delivery_to_type == 'Company') {
+			frm.set_value("delivery_company", frappe.defaults.get_default('company'));
+			frm.set_value("delivery_customer", '');
+			frm.set_value("delivery_supplier", '');
+		} else {
+			frm.trigger('clear_delivery_fields');
+		}
+		if (frm.doc.delivery_to_type == 'Customer') {
+			frm.set_value("delivery_company", '');
+			frm.set_value("delivery_supplier", '');
+		}
+		if (frm.doc.delivery_to_type == 'Supplier') {
+			frm.set_value("delivery_customer", '');
+			frm.set_value("delivery_company", '');
+			frm.toggle_display("shipment_delivery_note", false);
+		} else {
+			frm.toggle_display("shipment_delivery_note", true);
+		}
+	},
+	delivery_address_name: function(frm) {
+		if (frm.doc.delivery_to_type == 'Company') {
+			erpnext.utils.get_address_display(frm, 'delivery_address_name', 'delivery_address', true);
+		} else {
+			erpnext.utils.get_address_display(frm, 'delivery_address_name', 'delivery_address', false);
+		}
+	},
+	pickup_address_name: function(frm) {
+		if (frm.doc.pickup_from_type == 'Company') {
+			erpnext.utils.get_address_display(frm, 'pickup_address_name', 'pickup_address', true);
+		} else {
+			erpnext.utils.get_address_display(frm, 'pickup_address_name', 'pickup_address', false);
+		}
+	},
+	get_contact_display: function(frm, contact_name, contact_type) {
+		frappe.call({
+			method: "frappe.contacts.doctype.contact.contact.get_contact_details",
+			args: { contact: contact_name },
+			callback: function(r) {
+				if (r.message) {
+					if (!(r.message.contact_email && (r.message.contact_phone || r.message.contact_mobile))) {
+						if (contact_type == 'Delivery') {
+							frm.set_value('delivery_contact_name', '');
+							frm.set_value('delivery_contact', '');
+						} else {
+							frm.set_value('pickup_contact_name', '');
+							frm.set_value('pickup_contact', '');
+						}
+						frappe.throw(__("Email or Phone/Mobile of the Contact are mandatory to continue.") + "</br>" + __("Please set Email/Phone for the contact") + ` <a href='#Form/Contact/${contact_name}'>${contact_name}</a>`);
+					}
+					let contact_display = r.message.contact_display;
+					if (r.message.contact_email) {
+						contact_display += '<br>' + r.message.contact_email;
+					}
+					if (r.message.contact_phone) {
+						contact_display += '<br>' + r.message.contact_phone;
+					}
+					if (r.message.contact_mobile && !r.message.contact_phone) {
+						contact_display += '<br>' + r.message.contact_mobile;
+					}
+					if (contact_type == 'Delivery') {
+						frm.set_value('delivery_contact', contact_display);
+						if (r.message.contact_email) {
+							frm.set_value('delivery_contact_email', r.message.contact_email);
+						}
+					} else {
+						frm.set_value('pickup_contact', contact_display);
+						if (r.message.contact_email) {
+							frm.set_value('pickup_contact_email', r.message.contact_email);
+						}
+					}
+				}
+			}
+		});
+	},
+	delivery_contact_name: function(frm) {
+		if (frm.doc.delivery_contact_name) {
+			frm.events.get_contact_display(frm, frm.doc.delivery_contact_name, 'Delivery');
+		}
+	},
+	pickup_contact_name: function(frm) {
+		if (frm.doc.pickup_contact_name) {
+			frm.events.get_contact_display(frm, frm.doc.pickup_contact_name, 'Pickup');
+		}
+	},
+	pickup_contact_person: function(frm) {
+		if (frm.doc.pickup_contact_person) {
+			frappe.call({
+				method: "erpnext.stock.doctype.shipment.shipment.get_company_contact",
+				args: { user: frm.doc.pickup_contact_person },
+				callback: function({ message }) {
+					const r = message;
+					let contact_display = `${r.first_name} ${r.last_name}`;
+					if (r.email) {
+						contact_display += `<br>${ r.email }`;
+						frm.set_value('pickup_contact_email', r.email);
+					}
+					if (r.phone) {
+						contact_display += `<br>${ r.phone }`;
+					}
+					if (r.mobile_no && !r.phone) {
+						contact_display += `<br>${ r.mobile_no }`;
+					}
+					frm.set_value('pickup_contact', contact_display);
+				}
+			});
+		} else {
+			if (frm.doc.pickup_from_type === 'Company') {
+				frappe.call({
+					method: "erpnext.stock.doctype.shipment.shipment.get_company_contact",
+					args: { user: frappe.session.user },
+					callback: function({ message }) {
+						const r = message;
+						let contact_display = `${r.first_name} ${r.last_name}`;
+						if (r.email) {
+							contact_display += `<br>${ r.email }`;
+							frm.set_value('pickup_contact_email', r.email);
+						}
+						if (r.phone) {
+							contact_display += `<br>${ r.phone }`;
+						}
+						if (r.mobile_no && !r.phone) {
+							contact_display += `<br>${ r.mobile_no }`;
+						}
+						frm.set_value('pickup_contact', contact_display);
+					}
+				});
+			}
+		}
+	},
+	set_company_contact: function(frm, delivery_type) {
+		frappe.db.get_value('User', { name: frappe.session.user }, ['full_name', 'last_name', 'email', 'phone', 'mobile_no'], (r) => {
+			if (!(r.last_name && r.email && (r.phone || r.mobile_no))) {
+				if (delivery_type == 'Delivery') {
+					frm.set_value('delivery_company', '');
+					frm.set_value('delivery_contact', '');
+				} else {
+					frm.set_value('pickup_company', '');
+					frm.set_value('pickup_contact', '');
+				}
+				frappe.throw(__("Last Name, Email or Phone/Mobile of the user are mandatory to continue.") + "</br>" + __("Please first set Last Name, Email and Phone for the user") + ` <a href="#Form/User/${frappe.session.user}">${frappe.session.user}</a>`);
+			}
+			let contact_display = r.full_name;
+			if (r.email) {
+				contact_display += '<br>' + r.email;
+			}
+			if (r.phone) {
+				contact_display += '<br>' + r.phone;
+			}
+			if (r.mobile_no && !r.phone) {
+				contact_display += '<br>' + r.mobile_no;
+			}
+			if (delivery_type == 'Delivery') {
+				frm.set_value('delivery_contact', contact_display);
+				if (r.email) {
+					frm.set_value('delivery_contact_email', r.email);
+				}
+			} else {
+				frm.set_value('pickup_contact', contact_display);
+				if (r.email) {
+					frm.set_value('pickup_contact_email', r.email);
+				}
+			}
+		});
+		frm.set_value('pickup_contact_person', frappe.session.user);
+	},
+	pickup_company: function(frm) {
+		if (frm.doc.pickup_from_type == 'Company'  && frm.doc.pickup_company) {
+			frm.trigger('set_pickup_company_address');
+			frm.events.set_company_contact(frm, 'Pickup');
+		}
+	},
+	delivery_company: function(frm) {
+		if (frm.doc.delivery_to_type == 'Company' && frm.doc.delivery_company) {
+			frm.trigger('set_delivery_company_address');
+			frm.events.set_company_contact(frm, 'Delivery');
+		}
+	},
+	delivery_customer: function(frm) {
+		frm.trigger('clear_delivery_fields');
+		if (frm.doc.delivery_customer) {
+			frm.events.set_address_name(frm, 'Customer', frm.doc.delivery_customer, 'Delivery');
+			frm.events.set_contact_name(frm, 'Customer', frm.doc.delivery_customer, 'Delivery');
+		}
+	},
+	delivery_supplier: function(frm) {
+		frm.trigger('clear_delivery_fields');
+		if (frm.doc.delivery_supplier) {
+			frm.events.set_address_name(frm, 'Supplier', frm.doc.delivery_supplier, 'Delivery');
+			frm.events.set_contact_name(frm, 'Supplier', frm.doc.delivery_supplier, 'Delivery');
+		}
+	},
+	pickup_customer: function(frm) {
+		if (frm.doc.pickup_customer) {
+			frm.events.set_address_name(frm, 'Customer', frm.doc.pickup_customer, 'Pickup');
+			frm.events.set_contact_name(frm, 'Customer', frm.doc.pickup_customer, 'Pickup');
+		}
+	},
+	pickup_supplier: function(frm) {
+		if (frm.doc.pickup_supplier) {
+			frm.events.set_address_name(frm, 'Supplier', frm.doc.pickup_supplier, 'Pickup');
+			frm.events.set_contact_name(frm, 'Supplier', frm.doc.pickup_supplier, 'Pickup');
+		}
+	},
+	set_address_name: function(frm, ref_doctype, ref_docname, delivery_type) {
+		frappe.call({
+			method: "erpnext.stock.doctype.shipment.shipment.get_address_name",
+			args: {
+				ref_doctype: ref_doctype,
+				docname: ref_docname
+			},
+			callback: function(r) {
+				if (r.message) {
+					if (delivery_type == 'Delivery') {
+						frm.set_value('delivery_address_name', r.message);
+					} else {
+						frm.set_value('pickup_address_name', r.message);
+					}
+				}
+			}
+		});
+	},
+	set_contact_name: function(frm, ref_doctype, ref_docname, delivery_type) {
+		frappe.call({
+			method: "erpnext.stock.doctype.shipment.shipment.get_contact_name",
+			args: {
+				ref_doctype: ref_doctype,
+				docname: ref_docname
+			},
+			callback: function(r) {
+				if (r.message) {
+					if (delivery_type == 'Delivery') {
+						frm.set_value('delivery_contact_name', r.message);
+					} else {
+						frm.set_value('pickup_contact_name', r.message);
+					}
+				}
+			}
+		});
+	},
+	add_template: function(frm) {
+		if (frm.doc.parcel_template) {
+			frappe.model.with_doc("Shipment Parcel Template", frm.doc.parcel_template, () => {
+				let parcel_template = frappe.model.get_doc("Shipment Parcel Template", frm.doc.parcel_template);
+				let row = frappe.model.add_child(frm.doc, "Shipment Parcel", "shipment_parcel");
+				row.length = parcel_template.length;
+				row.width = parcel_template.width;
+				row.height = parcel_template.height;
+				row.weight = parcel_template.weight;
+				frm.refresh_fields("shipment_parcel");
+			});
+		}
+	},
+	pickup_date: function(frm) {
+		if (frm.doc.pickup_date < frappe.datetime.get_today()) {
+			frappe.throw(__("Pickup Date cannot be before this day"));
+		}
+		if (frm.doc.pickup_date == frappe.datetime.get_today()) {
+			var pickup_time = frm.events.get_pickup_time(frm);
+			frm.set_value("pickup_from", pickup_time);
+			frm.trigger('set_pickup_to_time');
+		}
+	},
+	pickup_from: function(frm) {
+		var pickup_time = frm.events.get_pickup_time(frm);
+		if (frm.doc.pickup_from && frm.doc.pickup_date == frappe.datetime.get_today()) {
+			let current_hour = pickup_time.split(':')[0];
+			let current_min = pickup_time.split(':')[1];
+			let pickup_hour = frm.doc.pickup_from.split(':')[0];
+			let pickup_min = frm.doc.pickup_from.split(':')[1];
+			if (pickup_hour < current_hour || (pickup_hour == current_hour && pickup_min < current_min)) {
+				frm.set_value("pickup_from", pickup_time);
+				frappe.throw(__("Pickup Time cannot be in the past"));
+			}
+		}
+		frm.trigger('set_pickup_to_time');
+	},
+	get_pickup_time: function() {
+		let current_hour = new Date().getHours();
+		let current_min = new Date().toLocaleString('en-US', {minute: 'numeric'});
+		if (current_min < 30) {
+			current_min = '30';
+		} else {
+			current_min = '00';
+			current_hour = Number(current_hour)+1;
+		}
+		let pickup_time = current_hour +':'+ current_min;
+		return pickup_time;
+	},
+	set_pickup_to_time: function(frm) {
+		let pickup_to_hour = Number(frm.doc.pickup_from.split(':')[0])+5;
+		let pickup_to_min = frm.doc.pickup_from.split(':')[1];
+		let pickup_to = pickup_to_hour +':'+ pickup_to_min;
+		frm.set_value("pickup_to", pickup_to);
+	},
+	clear_pickup_fields: function(frm) {
+		let fields = ["pickup_address_name", "pickup_contact_name", "pickup_address", "pickup_contact", "pickup_contact_email", "pickup_contact_person"];
+		for (let field of fields) {
+			frm.set_value(field,  '');
+		}
+	},
+	clear_delivery_fields: function(frm) {
+		let fields = ["delivery_address_name", "delivery_contact_name", "delivery_address", "delivery_contact", "delivery_contact_email"];
+		for (let field of fields) {
+			frm.set_value(field,  '');
+		}
+	},
+	remove_email_row: function(frm, table, fieldname) {
+		$.each(frm.doc[table] || [], function(i, detail) {
+			if (detail.email === fieldname) {
+				cur_frm.get_field(table).grid.grid_rows[i].remove();
+			}
+		});
+	}
+});
+
+frappe.ui.form.on('Shipment Delivery Note', {
+	delivery_note: function(frm, cdt, cdn) {
+		let row = locals[cdt][cdn];
+		if (row.delivery_note) {
+			let row_index = row.idx - 1;
+			if (validate_duplicate(frm, 'shipment_delivery_note', row.delivery_note, row_index)) {
+				frappe.throw(__("You have entered a duplicate Delivery Note on Row") + ` ${row.idx}. ` + __("Please rectify and try again."));
+			}
+		}
+	},
+	grand_total: function(frm, cdt, cdn) {
+		let row = locals[cdt][cdn];
+		if (row.grand_total) {
+			var value_of_goods = parseFloat(frm.doc.value_of_goods)+parseFloat(row.grand_total);
+			frm.set_value("value_of_goods", Math.round(value_of_goods));
+			frm.refresh_fields("value_of_goods");
+		}
+	},
+});
+
+var validate_duplicate =  function(frm, table, fieldname, index) {
+	return (
+		table === 'shipment_delivery_note'
+			? frm.doc[table].some((detail, i) => detail.delivery_note === fieldname && !(index === i))
+			: frm.doc[table].some((detail, i) => detail.email === fieldname && !(index === i))
+	);
+};
diff --git a/erpnext/stock/doctype/shipment/shipment.json b/erpnext/stock/doctype/shipment/shipment.json
new file mode 100644
index 0000000..76c331c
--- /dev/null
+++ b/erpnext/stock/doctype/shipment/shipment.json
@@ -0,0 +1,472 @@
+{
+ "actions": [],
+ "autoname": "SHIPMENT-.#####",
+ "creation": "2020-07-09 10:58:52.508703",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "heading_pickup_from",
+  "pickup_from_type",
+  "pickup_company",
+  "pickup_customer",
+  "pickup_supplier",
+  "pickup",
+  "pickup_address_name",
+  "pickup_address",
+  "pickup_contact_person",
+  "pickup_contact_name",
+  "pickup_contact_email",
+  "pickup_contact",
+  "column_break_2",
+  "heading_delivery_to",
+  "delivery_to_type",
+  "delivery_company",
+  "delivery_customer",
+  "delivery_supplier",
+  "delivery_to",
+  "delivery_address_name",
+  "delivery_address",
+  "delivery_contact_name",
+  "delivery_contact_email",
+  "delivery_contact",
+  "parcels_section",
+  "shipment_parcel",
+  "parcel_template",
+  "add_template",
+  "column_break_28",
+  "shipment_delivery_note",
+  "shipment_details_section",
+  "pallets",
+  "value_of_goods",
+  "pickup_date",
+  "pickup_from",
+  "pickup_to",
+  "column_break_36",
+  "shipment_type",
+  "pickup_type",
+  "incoterm",
+  "description_of_content",
+  "section_break_40",
+  "shipment_information_section",
+  "service_provider",
+  "shipment_id",
+  "shipment_amount",
+  "status",
+  "tracking_url",
+  "column_break_55",
+  "carrier",
+  "carrier_service",
+  "awb_number",
+  "tracking_status",
+  "tracking_status_info",
+  "amended_from"
+ ],
+ "fields": [
+  {
+   "fieldname": "heading_pickup_from",
+   "fieldtype": "Heading",
+   "label": "Pickup from"
+  },
+  {
+   "default": "Company",
+   "fieldname": "pickup_from_type",
+   "fieldtype": "Select",
+   "label": "Pickup from",
+   "options": "Company\nCustomer\nSupplier"
+  },
+  {
+   "depends_on": "eval:doc.pickup_from_type == 'Company'",
+   "fieldname": "pickup_company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company"
+  },
+  {
+   "depends_on": "eval:doc.pickup_from_type == 'Customer'",
+   "fieldname": "pickup_customer",
+   "fieldtype": "Link",
+   "label": "Customer",
+   "options": "Customer"
+  },
+  {
+   "depends_on": "eval:doc.pickup_from_type == 'Supplier'",
+   "fieldname": "pickup_supplier",
+   "fieldtype": "Link",
+   "label": "Supplier",
+   "options": "Supplier"
+  },
+  {
+   "fieldname": "pickup",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "in_list_view": 1,
+   "label": "Pickup From",
+   "read_only": 1
+  },
+  {
+   "depends_on": "eval: doc.pickup_customer || doc.pickup_supplier || doc.pickup_from_type == \"Company\"",
+   "fieldname": "pickup_address_name",
+   "fieldtype": "Link",
+   "label": "Address",
+   "options": "Address",
+   "reqd": 1
+  },
+  {
+   "fieldname": "pickup_address",
+   "fieldtype": "Small Text",
+   "read_only": 1
+  },
+  {
+   "depends_on": "eval: doc.pickup_customer || doc.pickup_supplier || doc.pickup_from_type !== \"Company\"",
+   "fieldname": "pickup_contact_name",
+   "fieldtype": "Link",
+   "label": "Contact",
+   "mandatory_depends_on": "eval: doc.pickup_from_type !== 'Company'",
+   "options": "Contact"
+  },
+  {
+   "fieldname": "pickup_contact_email",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "label": "Contact Email",
+   "read_only": 1
+  },
+  {
+   "fieldname": "pickup_contact",
+   "fieldtype": "Small Text",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_2",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "heading_delivery_to",
+   "fieldtype": "Heading",
+   "label": "Delivery to"
+  },
+  {
+   "default": "Customer",
+   "fieldname": "delivery_to_type",
+   "fieldtype": "Select",
+   "label": "Delivery to",
+   "options": "Company\nCustomer\nSupplier"
+  },
+  {
+   "depends_on": "eval:doc.delivery_to_type == 'Company'",
+   "fieldname": "delivery_company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company"
+  },
+  {
+   "depends_on": "eval:doc.delivery_to_type == 'Customer'",
+   "fieldname": "delivery_customer",
+   "fieldtype": "Link",
+   "label": "Customer",
+   "options": "Customer"
+  },
+  {
+   "depends_on": "eval:doc.delivery_to_type == 'Supplier'",
+   "fieldname": "delivery_supplier",
+   "fieldtype": "Link",
+   "label": "Supplier",
+   "options": "Supplier"
+  },
+  {
+   "fieldname": "delivery_to",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "in_list_view": 1,
+   "label": "Delivery To",
+   "read_only": 1
+  },
+  {
+   "depends_on": "eval: doc.delivery_customer || doc.delivery_supplier || doc.delivery_to_type == \"Company\"",
+   "fieldname": "delivery_address_name",
+   "fieldtype": "Link",
+   "label": "Address",
+   "options": "Address",
+   "reqd": 1
+  },
+  {
+   "fieldname": "delivery_address",
+   "fieldtype": "Small Text",
+   "read_only": 1
+  },
+  {
+   "depends_on": "eval: doc.delivery_customer || doc.delivery_supplier || doc.delivery_to_type == \"Company\"",
+   "fieldname": "delivery_contact_name",
+   "fieldtype": "Link",
+   "label": "Contact",
+   "mandatory_depends_on": "eval: doc.delivery_from_type !== 'Company'",
+   "options": "Contact"
+  },
+  {
+   "fieldname": "delivery_contact_email",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "label": "Contact Email",
+   "read_only": 1
+  },
+  {
+   "depends_on": "eval:doc.delivery_contact_name",
+   "fieldname": "delivery_contact",
+   "fieldtype": "Small Text",
+   "read_only": 1
+  },
+  {
+   "fieldname": "parcels_section",
+   "fieldtype": "Section Break",
+   "label": "Parcels"
+  },
+  {
+   "fieldname": "shipment_parcel",
+   "fieldtype": "Table",
+   "label": "Shipment Parcel",
+   "options": "Shipment Parcel"
+  },
+  {
+   "fieldname": "parcel_template",
+   "fieldtype": "Link",
+   "label": "Parcel Template",
+   "options": "Shipment Parcel Template"
+  },
+  {
+   "depends_on": "eval:doc.docstatus !== 1\n",
+   "fieldname": "add_template",
+   "fieldtype": "Button",
+   "label": "Add Template"
+  },
+  {
+   "fieldname": "column_break_28",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "shipment_details_section",
+   "fieldtype": "Section Break",
+   "label": "Shipment details"
+  },
+  {
+   "default": "No",
+   "fieldname": "pallets",
+   "fieldtype": "Select",
+   "label": "Pallets",
+   "options": "No\nYes"
+  },
+  {
+   "fieldname": "value_of_goods",
+   "fieldtype": "Currency",
+   "label": "Value of Goods",
+   "precision": "2",
+   "reqd": 1
+  },
+  {
+   "allow_on_submit": 1,
+   "fieldname": "pickup_date",
+   "fieldtype": "Date",
+   "in_list_view": 1,
+   "label": "Pickup Date",
+   "reqd": 1
+  },
+  {
+   "allow_on_submit": 1,
+   "default": "09:00",
+   "fieldname": "pickup_from",
+   "fieldtype": "Time",
+   "label": "Pickup from"
+  },
+  {
+   "allow_on_submit": 1,
+   "default": "17:00",
+   "fieldname": "pickup_to",
+   "fieldtype": "Time",
+   "label": "Pickup to"
+  },
+  {
+   "fieldname": "column_break_36",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "Goods",
+   "fieldname": "shipment_type",
+   "fieldtype": "Select",
+   "label": "Shipment Type",
+   "options": "Goods\nDocuments"
+  },
+  {
+   "default": "Pickup",
+   "fieldname": "pickup_type",
+   "fieldtype": "Select",
+   "label": "Pickup Type",
+   "options": "Pickup\nSelf delivery"
+  },
+  {
+   "fieldname": "description_of_content",
+   "fieldtype": "Small Text",
+   "label": "Description of Content",
+   "reqd": 1
+  },
+  {
+   "fieldname": "section_break_40",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "shipment_information_section",
+   "fieldtype": "Section Break",
+   "label": "Shipment Information"
+  },
+  {
+   "fieldname": "service_provider",
+   "fieldtype": "Data",
+   "label": "Service Provider",
+   "no_copy": 1,
+   "print_hide": 1
+  },
+  {
+   "fieldname": "shipment_id",
+   "fieldtype": "Data",
+   "label": "Shipment ID",
+   "no_copy": 1,
+   "print_hide": 1
+  },
+  {
+   "fieldname": "shipment_amount",
+   "fieldtype": "Currency",
+   "label": "Shipment Amount",
+   "no_copy": 1,
+   "precision": "2",
+   "print_hide": 1
+  },
+  {
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "label": "Status",
+   "no_copy": 1,
+   "options": "Draft\nSubmitted\nBooked\nCancelled\nCompleted",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "tracking_url",
+   "fieldtype": "Small Text",
+   "hidden": 1,
+   "label": "Tracking URL",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "carrier",
+   "fieldtype": "Data",
+   "label": "Carrier",
+   "no_copy": 1,
+   "print_hide": 1
+  },
+  {
+   "fieldname": "carrier_service",
+   "fieldtype": "Data",
+   "label": "Carrier Service",
+   "no_copy": 1,
+   "print_hide": 1
+  },
+  {
+   "fieldname": "awb_number",
+   "fieldtype": "Data",
+   "label": "AWB Number",
+   "no_copy": 1,
+   "print_hide": 1
+  },
+  {
+   "fieldname": "tracking_status",
+   "fieldtype": "Select",
+   "label": "Tracking Status",
+   "no_copy": 1,
+   "options": "\nIn Progress\nDelivered\nReturned\nLost",
+   "print_hide": 1
+  },
+  {
+   "fieldname": "tracking_status_info",
+   "fieldtype": "Data",
+   "label": "Tracking Status Info",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "amended_from",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "label": "Amended From",
+   "no_copy": 1,
+   "options": "Shipment",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_55",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "incoterm",
+   "fieldtype": "Select",
+   "label": "Incoterm",
+   "options": "EXW (Ex Works)\nFCA (Free Carrier)\nCPT (Carriage Paid To)\nCIP (Carriage and Insurance Paid to)\nDPU (Delivered At Place Unloaded)\nDAP (Delivered At Place)\nDDP (Delivered Duty Paid)"
+  },
+  {
+   "fieldname": "shipment_delivery_note",
+   "fieldtype": "Table",
+   "label": "Shipment Delivery Note",
+   "options": "Shipment Delivery Note"
+  },
+  {
+   "depends_on": "eval:doc.pickup_from_type === 'Company'",
+   "fieldname": "pickup_contact_person",
+   "fieldtype": "Link",
+   "label": "Pickup Contact Person",
+   "mandatory_depends_on": "eval:doc.pickup_from_type === 'Company'",
+   "options": "User"
+  }
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-12-25 15:02:34.891976",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Shipment",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Stock Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/shipment/shipment.py b/erpnext/stock/doctype/shipment/shipment.py
new file mode 100644
index 0000000..4697a7b
--- /dev/null
+++ b/erpnext/stock/doctype/shipment/shipment.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import flt, get_time
+from frappe.model.document import Document
+from erpnext.accounts.party import get_party_shipping_address
+from frappe.contacts.doctype.contact.contact import get_default_contact
+
+class Shipment(Document):
+	def validate(self):
+		self.validate_weight()
+		self.validate_pickup_time()
+		self.set_value_of_goods()
+		if self.docstatus == 0:
+			self.status = 'Draft'
+
+	def on_submit(self):
+		if not self.shipment_parcel:
+			frappe.throw(_('Please enter Shipment Parcel information'))
+		if self.value_of_goods == 0:
+			frappe.throw(_('Value of goods cannot be 0'))
+		self.status = 'Submitted'
+
+	def on_cancel(self):
+		self.status = 'Cancelled'
+
+	def validate_weight(self):
+		for parcel in self.shipment_parcel:
+			if flt(parcel.weight) <= 0:
+				frappe.throw(_('Parcel weight cannot be 0'))
+
+	def validate_pickup_time(self):
+		if self.pickup_from and self.pickup_to and get_time(self.pickup_to) < get_time(self.pickup_from):
+			frappe.throw(_("Pickup To time should be greater than Pickup From time"))
+
+	def set_value_of_goods(self):
+		value_of_goods = 0
+		for entry in self.get("shipment_delivery_note"):
+			value_of_goods += flt(entry.get("grand_total"))
+		self.value_of_goods = value_of_goods if value_of_goods else self.value_of_goods
+
+@frappe.whitelist()
+def get_address_name(ref_doctype, docname):
+	# Return address name
+	return get_party_shipping_address(ref_doctype, docname)
+
+@frappe.whitelist()
+def get_contact_name(ref_doctype, docname):
+	# Return address name
+	return get_default_contact(ref_doctype, docname)
+
+@frappe.whitelist()
+def get_company_contact(user):
+	contact = frappe.db.get_value('User', user, [
+		'first_name',
+		'last_name',
+		'email',
+		'phone',
+		'mobile_no',
+		'gender',
+	], as_dict=1)
+	if not contact.phone:
+		contact.phone = contact.mobile_no
+	return contact
diff --git a/erpnext/stock/doctype/shipment/shipment_list.js b/erpnext/stock/doctype/shipment/shipment_list.js
new file mode 100644
index 0000000..52b052c
--- /dev/null
+++ b/erpnext/stock/doctype/shipment/shipment_list.js
@@ -0,0 +1,8 @@
+frappe.listview_settings['Shipment'] = {
+	add_fields: ["status"],
+	get_indicator: function(doc) {
+		if (doc.status=='Booked') {
+			return [__("Booked"), "green"];
+		}
+	}
+};
\ No newline at end of file
diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py
new file mode 100644
index 0000000..e1fa207
--- /dev/null
+++ b/erpnext/stock/doctype/shipment/test_shipment.py
@@ -0,0 +1,240 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+from datetime import date, timedelta
+
+import frappe
+import unittest
+from erpnext.stock.doctype.delivery_note.delivery_note import make_shipment
+
+class TestShipment(unittest.TestCase):
+	def test_shipment_from_delivery_note(self):
+		delivery_note = create_test_delivery_note()
+		delivery_note.submit()
+		shipment = create_test_shipment([ delivery_note ])
+		shipment.submit()
+		second_shipment = make_shipment(delivery_note.name)
+		self.assertEqual(second_shipment.value_of_goods, delivery_note.grand_total)
+		self.assertEqual(len(second_shipment.shipment_delivery_note), 1)
+		self.assertEqual(second_shipment.shipment_delivery_note[0].delivery_note, delivery_note.name)
+
+def create_test_delivery_note():
+	company = get_shipment_company()
+	customer = get_shipment_customer()
+	item = get_shipment_item(company.name)
+	posting_date = date.today() + timedelta(days=1)
+	
+	create_material_receipt(item, company.name)
+	delivery_note = frappe.new_doc("Delivery Note")
+	delivery_note.company = company.name
+	delivery_note.posting_date = posting_date.strftime("%Y-%m-%d")
+	delivery_note.posting_time = '10:00'
+	delivery_note.customer = customer.name
+	delivery_note.append('items',
+		{
+			"item_code": item.name,
+			"item_name": item.item_name,
+			"description": 'Test delivery note for shipment',
+			"qty": 5,
+			"uom": 'Nos',
+			"warehouse": 'Stores - SC',
+			"rate": item.standard_rate,
+			"cost_center": 'Main - SC'
+		}
+	)
+	delivery_note.insert()
+	frappe.db.commit()
+	return delivery_note
+
+
+def create_test_shipment(delivery_notes = None):
+	company = get_shipment_company()
+	company_address = get_shipment_company_address(company.name)
+	customer = get_shipment_customer()
+	customer_address = get_shipment_customer_address(customer.name)
+	customer_contact = get_shipment_customer_contact(customer.name)
+	posting_date = date.today() + timedelta(days=5)
+
+	shipment = frappe.new_doc("Shipment")
+	shipment.pickup_from_type = 'Company'
+	shipment.pickup_company = company.name
+	shipment.pickup_address_name = company_address.name
+	shipment.delivery_to_type = 'Customer'
+	shipment.delivery_customer = customer.name
+	shipment.delivery_address_name = customer_address.name
+	shipment.delivery_contact_name = customer_contact.name
+	shipment.pallets = 'No'
+	shipment.shipment_type = 'Goods'
+	shipment.value_of_goods = 1000
+	shipment.pickup_type = 'Pickup'
+	shipment.pickup_date = posting_date.strftime("%Y-%m-%d")
+	shipment.pickup_from = '09:00'
+	shipment.pickup_to = '17:00'
+	shipment.description_of_content = 'unit test entry'
+	for delivery_note in delivery_notes:
+		shipment.append('shipment_delivery_note', 
+			{
+				"delivery_note": delivery_note.name
+			}
+		)
+	shipment.append('shipment_parcel',
+		{
+			"length": 5,
+			"width": 5,
+			"height": 5,
+			"weight": 5,
+			"count": 5
+		}
+	)
+	shipment.insert()
+	frappe.db.commit()
+	return shipment
+
+
+def get_shipment_customer_contact(customer_name):
+	contact_fname = 'Customer Shipment'
+	contact_lname = 'Testing'
+	customer_name = contact_fname + ' ' + contact_lname
+	contacts = frappe.get_all("Contact", fields=["name"], filters = {"name": customer_name})
+	if len(contacts):
+		return contacts[0]
+	else:
+		return create_customer_contact(contact_fname, contact_lname)
+
+
+def get_shipment_customer_address(customer_name):
+	address_title = customer_name + ' address 123'
+	customer_address = frappe.get_all("Address", fields=["name"], filters = {"address_title": address_title})
+	if len(customer_address):
+		return customer_address[0]
+	else:
+		return create_shipment_address(address_title, customer_name, 81929)
+
+def get_shipment_customer():
+	customer_name = 'Shipment Customer'
+	customer = frappe.get_all("Customer", fields=["name"], filters = {"name": customer_name})
+	if len(customer):
+		return customer[0]
+	else:
+		return create_shipment_customer(customer_name)
+
+def get_shipment_company_address(company_name):
+	address_title = company_name + ' address 123'
+	addresses = frappe.get_all("Address", fields=["name"], filters = {"address_title": address_title})
+	if len(addresses):
+		return addresses[0]
+	else:
+		return create_shipment_address(address_title, company_name, 80331)
+
+def get_shipment_company():
+	company_name = 'Shipment Company'
+	abbr = 'SC'
+	companies = frappe.get_all("Company", fields=["name"], filters = {"company_name": company_name})
+	if len(companies):
+		return companies[0]
+	else:
+		return create_shipment_company(company_name, abbr)
+
+def get_shipment_item(company_name):
+	item_name = 'Testing Shipment item'
+	items = frappe.get_all("Item",
+		fields=["name", "item_name", "item_code", "standard_rate"],
+		filters = {"item_name": item_name}
+	)
+	if len(items):
+		return items[0]
+	else:
+		return create_shipment_item(item_name, company_name)
+
+def create_shipment_address(address_title, company_name, postal_code):
+	address = frappe.new_doc("Address")
+	address.address_title = address_title
+	address.address_type = 'Shipping'
+	address.address_line1 = company_name + ' address line 1'
+	address.city = 'Random City'
+	address.postal_code = postal_code
+	address.country = 'Germany'
+	address.insert()
+	return address
+
+
+def create_customer_contact(fname, lname):
+	customer = frappe.new_doc("Contact")
+	customer.customer_name = fname + ' ' + lname
+	customer.first_name = fname
+	customer.last_name = lname
+	customer.is_primary_contact = 1
+	customer.is_billing_contact = 1
+	customer.append('email_ids',
+		{
+			'email_id': 'randomme@email.com',
+			'is_primary': 1
+		}
+	)
+	customer.append('phone_nos',
+		{
+			'phone': '123123123',
+			'is_primary_phone': 1,
+			'is_primary_mobile_no': 1
+		}
+	)
+	customer.status = 'Passive'
+	customer.insert()
+	return customer
+
+
+def create_shipment_company(company_name, abbr):
+	company = frappe.new_doc("Company")
+	company.company_name = company_name
+	company.abbr = abbr
+	company.default_currency = 'EUR'
+	company.country = 'Germany'
+	company.insert()
+	return company
+
+def create_shipment_customer(customer_name):
+	customer = frappe.new_doc("Customer")
+	customer.customer_name = customer_name
+	customer.customer_type = 'Company'
+	customer.customer_group = 'All Customer Groups'
+	customer.territory = 'All Territories'
+	customer.gst_category = 'Unregistered'
+	customer.insert()
+	return customer
+
+def create_material_receipt(item, company):
+	posting_date = date.today()
+	stock = frappe.new_doc("Stock Entry")
+	stock.company = company
+	stock.stock_entry_type = 'Material Receipt'
+	stock.posting_date = posting_date.strftime("%Y-%m-%d")
+	stock.append('items',
+		{
+			"t_warehouse": 'Stores - SC',
+			"item_code": item.name,
+			"qty": 5,
+			"uom": 'Nos',
+			"basic_rate": item.standard_rate,
+			"cost_center": 'Main - SC'
+		}
+	)
+	stock.insert()
+	stock.submit()
+	
+
+def create_shipment_item(item_name, company_name):
+	item = frappe.new_doc("Item")
+	item.item_name = item_name
+	item.item_code = item_name
+	item.item_group = 'All Item Groups'
+	item.stock_uom = 'Nos'
+	item.standard_rate = 50
+	item.append('item_defaults',
+		{
+			"company": company_name,
+			"default_warehouse": 'Stores - SC'
+		}
+	)
+	item.insert()
+	return item
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/stock/doctype/shipment_delivery_note/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/stock/doctype/shipment_delivery_note/__init__.py
diff --git a/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.json b/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.json
new file mode 100644
index 0000000..8625913
--- /dev/null
+++ b/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.json
@@ -0,0 +1,40 @@
+{
+ "actions": [],
+ "creation": "2020-07-09 11:52:57.939021",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "delivery_note",
+  "grand_total"
+ ],
+ "fields": [
+  {
+   "fieldname": "delivery_note",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Delivery Note",
+   "options": "Delivery Note",
+   "reqd": 1
+  },
+  {
+   "fieldname": "grand_total",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Value",
+   "read_only": 1
+  }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-12-02 15:44:34.028703",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Shipment Delivery Note",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.py b/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.py
new file mode 100644
index 0000000..4342151
--- /dev/null
+++ b/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class ShipmentDeliveryNote(Document):
+	pass
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/stock/doctype/shipment_parcel/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/stock/doctype/shipment_parcel/__init__.py
diff --git a/erpnext/stock/doctype/shipment_parcel/shipment_parcel.json b/erpnext/stock/doctype/shipment_parcel/shipment_parcel.json
new file mode 100644
index 0000000..6943edc
--- /dev/null
+++ b/erpnext/stock/doctype/shipment_parcel/shipment_parcel.json
@@ -0,0 +1,65 @@
+{
+ "actions": [],
+ "creation": "2020-07-09 11:28:48.887737",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "length",
+  "width",
+  "height",
+  "weight",
+  "count"
+ ],
+ "fields": [
+  {
+   "fieldname": "length",
+   "fieldtype": "Int",
+   "in_list_view": 1,
+   "label": "Length (cm)",
+   "reqd": 1
+  },
+  {
+   "fieldname": "width",
+   "fieldtype": "Int",
+   "in_list_view": 1,
+   "label": "Width (cm)",
+   "reqd": 1
+  },
+  {
+   "fieldname": "height",
+   "fieldtype": "Int",
+   "in_list_view": 1,
+   "label": "Height (cm)",
+   "reqd": 1
+  },
+  {
+   "fieldname": "weight",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Weight (kg)",
+   "precision": "1",
+   "reqd": 1
+  },
+  {
+   "default": "1",
+   "fieldname": "count",
+   "fieldtype": "Int",
+   "in_list_view": 1,
+   "label": "Count",
+   "reqd": 1
+  }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-07-09 12:54:14.847170",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Shipment Parcel",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/shipment_parcel/shipment_parcel.py b/erpnext/stock/doctype/shipment_parcel/shipment_parcel.py
new file mode 100644
index 0000000..53e6ed5
--- /dev/null
+++ b/erpnext/stock/doctype/shipment_parcel/shipment_parcel.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class ShipmentParcel(Document):
+	pass
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/stock/doctype/shipment_parcel_template/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/stock/doctype/shipment_parcel_template/__init__.py
diff --git a/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.js b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.js
new file mode 100644
index 0000000..785a3b3
--- /dev/null
+++ b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Shipment Parcel Template', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json
new file mode 100644
index 0000000..4735d9f
--- /dev/null
+++ b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json
@@ -0,0 +1,78 @@
+{
+ "actions": [],
+ "autoname": "field:parcel_template_name",
+ "creation": "2020-07-09 11:43:43.470339",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "parcel_template_name",
+  "length",
+  "width",
+  "height",
+  "weight"
+ ],
+ "fields": [
+  {
+   "fieldname": "length",
+   "fieldtype": "Int",
+   "in_list_view": 1,
+   "label": "Length (cm)",
+   "reqd": 1
+  },
+  {
+   "fieldname": "width",
+   "fieldtype": "Int",
+   "in_list_view": 1,
+   "label": "Width (cm)",
+   "reqd": 1
+  },
+  {
+   "fieldname": "height",
+   "fieldtype": "Int",
+   "in_list_view": 1,
+   "label": "Height (cm)",
+   "reqd": 1
+  },
+  {
+   "fieldname": "weight",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Weight (kg)",
+   "precision": "1",
+   "reqd": 1
+  },
+  {
+   "fieldname": "parcel_template_name",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Parcel Template Name",
+   "reqd": 1,
+   "unique": 1
+  }
+ ],
+ "links": [],
+ "modified": "2020-09-28 12:51:00.320421",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Shipment Parcel Template",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.py b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.py
new file mode 100644
index 0000000..2a8d58d
--- /dev/null
+++ b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class ShipmentParcelTemplate(Document):
+	pass
diff --git a/erpnext/stock/doctype/shipment_parcel_template/test_shipment_parcel_template.py b/erpnext/stock/doctype/shipment_parcel_template/test_shipment_parcel_template.py
new file mode 100644
index 0000000..6e2caa7
--- /dev/null
+++ b/erpnext/stock/doctype/shipment_parcel_template/test_shipment_parcel_template.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestShipmentParcelTemplate(unittest.TestCase):
+	pass
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 9121758..98116ec 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -510,22 +510,31 @@
 
 	calculate_amount: function(frm) {
 		frm.events.calculate_total_additional_costs(frm);
-
-		const total_basic_amount = frappe.utils.sum(
-			(frm.doc.items || []).map(function(i) { return i.t_warehouse ? flt(i.basic_amount) : 0; })
-		);
-
+		let total_basic_amount = 0;
+		if (in_list(["Repack", "Manufacture"], frm.doc.purpose)) {
+			total_basic_amount = frappe.utils.sum(
+				(frm.doc.items || []).map(function(i) {
+					return i.is_finished_item ? flt(i.basic_amount) : 0;
+				})
+			);
+		} else {
+			total_basic_amount = frappe.utils.sum(
+				(frm.doc.items || []).map(function(i) {
+					return i.t_warehouse ? flt(i.basic_amount) : 0;
+				})
+			);
+		}
+		
 		for (let i in frm.doc.items) {
 			let item = frm.doc.items[i];
 
-			if (item.t_warehouse && total_basic_amount) {
+			if (((in_list(["Repack", "Manufacture"], frm.doc.purpose) && item.is_finished_item) || item.t_warehouse) && total_basic_amount) {
 				item.additional_cost = (flt(item.basic_amount) / total_basic_amount) * frm.doc.total_additional_costs;
 			} else {
 				item.additional_cost = 0;
 			}
 
-			item.amount = flt(item.basic_amount + flt(item.additional_cost),
-				precision("amount", item));
+			item.amount = flt(item.basic_amount + flt(item.additional_cost), precision("amount", item));
 
 			if (flt(item.transfer_qty)) {
 				item.valuation_rate = flt(flt(item.basic_rate) + (flt(item.additional_cost) / flt(item.transfer_qty)),
@@ -841,6 +850,10 @@
 		}
 	},
 
+	fg_completed_qty: function() {
+		this.get_items();
+	},
+
 	get_items: function() {
 		var me = this;
 		if(!this.frm.doc.fg_completed_qty || !this.frm.doc.bom_no)
@@ -850,6 +863,7 @@
 			// if work order / bom is mentioned, get items
 			return this.frm.call({
 				doc: me.frm.doc,
+				freeze: true,
 				method: "get_items",
 				callback: function(r) {
 					if(!r.exc) refresh_field("items");
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json
index 61e0df6..5aed081 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.json
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.json
@@ -644,9 +644,10 @@
  ],
  "icon": "fa fa-file-text",
  "idx": 1,
+ "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-08-11 19:10:07.954981",
+ "modified": "2020-09-09 12:59:02.508943",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index e3159b9..2fc7da8 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -18,7 +18,7 @@
 from frappe.model.mapper import get_mapped_doc
 from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit, get_serial_nos
 from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import OpeningEntryAccountError
-
+from erpnext.accounts.general_ledger import process_gl_map
 import json
 
 from six import string_types, itervalues, iteritems
@@ -58,6 +58,7 @@
 		self.validate_warehouse()
 		self.validate_work_order()
 		self.validate_bom()
+		self.mark_finished_and_scrap_items()
 		self.validate_finished_goods()
 		self.validate_with_material_request()
 		self.validate_batch()
@@ -75,13 +76,11 @@
 		else:
 			set_batch_nos(self, 's_warehouse')
 
-		self.set_incoming_rate()
 		self.validate_serialized_batch()
 		self.set_actual_qty()
-		self.calculate_rate_and_amount(update_finished_item_rate=False)
+		self.calculate_rate_and_amount()
 
 	def on_submit(self):
-
 		self.update_stock_ledger()
 
 		update_serial_nos_after_submit(self, "items")
@@ -89,11 +88,15 @@
 		self.validate_purchase_order()
 		if self.purchase_order and self.purpose == "Send to Subcontractor":
 			self.update_purchase_order_supplied_items()
+
 		self.make_gl_entries()
+
+		self.repost_future_sle_and_gle()
 		self.update_cost_in_project()
 		self.validate_reserved_serial_no_consumption()
 		self.update_transferred_qty()
 		self.update_quality_inspection()
+
 		if self.work_order and self.purpose == "Manufacture":
 			self.update_so_in_serial_number()
 
@@ -113,13 +116,15 @@
 		self.update_work_order()
 		self.update_stock_ledger()
 
-		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
+		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
 
 		self.make_gl_entries_on_cancel()
+		self.repost_future_sle_and_gle()
 		self.update_cost_in_project()
 		self.update_transferred_qty()
 		self.update_quality_inspection()
 		self.delete_auto_created_batches()
+		self.delete_linked_stock_entry()
 
 		if self.purpose == 'Material Transfer' and self.add_to_transit:
 			self.set_material_request_transfer_status('Not Started')
@@ -152,6 +157,12 @@
 			frappe.throw(_("For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry")
 				.format(self.job_card))
 
+	def delete_linked_stock_entry(self):
+		if self.purpose == "Send to Warehouse":
+			for d in frappe.get_all("Stock Entry", filters={"docstatus": 0,
+				"outgoing_stock_entry": self.name, "purpose": "Receive at Warehouse"}):
+				frappe.delete_doc("Stock Entry", d.name)
+
 	def set_transfer_qty(self):
 		for item in self.get("items"):
 			if not flt(item.qty):
@@ -248,12 +259,16 @@
 								item_code.append(item.item_code)
 
 	def validate_fg_completed_qty(self):
+		item_wise_qty = {}
 		if self.purpose == "Manufacture" and self.work_order:
-			production_item = frappe.get_value('Work Order', self.work_order, 'production_item')
-			for item in self.items:
-				if item.item_code == production_item and item.t_warehouse and item.qty != self.fg_completed_qty:
-					frappe.throw(_("Finished product quantity <b>{0}</b> and For Quantity <b>{1}</b> cannot be different")
-						.format(item.qty, self.fg_completed_qty))
+			for d in self.items:
+				if d.is_finished_item:
+					item_wise_qty.setdefault(d.item_code, []).append(d.qty)
+
+		for item_code, qty_list in iteritems(item_wise_qty):
+			if self.fg_completed_qty != sum(qty_list):
+				frappe.throw(_("The finished product {0} quantity {1} and For Quantity {2} cannot be different")
+					.format(frappe.bold(item_code), frappe.bold(sum(qty_list)), frappe.bold(self.fg_completed_qty)))
 
 	def validate_difference_account(self):
 		if not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
@@ -309,7 +324,7 @@
 
 			if self.purpose == "Manufacture":
 				if validate_for_manufacture:
-					if d.bom_no:
+					if d.is_finished_item or d.is_scrap_item:
 						d.s_warehouse = None
 						if not d.t_warehouse:
 							frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
@@ -375,21 +390,6 @@
 				frappe.throw(_("Stock Entries already created for Work Order ")
 					+ self.work_order + ":" + ", ".join(other_ste), DuplicateEntryForWorkOrderError)
 
-	def set_incoming_rate(self):
-		if self.purpose == "Repack":
-			self.set_basic_rate_for_finished_goods()
-
-		for d in self.items:
-			if d.s_warehouse:
-				args = self.get_args_for_incoming_rate(d)
-				d.basic_rate = get_incoming_rate(args)
-			elif d.allow_zero_valuation_rate and not d.s_warehouse:
-				d.basic_rate = 0.0
-			elif d.t_warehouse and not d.basic_rate:
-				d.basic_rate = get_valuation_rate(d.item_code, d.t_warehouse,
-					self.doctype, self.name, d.allow_zero_valuation_rate,
-					currency=erpnext.get_company_currency(self.company), company=self.company)
-
 	def set_actual_qty(self):
 		allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock"))
 
@@ -425,57 +425,65 @@
 				d.serial_no = transferred_serial_no
 
 	def get_stock_and_rate(self):
+		"""
+			Updates rate and availability of all the items.
+			Called from Update Rate and Availability button.
+		"""
 		self.set_work_order_details()
 		self.set_transfer_qty()
 		self.set_actual_qty()
 		self.calculate_rate_and_amount()
 
-	def calculate_rate_and_amount(self, force=False,
-			update_finished_item_rate=True, raise_error_if_no_rate=True):
-		self.set_basic_rate(force, update_finished_item_rate, raise_error_if_no_rate)
+	def calculate_rate_and_amount(self, reset_outgoing_rate=True, raise_error_if_no_rate=True):
+		self.set_basic_rate(reset_outgoing_rate, raise_error_if_no_rate)
 		self.distribute_additional_costs()
 		self.update_valuation_rate()
 		self.set_total_incoming_outgoing_value()
 		self.set_total_amount()
 
-	def set_basic_rate(self, force=False, update_finished_item_rate=True, raise_error_if_no_rate=True):
-		"""get stock and incoming rate on posting date"""
-		raw_material_cost = 0.0
-		scrap_material_cost = 0.0
-		fg_basic_rate = 0.0
+	def set_basic_rate(self, reset_outgoing_rate=True, raise_error_if_no_rate=True):
+		"""
+			Set rate for outgoing, scrapped and finished items
+		"""
+		# Set rate for outgoing items
+		outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate)
+		finished_item_qty = sum([d.transfer_qty for d in self.items if d.is_finished_item])
 
+		# Set basic rate for incoming items
 		for d in self.get('items'):
-			if d.t_warehouse: fg_basic_rate = flt(d.basic_rate)
-			args = self.get_args_for_incoming_rate(d)
+			if d.s_warehouse or d.set_basic_rate_manually: continue
 
-			# get basic rate
-			if not d.bom_no:
-				if (not flt(d.basic_rate) and not d.allow_zero_valuation_rate) or d.s_warehouse or force:
-					basic_rate = flt(get_incoming_rate(args, raise_error_if_no_rate), self.precision("basic_rate", d))
-					if basic_rate > 0:
-						d.basic_rate = basic_rate
+			if d.allow_zero_valuation_rate:
+				d.basic_rate = 0.0
+			elif d.is_finished_item:
+				if self.purpose == "Manufacture":
+					d.basic_rate = self.get_basic_rate_for_manufactured_item(finished_item_qty, outgoing_items_cost)
+				elif self.purpose == "Repack":
+					d.basic_rate = self.get_basic_rate_for_repacked_items(d.transfer_qty, outgoing_items_cost)
+
+			if not d.basic_rate and not d.allow_zero_valuation_rate:
+				d.basic_rate = get_valuation_rate(d.item_code, d.t_warehouse,
+					self.doctype, self.name, d.allow_zero_valuation_rate,
+					currency=erpnext.get_company_currency(self.company), company=self.company,
+					raise_error_if_no_rate=raise_error_if_no_rate)
+
+			d.basic_rate = flt(d.basic_rate, d.precision("basic_rate"))
+			d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
+
+	def set_rate_for_outgoing_items(self, reset_outgoing_rate=True):
+		outgoing_items_cost = 0.0
+		for d in self.get('items'):
+			if d.s_warehouse:
+				if reset_outgoing_rate:
+					args = self.get_args_for_incoming_rate(d)
+					rate = get_incoming_rate(args)
+					if rate > 0:
+						d.basic_rate = rate
 
 				d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
 				if not d.t_warehouse:
-					raw_material_cost += flt(d.basic_amount)
-
-			# get scrap items basic rate
-			if d.bom_no:
-				if not flt(d.basic_rate) and not d.allow_zero_valuation_rate and \
-					getattr(self, "pro_doc", frappe._dict()).scrap_warehouse == d.t_warehouse:
-					basic_rate = flt(get_incoming_rate(args, raise_error_if_no_rate),
-						self.precision("basic_rate", d))
-					if basic_rate > 0:
-						d.basic_rate = basic_rate
-					d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
-
-				if getattr(self, "pro_doc", frappe._dict()).scrap_warehouse == d.t_warehouse:
-
-					scrap_material_cost += flt(d.basic_amount)
-
-		number_of_fg_items = len([t.t_warehouse for t in self.get("items") if t.t_warehouse])
-		if (fg_basic_rate == 0.0 and number_of_fg_items == 1) or update_finished_item_rate:
-			self.set_basic_rate_for_finished_goods(raw_material_cost, scrap_material_cost)
+					outgoing_items_cost += flt(d.basic_amount)
+		return outgoing_items_cost
 
 	def get_args_for_incoming_rate(self, item):
 		return frappe._dict({
@@ -491,44 +499,44 @@
 			"allow_zero_valuation": item.allow_zero_valuation_rate,
 		})
 
-	def set_basic_rate_for_finished_goods(self, raw_material_cost=0, scrap_material_cost=0):
-		total_fg_qty = 0
-		if not raw_material_cost and self.get("items"):
-			raw_material_cost = sum([flt(row.basic_amount) for row in self.items
-				if row.s_warehouse and not row.t_warehouse])
+	def get_basic_rate_for_repacked_items(self, finished_item_qty, outgoing_items_cost):
+		finished_items = [d.item_code for d in self.get("items") if d.is_finished_item]
+		if len(finished_items) == 1:
+			return flt(outgoing_items_cost / finished_item_qty)
+		else:
+			unique_finished_items = set(finished_items)
+			if len(unique_finished_items) == 1:
+				total_fg_qty = sum([flt(d.transfer_qty) for d in self.items if d.is_finished_item])
+				return flt(outgoing_items_cost / total_fg_qty)
 
-			total_fg_qty = sum([flt(row.qty) for row in self.items
-				if row.t_warehouse and not row.s_warehouse])
+	def get_basic_rate_for_manufactured_item(self, finished_item_qty, outgoing_items_cost=0):
+		scrap_items_cost = sum([flt(d.basic_amount) for d in self.get("items") if d.is_scrap_item])
 
-		if self.purpose in ["Manufacture", "Repack"]:
-			for d in self.get("items"):
-				if (d.transfer_qty and (d.bom_no or d.t_warehouse)
-					and (getattr(self, "pro_doc", frappe._dict()).scrap_warehouse != d.t_warehouse)):
+		# Get raw materials cost from BOM if multiple material consumption entries
+		if frappe.db.get_single_value("Manufacturing Settings", "material_consumption"):
+			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()])
 
-					if (self.work_order and self.purpose == "Manufacture"
-						and frappe.db.get_single_value("Manufacturing Settings", "material_consumption")):
-						bom_items = self.get_bom_raw_materials(d.transfer_qty)
-						raw_material_cost = sum([flt(row.qty)*flt(row.rate) for row in bom_items.values()])
-
-					if raw_material_cost and self.purpose == "Manufacture":
-						d.basic_rate = flt((raw_material_cost - scrap_material_cost) / flt(d.transfer_qty), d.precision("basic_rate"))
-						d.basic_amount = flt((raw_material_cost - scrap_material_cost), d.precision("basic_amount"))
-					elif self.purpose == "Repack" and total_fg_qty and not d.set_basic_rate_manually:
-						d.basic_rate = flt(raw_material_cost) / flt(total_fg_qty)
-						d.basic_amount = d.basic_rate * flt(d.qty)
+		return flt((outgoing_items_cost - scrap_items_cost) / finished_item_qty)
 
 	def distribute_additional_costs(self):
-		if self.purpose == "Material Issue":
+		# If no incoming items, set additional costs blank
+		if not any([d.item_code for d in self.items if d.t_warehouse]):
 			self.additional_costs = []
 
 		self.total_additional_costs = sum([flt(t.amount) for t in self.get("additional_costs")])
-		total_basic_amount = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse])
 
-		for d in self.get("items"):
-			if d.t_warehouse and total_basic_amount:
-				d.additional_cost = (flt(d.basic_amount) / total_basic_amount) * self.total_additional_costs
-			else:
-				d.additional_cost = 0
+		if self.purpose in ("Repack", "Manufacture"):
+			incoming_items_cost = sum([flt(t.basic_amount) for t in self.get("items") if t.is_finished_item])
+		else:
+			incoming_items_cost = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse])
+
+		if incoming_items_cost:
+			for d in self.get("items"):
+				if (self.purpose in ("Repack", "Manufacture") and d.is_finished_item) or d.t_warehouse:
+					d.additional_cost = (flt(d.basic_amount) / incoming_items_cost) * self.total_additional_costs
+				else:
+					d.additional_cost = 0
 
 	def update_valuation_rate(self):
 		for d in self.get("items"):
@@ -631,71 +639,115 @@
 				item_code = d.original_item or d.item_code
 				validate_bom_no(item_code, d.bom_no)
 
+	def mark_finished_and_scrap_items(self):
+		if self.purpose in ("Repack", "Manufacture"):
+			if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]):
+				return
+
+			finished_item = self.get_finished_item()
+
+			for d in self.items:
+				if d.t_warehouse and not d.s_warehouse:
+					if self.purpose=="Repack" or d.item_code == finished_item:
+						d.is_finished_item = 1
+					else:
+						d.is_scrap_item = 1
+				else:
+					d.is_finished_item = 0
+					d.is_scrap_item = 0
+
+	def get_finished_item(self):
+		finished_item = None
+		if self.work_order:
+			finished_item = frappe.db.get_value("Work Order", self.work_order, "production_item")
+		elif self.bom_no:
+			finished_item = frappe.db.get_value("BOM", self.bom_no, "item")
+
+		return finished_item
+
 	def validate_finished_goods(self):
 		"""validation: finished good quantity should be same as manufacturing quantity"""
 		if not self.work_order: return
 
-		items_with_target_warehouse = []
-		allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
-			"overproduction_percentage_for_work_order"))
-
 		production_item, wo_qty = frappe.db.get_value("Work Order",
 			self.work_order, ["production_item", "qty"])
 
+		finished_items = []
 		for d in self.get('items'):
-			if (self.purpose != "Send to Subcontractor" and d.bom_no
-				and flt(d.transfer_qty) > flt(self.fg_completed_qty) and d.item_code == production_item):
-				frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}"). \
-					format(d.idx, d.transfer_qty, self.fg_completed_qty))
+			if d.is_finished_item:
+				if d.item_code != production_item:
+					frappe.throw(_("Finished Item {0} does not match with Work Order {1}")
+						.format(d.item_code, self.work_order))
+				elif flt(d.transfer_qty) > flt(self.fg_completed_qty):
+					frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}"). \
+						format(d.idx, d.transfer_qty, self.fg_completed_qty))
+				finished_items.append(d.item_code)
 
-			if self.work_order and self.purpose == "Manufacture" and d.t_warehouse:
-				items_with_target_warehouse.append(d.item_code)
+		if len(set(finished_items)) > 1:
+			frappe.throw(_("Multiple items cannot be marked as finished item"))
 
-		if self.work_order and self.purpose == "Manufacture":
+		if self.purpose == "Manufacture":
+			allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
+				"overproduction_percentage_for_work_order"))
+
 			allowed_qty = wo_qty + (allowance_percentage/100 * wo_qty)
 			if self.fg_completed_qty > allowed_qty:
 				frappe.throw(_("For quantity {0} should not be greater than work order quantity {1}")
 					.format(flt(self.fg_completed_qty), wo_qty))
 
-			if production_item not in items_with_target_warehouse:
-				frappe.throw(_("Finished Item {0} must be entered for Manufacture type entry")
-					.format(production_item))
-
 	def update_stock_ledger(self):
 		sl_entries = []
+		finished_item_row = self.get_finished_item_row()
 
-		# make sl entries for source warehouse first, then do for target warehouse
-		for d in self.get('items'):
-			if cstr(d.s_warehouse):
-				sl_entries.append(self.get_sl_entries(d, {
-					"warehouse": cstr(d.s_warehouse),
-					"actual_qty": -flt(d.transfer_qty),
-					"incoming_rate": 0
-				}))
+		# make sl entries for source warehouse first
+		self.get_sle_for_source_warehouse(sl_entries, finished_item_row)
 
-		for d in self.get('items'):
-			if cstr(d.t_warehouse):
-				sl_entries.append(self.get_sl_entries(d, {
-					"warehouse": cstr(d.t_warehouse),
-					"actual_qty": flt(d.transfer_qty),
-					"incoming_rate": flt(d.valuation_rate)
-				}))
+		# SLE for target warehouse
+		self.get_sle_for_target_warehouse(sl_entries, finished_item_row)
 
-		# On cancellation, make stock ledger entry for
-		# target warehouse first, to update serial no values properly
-
-			# if cstr(d.s_warehouse) and self.docstatus == 2:
-			# 	sl_entries.append(self.get_sl_entries(d, {
-			# 		"warehouse": cstr(d.s_warehouse),
-			# 		"actual_qty": -flt(d.transfer_qty),
-			# 		"incoming_rate": 0
-			# 	}))
-
+		# reverse sl entries if cancel
 		if self.docstatus == 2:
 			sl_entries.reverse()
 
 		self.make_sl_entries(sl_entries)
 
+	def get_finished_item_row(self):
+		finished_item_row = None
+		if self.purpose in ("Manufacture", "Repack"):
+			for d in self.get('items'):
+				if d.is_finished_item:
+					finished_item_row = d
+
+		return finished_item_row
+
+	def get_sle_for_source_warehouse(self, sl_entries, finished_item_row):
+		for d in self.get('items'):
+			if cstr(d.s_warehouse):
+				sle = self.get_sl_entries(d, {
+					"warehouse": cstr(d.s_warehouse),
+					"actual_qty": -flt(d.transfer_qty),
+					"incoming_rate": 0
+				})
+				if cstr(d.t_warehouse):
+					sle.dependant_sle_voucher_detail_no = d.name
+				elif finished_item_row and (finished_item_row.item_code != d.item_code or finished_item_row.t_warehouse != d.s_warehouse):
+					sle.dependant_sle_voucher_detail_no = finished_item_row.name
+
+				sl_entries.append(sle)
+
+	def get_sle_for_target_warehouse(self, sl_entries, finished_item_row):
+		for d in self.get('items'):
+			if cstr(d.t_warehouse):
+				sle = self.get_sl_entries(d, {
+					"warehouse": cstr(d.t_warehouse),
+					"actual_qty": flt(d.transfer_qty),
+					"incoming_rate": flt(d.valuation_rate)
+				})
+				if cstr(d.s_warehouse) or (finished_item_row and d.name == finished_item_row.name):
+					sle.recalculate_rate = 1
+
+				sl_entries.append(sle)
+
 	def get_gl_entries(self, warehouse_account):
 		gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account)
 
@@ -740,7 +792,7 @@
 						"credit": -1 * amount # put it as negative credit instead of debit purposefully
 					}, item=d))
 
-		return gl_entries
+		return process_gl_map(gl_entries)
 
 	def update_work_order(self):
 		def _validate_work_order(pro_doc):
@@ -989,6 +1041,7 @@
 				"stock_uom": item.stock_uom,
 				"expense_account": item.get("expense_account"),
 				"cost_center": item.get("buying_cost_center"),
+				"is_finished_item": 1
 			}
 		}, bom_no = self.bom_no)
 
@@ -1027,32 +1080,29 @@
 
 		for item in itervalues(item_dict):
 			item.from_warehouse = ""
+			item.is_scrap_item = 1
 		return item_dict
 
 	def get_unconsumed_raw_materials(self):
 		wo = frappe.get_doc("Work Order", self.work_order)
 		wo_items = frappe.get_all('Work Order Item',
 			filters={'parent': self.work_order},
-			fields=["item_code", "required_qty", "consumed_qty"]
+			fields=["item_code", "required_qty", "consumed_qty", "transferred_qty"]
 			)
 
+		work_order_qty = wo.material_transferred_for_manufacturing or wo.qty
 		for item in wo_items:
-			qty = item.required_qty
-
 			item_account_details = get_item_defaults(item.item_code, self.company)
 			# Take into account consumption if there are any.
-			if self.purpose == 'Manufacture':
-				req_qty_each = flt(item.required_qty / wo.qty)
-				if (flt(item.consumed_qty) != 0):
-					remaining_qty = flt(item.consumed_qty) - (flt(wo.produced_qty) * req_qty_each)
-					exhaust_qty = req_qty_each * wo.produced_qty
-					if remaining_qty > exhaust_qty :
-						if (remaining_qty/(req_qty_each * flt(self.fg_completed_qty))) >= 1:
-							qty =0
-						else:
-							qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty
-				else:
-					qty = req_qty_each * flt(self.fg_completed_qty)
+
+			wo_item_qty = item.transferred_qty or item.required_qty
+
+			req_qty_each = (
+				(flt(wo_item_qty) - flt(item.consumed_qty)) /
+					(flt(work_order_qty) - flt(wo.produced_qty))
+			)
+
+			qty = req_qty_each * flt(self.fg_completed_qty)
 
 			if qty > 0:
 				self.add_to_stock_entry_detail({
@@ -1134,13 +1184,15 @@
 					else:
 						qty = req_qty_each * flt(self.fg_completed_qty)
 
-
 			elif backflushed_materials.get(item.item_code):
 				for d in backflushed_materials.get(item.item_code):
 					if d.get(item.warehouse):
 						if (qty > req_qty):
 							qty = (qty/trans_qty) * flt(self.fg_completed_qty)
 
+						if consumed_qty:
+							qty -= consumed_qty
+
 			if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')):
 				qty = frappe.utils.ceil(qty)
 
@@ -1241,6 +1293,8 @@
 			se_child.subcontracted_item = item_dict[d].get("main_item_code")
 			se_child.cost_center = (item_dict[d].get("cost_center") or
 				get_default_cost_center(item_dict[d], company = self.company))
+			se_child.is_finished_item = item_dict[d].get("is_finished_item", 0)
+			se_child.is_scrap_item = item_dict[d].get("is_scrap_item", 0)
 
 			for field in ["idx", "po_detail", "original_item",
 				"expense_account", "description", "item_name"]:
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 9b6744c..123f0c8 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -6,7 +6,6 @@
 import frappe.defaults
 from frappe.utils import flt, nowdate, nowtime
 from erpnext.stock.doctype.serial_no.serial_no import *
-from erpnext import set_perpetual_inventory
 from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import StockFreezeError
 from erpnext.stock.stock_ledger import get_previous_sle
 from frappe.permissions import add_user_permission, remove_user_permission
@@ -32,7 +31,6 @@
 class TestStockEntry(unittest.TestCase):
 	def tearDown(self):
 		frappe.set_user("Administrator")
-		set_perpetual_inventory(0)
 
 	def test_fifo(self):
 		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
@@ -181,22 +179,20 @@
 	def test_material_transfer_gl_entry(self):
 		company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
 
-		create_stock_reconciliation(qty=100, rate=100)
-
 		mtn = make_stock_entry(item_code="_Test Item", source="Stores - TCP1",
-			target="Finished Goods - TCP1", qty=45)
+			target="Finished Goods - TCP1", qty=45, company=company)
 
 		self.check_stock_ledger_entries("Stock Entry", mtn.name,
 			[["_Test Item", "Stores - TCP1", -45.0], ["_Test Item", "Finished Goods - TCP1", 45.0]])
 
-		stock_in_hand_account = get_inventory_account(mtn.company, mtn.get("items")[0].s_warehouse)
+		source_warehouse_account = get_inventory_account(mtn.company, mtn.get("items")[0].s_warehouse)
 
-		fixed_asset_account = get_inventory_account(mtn.company, mtn.get("items")[0].t_warehouse)
+		target_warehouse_account = get_inventory_account(mtn.company, mtn.get("items")[0].t_warehouse)
 
-		if stock_in_hand_account == fixed_asset_account:
+		if source_warehouse_account == target_warehouse_account:
 			# no gl entry as both source and target warehouse has linked to same account.
 			self.assertFalse(frappe.db.sql("""select * from `tabGL Entry`
-				where voucher_type='Stock Entry' and voucher_no=%s""", mtn.name))
+				where voucher_type='Stock Entry' and voucher_no=%s""", mtn.name, as_dict=1))
 
 		else:
 			stock_value_diff = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Stock Entry",
@@ -204,8 +200,8 @@
 
 			self.check_gl_entries("Stock Entry", mtn.name,
 				sorted([
-					[stock_in_hand_account, 0.0, stock_value_diff],
-					[fixed_asset_account, stock_value_diff, 0.0],
+					[source_warehouse_account, 0.0, stock_value_diff],
+					[target_warehouse_account, stock_value_diff, 0.0],
 				])
 			)
 
@@ -213,7 +209,6 @@
 
 	def test_repack_no_change_in_valuation(self):
 		company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company')
-		set_perpetual_inventory(0, company)
 
 		make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
 		make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC",
@@ -235,8 +230,6 @@
 			order by account desc""", repack.name, as_dict=1)
 		self.assertFalse(gl_entries)
 
-		set_perpetual_inventory(0, repack.company)
-
 	def test_repack_with_additional_costs(self):
 		company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
 
@@ -474,7 +467,6 @@
 
 	def test_warehouse_company_validation(self):
 		company = frappe.db.get_value('Warehouse', '_Test Warehouse 2 - _TC1', 'company')
-		set_perpetual_inventory(0, company)
 		frappe.get_doc("User", "test2@example.com")\
 			.add_roles("Sales User", "Sales Manager", "Stock User", "Stock Manager")
 		frappe.set_user("test2@example.com")
@@ -500,7 +492,7 @@
 
 		st1 = frappe.copy_doc(test_records[0])
 		st1.company = "_Test Company 1"
-		set_perpetual_inventory(0, st1.company)
+
 		frappe.set_user("test@example.com")
 		st1.get("items")[0].t_warehouse="_Test Warehouse 2 - _TC1"
 		self.assertRaises(frappe.PermissionError, st1.insert)
@@ -698,47 +690,54 @@
 		repack.insert()
 		self.assertRaises(frappe.ValidationError, repack.submit)
 
-	def test_material_consumption(self):
-		from erpnext.manufacturing.doctype.work_order.work_order \
-			import make_stock_entry as _make_stock_entry
-		bom_no = frappe.db.get_value("BOM", {"item": "_Test FG Item 2",
-			"is_default": 1, "docstatus": 1})
+	# def test_material_consumption(self):
+	# 	frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM")
+	# 	frappe.db.set_value("Manufacturing Settings", None, "material_consumption", "0")
 
-		work_order = frappe.new_doc("Work Order")
-		work_order.update({
-			"company": "_Test Company",
-			"fg_warehouse": "_Test Warehouse 1 - _TC",
-			"production_item": "_Test FG Item 2",
-			"bom_no": bom_no,
-			"qty": 4.0,
-			"stock_uom": "_Test UOM",
-			"wip_warehouse": "_Test Warehouse - _TC",
-			"additional_operating_cost": 1000
-		})
-		work_order.insert()
-		work_order.submit()
+	# 	from erpnext.manufacturing.doctype.work_order.work_order \
+	# 		import make_stock_entry as _make_stock_entry
+	# 	bom_no = frappe.db.get_value("BOM", {"item": "_Test FG Item 2",
+	# 		"is_default": 1, "docstatus": 1})
 
-		make_stock_entry(item_code="_Test Serialized Item With Series", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
-		make_stock_entry(item_code="_Test Item 2", target="_Test Warehouse - _TC", qty=50, basic_rate=20)
+	# 	work_order = frappe.new_doc("Work Order")
+	# 	work_order.update({
+	# 		"company": "_Test Company",
+	# 		"fg_warehouse": "_Test Warehouse 1 - _TC",
+	# 		"production_item": "_Test FG Item 2",
+	# 		"bom_no": bom_no,
+	# 		"qty": 4.0,
+	# 		"stock_uom": "_Test UOM",
+	# 		"wip_warehouse": "_Test Warehouse - _TC",
+	# 		"additional_operating_cost": 1000,
+	# 		"use_multi_level_bom": 1
+	# 	})
+	# 	work_order.insert()
+	# 	work_order.submit()
 
-		item_quantity = {
-			'_Test Item': 10.0,
-			'_Test Item 2': 12.0,
-			'_Test Serialized Item With Series': 6.0
-		}
+	# 	make_stock_entry(item_code="_Test Serialized Item With Series", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
+	# 	make_stock_entry(item_code="_Test Item 2", target="_Test Warehouse - _TC", qty=50, basic_rate=20)
 
-		stock_entry = frappe.get_doc(_make_stock_entry(work_order.name, "Material Consumption for Manufacture", 2))
-		for d in stock_entry.get('items'):
-			self.assertEqual(item_quantity.get(d.item_code), d.qty)
+	# 	item_quantity = {
+	# 		'_Test Item': 2.0,
+	# 		'_Test Item 2': 12.0,
+	# 		'_Test Serialized Item With Series': 6.0
+	# 	}
+
+	# 	stock_entry = frappe.get_doc(_make_stock_entry(work_order.name, "Material Consumption for Manufacture", 2))
+	# 	for d in stock_entry.get('items'):
+	# 		self.assertEqual(item_quantity.get(d.item_code), d.qty)
 
 	def test_customer_provided_parts_se(self):
 		create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
-		se = make_stock_entry(item_code='CUST-0987', purpose = 'Material Receipt', qty=4, to_warehouse = "_Test Warehouse - _TC")
+		se = make_stock_entry(item_code='CUST-0987', purpose = 'Material Receipt',
+			qty=4, to_warehouse = "_Test Warehouse - _TC")
 		self.assertEqual(se.get("items")[0].allow_zero_valuation_rate, 1)
 		self.assertEqual(se.get("items")[0].amount, 0)
 
 	def test_gle_for_opening_stock_entry(self):
-		mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company="_Test Company with perpetual inventory",qty=50, basic_rate=100, expense_account="Stock Adjustment - TCP1", is_opening="Yes", do_not_save=True)
+		mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1",
+			company="_Test Company with perpetual inventory", qty=50, basic_rate=100,
+			expense_account="Stock Adjustment - TCP1", is_opening="Yes", do_not_save=True)
 
 		self.assertRaises(OpeningEntryAccountError, mr.save)
 
@@ -753,37 +752,37 @@
 
 	def test_total_basic_amount_zero(self):
 		se = frappe.get_doc({"doctype":"Stock Entry",
-		"purpose":"Material Receipt",
-		"stock_entry_type":"Material Receipt",
-		"posting_date": nowdate(),
-		"company":"_Test Company with perpetual inventory",
-		"items":[
-			{
-				"item_code":"Basil Leaves",
-				"description":"Basil Leaves",
-				"qty": 1,
-				"basic_rate": 0,
-				"uom":"Nos",
-				"t_warehouse": "Stores - TCP1",
-				"allow_zero_valuation_rate": 1,
-				"cost_center": "Main - TCP1"
-			 },
-			 {
-				"item_code":"Basil Leaves",
-				"description":"Basil Leaves",
-				"qty": 2,
-				"basic_rate": 0,
-				"uom":"Nos",
-				"t_warehouse": "Stores - TCP1",
-				"allow_zero_valuation_rate": 1,
-				"cost_center": "Main - TCP1"
-			 },
-			 ],
-		"additional_costs":[
-			{"expense_account":"Miscellaneous Expenses - TCP1",
-			"amount":100,
-			"description": "miscellanous"}
-			]
+			"purpose":"Material Receipt",
+			"stock_entry_type":"Material Receipt",
+			"posting_date": nowdate(),
+			"company":"_Test Company with perpetual inventory",
+			"items":[
+				{
+					"item_code":"_Test Item",
+					"description":"_Test Item",
+					"qty": 1,
+					"basic_rate": 0,
+					"uom":"Nos",
+					"t_warehouse": "Stores - TCP1",
+					"allow_zero_valuation_rate": 1,
+					"cost_center": "Main - TCP1"
+				},
+				{
+					"item_code":"_Test Item",
+					"description":"_Test Item",
+					"qty": 2,
+					"basic_rate": 0,
+					"uom":"Nos",
+					"t_warehouse": "Stores - TCP1",
+					"allow_zero_valuation_rate": 1,
+					"cost_center": "Main - TCP1"
+				},
+			],
+			"additional_costs":[
+				{"expense_account":"Miscellaneous Expenses - TCP1",
+				"amount":100,
+				"description": "miscellanous"
+			}]
 		})
 		se.insert()
 		se.submit()
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
index 79e8f9a..b78ae6d 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -13,8 +13,10 @@
   "t_warehouse",
   "sec_break1",
   "item_code",
-  "col_break2",
   "item_name",
+  "col_break2",
+  "is_finished_item",
+  "is_scrap_item",
   "subcontracted_item",
   "section_break_8",
   "description",
@@ -22,35 +24,37 @@
   "item_group",
   "image",
   "image_view",
-  "quantity_and_rate",
-  "set_basic_rate_manually",
+  "quantity_section",
   "qty",
-  "basic_rate",
-  "basic_amount",
-  "additional_cost",
-  "amount",
-  "valuation_rate",
-  "col_break3",
-  "uom",
-  "conversion_factor",
-  "stock_uom",
   "transfer_qty",
   "retain_sample",
+  "column_break_20",
+  "uom",
+  "stock_uom",
+  "conversion_factor",
   "sample_quantity",
+  "rates_section",
+  "basic_rate",
+  "additional_cost",
+  "valuation_rate",
+  "allow_zero_valuation_rate",
+  "col_break3",
+  "set_basic_rate_manually",
+  "basic_amount",
+  "amount",
   "serial_no_batch",
   "serial_no",
   "col_break4",
   "batch_no",
-  "quality_inspection",
   "accounting",
   "expense_account",
-  "col_break5",
   "accounting_dimensions_section",
   "cost_center",
+  "project",
   "dimension_col_break",
   "more_info",
-  "allow_zero_valuation_rate",
   "actual_qty",
+  "transferred_qty",
   "bom_no",
   "allow_alternative_item",
   "col_break6",
@@ -62,9 +66,8 @@
   "ste_detail",
   "po_detail",
   "column_break_51",
-  "transferred_qty",
   "reference_purchase_receipt",
-  "project"
+  "quality_inspection"
  ],
  "fields": [
   {
@@ -160,11 +163,6 @@
    "print_hide": 1
   },
   {
-   "fieldname": "quantity_and_rate",
-   "fieldtype": "Section Break",
-   "label": "Quantity and Rate"
-  },
-  {
    "bold": 1,
    "fieldname": "qty",
    "fieldtype": "Float",
@@ -322,10 +320,6 @@
    "print_hide": 1
   },
   {
-   "fieldname": "col_break5",
-   "fieldtype": "Column Break"
-  },
-  {
    "default": ":Company",
    "depends_on": "eval:cint(erpnext.is_perpetual_inventory_enabled(parent.company))",
    "fieldname": "cost_center",
@@ -335,6 +329,7 @@
    "print_hide": 1
   },
   {
+   "collapsible": 1,
    "fieldname": "more_info",
    "fieldtype": "Section Break",
    "label": "More Information"
@@ -456,6 +451,7 @@
    "read_only": 1
   },
   {
+   "collapsible": 1,
    "fieldname": "accounting_dimensions_section",
    "fieldtype": "Section Break",
    "label": "Accounting Dimensions"
@@ -498,13 +494,39 @@
    "fieldname": "set_basic_rate_manually",
    "fieldtype": "Check",
    "label": "Set Basic Rate Manually"
+  },
+  {
+   "fieldname": "quantity_section",
+   "fieldtype": "Section Break",
+   "label": "Quantity"
+  },
+  {
+   "fieldname": "column_break_20",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "rates_section",
+   "fieldtype": "Section Break",
+   "label": "Rates"
+  },
+  {
+   "default": "0",
+   "fieldname": "is_scrap_item",
+   "fieldtype": "Check",
+   "label": "Is Scrap Item"
+  },
+  {
+   "default": "0",
+   "fieldname": "is_finished_item",
+   "fieldtype": "Check",
+   "label": "Is Finished Item"
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-09-23 17:55:03.384138",
+ "modified": "2020-12-23 17:55:03.384138",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry Detail",
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
index fda17e0..2463a21 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
@@ -8,26 +8,33 @@
  "engine": "InnoDB",
  "field_order": [
   "item_code",
-  "serial_no",
-  "batch_no",
   "warehouse",
   "posting_date",
   "posting_time",
+  "column_break_6",
   "voucher_type",
   "voucher_no",
   "voucher_detail_no",
+  "dependant_sle_voucher_detail_no",
+  "recalculate_rate",
+  "section_break_11",
   "actual_qty",
+  "qty_after_transaction",
   "incoming_rate",
   "outgoing_rate",
-  "stock_uom",
-  "qty_after_transaction",
+  "column_break_17",
   "valuation_rate",
   "stock_value",
   "stock_value_difference",
   "stock_queue",
-  "project",
+  "section_break_21",
   "company",
+  "stock_uom",
+  "project",
+  "batch_no",
+  "column_break_26",
   "fiscal_year",
+  "serial_no",
   "is_cancelled",
   "to_rename"
  ],
@@ -50,7 +57,6 @@
   {
    "fieldname": "serial_no",
    "fieldtype": "Long Text",
-   "in_list_view": 1,
    "label": "Serial No",
    "print_width": "100px",
    "read_only": 1,
@@ -59,7 +65,6 @@
   {
    "fieldname": "batch_no",
    "fieldtype": "Data",
-   "in_list_view": 1,
    "label": "Batch No",
    "oldfieldname": "batch_no",
    "oldfieldtype": "Data",
@@ -119,6 +124,7 @@
    "fieldname": "voucher_no",
    "fieldtype": "Dynamic Link",
    "in_filter": 1,
+   "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Voucher No",
    "oldfieldname": "voucher_no",
@@ -142,6 +148,7 @@
    "fieldname": "actual_qty",
    "fieldtype": "Float",
    "in_filter": 1,
+   "in_list_view": 1,
    "label": "Actual Quantity",
    "oldfieldname": "actual_qty",
    "oldfieldtype": "Currency",
@@ -152,6 +159,7 @@
   {
    "fieldname": "incoming_rate",
    "fieldtype": "Currency",
+   "in_list_view": 1,
    "label": "Incoming Rate",
    "oldfieldname": "incoming_rate",
    "oldfieldtype": "Currency",
@@ -217,13 +225,11 @@
   {
    "fieldname": "stock_queue",
    "fieldtype": "Text",
-   "hidden": 1,
    "label": "Stock Queue (FIFO)",
    "oldfieldname": "fcfs_stack",
    "oldfieldtype": "Text",
    "print_hide": 1,
-   "read_only": 1,
-   "report_hide": 1
+   "read_only": 1
   },
   {
    "fieldname": "project",
@@ -269,14 +275,48 @@
    "hidden": 1,
    "label": "To Rename",
    "search_index": 1
+  },
+  {
+   "fieldname": "dependant_sle_voucher_detail_no",
+   "fieldtype": "Data",
+   "label": "Dependant SLE Voucher Detail No"
+  },
+  {
+   "fieldname": "column_break_6",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_11",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_17",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_21",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_26",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fieldname": "recalculate_rate",
+   "fieldtype": "Check",
+   "label": "Recalculate Incoming/Outgoing Rate",
+   "no_copy": 1,
+   "read_only": 1
   }
  ],
  "hide_toolbar": 1,
  "icon": "fa fa-list",
  "idx": 1,
  "in_create": 1,
+ "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2020-04-23 05:57:03.985520",
+ "modified": "2020-09-07 11:10:35.318872",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Ledger Entry",
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 bb356f6..a5c303c 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -10,8 +10,10 @@
 from datetime import date
 from erpnext.controllers.item_variant import ItemTemplateCannotHaveStock
 from erpnext.accounts.utils import get_fiscal_year
+from frappe.core.doctype.role.role import get_users
 
 class StockFreezeError(frappe.ValidationError): pass
+class BackDatedStockTransaction(frappe.ValidationError): pass
 
 exclude_from_linked_with = True
 
@@ -34,7 +36,6 @@
 		self.validate_and_set_fiscal_year()
 		self.block_transactions_against_group_warehouse()
 		self.validate_with_last_transaction_posting_time()
-		self.validate_future_posting()
 
 	def on_submit(self):
 		self.check_stock_frozen_date()
@@ -48,7 +49,7 @@
 	def calculate_batch_qty(self):
 		if self.batch_no:
 			batch_qty = frappe.db.get_value("Stock Ledger Entry",
-				{"docstatus": 1, "batch_no": self.batch_no},
+				{"docstatus": 1, "batch_no": self.batch_no, "is_cancelled": 0},
 				"sum(actual_qty)") or 0
 			frappe.db.set_value("Batch", self.batch_no, "batch_qty", batch_qty)
 
@@ -88,14 +89,14 @@
 
 		# check if batch number is required
 		if self.voucher_type != 'Stock Reconciliation':
-			if item_det.has_batch_no ==1:
+			if item_det.has_batch_no == 1:
 				batch_item = self.item_code if self.item_code == item_det.item_name else self.item_code + ":" +  item_det.item_name
 				if not self.batch_no:
 					frappe.throw(_("Batch number is mandatory for Item {0}").format(batch_item))
 				elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}):
 					frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item))
 
-			elif item_det.has_batch_no ==0 and self.batch_no:
+			elif item_det.has_batch_no == 0 and self.batch_no and self.is_cancelled == 0:
 				frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code))
 
 		if item_det.has_variants:
@@ -142,28 +143,28 @@
 		is_group_warehouse(self.warehouse)
 
 	def validate_with_last_transaction_posting_time(self):
-		last_transaction_time = frappe.db.sql("""
-			select MAX(timestamp(posting_date, posting_time)) as posting_time
-			from `tabStock Ledger Entry`
-			where docstatus = 1 and item_code = %s
-			and warehouse = %s""", (self.item_code, self.warehouse))[0][0]
+		authorized_role = frappe.db.get_single_value("Stock Settings", "role_allowed_to_create_edit_back_dated_transactions")
+		if authorized_role:
+			authorized_users = get_users(authorized_role)
+			if authorized_users and frappe.session.user not in authorized_users:
+				last_transaction_time = frappe.db.sql("""
+					select MAX(timestamp(posting_date, posting_time)) as posting_time
+					from `tabStock Ledger Entry`
+					where docstatus = 1 and item_code = %s
+					and warehouse = %s""", (self.item_code, self.warehouse))[0][0]
 
-		cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00")
+				cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00")
 
-		if last_transaction_time and get_datetime(cur_doc_posting_datetime) < get_datetime(last_transaction_time):
-			msg = _("Last Stock Transaction for item {0} under warehouse {1} was on {2}.").format(frappe.bold(self.item_code),
-				frappe.bold(self.warehouse), frappe.bold(last_transaction_time))
+				if last_transaction_time and get_datetime(cur_doc_posting_datetime) < get_datetime(last_transaction_time):
+					msg = _("Last Stock Transaction for item {0} under warehouse {1} was on {2}.").format(frappe.bold(self.item_code),
+						frappe.bold(self.warehouse), frappe.bold(last_transaction_time))
 
-			msg += "<br><br>" + _("Stock Transactions for Item {0} under warehouse {1} cannot be posted before this time.").format(
-				frappe.bold(self.item_code), frappe.bold(self.warehouse))
+					msg += "<br><br>" + _("You are not authorized to make/edit Stock Transactions for Item {0} under warehouse {1} before this time.").format(
+						frappe.bold(self.item_code), frappe.bold(self.warehouse))
 
-			msg += "<br><br>" + _("Please remove this item and try to submit again or update the posting time.")
-			frappe.throw(msg, title=_("Backdated Stock Entry"))
-
-	def validate_future_posting(self):
-		if date_diff(self.posting_date, getdate()) > 0:
-			msg = _("Posting future stock transactions are not allowed due to Immutable Ledger")
-			frappe.throw(msg, title=_("Future Posting Not Allowed"))
+					msg += "<br><br>" + _("Please contact any of the following users to {} this transaction.")
+					msg += "<br>" + "<br>".join(authorized_users)
+					frappe.throw(msg, BackDatedStockTransaction, title=_("Backdated Stock Entry"))
 
 def on_doctype_update():
 	if not frappe.db.has_index('tabStock Ledger Entry', 'posting_sort_index'):
diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
index 04dae83..59f1f39 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
@@ -5,8 +5,397 @@
 
 import frappe
 import unittest
-
-# test_records = frappe.get_test_records('Stock Ledger Entry')
+from frappe.utils import today, add_days
+from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation \
+	import create_stock_reconciliation
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.stock.stock_ledger import get_previous_sle
+from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import create_landed_cost_voucher
+from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import BackDatedStockTransaction
 
 class TestStockLedgerEntry(unittest.TestCase):
-	pass
+	def setUp(self):
+		items = create_items()
+
+		# delete SLE and BINs for all items
+		frappe.db.sql("delete from `tabStock Ledger Entry` where item_code in (%s)" % (', '.join(['%s']*len(items))), items)
+		frappe.db.sql("delete from `tabBin` where item_code in (%s)" % (', '.join(['%s']*len(items))), items)
+
+	def test_item_cost_reposting(self):
+		company = "_Test Company"
+
+		# _Test Item for Reposting at Stores warehouse on 10-04-2020: Qty = 50, Rate = 100
+		create_stock_reconciliation(
+			item_code="_Test Item for Reposting",
+			warehouse="Stores - _TC",
+			qty=50,
+			rate=100,
+			company=company,
+			expense_account = "Stock Adjustment - _TC",
+			posting_date='2020-04-10',
+			posting_time='14:00'
+		)
+
+		# _Test Item for Reposting at FG warehouse on 20-04-2020: Qty = 10, Rate = 200
+		create_stock_reconciliation(
+			item_code="_Test Item for Reposting",
+			warehouse="Finished Goods - _TC",
+			qty=10,
+			rate=200,
+			company=company,
+			expense_account = "Stock Adjustment - _TC",
+			posting_date='2020-04-20',
+			posting_time='14:00'
+		)
+
+		# _Test Item for Reposting transferred from Stores to FG warehouse on 30-04-2020
+		make_stock_entry(
+			item_code="_Test Item for Reposting",
+			source="Stores - _TC",
+			target="Finished Goods - _TC",
+			company=company,
+			qty=10,
+			expense_account="Stock Adjustment - _TC",
+			posting_date='2020-04-30',
+			posting_time='14:00'
+		)
+		target_wh_sle = get_previous_sle({
+			"item_code": "_Test Item for Reposting",
+			"warehouse": "Finished Goods - _TC",
+			"posting_date": '2020-04-30',
+			"posting_time": '14:00'
+		})
+
+		self.assertEqual(target_wh_sle.get("valuation_rate"), 150)
+
+		# Repack entry on 5-5-2020
+		repack = create_repack_entry(company=company, posting_date='2020-05-05', posting_time='14:00')
+
+		finished_item_sle = get_previous_sle({
+			"item_code": "_Test Finished Item for Reposting",
+			"warehouse": "Finished Goods - _TC",
+			"posting_date": '2020-05-05',
+			"posting_time": '14:00'
+		})
+		self.assertEqual(finished_item_sle.get("incoming_rate"), 540)
+		self.assertEqual(finished_item_sle.get("valuation_rate"), 540)
+
+		# Reconciliation for _Test Item for Reposting at Stores on 12-04-2020: Qty = 50, Rate = 150
+		create_stock_reconciliation(
+			item_code="_Test Item for Reposting",
+			warehouse="Stores - _TC",
+			qty=50,
+			rate=150,
+			company=company,
+			expense_account = "Stock Adjustment - _TC",
+			posting_date='2020-04-12',
+			posting_time='14:00'
+		)
+
+
+		# Check valuation rate of finished goods warehouse after back-dated entry at Stores
+		target_wh_sle = get_previous_sle({
+			"item_code": "_Test Item for Reposting",
+			"warehouse": "Finished Goods - _TC",
+			"posting_date": '2020-04-30',
+			"posting_time": '14:00'
+		})
+		self.assertEqual(target_wh_sle.get("incoming_rate"), 150)
+		self.assertEqual(target_wh_sle.get("valuation_rate"), 175)
+
+		# Check valuation rate of repacked item after back-dated entry at Stores
+		finished_item_sle = get_previous_sle({
+			"item_code": "_Test Finished Item for Reposting",
+			"warehouse": "Finished Goods - _TC",
+			"posting_date": '2020-05-05',
+			"posting_time": '14:00'
+		})
+		self.assertEqual(finished_item_sle.get("incoming_rate"), 790)
+		self.assertEqual(finished_item_sle.get("valuation_rate"), 790)
+
+		# Check updated rate in Repack entry
+		repack.reload()
+		self.assertEqual(repack.items[0].get("basic_rate"), 150)
+		self.assertEqual(repack.items[1].get("basic_rate"), 750)
+
+	def test_purchase_return_valuation_reposting(self):
+		pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-10',
+			warehouse="Stores - _TC", item_code="_Test Item for Reposting", qty=5, rate=100)
+
+		return_pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-15', 
+			warehouse="Stores - _TC", item_code="_Test Item for Reposting", is_return=1, return_against=pr.name, qty=-2)
+
+		# check sle
+		outgoing_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
+			"voucher_no": return_pr.name}, ["outgoing_rate", "stock_value_difference"])
+
+		self.assertEqual(outgoing_rate, 100)
+		self.assertEqual(stock_value_difference, -200)
+
+		create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
+
+		outgoing_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
+			"voucher_no": return_pr.name}, ["outgoing_rate", "stock_value_difference"])
+
+		self.assertEqual(outgoing_rate, 110)
+		self.assertEqual(stock_value_difference, -220)
+
+	def test_sales_return_valuation_reposting(self):
+		company = "_Test Company"
+		item_code="_Test Item for Reposting"
+
+		# Purchase Return: Qty = 5, Rate = 100
+		pr = make_purchase_receipt(company=company, posting_date='2020-04-10',
+			warehouse="Stores - _TC", item_code=item_code, qty=5, rate=100)
+
+		#Delivery Note: Qty = 5, Rate = 150
+		dn = create_delivery_note(item_code=item_code, qty=5, rate=150, warehouse="Stores - _TC",
+			company=company, expense_account="Cost of Goods Sold - _TC", cost_center="Main - _TC")
+
+		# check outgoing_rate for DN
+		outgoing_rate = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note",
+			"voucher_no": dn.name}, "stock_value_difference") / 5)
+
+		self.assertEqual(dn.items[0].incoming_rate, 100)
+		self.assertEqual(outgoing_rate, 100)
+
+		# Return Entry: Qty = -2, Rate = 150
+		return_dn = create_delivery_note(is_return=1, return_against=dn.name, item_code=item_code, qty=-2, rate=150,
+			company=company, warehouse="Stores - _TC", expense_account="Cost of Goods Sold - _TC", cost_center="Main - _TC")
+
+		# check incoming rate for Return entry
+		incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
+			{"voucher_type": "Delivery Note", "voucher_no": return_dn.name},
+			["incoming_rate", "stock_value_difference"])
+
+		self.assertEqual(return_dn.items[0].incoming_rate, 100)
+		self.assertEqual(incoming_rate, 100)
+		self.assertEqual(stock_value_difference, 200)
+
+		#-------------------------------
+
+		# Landed Cost Voucher to update the rate of incoming Purchase Return: Additional cost = 50
+		lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
+
+		# check outgoing_rate for DN after reposting
+		outgoing_rate = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note",
+			"voucher_no": dn.name}, "stock_value_difference") / 5)
+		self.assertEqual(outgoing_rate, 110)
+
+		dn.reload()
+		self.assertEqual(dn.items[0].incoming_rate, 110)
+
+		# check incoming rate for Return entry after reposting
+		incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
+			{"voucher_type": "Delivery Note", "voucher_no": return_dn.name},
+			["incoming_rate", "stock_value_difference"])
+
+		self.assertEqual(incoming_rate, 110)
+		self.assertEqual(stock_value_difference, 220)
+
+		return_dn.reload()
+		self.assertEqual(return_dn.items[0].incoming_rate, 110)
+
+		# Cleanup data
+		return_dn.cancel()
+		dn.cancel()
+		lcv.cancel()
+		pr.cancel()
+
+	def test_reposting_of_sales_return_for_packed_item(self):
+		company = "_Test Company"
+		packed_item_code="_Test Item for Reposting"
+		bundled_item = "_Test Bundled Item for Reposting"
+		create_product_bundle_item(bundled_item, [[packed_item_code, 4]])
+
+		# Purchase Return: Qty = 50, Rate = 100
+		pr = make_purchase_receipt(company=company, posting_date='2020-04-10',
+			warehouse="Stores - _TC", item_code=packed_item_code, qty=50, rate=100)
+
+		#Delivery Note: Qty = 5, Rate = 150
+		dn = create_delivery_note(item_code=bundled_item, qty=5, rate=150, warehouse="Stores - _TC",
+			company=company, expense_account="Cost of Goods Sold - _TC", cost_center="Main - _TC")
+
+		# check outgoing_rate for DN
+		outgoing_rate = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note",
+			"voucher_no": dn.name}, "stock_value_difference") / 20)
+
+		self.assertEqual(dn.packed_items[0].incoming_rate, 100)
+		self.assertEqual(outgoing_rate, 100)
+
+		# Return Entry: Qty = -2, Rate = 150
+		return_dn = create_delivery_note(is_return=1, return_against=dn.name, item_code=bundled_item, qty=-2, rate=150,
+			company=company, warehouse="Stores - _TC", expense_account="Cost of Goods Sold - _TC", cost_center="Main - _TC")
+
+		# check incoming rate for Return entry
+		incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
+			{"voucher_type": "Delivery Note", "voucher_no": return_dn.name},
+			["incoming_rate", "stock_value_difference"])
+
+		self.assertEqual(return_dn.packed_items[0].incoming_rate, 100)
+		self.assertEqual(incoming_rate, 100)
+		self.assertEqual(stock_value_difference, 800)
+
+		#-------------------------------
+
+		# Landed Cost Voucher to update the rate of incoming Purchase Return: Additional cost = 50
+		lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
+
+		# check outgoing_rate for DN after reposting
+		outgoing_rate = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note",
+			"voucher_no": dn.name}, "stock_value_difference") / 20)
+		self.assertEqual(outgoing_rate, 101)
+
+		dn.reload()
+		self.assertEqual(dn.packed_items[0].incoming_rate, 101)
+
+		# check incoming rate for Return entry after reposting
+		incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
+			{"voucher_type": "Delivery Note", "voucher_no": return_dn.name},
+			["incoming_rate", "stock_value_difference"])
+
+		self.assertEqual(incoming_rate, 101)
+		self.assertEqual(stock_value_difference, 808)
+
+		return_dn.reload()
+		self.assertEqual(return_dn.packed_items[0].incoming_rate, 101)
+
+		# Cleanup data
+		return_dn.cancel()
+		dn.cancel()
+		lcv.cancel()
+		pr.cancel()
+
+	def test_sub_contracted_item_costing(self):
+		from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+
+		company = "_Test Company"
+		rm_item_code="_Test Item for Reposting"
+		subcontracted_item = "_Test Subcontracted Item for Reposting"
+
+		frappe.db.set_value("Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM")
+		make_bom(item = subcontracted_item, raw_materials =[rm_item_code], currency="INR")
+		
+		# Purchase raw materials on supplier warehouse: Qty = 50, Rate = 100
+		pr = make_purchase_receipt(company=company, posting_date='2020-04-10',
+			warehouse="Stores - _TC", item_code=rm_item_code, qty=10, rate=100)
+
+		# Purchase Receipt for subcontracted item
+		pr1 = make_purchase_receipt(company=company, posting_date='2020-04-20',
+			warehouse="Finished Goods - _TC", supplier_warehouse="Stores - _TC",
+			item_code=subcontracted_item, qty=10, rate=20, is_subcontracted="Yes")
+
+		self.assertEqual(pr1.items[0].valuation_rate, 120)
+
+		# Update raw material's valuation via LCV, Additional cost = 50
+		lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
+		
+		pr1.reload()
+		self.assertEqual(pr1.items[0].valuation_rate, 125)
+
+		# check outgoing_rate for DN after reposting
+		incoming_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
+			"voucher_no": pr1.name, "item_code": subcontracted_item}, "incoming_rate")
+		self.assertEqual(incoming_rate, 125)
+
+		# cleanup data
+		pr1.cancel()
+		lcv.cancel()
+		pr.cancel()
+
+	def test_back_dated_entry_not_allowed(self):
+		# Back dated stock transactions are only allowed to stock managers
+		frappe.db.set_value("Stock Settings", None,
+			"role_allowed_to_create_edit_back_dated_transactions", "Stock Manager")
+		
+		# Set User with Stock User role but not Stock Manager
+		frappe.set_user("test@example.com")
+		user = frappe.get_doc("User", "test@example.com")
+		user.add_roles("Stock User")
+		user.remove_roles("Stock Manager")
+
+		stock_entry_on_today = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
+		back_dated_se_1 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100,
+			posting_date=add_days(today(), -1), do_not_submit=True)
+
+		# Block back-dated entry
+		self.assertRaises(BackDatedStockTransaction, back_dated_se_1.submit)
+
+		user.add_roles("Stock Manager")
+
+		# Back dated entry allowed to Stock Manager
+		back_dated_se_2 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100,
+			posting_date=add_days(today(), -1))
+
+		back_dated_se_2.cancel()
+		stock_entry_on_today.cancel()
+
+		frappe.db.set_value("Stock Settings", None, "role_allowed_to_create_edit_back_dated_transactions", None)
+		frappe.set_user("Administrator")
+
+
+def create_repack_entry(**args):
+	args = frappe._dict(args)
+	repack = frappe.new_doc("Stock Entry")
+	repack.stock_entry_type = "Repack"
+	repack.company = args.company or "_Test Company"
+	repack.posting_date = args.posting_date
+	repack.set_posting_time = 1
+	repack.append("items", {
+		"item_code": "_Test Item for Reposting",
+		"s_warehouse": "Stores - _TC",
+		"qty": 5,
+		"conversion_factor": 1,
+		"expense_account": "Stock Adjustment - _TC",
+		"cost_center": "Main - _TC"
+	})
+
+	repack.append("items", {
+		"item_code": "_Test Finished Item for Reposting",
+		"t_warehouse": "Finished Goods - _TC",
+		"qty": 1,
+		"conversion_factor": 1,
+		"expense_account": "Stock Adjustment - _TC",
+		"cost_center": "Main - _TC"
+	})
+
+	repack.append("additional_costs", {
+		"expense_account": "Freight and Forwarding Charges - _TC",
+		"description": "transport cost",
+		"amount": 40
+	})
+
+	repack.save()
+	repack.submit()
+
+	return repack
+
+def create_product_bundle_item(new_item_code, packed_items):
+	if not frappe.db.exists("Product Bundle", new_item_code):
+		item = frappe.new_doc("Product Bundle")
+		item.new_item_code = new_item_code
+
+		for d in packed_items:
+			item.append("items", {
+				"item_code": d[0],
+				"qty": d[1]
+			})
+
+		item.save()
+
+def create_items():
+	items = ["_Test Item for Reposting", "_Test Finished Item for Reposting",
+		"_Test Subcontracted Item for Reposting", "_Test Bundled Item for Reposting"]
+	for d in items:
+		properties = {"valuation_method": "FIFO"}
+		if d == "_Test Bundled Item for Reposting":
+			properties.update({"is_stock_item": 0})
+		elif d == "_Test Subcontracted Item for Reposting":
+			properties.update({"is_sub_contracted_item": 1})
+
+		make_item(d, properties=properties)
+
+	return items
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 00b8f69..5b40292 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -37,14 +37,16 @@
 	def on_submit(self):
 		self.update_stock_ledger()
 		self.make_gl_entries()
+		self.repost_future_sle_and_gle()
 
 		from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
 		update_serial_nos_after_submit(self, "items")
 
 	def on_cancel(self):
-		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
+		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
 		self.make_sle_on_cancel()
 		self.make_gl_entries_on_cancel()
+		self.repost_future_sle_and_gle()
 
 	def remove_items_with_no_change(self):
 		"""Remove items if qty or rate is not changed"""
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 23d48d4..088456f 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -8,12 +8,11 @@
 import frappe, unittest
 from frappe.utils import flt, nowdate, nowtime
 from erpnext.accounts.utils import get_stock_and_account_balance
-from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
 from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after
 from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items
 from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
 from erpnext.stock.doctype.item.test_item import create_item
-from erpnext.stock.utils import get_stock_balance, get_incoming_rate, get_available_serial_nos, get_stock_value_on
+from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method
 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 
 class TestStockReconciliation(unittest.TestCase):
@@ -29,16 +28,17 @@
 		self._test_reco_sle_gle("Moving Average")
 
 	def _test_reco_sle_gle(self, valuation_method):
-		insert_existing_sle(warehouse='Stores - TCP1')
+		se1, se2, se3 = insert_existing_sle(warehouse='Stores - TCP1')
 		company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
 		# [[qty, valuation_rate, posting_date,
 		#		posting_time, expected_stock_value, bin_qty, bin_valuation]]
+		
 		input_data = [
-			[50, 1000],
-			[25, 900],
-			["", 1000],
-			[20, ""],
-			[0, ""]
+			[50, 1000, "2012-12-26", "12:00"],
+			[25, 900, "2012-12-26", "12:00"],
+			["", 1000, "2012-12-20", "12:05"],
+			[20, "", "2012-12-26", "12:05"],
+			[0, "", "2012-12-31", "12:10"]
 		]
 
 		for d in input_data:
@@ -47,13 +47,13 @@
 			last_sle = get_previous_sle({
 				"item_code": "_Test Item",
 				"warehouse": "Stores - TCP1",
-				"posting_date": nowdate(),
-				"posting_time": nowtime()
+				"posting_date": d[2],
+				"posting_time": d[3]
 			})
 
 			# submit stock reconciliation
 			stock_reco = create_stock_reconciliation(qty=d[0], rate=d[1],
-				posting_date=nowdate(), posting_time=nowtime(), warehouse="Stores - TCP1",
+				posting_date=d[2], posting_time=d[3], warehouse="Stores - TCP1",
 				company=company, expense_account = "Stock Adjustment - TCP1")
 
 			# check stock value
@@ -81,10 +81,15 @@
 
 				stock_reco.cancel()
 
+		se3.cancel()
+		se2.cancel()
+		se1.cancel()
+
 	def test_get_items(self):
-		create_warehouse("_Test Warehouse Group 1", {"is_group": 1})
+		create_warehouse("_Test Warehouse Group 1", 
+			{"is_group": 1, "company": "_Test Company", "parent_warehouse": "All Warehouses - _TC"})
 		create_warehouse("_Test Warehouse Ledger 1",
-			{"is_group": 0, "parent_warehouse": "_Test Warehouse Group 1 - _TC"})
+			{"is_group": 0, "parent_warehouse": "_Test Warehouse Group 1 - _TC", "company": "_Test Company"})
 
 		create_item("_Test Stock Reco Item", is_stock_item=1, valuation_rate=100,
 			warehouse="_Test Warehouse Ledger 1 - _TC", opening_stock=100)
@@ -95,8 +100,6 @@
 			[items[0]["item_code"], items[0]["warehouse"], items[0]["qty"]])
 
 	def test_stock_reco_for_serialized_item(self):
-		set_perpetual_inventory()
-
 		to_delete_records = []
 		to_delete_serial_nos = []
 
@@ -148,8 +151,6 @@
 			stock_doc.cancel()
 
 	def test_stock_reco_for_batch_item(self):
-		set_perpetual_inventory()
-
 		to_delete_records = []
 		to_delete_serial_nos = []
 
@@ -196,15 +197,17 @@
 def insert_existing_sle(warehouse):
 	from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
 
-	make_stock_entry(posting_date=nowdate(), posting_time=nowtime(), item_code="_Test Item",
+	se1 = make_stock_entry(posting_date="2012-12-15", posting_time="02:00", item_code="_Test Item",
 		target=warehouse, qty=10, basic_rate=700)
 
-	make_stock_entry(posting_date=nowdate(), posting_time=nowtime(), item_code="_Test Item",
+	se2 = make_stock_entry(posting_date="2012-12-25", posting_time="03:00", item_code="_Test Item",
 		source=warehouse, qty=15)
 
-	make_stock_entry(posting_date=nowdate(), posting_time=nowtime(), item_code="_Test Item",
+	se3 = make_stock_entry(posting_date="2013-01-05", posting_time="07:00", item_code="_Test Item",
 		target=warehouse, qty=15, basic_rate=1200)
 
+	return se1, se2, se3
+
 def create_batch_or_serial_no_items():
 	create_warehouse("_Test Warehouse for Stock Reco1",
 		{"is_group": 0, "parent_warehouse": "_Test Warehouse Group - _TC"})
@@ -256,6 +259,10 @@
 	return sr
 
 def set_valuation_method(item_code, valuation_method):
+	existing_valuation_method = get_valuation_method(item_code)
+	if valuation_method == existing_valuation_method:
+		return
+
 	frappe.db.set_value("Item", item_code, "valuation_method", valuation_method)
 
 	for warehouse in frappe.get_all("Warehouse", filters={"company": "_Test Company"}, fields=["name", "is_group"]):
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index 067659f..3ff396b 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -28,7 +28,9 @@
   "inter_warehouse_transfer_settings_section",
   "allow_from_dn",
   "allow_from_pr",
-  "freeze_stock_entries",
+  "control_historical_stock_transactions_section",
+  "role_allowed_to_create_edit_back_dated_transactions",
+  "column_break_26",
   "stock_frozen_upto",
   "stock_frozen_upto_days",
   "stock_auth_role",
@@ -156,21 +158,20 @@
    "label": "Notify by Email on Creation of Automatic Material Request"
   },
   {
-   "fieldname": "freeze_stock_entries",
-   "fieldtype": "Section Break",
-   "label": "Freeze Stock Entries"
-  },
-  {
+   "description": "No stock transactions can be created or modified before this date.",
    "fieldname": "stock_frozen_upto",
    "fieldtype": "Date",
    "label": "Stock Frozen Upto"
   },
   {
+   "description": "Stock transactions that are older than the mentioned days cannot be modified.",
    "fieldname": "stock_frozen_upto_days",
    "fieldtype": "Int",
    "label": "Freeze Stocks Older Than (Days)"
   },
   {
+   "depends_on": "eval:(doc.stock_frozen_upto || doc.stock_frozen_upto_days)",
+   "description": "The users with this Role are allowed to create/modify a stock transaction, even though the transaction is frozen.",
    "fieldname": "stock_auth_role",
    "fieldtype": "Link",
    "label": "Role Allowed to Edit Frozen Stock",
@@ -210,6 +211,22 @@
    "fieldname": "allow_from_pr",
    "fieldtype": "Check",
    "label": "Allow Material Transfer from Purchase Receipt to Purchase Invoice"
+  },
+  {
+   "description": "If mentioned, the system will allow only the users with this Role to create or modify any stock transaction earlier than the latest stock transaction for a specific item and warehouse. If set as blank, it allows all users to create/edit back-dated transactions.",
+   "fieldname": "role_allowed_to_create_edit_back_dated_transactions",
+   "fieldtype": "Link",
+   "label": "Role Allowed to Create/Edit Back-dated Transactions",
+   "options": "Role"
+  },
+  {
+   "fieldname": "column_break_26",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "control_historical_stock_transactions_section",
+   "fieldtype": "Section Break",
+   "label": "Control Historical Stock Transactions"
   }
  ],
  "icon": "icon-cog",
@@ -217,7 +234,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-10-13 10:33:29.147682",
+ "modified": "2020-12-29 12:53:31.162247",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Settings",
@@ -235,5 +252,6 @@
  ],
  "quick_entry": 1,
  "sort_field": "modified",
- "sort_order": "ASC"
+ "sort_order": "ASC",
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py
index 4c7828b..3b9608b 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.py
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.py
@@ -55,7 +55,7 @@
 			""")
 
 			if sle:
-				frappe.throw(_("Can't change valuation method, as there are transactions against some items which does not have it's own valuation method"))
+				frappe.throw(_("Can't change the valuation method, as there are transactions against some items which do not have its own valuation method"))
 
 	def validate_clean_description_html(self):
 		if int(self.clean_description_html or 0) \
diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py
index 3101e8a..95478f6 100644
--- a/erpnext/stock/doctype/warehouse/test_warehouse.py
+++ b/erpnext/stock/doctype/warehouse/test_warehouse.py
@@ -10,13 +10,10 @@
 
 import erpnext
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
-from erpnext import set_perpetual_inventory
 from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
 
-
 test_records = frappe.get_test_records('Warehouse')
 
-
 class TestWarehouse(unittest.TestCase):
 	def setUp(self):
 		if not frappe.get_value('Item', '_Test Item'):
@@ -37,63 +34,63 @@
 			self.assertEqual(child_warehouse.is_group, 0)
 
 	def test_warehouse_renaming(self):
-		set_perpetual_inventory(1)
-		create_warehouse("Test Warehouse for Renaming 1")
-		account = get_inventory_account("_Test Company", "Test Warehouse for Renaming 1 - _TC")
+		create_warehouse("Test Warehouse for Renaming 1", company="_Test Company with perpetual inventory")
+		account = get_inventory_account("_Test Company with perpetual inventory", "Test Warehouse for Renaming 1 - TCP1")
 		self.assertTrue(frappe.db.get_value("Warehouse", filters={"account": account}))
 
 		# Rename with abbr
-		if frappe.db.exists("Warehouse", "Test Warehouse for Renaming 2 - _TC"):
-			frappe.delete_doc("Warehouse", "Test Warehouse for Renaming 2 - _TC")
-		frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 1 - _TC", "Test Warehouse for Renaming 2 - _TC")
+		if frappe.db.exists("Warehouse", "Test Warehouse for Renaming 2 - TCP1"):
+			frappe.delete_doc("Warehouse", "Test Warehouse for Renaming 2 - TCP1")
+		frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 1 - TCP1", "Test Warehouse for Renaming 2 - TCP1")
 
 		self.assertTrue(frappe.db.get_value("Warehouse",
-			filters={"account": "Test Warehouse for Renaming 1 - _TC"}))
+			filters={"account": "Test Warehouse for Renaming 1 - TCP1"}))
 
 		# Rename without abbr
-		if frappe.db.exists("Warehouse", "Test Warehouse for Renaming 3 - _TC"):
-			frappe.delete_doc("Warehouse", "Test Warehouse for Renaming 3 - _TC")
+		if frappe.db.exists("Warehouse", "Test Warehouse for Renaming 3 - TCP1"):
+			frappe.delete_doc("Warehouse", "Test Warehouse for Renaming 3 - TCP1")
 
-		frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 2 - _TC", "Test Warehouse for Renaming 3")
+		frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 2 - TCP1", "Test Warehouse for Renaming 3")
 
 		self.assertTrue(frappe.db.get_value("Warehouse",
-			filters={"account": "Test Warehouse for Renaming 1 - _TC"}))
+			filters={"account": "Test Warehouse for Renaming 1 - TCP1"}))
 
 		# Another rename with multiple dashes
-		if frappe.db.exists("Warehouse", "Test - Warehouse - Company - _TC"):
-			frappe.delete_doc("Warehouse", "Test - Warehouse - Company - _TC")
-		frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 3 - _TC", "Test - Warehouse - Company")
+		if frappe.db.exists("Warehouse", "Test - Warehouse - Company - TCP1"):
+			frappe.delete_doc("Warehouse", "Test - Warehouse - Company - TCP1")
+		frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 3 - TCP1", "Test - Warehouse - Company")
 
 	def test_warehouse_merging(self):
-		set_perpetual_inventory(1)
+		company = "_Test Company with perpetual inventory"
+		create_warehouse("Test Warehouse for Merging 1", company=company,
+			properties={"parent_warehouse": "All Warehouses - TCP1"})
+		create_warehouse("Test Warehouse for Merging 2", company=company,
+			properties={"parent_warehouse": "All Warehouses - TCP1"})
 
-		create_warehouse("Test Warehouse for Merging 1")
-		create_warehouse("Test Warehouse for Merging 2")
-
-		make_stock_entry(item_code="_Test Item", target="Test Warehouse for Merging 1 - _TC",
-			qty=1, rate=100)
-		make_stock_entry(item_code="_Test Item", target="Test Warehouse for Merging 2 - _TC",
-			qty=1, rate=100)
+		make_stock_entry(item_code="_Test Item", target="Test Warehouse for Merging 1 - TCP1",
+			qty=1, rate=100, company=company)
+		make_stock_entry(item_code="_Test Item", target="Test Warehouse for Merging 2 - TCP1",
+			qty=1, rate=100, company=company)
 
 		existing_bin_qty = (
 			cint(frappe.db.get_value("Bin",
-				{"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 1 - _TC"}, "actual_qty"))
+				{"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 1 - TCP1"}, "actual_qty"))
 			+ cint(frappe.db.get_value("Bin",
-				{"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 2 - _TC"}, "actual_qty"))
+				{"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 2 - TCP1"}, "actual_qty"))
 		)
 
-		frappe.rename_doc("Warehouse", "Test Warehouse for Merging 1 - _TC",
-			"Test Warehouse for Merging 2 - _TC", merge=True)
+		frappe.rename_doc("Warehouse", "Test Warehouse for Merging 1 - TCP1",
+			"Test Warehouse for Merging 2 - TCP1", merge=True)
 
-		self.assertFalse(frappe.db.exists("Warehouse", "Test Warehouse for Merging 1 - _TC"))
+		self.assertFalse(frappe.db.exists("Warehouse", "Test Warehouse for Merging 1 - TCP1"))
 
 		bin_qty = frappe.db.get_value("Bin",
-			{"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 2 - _TC"}, "actual_qty")
+			{"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 2 - TCP1"}, "actual_qty")
 
 		self.assertEqual(bin_qty, existing_bin_qty)
 
 		self.assertTrue(frappe.db.get_value("Warehouse",
-			filters={"account": "Test Warehouse for Merging 2 - _TC"}))
+			filters={"account": "Test Warehouse for Merging 2 - TCP1"}))
 
 def create_warehouse(warehouse_name, properties=None, company=None):
 	if not company:
diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py
index cd86be3..6c84f16 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -29,7 +29,6 @@
 				self.set_onload('account', account)
 		load_address_and_contact(self)
 
-
 	def on_update(self):
 		self.update_nsm_model()
 
diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.py b/erpnext/stock/report/stock_analytics/stock_analytics.py
index 54eefdf..0cc8ca4 100644
--- a/erpnext/stock/report/stock_analytics/stock_analytics.py
+++ b/erpnext/stock/report/stock_analytics/stock_analytics.py
@@ -7,9 +7,11 @@
 from frappe.utils import getdate, flt
 from erpnext.stock.report.stock_balance.stock_balance import (get_items, get_stock_ledger_entries, get_item_details)
 from erpnext.accounts.utils import get_fiscal_year
+from erpnext.stock.utils import is_reposting_item_valuation_in_progress
 from six import iteritems
 
 def execute(filters=None):
+	is_reposting_item_valuation_in_progress()
 	filters = frappe._dict(filters or {})
 	columns = get_columns(filters)
 	data = get_data(filters)
diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
index 1af68dd..14d543b 100644
--- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
+++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
@@ -57,8 +57,7 @@
 	if report_filters.account:
 		stock_accounts = [report_filters.account]
 	else:
-		stock_accounts = [k.name
-			for k in get_stock_accounts(report_filters.company)]
+		stock_accounts = get_stock_accounts(report_filters.company)
 
 	filters.update({
 		"account": ("in", stock_accounts)
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index 1339d9b..e5d4d62 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -7,12 +7,13 @@
 from frappe.utils import flt, cint, getdate, now, date_diff
 from erpnext.stock.utils import add_additional_uom_columns
 from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
-
+from erpnext.stock.utils import is_reposting_item_valuation_in_progress
 from erpnext.stock.report.stock_ageing.stock_ageing import get_fifo_queue, get_average_age
 
 from six import iteritems
 
 def execute(filters=None):
+	is_reposting_item_valuation_in_progress()
 	if not filters: filters = {}
 
 	validate_filters(filters)
@@ -164,7 +165,7 @@
 		select
 			sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate,
 			sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference,
-			sle.item_code as name, sle.voucher_no
+			sle.item_code as name, sle.voucher_no, sle.stock_value
 		from
 			`tabStock Ledger Entry` sle force index (posting_sort_index)
 		where sle.docstatus < 2 %s %s
@@ -197,7 +198,7 @@
 		else:
 			qty_diff = flt(d.actual_qty)
 
-		value_diff = flt(d.stock_value_difference)
+		value_diff = flt(d.stock_value) - flt(qty_dict.bal_val)
 
 		if d.posting_date < from_date:
 			qty_dict.opening_qty += qty_diff
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index 86af5e0..7b5701a 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -5,11 +5,12 @@
 
 import frappe
 from frappe.utils import cint, flt
-from erpnext.stock.utils import update_included_uom_in_report
+from erpnext.stock.utils import update_included_uom_in_report, is_reposting_item_valuation_in_progress
 from frappe import _
 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 
 def execute(filters=None):
+	is_reposting_item_valuation_in_progress()
 	include_uom = filters.get("include_uom")
 	columns = get_columns()
 	items = get_items(filters)
diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
index c8efb16..1183e41 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
@@ -5,9 +5,10 @@
 import frappe
 from frappe import _
 from frappe.utils import flt, today
-from erpnext.stock.utils import update_included_uom_in_report
+from erpnext.stock.utils import update_included_uom_in_report, is_reposting_item_valuation_in_progress
 
 def execute(filters=None):
+	is_reposting_item_valuation_in_progress()
 	filters = frappe._dict(filters or {})
 	include_uom = filters.get("include_uom")
 	columns = get_columns()
diff --git a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py
index ebcb106..04f7d34 100644
--- a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py
+++ b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py
@@ -11,9 +11,11 @@
 from erpnext.stock.report.stock_balance.stock_balance import (get_item_details,
 	get_item_reorder_details, get_item_warehouse_map, get_items, get_stock_ledger_entries)
 from erpnext.stock.report.stock_ageing.stock_ageing import get_fifo_queue, get_average_age
+from erpnext.stock.utils import is_reposting_item_valuation_in_progress
 from six import iteritems
 
 def execute(filters=None):
+	is_reposting_item_valuation_in_progress()
 	if not filters: filters = {}
 
 	validate_filters(filters)
diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py
index b5ae1b7..8ba1f1c 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -6,6 +6,7 @@
 from frappe.utils import flt, cstr, nowdate, nowtime
 from erpnext.stock.utils import update_bin
 from erpnext.stock.stock_ledger import update_entries_after
+from erpnext.controllers.stock_controller import create_repost_item_valuation_entry
 
 def repost(only_actual=False, allow_negative_stock=False, allow_zero_rate=False, only_bin=False):
 	"""
@@ -56,12 +57,18 @@
 		update_bin_qty(item_code, warehouse, qty_dict)
 
 def repost_actual_qty(item_code, warehouse, allow_zero_rate=False, allow_negative_stock=False):
-	update_entries_after({ "item_code": item_code, "warehouse": warehouse },
-		allow_zero_rate=allow_zero_rate, allow_negative_stock=allow_negative_stock)
+	create_repost_item_valuation_entry({
+		"item_code": item_code,
+		"warehouse": warehouse,
+		"posting_date": "1900-01-01",
+		"posting_time": "00:01",
+		"allow_negative_stock": allow_negative_stock,
+		"allow_zero_rate": allow_zero_rate
+	})
 
 def get_balance_qty_from_sle(item_code, warehouse):
 	balance_qty = frappe.db.sql("""select qty_after_transaction from `tabStock Ledger Entry`
-		where item_code=%s and warehouse=%s
+		where item_code=%s and warehouse=%s and is_cancelled=0
 		order by posting_date desc, posting_time desc, creation desc
 		limit 1""", (item_code, warehouse))
 
@@ -191,7 +198,7 @@
 			print(d[0], d[1], d[2], serial_nos[0][0])
 
 		sle = frappe.db.sql("""select valuation_rate, company from `tabStock Ledger Entry`
-			where item_code = %s and warehouse = %s
+			where item_code = %s and warehouse = %s and is_cancelled = 0
 			order by posting_date desc limit 1""", (d[0], d[1]))
 
 		sle_dict = {
@@ -223,7 +230,8 @@
 		})
 
 		update_bin(args)
-		update_entries_after({
+		
+		create_repost_item_valuation_entry({
 			"item_code": d[0],
 			"warehouse": d[1],
 			"posting_date": posting_date,
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index f4490f1..5b9ada0 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -5,9 +5,10 @@
 import frappe, erpnext
 from frappe import _
 from frappe.utils import cint, flt, cstr, now, now_datetime
+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
@@ -25,32 +26,23 @@
 			set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no'))
 
 		for sle in sl_entries:
-			sle_id = None
-			if via_landed_cost_voucher or cancel:
-				sle['posting_date'] = now_datetime().strftime('%Y-%m-%d')
-				sle['posting_time'] = now_datetime().strftime('%H:%M:%S.%f')
+			if cancel:
+				sle['actual_qty'] = -flt(sle.get('actual_qty'))
 
-				if cancel:
-					sle['actual_qty'] = -flt(sle.get('actual_qty'))
+				if sle['actual_qty'] < 0 and not sle.get('outgoing_rate'):
+					sle['outgoing_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code,
+						sle.voucher_type, sle.voucher_no, sle.voucher_detail_no)
+					sle['incoming_rate'] = 0.0
 
-					if sle['actual_qty'] < 0 and not sle.get('outgoing_rate'):
-						sle['outgoing_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code,
-							sle.voucher_type, sle.voucher_no, sle.voucher_detail_no)
-						sle['incoming_rate'] = 0.0
-
-					if sle['actual_qty'] > 0 and not sle.get('incoming_rate'):
-						sle['incoming_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code,
-							sle.voucher_type, sle.voucher_no, sle.voucher_detail_no)
-						sle['outgoing_rate'] = 0.0
-
+				if sle['actual_qty'] > 0 and not sle.get('incoming_rate'):
+					sle['incoming_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code,
+						sle.voucher_type, sle.voucher_no, sle.voucher_detail_no)
+					sle['outgoing_rate'] = 0.0
 
 			if sle.get("actual_qty") or sle.get("voucher_type")=="Stock Reconciliation":
-				sle_id = make_entry(sle, allow_negative_stock, via_landed_cost_voucher)
-
-			args = sle.copy()
-			args.update({
-				"sle_id": sle_id
-			})
+				sle_doc = make_entry(sle, allow_negative_stock, via_landed_cost_voucher)
+			
+			args = sle_doc.as_dict()
 			update_bin(args, allow_negative_stock, via_landed_cost_voucher)
 
 
@@ -68,8 +60,36 @@
 	sle.via_landed_cost_voucher = via_landed_cost_voucher
 	sle.insert()
 	sle.submit()
-	return sle.name
+	return sle
 
+def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negative_stock=False, via_landed_cost_voucher=False):
+	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]
+
+	i = 0
+	while i < len(args):
+		obj = update_entries_after({
+			"item_code": args[i].item_code,
+			"warehouse": args[i].warehouse,
+			"posting_date": args[i].posting_date,
+			"posting_time": args[i].posting_time
+		}, 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)
+		
+		i += 1
+
+def get_args_for_voucher(voucher_type, voucher_no):
+	return frappe.db.get_all("Stock Ledger Entry",
+		filters={"voucher_type": voucher_type, "voucher_no": voucher_no},
+		fields=["item_code", "warehouse", "posting_date", "posting_time"],
+		order_by="creation asc",
+		group_by="item_code, warehouse"
+	)
 
 class update_entries_after(object):
 	"""
@@ -86,141 +106,299 @@
 			}
 	"""
 	def __init__(self, args, allow_zero_rate=False, allow_negative_stock=None, via_landed_cost_voucher=False, verbose=1):
-		from frappe.model.meta import get_field_precision
-
-		self.exceptions = []
+		self.exceptions = {}
 		self.verbose = verbose
 		self.allow_zero_rate = allow_zero_rate
-		self.allow_negative_stock = allow_negative_stock
 		self.via_landed_cost_voucher = via_landed_cost_voucher
-		if not self.allow_negative_stock:
-			self.allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings",
-				"allow_negative_stock"))
+		self.allow_negative_stock = allow_negative_stock \
+			or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
 
-		self.args = args
-		for key, value in iteritems(args):
-			setattr(self, key, value)
+		self.args = frappe._dict(args)
+		self.item_code = args.get("item_code")
+		if self.args.sle_id:
+			self.args['name'] = self.args.sle_id
 
-		self.previous_sle = self.get_sle_before_datetime()
-		self.previous_sle = self.previous_sle[0] if self.previous_sle else frappe._dict()
+		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.data = frappe._dict()
+		self.initialize_previous_data(self.args)
+
+		self.build()
+	
+	def get_precision(self):
+		company_base_currency = frappe.get_cached_value('Company',  self.company,  "default_currency")
+		self.precision = get_field_precision(frappe.get_meta("Stock Ledger Entry").get_field("stock_value"),
+			currency=company_base_currency)
+
+	def initialize_previous_data(self, args):
+		"""
+			Get previous sl entries for current item for each related warehouse
+			and assigns into self.data dict
+
+			:Data Structure:
+
+			self.data = {
+				warehouse1: {
+					'previus_sle': {},
+					'qty_after_transaction': 10,
+					'valuation_rate': 100,
+					'stock_value': 1000,
+					'prev_stock_value': 1000,
+					'stock_queue': '[[10, 100]]',
+					'stock_value_difference': 1000
+				}
+			}
+
+		"""
+		self.data.setdefault(args.warehouse, frappe._dict())
+		warehouse_dict = self.data[args.warehouse]
+		previous_sle = self.get_sle_before_datetime(args)
+		warehouse_dict.previous_sle = previous_sle
 
 		for key in ("qty_after_transaction", "valuation_rate", "stock_value"):
-			setattr(self, key, flt(self.previous_sle.get(key)))
+			setattr(warehouse_dict, key, flt(previous_sle.get(key)))
 
-		self.company = frappe.db.get_value("Warehouse", self.warehouse, "company")
-		self.precision = get_field_precision(frappe.get_meta("Stock Ledger Entry").get_field("stock_value"),
-			currency=frappe.get_cached_value('Company',  self.company,  "default_currency"))
+		warehouse_dict.update({
+			"prev_stock_value": previous_sle.stock_value or 0.0,
+			"stock_queue": json.loads(previous_sle.stock_queue or "[]"),
+			"stock_value_difference": 0.0
+		})
 
-		self.prev_stock_value = self.previous_sle.stock_value or 0.0
-		self.stock_queue = json.loads(self.previous_sle.stock_queue or "[]")
-		self.valuation_method = get_valuation_method(self.item_code)
-		self.stock_value_difference = 0.0
-		self.build(args.get('sle_id'))
-
-	def build(self, sle_id):
-		if sle_id:
-			sle = get_sle_by_id(sle_id)
-			self.process_sle(sle)
+	def build(self):
+		if self.args.get("sle_id"):
+			self.process_sle_against_current_voucher()
 		else:
-			# includes current entry!
-			entries_to_fix = self.get_sle_after_datetime()
-			for sle in entries_to_fix:
+			entries_to_fix = self.get_future_entries_to_fix()
+
+			i = 0
+			while i < len(entries_to_fix):
+				sle = entries_to_fix[i]
+				i += 1
+
 				self.process_sle(sle)
 
+				if sle.dependant_sle_voucher_detail_no:
+					self.get_dependent_entries_to_fix(entries_to_fix, sle)
+
 		if self.exceptions:
 			self.raise_exceptions()
 
 		self.update_bin()
 
-	def update_bin(self):
-		# update bin
-		bin_name = frappe.db.get_value("Bin", {
-			"item_code": self.item_code,
-			"warehouse": self.warehouse
-		})
+	def process_sle_against_current_voucher(self):
+		sl_entries = self.get_sle_against_current_voucher()
+		for sle in sl_entries:
+			self.process_sle(sle)
 
-		if not bin_name:
-			bin_doc = frappe.get_doc({
-				"doctype": "Bin",
-				"item_code": self.item_code,
-				"warehouse": self.warehouse
-			})
-			bin_doc.insert(ignore_permissions=True)
-		else:
-			bin_doc = frappe.get_doc("Bin", bin_name)
+	def get_sle_against_current_voucher(self):
+		return frappe.db.sql("""
+			select
+				*, timestamp(posting_date, posting_time) as "timestamp"
+			from
+				`tabStock Ledger Entry`
+			where
+				item_code = %(item_code)s
+				and warehouse = %(warehouse)s
+				and voucher_type = %(voucher_type)s
+				and voucher_no = %(voucher_no)s
+			order by
+				creation ASC
+			for update
+		""", self.args, as_dict=1)
 
-		bin_doc.update({
-			"valuation_rate": self.valuation_rate,
-			"actual_qty": self.qty_after_transaction,
-			"stock_value": self.stock_value
-		})
-		bin_doc.flags.via_stock_ledger_entry = True
+	def get_future_entries_to_fix(self):
+		# includes current entry!
+		args = self.data[self.args.warehouse].previous_sle \
+			or frappe._dict({"item_code": self.item_code, "warehouse": self.args.warehouse})
+		
+		return list(self.get_sle_after_datetime(args))
 
-		bin_doc.save(ignore_permissions=True)
+	def get_dependent_entries_to_fix(self, entries_to_fix, sle):
+		dependant_sle = get_sle_by_voucher_detail_no(sle.dependant_sle_voucher_detail_no,
+			excluded_sle=sle.name)
+		
+		if not dependant_sle:
+			return
+		elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse == self.args.warehouse:
+			return
+		elif dependant_sle.item_code != self.item_code \
+				and (dependant_sle.item_code, dependant_sle.warehouse) not in self.new_items:
+			self.new_items[(dependant_sle.item_code, dependant_sle.warehouse)] = dependant_sle
+			return
+
+		self.initialize_previous_data(dependant_sle)
+
+		args = self.data[dependant_sle.warehouse].previous_sle \
+			or frappe._dict({"item_code": self.item_code, "warehouse": dependant_sle.warehouse})
+		future_sle_for_dependant = list(self.get_sle_after_datetime(args))
+
+		entries_to_fix.extend(future_sle_for_dependant)
+		entries_to_fix = sorted(entries_to_fix, key=lambda k: k['timestamp'])
 
 	def process_sle(self, sle):
+		# previous sle data for this warehouse
+		self.wh_data = self.data[sle.warehouse]
+
 		if (sle.serial_no and not self.via_landed_cost_voucher) or not cint(self.allow_negative_stock):
 			# validate negative stock for serialized items, fifo valuation
 			# or when negative stock is not allowed for moving average
 			if not self.validate_negative_stock(sle):
-				self.qty_after_transaction += flt(sle.actual_qty)
+				self.wh_data.qty_after_transaction += flt(sle.actual_qty)
 				return
 
+		# Get dynamic incoming/outgoing rate
+		self.get_dynamic_incoming_outgoing_rate(sle)
+		
 		if sle.serial_no:
 			self.get_serialized_values(sle)
-			self.qty_after_transaction += flt(sle.actual_qty)
+			self.wh_data.qty_after_transaction += flt(sle.actual_qty)
 			if sle.voucher_type == "Stock Reconciliation":
-				self.qty_after_transaction = sle.qty_after_transaction
+				self.wh_data.qty_after_transaction = sle.qty_after_transaction
 
-			self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate)
+			self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(self.wh_data.valuation_rate)
 		else:
 			if sle.voucher_type=="Stock Reconciliation" and not sle.batch_no:
 				# assert
-				self.valuation_rate = sle.valuation_rate
-				self.qty_after_transaction = sle.qty_after_transaction
-				self.stock_queue = [[self.qty_after_transaction, self.valuation_rate]]
-				self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate)
+				self.wh_data.valuation_rate = sle.valuation_rate
+				self.wh_data.qty_after_transaction = sle.qty_after_transaction
+				self.wh_data.stock_queue = [[self.wh_data.qty_after_transaction, self.wh_data.valuation_rate]]
+				self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(self.wh_data.valuation_rate)
 			else:
 				if self.valuation_method == "Moving Average":
 					self.get_moving_average_values(sle)
-					self.qty_after_transaction += flt(sle.actual_qty)
-					self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate)
+					self.wh_data.qty_after_transaction += flt(sle.actual_qty)
+					self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(self.wh_data.valuation_rate)
 				else:
 					self.get_fifo_values(sle)
-					self.qty_after_transaction += flt(sle.actual_qty)
-					self.stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.stock_queue))
+					self.wh_data.qty_after_transaction += flt(sle.actual_qty)
+					self.wh_data.stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.wh_data.stock_queue))
 
 		# rounding as per precision
-		self.stock_value = flt(self.stock_value, self.precision)
-
-		stock_value_difference = self.stock_value - self.prev_stock_value
-
-		self.prev_stock_value = self.stock_value
+		self.wh_data.stock_value = flt(self.wh_data.stock_value, self.precision)
+		stock_value_difference = self.wh_data.stock_value - self.wh_data.prev_stock_value
+		self.wh_data.prev_stock_value = self.wh_data.stock_value
 
 		# update current sle
-		sle.qty_after_transaction = self.qty_after_transaction
-		sle.valuation_rate = self.valuation_rate
-		sle.stock_value = self.stock_value
-		sle.stock_queue = json.dumps(self.stock_queue)
+		sle.qty_after_transaction = self.wh_data.qty_after_transaction
+		sle.valuation_rate = self.wh_data.valuation_rate
+		sle.stock_value = self.wh_data.stock_value
+		sle.stock_queue = json.dumps(self.wh_data.stock_queue)
 		sle.stock_value_difference = stock_value_difference
 		sle.doctype="Stock Ledger Entry"
 		frappe.get_doc(sle).db_update()
 
+		self.update_outgoing_rate_on_transaction(sle)
+
 	def validate_negative_stock(self, sle):
 		"""
 			validate negative stock for entries current datetime onwards
 			will not consider cancelled entries
 		"""
-		diff = self.qty_after_transaction + flt(sle.actual_qty)
+		diff = self.wh_data.qty_after_transaction + flt(sle.actual_qty)
 
 		if diff < 0 and abs(diff) > 0.0001:
 			# negative stock!
 			exc = sle.copy().update({"diff": diff})
-			self.exceptions.append(exc)
+			self.exceptions.setdefault(sle.warehouse, []).append(exc)
 			return False
 		else:
 			return True
 
+	def get_dynamic_incoming_outgoing_rate(self, sle):
+		# Get updated incoming/outgoing rate from transaction
+		if sle.recalculate_rate:
+			rate = self.get_incoming_outgoing_rate_from_transaction(sle)
+
+			if flt(sle.actual_qty) >= 0:
+				sle.incoming_rate = rate
+			else:
+				sle.outgoing_rate = rate
+
+	def get_incoming_outgoing_rate_from_transaction(self, sle):
+		rate = 0
+		# Material Transfer, Repack, Manufacturing
+		if sle.voucher_type == "Stock Entry":
+			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"):
+			if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_return"):
+				from erpnext.controllers.sales_and_purchase_return import get_rate_for_return # don't move this import to top
+				rate = get_rate_for_return(sle.voucher_type, sle.voucher_no, sle.item_code, voucher_detail_no=sle.voucher_detail_no)
+			else:
+				if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
+					rate_field = "valuation_rate" 
+				else:
+					rate_field = "incoming_rate"
+
+				# check in item table
+				item_code, incoming_rate = frappe.db.get_value(sle.voucher_type + " Item",
+					sle.voucher_detail_no, ["item_code", rate_field])
+
+				if item_code == sle.item_code:
+					rate = incoming_rate
+				else:
+					if sle.voucher_type in ("Delivery Note", "Sales Invoice"):
+						ref_doctype = "Packed Item"
+					else:
+						ref_doctype = "Purchase Receipt Item Supplied"
+	
+					rate = frappe.db.get_value(ref_doctype, {"parent_detail_docname": sle.voucher_detail_no,
+						"item_code": sle.item_code}, rate_field)
+
+		return rate
+
+	def update_outgoing_rate_on_transaction(self, sle):
+		"""
+			Update outgoing rate in Stock Entry, Delivery Note, Sales Invoice and Sales Return
+			In case of Stock Entry, also calculate FG Item rate and total incoming/outgoing amount
+		"""
+		if sle.actual_qty and sle.voucher_detail_no:
+			outgoing_rate = abs(flt(sle.stock_value_difference)) / abs(sle.actual_qty)
+
+			if flt(sle.actual_qty) < 0 and sle.voucher_type == "Stock Entry":
+				self.update_rate_on_stock_entry(sle, outgoing_rate)
+			elif sle.voucher_type in ("Delivery Note", "Sales Invoice"):
+				self.update_rate_on_delivery_and_sales_return(sle, outgoing_rate)
+			elif flt(sle.actual_qty) < 0 and sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
+				self.update_rate_on_purchase_receipt(sle, outgoing_rate)
+
+	def update_rate_on_stock_entry(self, sle, outgoing_rate):
+		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)
+		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:
+			d.db_update()
+	
+	def update_rate_on_delivery_and_sales_return(self, sle, outgoing_rate):
+		# Update item's incoming rate on transaction
+		item_code = frappe.db.get_value(sle.voucher_type + " Item", sle.voucher_detail_no, "item_code")
+		if item_code == sle.item_code:
+			frappe.db.set_value(sle.voucher_type + " Item", sle.voucher_detail_no, "incoming_rate", outgoing_rate)
+		else:
+			# packed item
+			frappe.db.set_value("Packed Item",
+				{"parent_detail_docname": sle.voucher_detail_no, "item_code": sle.item_code},
+				"incoming_rate", outgoing_rate)
+
+	def update_rate_on_purchase_receipt(self, sle, outgoing_rate):
+		if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no):
+			frappe.db.set_value(sle.voucher_type + " Item", sle.voucher_detail_no, "base_net_rate", outgoing_rate)
+		else:
+			frappe.db.set_value("Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate)
+
+		# Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice
+		if frappe.db.get_value(sle.voucher_type, sle.voucher_no, "is_subcontracted"):
+			doc = frappe.get_cached_doc(sle.voucher_type, sle.voucher_no)
+			doc.update_valuation_rate(reset_outgoing_rate=False)
+			for d in (doc.items + doc.supplied_items):
+				d.db_update()
+
 	def get_serialized_values(self, sle):
 		incoming_rate = flt(sle.incoming_rate)
 		actual_qty = flt(sle.actual_qty)
@@ -228,7 +406,7 @@
 
 		if incoming_rate < 0:
 			# wrong incoming rate
-			incoming_rate = self.valuation_rate
+			incoming_rate = self.wh_data.valuation_rate
 
 		stock_value_change = 0
 		if incoming_rate:
@@ -236,22 +414,25 @@
 		elif actual_qty < 0:
 			# In case of delivery/stock issue, get average purchase rate
 			# of serial nos of current entry
-			outgoing_value = self.get_incoming_value_for_serial_nos(sle, serial_nos)
-			stock_value_change = -1 * outgoing_value
+			if not sle.is_cancelled:
+				outgoing_value = self.get_incoming_value_for_serial_nos(sle, serial_nos)
+				stock_value_change = -1 * outgoing_value
+			else:
+				stock_value_change = actual_qty * sle.outgoing_rate
 
-		new_stock_qty = self.qty_after_transaction + actual_qty
+		new_stock_qty = self.wh_data.qty_after_transaction + actual_qty
 
 		if new_stock_qty > 0:
-			new_stock_value = (self.qty_after_transaction * self.valuation_rate) + stock_value_change
+			new_stock_value = (self.wh_data.qty_after_transaction * self.wh_data.valuation_rate) + stock_value_change
 			if new_stock_value >= 0:
 				# calculate new valuation rate only if stock value is positive
 				# else it remains the same as that of previous entry
-				self.valuation_rate = new_stock_value / new_stock_qty
+				self.wh_data.valuation_rate = new_stock_value / new_stock_qty
 
-		if not self.valuation_rate and sle.voucher_detail_no:
+		if not self.wh_data.valuation_rate and sle.voucher_detail_no:
 			allow_zero_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no)
 			if not allow_zero_rate:
-				self.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
+				self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
 					sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
 					currency=erpnext.get_company_currency(sle.company))
 
@@ -287,39 +468,39 @@
 
 	def get_moving_average_values(self, sle):
 		actual_qty = flt(sle.actual_qty)
-		new_stock_qty = flt(self.qty_after_transaction) + actual_qty
+		new_stock_qty = flt(self.wh_data.qty_after_transaction) + actual_qty
 		if new_stock_qty >= 0:
 			if actual_qty > 0:
-				if flt(self.qty_after_transaction) <= 0:
-					self.valuation_rate = sle.incoming_rate
+				if flt(self.wh_data.qty_after_transaction) <= 0:
+					self.wh_data.valuation_rate = sle.incoming_rate
 				else:
-					new_stock_value = (self.qty_after_transaction * self.valuation_rate) + \
+					new_stock_value = (self.wh_data.qty_after_transaction * self.wh_data.valuation_rate) + \
 						(actual_qty * sle.incoming_rate)
 
-					self.valuation_rate = new_stock_value / new_stock_qty
+					self.wh_data.valuation_rate = new_stock_value / new_stock_qty
 
 			elif sle.outgoing_rate:
 				if new_stock_qty:
-					new_stock_value = (self.qty_after_transaction * self.valuation_rate) + \
+					new_stock_value = (self.wh_data.qty_after_transaction * self.wh_data.valuation_rate) + \
 						(actual_qty * sle.outgoing_rate)
 
-					self.valuation_rate = new_stock_value / new_stock_qty
+					self.wh_data.valuation_rate = new_stock_value / new_stock_qty
 				else:
-					self.valuation_rate = sle.outgoing_rate
+					self.wh_data.valuation_rate = sle.outgoing_rate
 
 		else:
-			if flt(self.qty_after_transaction) >= 0 and sle.outgoing_rate:
-				self.valuation_rate = sle.outgoing_rate
+			if flt(self.wh_data.qty_after_transaction) >= 0 and sle.outgoing_rate:
+				self.wh_data.valuation_rate = sle.outgoing_rate
 
-			if not self.valuation_rate and actual_qty > 0:
-				self.valuation_rate = sle.incoming_rate
+			if not self.wh_data.valuation_rate and actual_qty > 0:
+				self.wh_data.valuation_rate = sle.incoming_rate
 
 			# Get valuation rate from previous SLE or Item master, if item does not have the
 			# allow zero valuration rate flag set
-			if not self.valuation_rate and sle.voucher_detail_no:
+			if not self.wh_data.valuation_rate and sle.voucher_detail_no:
 				allow_zero_valuation_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no)
 				if not allow_zero_valuation_rate:
-					self.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
+					self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
 						sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
 						currency=erpnext.get_company_currency(sle.company))
 
@@ -329,22 +510,22 @@
 		outgoing_rate = flt(sle.outgoing_rate)
 
 		if actual_qty > 0:
-			if not self.stock_queue:
-				self.stock_queue.append([0, 0])
+			if not self.wh_data.stock_queue:
+				self.wh_data.stock_queue.append([0, 0])
 
 			# last row has the same rate, just updated the qty
-			if self.stock_queue[-1][1]==incoming_rate:
-				self.stock_queue[-1][0] += actual_qty
+			if self.wh_data.stock_queue[-1][1]==incoming_rate:
+				self.wh_data.stock_queue[-1][0] += actual_qty
 			else:
-				if self.stock_queue[-1][0] > 0:
-					self.stock_queue.append([actual_qty, incoming_rate])
+				if self.wh_data.stock_queue[-1][0] > 0:
+					self.wh_data.stock_queue.append([actual_qty, incoming_rate])
 				else:
-					qty = self.stock_queue[-1][0] + actual_qty
-					self.stock_queue[-1] = [qty, incoming_rate]
+					qty = self.wh_data.stock_queue[-1][0] + actual_qty
+					self.wh_data.stock_queue[-1] = [qty, incoming_rate]
 		else:
 			qty_to_pop = abs(actual_qty)
 			while qty_to_pop:
-				if not self.stock_queue:
+				if not self.wh_data.stock_queue:
 					# Get valuation rate from last sle if exists or from valuation rate field in item master
 					allow_zero_valuation_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no)
 					if not allow_zero_valuation_rate:
@@ -354,35 +535,35 @@
 					else:
 						_rate = 0
 
-					self.stock_queue.append([0, _rate])
+					self.wh_data.stock_queue.append([0, _rate])
 
 				index = None
 				if outgoing_rate > 0:
 					# Find the entry where rate matched with outgoing rate
-					for i, v in enumerate(self.stock_queue):
+					for i, v in enumerate(self.wh_data.stock_queue):
 						if v[1] == outgoing_rate:
 							index = i
 							break
 
 					# If no entry found with outgoing rate, collapse stack
 					if index == None:
-						new_stock_value = sum((d[0]*d[1] for d in self.stock_queue)) - qty_to_pop*outgoing_rate
-						new_stock_qty = sum((d[0] for d in self.stock_queue)) - qty_to_pop
-						self.stock_queue = [[new_stock_qty, new_stock_value/new_stock_qty if new_stock_qty > 0 else outgoing_rate]]
+						new_stock_value = sum((d[0]*d[1] for d in self.wh_data.stock_queue)) - qty_to_pop*outgoing_rate
+						new_stock_qty = sum((d[0] for d in self.wh_data.stock_queue)) - qty_to_pop
+						self.wh_data.stock_queue = [[new_stock_qty, new_stock_value/new_stock_qty if new_stock_qty > 0 else outgoing_rate]]
 						break
 				else:
 					index = 0
 
 				# select first batch or the batch with same rate
-				batch = self.stock_queue[index]
+				batch = self.wh_data.stock_queue[index]
 				if qty_to_pop >= batch[0]:
 					# consume current batch
 					qty_to_pop = qty_to_pop - batch[0]
-					self.stock_queue.pop(index)
-					if not self.stock_queue and qty_to_pop:
+					self.wh_data.stock_queue.pop(index)
+					if not self.wh_data.stock_queue and qty_to_pop:
 						# stock finished, qty still remains to be withdrawn
 						# negative stock, keep in as a negative batch
-						self.stock_queue.append([-qty_to_pop, outgoing_rate or batch[1]])
+						self.wh_data.stock_queue.append([-qty_to_pop, outgoing_rate or batch[1]])
 						break
 
 				else:
@@ -391,14 +572,14 @@
 					batch[0] = batch[0] - qty_to_pop
 					qty_to_pop = 0
 
-		stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.stock_queue))
-		stock_qty = sum((flt(batch[0]) for batch in self.stock_queue))
+		stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.wh_data.stock_queue))
+		stock_qty = sum((flt(batch[0]) for batch in self.wh_data.stock_queue))
 
 		if stock_qty:
-			self.valuation_rate = stock_value / flt(stock_qty)
+			self.wh_data.valuation_rate = stock_value / flt(stock_qty)
 
-		if not self.stock_queue:
-			self.stock_queue.append([0, sle.incoming_rate or sle.outgoing_rate or self.valuation_rate])
+		if not self.wh_data.stock_queue:
+			self.wh_data.stock_queue.append([0, sle.incoming_rate or sle.outgoing_rate or self.wh_data.valuation_rate])
 
 	def check_if_allow_zero_valuation_rate(self, voucher_type, voucher_detail_no):
 		ref_item_dt = ""
@@ -413,39 +594,56 @@
 		else:
 			return 0
 
-	def get_sle_before_datetime(self):
+	def get_sle_before_datetime(self, args):
 		"""get previous stock ledger entry before current time-bucket"""
-		if self.args.get('sle_id'):
-			self.args['name'] = self.args.get('sle_id')
+		sle = get_stock_ledger_entries(args, "<", "desc", "limit 1", for_update=False)
+		sle = sle[0] if sle else frappe._dict()
+		return sle
 
-		return get_stock_ledger_entries(self.args, "<=", "desc", "limit 1", for_update=False)
-
-	def get_sle_after_datetime(self):
+	def get_sle_after_datetime(self, args):
 		"""get Stock Ledger Entries after a particular datetime, for reposting"""
-		return get_stock_ledger_entries(self.previous_sle or frappe._dict({
-				"item_code": self.args.get("item_code"), "warehouse": self.args.get("warehouse") }),
-			">", "asc", for_update=True, check_serial_no=False)
+		return get_stock_ledger_entries(args, ">", "asc", for_update=True, check_serial_no=False)
 
 	def raise_exceptions(self):
-		deficiency = min(e["diff"] for e in self.exceptions)
+		msg_list = []
+		for warehouse, exceptions in iteritems(self.exceptions):
+			deficiency = min(e["diff"] for e in exceptions)
 
-		if ((self.exceptions[0]["voucher_type"], self.exceptions[0]["voucher_no"]) in
-			frappe.local.flags.currently_saving):
+			if ((exceptions[0]["voucher_type"], exceptions[0]["voucher_no"]) in
+				frappe.local.flags.currently_saving):
 
-			msg = _("{0} units of {1} needed in {2} to complete this transaction.").format(
-				abs(deficiency), frappe.get_desk_link('Item', self.item_code),
-				frappe.get_desk_link('Warehouse', self.warehouse))
-		else:
-			msg = _("{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction.").format(
-				abs(deficiency), frappe.get_desk_link('Item', self.item_code),
-				frappe.get_desk_link('Warehouse', self.warehouse),
-				self.exceptions[0]["posting_date"], self.exceptions[0]["posting_time"],
-				frappe.get_desk_link(self.exceptions[0]["voucher_type"], self.exceptions[0]["voucher_no"]))
+				msg = _("{0} units of {1} needed in {2} to complete this transaction.").format(
+					abs(deficiency), frappe.get_desk_link('Item', self.item_code),
+					frappe.get_desk_link('Warehouse', warehouse))
+			else:
+				msg = _("{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction.").format(
+					abs(deficiency), frappe.get_desk_link('Item', self.item_code),
+					frappe.get_desk_link('Warehouse', warehouse),
+					exceptions[0]["posting_date"], exceptions[0]["posting_time"],
+					frappe.get_desk_link(exceptions[0]["voucher_type"], exceptions[0]["voucher_no"]))
 
-		if self.verbose:
-			frappe.throw(msg, NegativeStockError, title='Insufficient Stock')
-		else:
-			raise NegativeStockError(msg)
+			if msg:
+				msg_list.append(msg)
+
+		if msg_list:
+			message = "\n\n".join(msg_list)
+			if self.verbose:
+				frappe.throw(message, NegativeStockError, title='Insufficient Stock')
+			else:
+				raise NegativeStockError(message)
+	
+	def update_bin(self):
+		# update bin for each warehouse
+		for warehouse, data in iteritems(self.data):
+			bin_doc = get_bin(self.item_code, warehouse)
+
+			bin_doc.update({
+				"valuation_rate": data.valuation_rate,
+				"actual_qty": data.qty_after_transaction,
+				"stock_value": data.stock_value
+			})
+			bin_doc.flags.via_stock_ledger_entry = True
+			bin_doc.save(ignore_permissions=True)
 
 def get_previous_sle(args, for_update=False):
 	"""
@@ -489,6 +687,7 @@
 		select *, timestamp(posting_date, posting_time) as "timestamp"
 		from `tabStock Ledger Entry`
 		where item_code = %%(item_code)s
+		and is_cancelled = 0
 		%(conditions)s
 		order by timestamp(posting_date, posting_time) %(order)s, creation %(order)s
 		%(limit)s %(for_update)s""" % {
@@ -498,10 +697,11 @@
 			"order": order
 		}, previous_sle, as_dict=1, debug=debug)
 
-def get_sle_by_id(sle_id):
-	return frappe.db.get_all('Stock Ledger Entry',
-		fields=['*', 'timestamp(posting_date, posting_time) as timestamp'],
-		filters={'name': sle_id})[0]
+def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None):
+	return frappe.db.get_value('Stock Ledger Entry',
+		{'voucher_detail_no': voucher_detail_no, 'name': ['!=', excluded_sle]},
+		['item_code', 'warehouse', 'posting_date', 'posting_time', 'timestamp(posting_date, posting_time) as timestamp'],
+		as_dict=1)
 
 def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
 	allow_zero_rate=False, currency=None, company=None, raise_error_if_no_rate=True):
@@ -529,7 +729,7 @@
 			order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, voucher_no, voucher_type))
 
 	if last_valuation_rate:
-		return flt(last_valuation_rate[0][0]) # as there is previous records, it might come with zero rate
+		return flt(last_valuation_rate[0][0])
 
 	# If negative stock allowed, and item delivered without any incoming entry,
 	# system does not found any SLE, then take valuation rate from Item
@@ -561,3 +761,54 @@
 		frappe.throw(msg=msg, title=_("Valuation Rate Missing"))
 
 	return valuation_rate
+
+def update_qty_in_future_sle(args, allow_negative_stock=None):
+	frappe.db.sql("""
+		update `tabStock Ledger Entry`
+		set qty_after_transaction = qty_after_transaction + {qty}
+		where 
+			item_code = %(item_code)s
+			and warehouse = %(warehouse)s
+			and voucher_no != %(voucher_no)s
+			and is_cancelled = 0
+			and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s)
+				or (
+					timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s)
+					and creation > %(creation)s
+				)
+			)
+	""".format(qty=args.actual_qty), args)
+
+	validate_negative_qty_in_future_sle(args, allow_negative_stock)
+
+def validate_negative_qty_in_future_sle(args, allow_negative_stock=None):
+	allow_negative_stock = allow_negative_stock \
+		or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
+
+	if args.actual_qty < 0 and not allow_negative_stock:
+		sle = get_future_sle_with_negative_qty(args)
+		if sle:
+			message = _("{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction.").format(
+				abs(sle[0]["qty_after_transaction"]),
+				frappe.get_desk_link('Item', args.item_code),
+				frappe.get_desk_link('Warehouse', args.warehouse),
+				sle[0]["posting_date"], sle[0]["posting_time"],
+				frappe.get_desk_link(sle[0]["voucher_type"], sle[0]["voucher_no"]))
+						
+			frappe.throw(message, NegativeStockError, title='Insufficient Stock')
+
+def get_future_sle_with_negative_qty(args):
+	return frappe.db.sql("""
+		select
+			qty_after_transaction, posting_date, posting_time,
+			voucher_type, voucher_no
+		from `tabStock Ledger Entry`
+		where 
+			item_code = %(item_code)s
+			and warehouse = %(warehouse)s
+			and voucher_no != %(voucher_no)s
+			and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
+			and is_cancelled = 0
+			and qty_after_transaction < 0
+		limit 1
+	""", args, as_dict=1)
\ No newline at end of file
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index f9ac254..4ea7e4f 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -63,6 +63,7 @@
 		SELECT item_code, stock_value, name, warehouse
 		FROM `tabStock Ledger Entry` sle
 		WHERE posting_date <= %s {0}
+			and is_cancelled = 0
 		ORDER BY timestamp(posting_date, posting_time) DESC, creation DESC
 	""".format(condition), values, as_dict=1)
 
@@ -211,7 +212,7 @@
 			currency=erpnext.get_company_currency(args.get('company')), company=args.get('company'),
 			raise_error_if_no_rate=raise_error_if_no_rate)
 
-	return in_rate
+	return flt(in_rate)
 
 def get_avg_purchase_rate(serial_nos):
 	"""get average value of serial numbers"""
@@ -375,4 +376,10 @@
 
 	outgoing_rate = outgoing_rate[0][0] if outgoing_rate else 0.0
 
-	return outgoing_rate
\ No newline at end of file
+	return outgoing_rate
+
+def is_reposting_item_valuation_in_progress():
+	reposting_in_progress = frappe.db.exists("Repost Item Valuation",
+		{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
+	if reposting_in_progress:
+		frappe.msgprint(_("Item valuation reposting in progress. Report might show incorrect item valuation."), alert=1)
\ No newline at end of file
diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js
index fe01d4b..086755b 100644
--- a/erpnext/support/doctype/issue/issue.js
+++ b/erpnext/support/doctype/issue/issue.js
@@ -1,6 +1,13 @@
 frappe.ui.form.on("Issue", {
 	onload: function(frm) {
 		frm.email_field = "raised_by";
+		frm.set_query("customer", function () {
+			return {
+				filters: {
+					"disabled": 0
+				}
+			};
+		});
 
 		frappe.db.get_value("Support Settings", {name: "Support Settings"},
 			["allow_resetting_service_level_agreement", "track_service_level_agreement"], (r) => {
@@ -21,14 +28,14 @@
 				},
 				callback: function (r) {
 					if (r && r.message) {
-						frm.set_query('priority', function() {
+						frm.set_query("priority", function() {
 							return {
 								filters: {
 									"name": ["in", r.message.priority],
 								}
 							};
 						});
-						frm.set_query('service_level_agreement', function() {
+						frm.set_query("service_level_agreement", function() {
 							return {
 								filters: {
 									"name": ["in", r.message.service_level_agreements],
@@ -45,9 +52,9 @@
 		if (frm.doc.status !== "Closed" && frm.doc.agreement_status === "Ongoing") {
 			if (frm.doc.service_level_agreement) {
 				frappe.call({
-					'method': 'frappe.client.get',
+					"method": "frappe.client.get",
 					args: {
-						doctype: 'Service Level Agreement',
+						doctype: "Service Level Agreement",
 						name: frm.doc.service_level_agreement
 					},
 					callback: function(data) {
@@ -127,8 +134,8 @@
 				reset_sla.clear();
 
 				frappe.show_alert({
-					indicator: 'green',
-					message: __('Resetting Service Level Agreement.')
+					indicator: "green",
+					message: __("Resetting Service Level Agreement.")
 				});
 
 				frm.call("reset_service_level_agreement", {
@@ -145,35 +152,36 @@
 		reset_sla.show();
 	},
 
+
 	timeline_refresh: function(frm) {
 		// create button for "Help Article"
-		if(frappe.model.can_create('Help Article')) {
+		if (frappe.model.can_create("Help Article")) {
 			// Removing Help Article button if exists to avoid multiple occurance
 			frm.timeline.wrapper.find('.comment-header .asset-details .btn-add-to-kb').remove();
 			$('<button class="btn btn-xs btn-link btn-add-to-kb text-muted hidden-xs pull-right">'+
 				__('Help Article') + '</button>')
 				.appendTo(frm.timeline.wrapper.find('.comment-header .asset-details:not([data-communication-type="Comment"])'))
-				.on('click', function() {
-					var content = $(this).parents('.timeline-item:first').find('.timeline-item-content').html();
-					var doc = frappe.model.get_new_doc('Help Article');
+				.on("click", function() {
+					var content = $(this).parents(".timeline-item:first").find(".timeline-item-content").html();
+					var doc = frappe.model.get_new_doc("Help Article");
 					doc.title = frm.doc.subject;
 					doc.content = content;
-					frappe.set_route('Form', 'Help Article', doc.name);
+					frappe.set_route("Form", "Help Article", doc.name);
 				});
 		}
 
-		if (!frm.timeline.wrapper.find('.btn-split-issue').length) {
+		if (!frm.timeline.wrapper.find(".btn-split-issue").length) {
 			let split_issue = __("Split Issue")
 			$(`<button class="btn btn-xs btn-link btn-add-to-kb text-muted hidden-xs btn-split-issue pull-right" style="display:inline-block; margin-right: 15px">
 				${split_issue}
 			</button>`)
 				.appendTo(frm.timeline.wrapper.find('.comment-header .asset-details:not([data-communication-type="Comment"])'))
 			if (!frm.timeline.wrapper.data("split-issue-event-attached")){
-				frm.timeline.wrapper.on('click', '.btn-split-issue', (e) => {
+				frm.timeline.wrapper.on("click", ".btn-split-issue", (e) => {
 					var dialog = new frappe.ui.Dialog({
 						title: __("Split Issue"),
 						fields: [
-							{fieldname: 'subject', fieldtype: 'Data', reqd:1, label: __('Subject'), description: __('All communications including and above this shall be moved into the new Issue')}
+							{fieldname: "subject", fieldtype: "Data", reqd: 1, label: __("Subject"), description: __("All communications including and above this shall be moved into the new Issue")}
 						],
 						primary_action_label: __("Split"),
 						primary_action: function() {
@@ -226,7 +234,7 @@
 function get_time_left(timestamp, agreement_status) {
 	const diff = moment(timestamp).diff(moment());
 	const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : "Failed";
-	let indicator = (diff_display == 'Failed' && agreement_status != "Fulfilled") ? "red" : "green";
+	let indicator = (diff_display == "Failed" && agreement_status != "Fulfilled") ? "red" : "green";
 	return {"diff_display": diff_display, "indicator": indicator};
 }
 
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/telephony/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/telephony/__init__.py
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/telephony/doctype/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/telephony/doctype/__init__.py
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/telephony/doctype/call_log/__init__.py
similarity index 100%
rename from erpnext/communication/doctype/call_log/__init__.py
rename to erpnext/telephony/doctype/call_log/__init__.py
diff --git a/erpnext/telephony/doctype/call_log/call_log.js b/erpnext/telephony/doctype/call_log/call_log.js
new file mode 100644
index 0000000..977f86d
--- /dev/null
+++ b/erpnext/telephony/doctype/call_log/call_log.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Call Log', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/communication/doctype/call_log/call_log.json b/erpnext/telephony/doctype/call_log/call_log.json
similarity index 96%
rename from erpnext/communication/doctype/call_log/call_log.json
rename to erpnext/telephony/doctype/call_log/call_log.json
index 31e79f1..55ad2ba 100644
--- a/erpnext/communication/doctype/call_log/call_log.json
+++ b/erpnext/telephony/doctype/call_log/call_log.json
@@ -137,12 +137,11 @@
    "read_only": 1
   }
  ],
- "in_create": 1,
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2020-08-25 17:08:34.085731",
+ "modified": "2020-11-25 14:32:44.407815",
  "modified_by": "Administrator",
- "module": "Communication",
+ "module": "Telephony",
  "name": "Call Log",
  "owner": "Administrator",
  "permissions": [
diff --git a/erpnext/communication/doctype/call_log/call_log.py b/erpnext/telephony/doctype/call_log/call_log.py
similarity index 100%
rename from erpnext/communication/doctype/call_log/call_log.py
rename to erpnext/telephony/doctype/call_log/call_log.py
diff --git a/erpnext/telephony/doctype/call_log/test_call_log.py b/erpnext/telephony/doctype/call_log/test_call_log.py
new file mode 100644
index 0000000..faa6304
--- /dev/null
+++ b/erpnext/telephony/doctype/call_log/test_call_log.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestCallLog(unittest.TestCase):
+	pass
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/telephony/doctype/incoming_call_handling_schedule/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/telephony/doctype/incoming_call_handling_schedule/__init__.py
diff --git a/erpnext/telephony/doctype/incoming_call_handling_schedule/incoming_call_handling_schedule.json b/erpnext/telephony/doctype/incoming_call_handling_schedule/incoming_call_handling_schedule.json
new file mode 100644
index 0000000..6d46b4e
--- /dev/null
+++ b/erpnext/telephony/doctype/incoming_call_handling_schedule/incoming_call_handling_schedule.json
@@ -0,0 +1,60 @@
+{
+ "actions": [],
+ "creation": "2020-11-19 11:15:54.967710",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "day_of_week",
+  "from_time",
+  "to_time",
+  "agent_group"
+ ],
+ "fields": [
+  {
+   "fieldname": "day_of_week",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Day Of Week",
+   "options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday",
+   "reqd": 1
+  },
+  {
+   "default": "9:00:00",
+   "fieldname": "from_time",
+   "fieldtype": "Time",
+   "in_list_view": 1,
+   "label": "From Time",
+   "reqd": 1
+  },
+  {
+   "default": "17:00:00",
+   "fieldname": "to_time",
+   "fieldtype": "Time",
+   "in_list_view": 1,
+   "label": "To Time",
+   "reqd": 1
+  },
+  {
+   "fieldname": "agent_group",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Agent Group",
+   "options": "Employee Group",
+   "reqd": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2020-11-19 11:15:54.967710",
+ "modified_by": "Administrator",
+ "module": "Telephony",
+ "name": "Incoming Call Handling Schedule",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/telephony/doctype/incoming_call_handling_schedule/incoming_call_handling_schedule.py b/erpnext/telephony/doctype/incoming_call_handling_schedule/incoming_call_handling_schedule.py
new file mode 100644
index 0000000..fcf2974
--- /dev/null
+++ b/erpnext/telephony/doctype/incoming_call_handling_schedule/incoming_call_handling_schedule.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class IncomingCallHandlingSchedule(Document):
+	pass
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/telephony/doctype/incoming_call_settings/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/telephony/doctype/incoming_call_settings/__init__.py
diff --git a/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.js b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.js
new file mode 100644
index 0000000..1bcc846
--- /dev/null
+++ b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.js
@@ -0,0 +1,102 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+function time_to_seconds(time_str) {
+	// Convert time string of format HH:MM:SS into seconds.
+	let seq = time_str.split(':');
+	seq = seq.map((n) => parseInt(n));
+	return (seq[0]*60*60) + (seq[1]*60) + seq[2];
+}
+
+function number_sort(array, ascending=true) {
+	let array_copy = [...array];
+	if (ascending) {
+		array_copy.sort((a, b) => a-b); // ascending order
+	} else {
+		array_copy.sort((a, b) => b-a); // descending order
+	}
+	return array_copy;
+}
+
+function groupby(items, key) {
+	// Group the list of items using the given key.
+	const obj = {};
+	items.forEach((item) => {
+		if (item[key] in obj) {
+			obj[item[key]].push(item);
+		} else {
+			obj[item[key]] = [item];
+		}
+	});
+	return obj;
+}
+
+function check_timeslot_overlap(ts1, ts2) {
+	/// Timeslot is a an array of length 2 ex: [from_time, to_time]
+	/// time in timeslot is an integer represents number of seconds.
+	if ((ts1[0] < ts2[0] && ts1[1] <= ts2[0]) || (ts1[0] >= ts2[1] && ts1[1] > ts2[1])) {
+		return false;
+	}
+	return true;
+}
+
+function validate_call_schedule(schedule) {
+	validate_call_schedule_timeslot(schedule);
+	validate_call_schedule_overlaps(schedule);
+}
+
+function validate_call_schedule_timeslot(schedule) {
+	// Make sure that to time slot is ahead of from time slot.
+	let errors = [];
+
+	for (let row in schedule) {
+		let record = schedule[row];
+		let from_time_in_secs = time_to_seconds(record.from_time);
+		let to_time_in_secs = time_to_seconds(record.to_time);
+		if (from_time_in_secs >= to_time_in_secs) {
+			errors.push(__('Call Schedule Row {0}: To time slot should always be ahead of From time slot.', [row]));
+		}
+	}
+
+	if (errors.length > 0) {
+		frappe.throw(errors.join("<br/>"));
+	}
+}
+
+function is_call_schedule_overlapped(day_schedule) {
+	// Check if any time slots are overlapped in a day schedule.
+	let timeslots = [];
+	day_schedule.forEach((record)=> {
+		timeslots.push([time_to_seconds(record.from_time), time_to_seconds(record.to_time)]);
+	});
+
+	if (timeslots.length < 2) {
+		return false;
+	}
+
+	timeslots = number_sort(timeslots);
+
+	// Sorted timeslots will be in ascending order if not overlapped.
+	for (let i=1; i < timeslots.length; i++) {
+		if (check_timeslot_overlap(timeslots[i-1], timeslots[i])) {
+			return true;
+		}
+	}
+	return false;
+}
+
+function validate_call_schedule_overlaps(schedule) {
+	let group_by_day = groupby(schedule, 'day_of_week');
+	for (const [day, day_schedule] of Object.entries(group_by_day)) {
+		if (is_call_schedule_overlapped(day_schedule)) {
+			frappe.throw(__('Please fix overlapping time slots for {0}', [day]));
+		}
+	}
+}
+
+frappe.ui.form.on('Incoming Call Settings', {
+	validate(frm) {
+		validate_call_schedule(frm.doc.call_handling_schedule);
+	}
+});
+
diff --git a/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.json b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.json
new file mode 100644
index 0000000..3ffb3e4
--- /dev/null
+++ b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.json
@@ -0,0 +1,82 @@
+{
+ "actions": [],
+ "autoname": "Prompt",
+ "creation": "2020-11-19 10:37:20.734245",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "call_routing",
+  "column_break_2",
+  "greeting_message",
+  "agent_busy_message",
+  "agent_unavailable_message",
+  "section_break_6",
+  "call_handling_schedule"
+ ],
+ "fields": [
+  {
+   "default": "Sequential",
+   "fieldname": "call_routing",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Call Routing",
+   "options": "Sequential\nSimultaneous"
+  },
+  {
+   "fieldname": "greeting_message",
+   "fieldtype": "Data",
+   "label": "Greeting Message"
+  },
+  {
+   "fieldname": "agent_busy_message",
+   "fieldtype": "Data",
+   "label": "Agent Busy Message"
+  },
+  {
+   "fieldname": "agent_unavailable_message",
+   "fieldtype": "Data",
+   "label": "Agent Unavailable Message"
+  },
+  {
+   "fieldname": "column_break_2",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_6",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "call_handling_schedule",
+   "fieldtype": "Table",
+   "label": "Call Handling Schedule",
+   "options": "Incoming Call Handling Schedule",
+   "reqd": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-11-19 11:17:14.527862",
+ "modified_by": "Administrator",
+ "module": "Telephony",
+ "name": "Incoming Call Settings",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.py b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.py
new file mode 100644
index 0000000..2b2008a
--- /dev/null
+++ b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+from datetime import datetime
+from typing import Tuple
+from frappe import _
+
+class IncomingCallSettings(Document):
+	def validate(self):
+		"""List of validations
+		* Make sure that to time slot is ahead of from time slot in call schedule
+		* Make sure that no overlapping timeslots for a given day
+		"""
+		self.validate_call_schedule_timeslot(self.call_handling_schedule)
+		self.validate_call_schedule_overlaps(self.call_handling_schedule)
+
+	def validate_call_schedule_timeslot(self, schedule: list):
+		"""	Make sure that to time slot is ahead of from time slot.
+		"""
+		errors = []
+		for record in schedule:
+			from_time = self.time_to_seconds(record.from_time)
+			to_time = self.time_to_seconds(record.to_time)
+			if from_time >= to_time:
+				errors.append(
+					_('Call Schedule Row {0}: To time slot should always be ahead of From time slot.').format(record.idx)
+				)
+
+		if errors:
+			frappe.throw('<br/>'.join(errors))
+
+	def validate_call_schedule_overlaps(self, schedule: list):
+		"""Check if any time slots are overlapped in a day schedule.
+		"""
+		week_days = set([each.day_of_week for each in schedule])
+
+		for day in week_days:
+			timeslots = [(record.from_time, record.to_time) for record in schedule if record.day_of_week==day]
+
+			# convert time in timeslot into an integer represents number of seconds
+			timeslots = sorted(map(lambda seq: tuple(map(self.time_to_seconds, seq)), timeslots))
+			if len(timeslots) < 2: continue
+
+			for i in range(1, len(timeslots)):
+				if self.check_timeslots_overlap(timeslots[i-1], timeslots[i]):
+					frappe.throw(_('Please fix overlapping time slots for {0}.').format(day))
+
+	@staticmethod
+	def check_timeslots_overlap(ts1: Tuple[int, int], ts2: Tuple[int, int]) -> bool:
+		if (ts1[0] < ts2[0] and ts1[1] <= ts2[0]) or (ts1[0] >= ts2[1] and ts1[1] > ts2[1]):
+			return False
+		return True
+
+	@staticmethod
+	def time_to_seconds(time: str) -> int:
+		"""Convert time string of format HH:MM:SS into seconds
+		"""
+		date_time = datetime.strptime(time, "%H:%M:%S")
+		return date_time - datetime(1900, 1, 1)
diff --git a/erpnext/telephony/doctype/incoming_call_settings/test_incoming_call_settings.py b/erpnext/telephony/doctype/incoming_call_settings/test_incoming_call_settings.py
new file mode 100644
index 0000000..c058c11
--- /dev/null
+++ b/erpnext/telephony/doctype/incoming_call_settings/test_incoming_call_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestIncomingCallSettings(unittest.TestCase):
+	pass
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/telephony/doctype/voice_call_settings/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/telephony/doctype/voice_call_settings/__init__.py
diff --git a/erpnext/telephony/doctype/voice_call_settings/test_voice_call_settings.py b/erpnext/telephony/doctype/voice_call_settings/test_voice_call_settings.py
new file mode 100644
index 0000000..85d6add
--- /dev/null
+++ b/erpnext/telephony/doctype/voice_call_settings/test_voice_call_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestVoiceCallSettings(unittest.TestCase):
+	pass
diff --git a/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.js b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.js
new file mode 100644
index 0000000..4a61b61
--- /dev/null
+++ b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Voice Call Settings', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.json b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.json
new file mode 100644
index 0000000..25e55a2
--- /dev/null
+++ b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.json
@@ -0,0 +1,124 @@
+{
+ "actions": [],
+ "autoname": "field:user",
+ "creation": "2020-12-08 16:52:40.590146",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "user",
+  "call_receiving_device",
+  "column_break_3",
+  "greeting_message",
+  "agent_busy_message",
+  "agent_unavailable_message"
+ ],
+ "fields": [
+  {
+   "fieldname": "user",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "User",
+   "options": "User",
+   "permlevel": 1,
+   "reqd": 1,
+   "unique": 1
+  },
+  {
+   "fieldname": "greeting_message",
+   "fieldtype": "Data",
+   "label": "Greeting Message"
+  },
+  {
+   "fieldname": "agent_busy_message",
+   "fieldtype": "Data",
+   "label": "Agent Busy Message"
+  },
+  {
+   "fieldname": "agent_unavailable_message",
+   "fieldtype": "Data",
+   "label": "Agent Unavailable Message"
+  },
+  {
+   "default": "Computer",
+   "fieldname": "call_receiving_device",
+   "fieldtype": "Select",
+   "label": "Call Receiving Device",
+   "options": "Computer\nPhone"
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-12-14 18:49:34.600194",
+ "modified_by": "Administrator",
+ "module": "Telephony",
+ "name": "Voice Call Settings",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "All",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "permlevel": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "permlevel": 2,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "permlevel": 2,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "All",
+   "share": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.py b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.py
new file mode 100644
index 0000000..ad3bbf1
--- /dev/null
+++ b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class VoiceCallSettings(Document):
+	pass
diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html
index 40a064f..74b2ae3 100644
--- a/erpnext/templates/generators/item_group.html
+++ b/erpnext/templates/generators/item_group.html
@@ -1,5 +1,9 @@
 {% extends "templates/web.html" %}
 
+{% block breadcrumbs %}
+	{% include "templates/includes/breadcrumbs.html" %}
+{% endblock %}
+
 {% block header %}<h1>{{ name }}</h1>{% endblock %}
 
 {% block page_content %}
diff --git a/erpnext/templates/print_formats/includes/taxes.html b/erpnext/templates/print_formats/includes/taxes.html
index 6e984f3..304e845 100644
--- a/erpnext/templates/print_formats/includes/taxes.html
+++ b/erpnext/templates/print_formats/includes/taxes.html
@@ -20,10 +20,10 @@
 			{%- if (charge.tax_amount or doc.flags.print_taxes_with_zero_amount) and (not charge.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
 			<div class="row">
 				<div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}">
-					<label>{{ charge.get_formatted("description") }}</label></div>
+					<label>{{ charge.get_formatted("description") }}</label>
+				</div>
 				<div class="col-xs-7 text-right">
-					{{ frappe.format_value(frappe.utils.flt(charge.tax_amount),
-						table_meta.get_field("tax_amount"), doc, currency=doc.currency) }}
+					{{ charge.get_formatted('tax_amount', doc) }}
 				</div>
 			</div>
 			{%- endif -%}
diff --git a/erpnext/www/all-products/index.py b/erpnext/www/all-products/index.py
index 0394e4b..7d7793a 100644
--- a/erpnext/www/all-products/index.py
+++ b/erpnext/www/all-products/index.py
@@ -15,6 +15,9 @@
 
 	context.items = get_products_for_website(field_filters, attribute_filters, search)
 
+	# Add homepage as parent
+	context.parents = [{"name": frappe._("Home"), "route":"/"}]
+
 	product_settings = get_product_settings()
 	context.field_filters = get_field_filter_data() \
 		if product_settings.enable_field_filters else []
diff --git a/requirements.txt b/requirements.txt
index c4f9171..4511aa5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,7 +2,7 @@
 frappe
 gocardless-pro==1.11.0
 googlemaps==3.1.1
-pandas==1.0.5
+pandas>=1.0.5
 plaid-python==6.0.0
 pycountry==19.8.18
 PyGithub==1.44.1
@@ -12,3 +12,4 @@
 tweepy==3.8.0
 Unidecode==1.1.1
 WooCommerce==2.1.1
+pycryptodome==3.9.8
\ No newline at end of file