From ab92e330dae8528faf5a98910b693b31a7ceb2ba Mon Sep 17 00:00:00 2001
From: P-Froggy <60393001+P-Froggy@users.noreply.github.com>
Date: Sun, 16 Feb 2020 23:08:32 +0100
Subject: [PATCH 01/93] Fix: Wrong filter for supplier default bank account

The query for default bank account field in the supplier master only allowed company bank accounts. With this fix it's now possible to one of the supplier bank accounts as default bank account.
---
 erpnext/buying/doctype/supplier/supplier.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index 4ddc458175..074888afd9 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -17,10 +17,11 @@ frappe.ui.form.on("Supplier", {
 				}
 			}
 		});
-		frm.set_query("default_bank_account", function() {
+		frm.set_query("default_bank_account", function(doc, cdt, cdn) {
 			return {
 				filters: {
-					"is_company_account":1
+					"is_company_account":0,
+					"party":doc.name
 				}
 			}
 		});

From 7ee8a209647ee6a09557d8e40fc8712ec9321e49 Mon Sep 17 00:00:00 2001
From: P-Froggy <60393001+P-Froggy@users.noreply.github.com>
Date: Mon, 17 Feb 2020 00:15:29 +0100
Subject: [PATCH 02/93] Added bank account reference in supplier dashboard

---
 erpnext/buying/doctype/supplier/supplier_dashboard.py | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/erpnext/buying/doctype/supplier/supplier_dashboard.py b/erpnext/buying/doctype/supplier/supplier_dashboard.py
index 887a093736..f346e7e72a 100644
--- a/erpnext/buying/doctype/supplier/supplier_dashboard.py
+++ b/erpnext/buying/doctype/supplier/supplier_dashboard.py
@@ -9,8 +9,9 @@ def get_data():
 		'heatmap_message': _('This is based on transactions against this Supplier. See timeline below for details'),
 		'fieldname': 'supplier',
 		'non_standard_fieldnames': {
-			'Payment Entry': 'party_name'
-		},
+			'Payment Entry': 'party_name',
+			'Bank Account': 'party'
+		},		
 		'transactions': [
 			{
 				'label': _('Procurement'),
@@ -24,6 +25,10 @@ def get_data():
 				'label': _('Payments'),
 				'items': ['Payment Entry']
 			},
+			{
+				'label': _('Bank'),
+				'items': ['Bank Account']
+			},
 			{
 				'label': _('Pricing'),
 				'items': ['Pricing Rule']

From 7ccb8b6707b821b6d38c280c325c1f8349795db2 Mon Sep 17 00:00:00 2001
From: P-Froggy <60393001+P-Froggy@users.noreply.github.com>
Date: Mon, 17 Feb 2020 11:33:10 +0100
Subject: [PATCH 03/93] Revert "Merge pull request #1 from P-Froggy/patch-1"

This reverts commit 79c5266d0d0057e7c76365d47ff99604f0615700, reversing
changes made to bd582cfc39610afe794459a1bb92fb1e15c3577b.
---
 erpnext/buying/doctype/supplier/supplier.js | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index 074888afd9..4ddc458175 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -17,11 +17,10 @@ frappe.ui.form.on("Supplier", {
 				}
 			}
 		});
-		frm.set_query("default_bank_account", function(doc, cdt, cdn) {
+		frm.set_query("default_bank_account", function() {
 			return {
 				filters: {
-					"is_company_account":0,
-					"party":doc.name
+					"is_company_account":1
 				}
 			}
 		});

From 4cc929cabbe8630d5919eaf39c483a4933eca72e Mon Sep 17 00:00:00 2001
From: P-Froggy <60393001+P-Froggy@users.noreply.github.com>
Date: Tue, 18 Feb 2020 00:38:46 +0100
Subject: [PATCH 04/93] Move SWIFT-number and branch-code from bank account to
 bank

The bank's SWIFT-Number and branch-code should be a field in bank DocType, not in bank account DocType.
---
 erpnext/accounts/doctype/bank/bank.json       | 320 ++++++------------
 .../doctype/bank_account/bank_account.json    |  13 -
 .../doctype/bank_guarantee/bank_guarantee.js  |   4 +-
 .../payment_request/payment_request.json      |   4 +-
 erpnext/patches.txt                           |   3 +-
 .../move_bank_account_swift_number_to_bank.py |  20 ++
 6 files changed, 136 insertions(+), 228 deletions(-)
 create mode 100644 erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py

diff --git a/erpnext/accounts/doctype/bank/bank.json b/erpnext/accounts/doctype/bank/bank.json
index 4fa0e4f29b..7a628b5228 100644
--- a/erpnext/accounts/doctype/bank/bank.json
+++ b/erpnext/accounts/doctype/bank/bank.json
@@ -1,224 +1,124 @@
 {
- "allow_copy": 0, 
- "allow_events_in_timeline": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "field:bank_name", 
- "beta": 0, 
- "creation": "2018-04-07 16:59:59.496668", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "field:bank_name",
+ "creation": "2018-04-07 16:59:59.496668",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "bank_name",
+  "bank_details_section",
+  "swift_number",
+  "column_break_1",
+  "branch_code",
+  "data_import_configuration_section",
+  "bank_transaction_mapping",
+  "section_break_4",
+  "plaid_access_token"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "bank_name", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Bank Name", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
+   "fieldname": "bank_name",
+   "fieldtype": "Data",
+   "label": "Bank Name",
+   "reqd": 1,
    "unique": 1
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 1, 
-   "columns": 0, 
-   "fieldname": "data_import_configuration_section", 
-   "fieldtype": "Section Break", 
-   "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": "Data Import Configuration", 
-   "length": 0, 
-   "no_copy": 0, 
-   "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
-  }, 
+   "fieldname": "bank_details_section",
+   "fieldtype": "Section Break",
+   "label": "Bank Details"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "bank_transaction_mapping", 
-   "fieldtype": "Table", 
-   "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": "Bank Transaction Mapping", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Bank Transaction Mapping", 
-   "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
-  }, 
+   "fieldname": "swift_number",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "SWIFT number",
+   "reqd": 0,
+   "unique": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_4", 
-   "fieldtype": "Section Break", 
-   "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, 
-   "length": 0, 
-   "no_copy": 0, 
-   "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
-  }, 
+   "fieldname": "column_break_1",
+   "fieldtype": "Column Break",
+   "search_index": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "plaid_access_token", 
-   "fieldtype": "Data", 
-   "hidden": 1, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Plaid Access Token", 
-   "length": 0, 
-   "no_copy": 1, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
+   "fieldname": "branch_code",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Branch Code",
+   "reqd": 0,
+   "unique": 1
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "data_import_configuration_section",
+   "fieldtype": "Section Break",
+   "label": "Data Import Configuration"
+  },
+  {
+   "fieldname": "bank_transaction_mapping",
+   "fieldtype": "Table",
+   "label": "Bank Transaction Mapping",
+   "options": "Bank Transaction Mapping"
+  },
+  {
+   "fieldname": "section_break_4",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "plaid_access_token",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "label": "Plaid Access Token",
+   "no_copy": 1,
+   "read_only": 1
   }
- ], 
- "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-11-27 16:12:13.938776", 
- "modified_by": "Administrator", 
- "module": "Accounts", 
- "name": "Bank", 
- "name_case": "", 
- "owner": "Administrator", 
+ ],
+ "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-11-27 16:12:13.938776",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Bank",
+ "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, 
+   "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
   }
- ], 
- "quick_entry": 1, 
- "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
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/bank_account/bank_account.json b/erpnext/accounts/doctype/bank_account/bank_account.json
index c8ae26d9f2..ade379b11d 100644
--- a/erpnext/accounts/doctype/bank_account/bank_account.json
+++ b/erpnext/accounts/doctype/bank_account/bank_account.json
@@ -24,8 +24,6 @@
   "iban",
   "column_break_12",
   "bank_account_no",
-  "branch_code",
-  "swift_number",
   "address_and_contact",
   "address_html",
   "website",
@@ -145,17 +143,6 @@
    "label": "Bank Account No",
    "length": 30
   },
-  {
-   "fieldname": "branch_code",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "Branch Code"
-  },
-  {
-   "fieldname": "swift_number",
-   "fieldtype": "Data",
-   "label": "SWIFT number"
-  },
   {
    "fieldname": "address_and_contact",
    "fieldtype": "Section Break",
diff --git a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js
index 0acbe2009f..065d25e6c3 100644
--- a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js
+++ b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js
@@ -4,8 +4,8 @@
 cur_frm.add_fetch('bank_account','account','account');
 cur_frm.add_fetch('bank_account','bank_account_no','bank_account_no');
 cur_frm.add_fetch('bank_account','iban','iban');
-cur_frm.add_fetch('bank_account','branch_code','branch_code');
-cur_frm.add_fetch('bank_account','swift_number','swift_number');
+cur_frm.add_fetch('bank','branch_code','branch_code');
+cur_frm.add_fetch('bank','swift_number','swift_number');
 
 frappe.ui.form.on('Bank Guarantee', {
 	setup: function(frm) {
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json
index bff995ec5a..c1559a74f2 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.json
+++ b/erpnext/accounts/doctype/payment_request/payment_request.json
@@ -839,7 +839,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "fetch_from": "bank_account.branch_code", 
+   "fetch_from": "bank.branch_code", 
    "fieldname": "branch_code", 
    "fieldtype": "Read Only", 
    "hidden": 0, 
@@ -873,7 +873,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "fetch_from": "bank_account.swift_number", 
+   "fetch_from": "bank.swift_number", 
    "fieldname": "swift_number", 
    "fieldtype": "Read Only", 
    "hidden": 0, 
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index a8938406f2..11a990df73 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -656,4 +656,5 @@ erpnext.patches.v12_0.set_lead_title_field
 erpnext.patches.v12_0.set_permission_einvoicing
 erpnext.patches.v12_0.set_published_in_hub_tracked_item
 erpnext.patches.v12_0.set_job_offer_applicant_email
-erpnext.patches.v12_0.create_irs_1099_field_united_states
\ No newline at end of file
+erpnext.patches.v12_0.create_irs_1099_field_united_states
+erpnext.patches.v12_0.move_bank_account_swift_number_to_bank
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
new file mode 100644
index 0000000000..2675b98480
--- /dev/null
+++ b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
@@ -0,0 +1,20 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	frappe.reload_doc('accounts', 'doctype', 'bank', force=1)
+
+	banks = frappe.get_all('Bank', 'name')
+	for bank in banks:
+		bank_accounts = frappe.get_all('Bank Account', filters={'bank': bank.name}, fields=['swift_number', 'branch_code'])
+		bank_account = ''
+		if len(bank_accounts):
+			bank_account = bank_accounts[0]
+			if bank_account and bank_account.swift_number:
+				bank.swift_number = bank_account.swift_number
+			if bank_account and bank_account.branch_code:
+				bank.branch_code = bank_account.branch_code
+			bank.save()
+
+	frappe.reload_doc('accounts', 'doctype', 'bank_account')
+	frappe.reload_doc('accounts', 'doctype', 'payment_request')
\ No newline at end of file

From e4d8ee5ba84e08810182429b6351fffc9ead1358 Mon Sep 17 00:00:00 2001
From: P-Froggy <60393001+P-Froggy@users.noreply.github.com>
Date: Tue, 18 Feb 2020 01:05:19 +0100
Subject: [PATCH 05/93] Revert "Added bank account reference in supplier
 dashboard"

This reverts commit 7ee8a209647ee6a09557d8e40fc8712ec9321e49.
---
 erpnext/buying/doctype/supplier/supplier_dashboard.py | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/erpnext/buying/doctype/supplier/supplier_dashboard.py b/erpnext/buying/doctype/supplier/supplier_dashboard.py
index f346e7e72a..887a093736 100644
--- a/erpnext/buying/doctype/supplier/supplier_dashboard.py
+++ b/erpnext/buying/doctype/supplier/supplier_dashboard.py
@@ -9,9 +9,8 @@ def get_data():
 		'heatmap_message': _('This is based on transactions against this Supplier. See timeline below for details'),
 		'fieldname': 'supplier',
 		'non_standard_fieldnames': {
-			'Payment Entry': 'party_name',
-			'Bank Account': 'party'
-		},		
+			'Payment Entry': 'party_name'
+		},
 		'transactions': [
 			{
 				'label': _('Procurement'),
@@ -25,10 +24,6 @@ def get_data():
 				'label': _('Payments'),
 				'items': ['Payment Entry']
 			},
-			{
-				'label': _('Bank'),
-				'items': ['Bank Account']
-			},
 			{
 				'label': _('Pricing'),
 				'items': ['Pricing Rule']

From 3cb9f4ab73b3eaa65a50658f7ead1b9733654fdd Mon Sep 17 00:00:00 2001
From: P-Froggy <60393001+P-Froggy@users.noreply.github.com>
Date: Tue, 18 Feb 2020 02:14:03 +0100
Subject: [PATCH 06/93] Add address and contact fields to bank

Address and contact fields are enabled for DocType Bank
---
 erpnext/accounts/doctype/bank/bank.js | 13 ++++++++++++-
 erpnext/accounts/doctype/bank/bank.py |  8 +++++++-
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js
index 463d29c9f8..39f057913d 100644
--- a/erpnext/accounts/doctype/bank/bank.js
+++ b/erpnext/accounts/doctype/bank/bank.js
@@ -7,7 +7,18 @@ frappe.ui.form.on('Bank', {
 	},
 	refresh: function(frm) {
 		add_fields_to_mapping_table(frm);
-	}
+
+		frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Bank' }
+
+		frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
+
+		if (frm.doc.__islocal) {
+			frappe.contacts.clear_address_and_contact(frm);
+		}
+		else {
+			frappe.contacts.render_address_and_contact(frm);
+		}
+	},
 });
 
 
diff --git a/erpnext/accounts/doctype/bank/bank.py b/erpnext/accounts/doctype/bank/bank.py
index b205d56678..41aae14362 100644
--- a/erpnext/accounts/doctype/bank/bank.py
+++ b/erpnext/accounts/doctype/bank/bank.py
@@ -5,6 +5,12 @@
 from __future__ import unicode_literals
 import frappe
 from frappe.model.document import Document
+from frappe.contacts.address_and_contact import load_address_and_contact, delete_contact_and_address
 
 class Bank(Document):
-	pass
+	def onload(self):
+		"""Load address and contacts in `__onload`"""
+		load_address_and_contact(self)
+
+	def on_trash(self):
+		delete_contact_and_address('Bank', self.name)
\ No newline at end of file

From 55d10f77f963eb0cdbe29e04fe910f65c4edaec4 Mon Sep 17 00:00:00 2001
From: P-Froggy <60393001+P-Froggy@users.noreply.github.com>
Date: Fri, 28 Feb 2020 13:14:11 +0100
Subject: [PATCH 07/93] fix: Add linked bank accounts to supplier dashboard

---
 erpnext/buying/doctype/supplier/supplier_dashboard.py | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/erpnext/buying/doctype/supplier/supplier_dashboard.py b/erpnext/buying/doctype/supplier/supplier_dashboard.py
index 887a093736..d0d5b73984 100644
--- a/erpnext/buying/doctype/supplier/supplier_dashboard.py
+++ b/erpnext/buying/doctype/supplier/supplier_dashboard.py
@@ -9,7 +9,8 @@ def get_data():
 		'heatmap_message': _('This is based on transactions against this Supplier. See timeline below for details'),
 		'fieldname': 'supplier',
 		'non_standard_fieldnames': {
-			'Payment Entry': 'party_name'
+			'Payment Entry': 'party_name',
+			'Bank Account': 'party'
 		},
 		'transactions': [
 			{
@@ -24,9 +25,13 @@ def get_data():
 				'label': _('Payments'),
 				'items': ['Payment Entry']
 			},
+			{
+				'label': _('Bank'),
+				'items': ['Bank Account']
+			},
 			{
 				'label': _('Pricing'),
 				'items': ['Pricing Rule']
 			}
 		]
-	}
+	}
\ No newline at end of file

From 393a626834ad5b66ce152bd60371b66dab548a44 Mon Sep 17 00:00:00 2001
From: P-Froggy <60393001+P-Froggy@users.noreply.github.com>
Date: Fri, 28 Feb 2020 20:45:48 +0100
Subject: [PATCH 08/93] fix: Allow only bank accounts of the respective party
 in payment entry

---
 erpnext/accounts/doctype/payment_entry/payment_entry.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 2192b7bf98..2aa999d07f 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -33,7 +33,9 @@ frappe.ui.form.on('Payment Entry', {
 		frm.set_query("party_bank_account", function() {
 			return {
 				filters: {
-					"is_company_account":0
+					"is_company_account":0,
+					party_type: frm.doc.party_type,
+					party: frm.doc.party
 				}
 			}
 		});

From 266e689c4fbdee14cd1c86b4d49255ac52acaa94 Mon Sep 17 00:00:00 2001
From: P-Froggy <60393001+P-Froggy@users.noreply.github.com>
Date: Fri, 28 Feb 2020 20:46:36 +0100
Subject: [PATCH 09/93] fix: Show swift number and branch code in quick entry
 of bank doctype

---
 erpnext/accounts/doctype/bank/bank.json | 32 +++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/erpnext/accounts/doctype/bank/bank.json b/erpnext/accounts/doctype/bank/bank.json
index 7a628b5228..fca171d392 100644
--- a/erpnext/accounts/doctype/bank/bank.json
+++ b/erpnext/accounts/doctype/bank/bank.json
@@ -14,6 +14,11 @@
   "swift_number",
   "column_break_1",
   "branch_code",
+  "address_and_contact",
+  "address_html",
+  "website",
+  "column_break_13",
+  "contact_html",
   "data_import_configuration_section",
   "bank_transaction_mapping",
   "section_break_4",
@@ -33,6 +38,7 @@
    "label": "Bank Details"
   },
   {
+   "allow_in_quick_entry": 1,
    "fieldname": "swift_number",
    "fieldtype": "Data",
    "in_list_view": 1,
@@ -47,6 +53,7 @@
    "search_index": 1
   },
   {
+   "allow_in_quick_entry": 1,
    "fieldname": "branch_code",
    "fieldtype": "Data",
    "in_list_view": 1,
@@ -55,6 +62,31 @@
    "reqd": 0,
    "unique": 1
   },
+  {
+   "fieldname": "address_and_contact",
+   "fieldtype": "Section Break",
+   "label": "Address and Contact",
+   "options": "fa fa-map-marker"
+  },
+  {
+   "fieldname": "address_html",
+   "fieldtype": "HTML",
+   "label": "Address HTML"
+  },
+  {
+   "fieldname": "website",
+   "fieldtype": "Data",
+   "label": "Website"
+  },
+  {
+   "fieldname": "column_break_13",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "contact_html",
+   "fieldtype": "HTML",
+   "label": "Contact HTML"
+  },
   {
    "collapsible": 1,
    "fieldname": "data_import_configuration_section",

From 313efe9b32ddc04e7b18e11b78c12158d12521af Mon Sep 17 00:00:00 2001
From: P-Froggy <60393001+P-Froggy@users.noreply.github.com>
Date: Mon, 2 Mar 2020 00:21:08 +0100
Subject: [PATCH 10/93] fix: add missing semicolon

---
 erpnext/accounts/doctype/bank/bank.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js
index 39f057913d..e21c77e9ca 100644
--- a/erpnext/accounts/doctype/bank/bank.js
+++ b/erpnext/accounts/doctype/bank/bank.js
@@ -8,7 +8,7 @@ frappe.ui.form.on('Bank', {
 	refresh: function(frm) {
 		add_fields_to_mapping_table(frm);
 
-		frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Bank' }
+		frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Bank' };
 
 		frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
 

From 86636212c38592769abb421c9338174673fdbcaa Mon Sep 17 00:00:00 2001
From: "Chinmay D. Pai" <chinmaydpai@gmail.com>
Date: Wed, 11 Mar 2020 16:20:07 +0530
Subject: [PATCH 11/93] fix: remove make_purchase_invoice from demo script

currently demo fails with the following error:

frappe@bf54a9834cfe:/workspace/development/frappe-bench$ bench --site test1.localhost execute erpnext.demo.demo.make
Traceback (most recent call last):
  File "/usr/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/workspace/development/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 97, in <module>
    main()
  File "/workspace/development/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 18, in main
    click.Group(commands=commands)(prog_name='bench')
  File "/workspace/development/frappe-bench/env/lib/python3.7/site-packages/click/core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "/workspace/development/frappe-bench/env/lib/python3.7/site-packages/click/core.py", line 717, in main
    rv = self.invoke(ctx)
  File "/workspace/development/frappe-bench/env/lib/python3.7/site-packages/click/core.py", line 1137, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/workspace/development/frappe-bench/env/lib/python3.7/site-packages/click/core.py", line 1137, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/workspace/development/frappe-bench/env/lib/python3.7/site-packages/click/core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/workspace/development/frappe-bench/env/lib/python3.7/site-packages/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "/workspace/development/frappe-bench/env/lib/python3.7/site-packages/click/decorators.py", line 17, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/workspace/development/frappe-bench/apps/frappe/frappe/commands/__init__.py", line 25, in _func
    ret = f(frappe._dict(ctx.obj), *args, **kwargs)
  File "/workspace/development/frappe-bench/apps/frappe/frappe/commands/utils.py", line 145, in execute
    ret = frappe.get_attr(method)(*args, **kwargs)
  File "/workspace/development/frappe-bench/apps/frappe/frappe/__init__.py", line 1042, in get_attr
    return getattr(get_module(modulename), methodname)
  File "/workspace/development/frappe-bench/apps/frappe/frappe/__init__.py", line 823, in get_module
    return importlib.import_module(modulename)
  File "/workspace/development/frappe-bench/env/lib/python3.7/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 728, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/workspace/development/frappe-bench/apps/erpnext/erpnext/demo/demo.py", line 6, in <module>
    from erpnext.demo.user import hr, sales, purchase, manufacturing, stock, accounts, projects, fixed_asset
  File "/workspace/development/frappe-bench/apps/erpnext/erpnext/demo/user/fixed_asset.py", line 9, in <module>
    from erpnext.assets.doctype.asset.asset import make_purchase_invoice, make_sales_invoice
ImportError: cannot import name 'make_purchase_invoice' from 'erpnext.assets.doctype.asset.asset' (/workspace/development/frappe-bench/apps/erpnext/erpnext/assets/doctype/asset/asset.py)

Signed-off-by: Chinmay D. Pai <chinmaydpai@gmail.com>
---
 erpnext/demo/user/fixed_asset.py | 39 +++++++++-----------------------
 1 file changed, 11 insertions(+), 28 deletions(-)

diff --git a/erpnext/demo/user/fixed_asset.py b/erpnext/demo/user/fixed_asset.py
index e6d1687202..dc094e1c91 100644
--- a/erpnext/demo/user/fixed_asset.py
+++ b/erpnext/demo/user/fixed_asset.py
@@ -6,46 +6,28 @@ from __future__ import unicode_literals
 
 import frappe
 from frappe.utils.make_random import get_random
-from erpnext.assets.doctype.asset.asset import make_purchase_invoice, make_sales_invoice
+from erpnext.assets.doctype.asset.asset import make_sales_invoice
 from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset
 
+
 def work():
 	frappe.set_user(frappe.db.get_global('demo_accounts_user'))
 
-	asset_list = make_asset_purchase_entry()
-
-	if not asset_list:
-		# fixed_asset.work() already run
-		return
-		
 	# Enable booking asset depreciation entry automatically
 	frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
-		
+
 	# post depreciation entries as on today
 	post_depreciation_entries()
-	
+
 	# scrap a random asset
 	frappe.db.set_value("Company", "Wind Power LLC", "disposal_account", "Gain/Loss on Asset Disposal - WPL")
-	
+
 	asset = get_random_asset()
 	scrap_asset(asset.name)
-	
-	# Sell a random asset
-	sell_an_asset()	
 
-def make_asset_purchase_entry():
-	asset_list = frappe.get_all("Asset", filters={"purchase_invoice": ["in", ("", None)]}, 
-		fields=["name", "item_code", "gross_purchase_amount", "company", "purchase_date"])
-				
-	# make purchase invoice
-	for asset in asset_list:
-		pi = make_purchase_invoice(asset.name, asset.item_code, asset.gross_purchase_amount, 
-			asset.company, asset.purchase_date)
-		pi.supplier = get_random("Supplier")
-		pi.save()
-		pi.submit()
-		
-	return asset_list
+	# Sell a random asset
+	sell_an_asset()
+
 
 def sell_an_asset():
 	asset = get_random_asset()
@@ -55,8 +37,9 @@ def sell_an_asset():
 		if asset.value_after_depreciation else asset.gross_purchase_amount * 0.9
 	si.save()
 	si.submit()
-	
+
+
 def get_random_asset():
 	return frappe.db.sql(""" select name, item_code, value_after_depreciation, gross_purchase_amount
-		from `tabAsset` 
+		from `tabAsset`
 		where docstatus=1 and status not in ("Scrapped", "Sold") order by rand() limit 1""", as_dict=1)[0]

From fcb54762a185d134838b37f4a668162d3a52f426 Mon Sep 17 00:00:00 2001
From: Ronel Cabrera <ronelvcabrera007@gmail.com>
Date: Mon, 18 Nov 2019 17:00:07 +0800
Subject: [PATCH 12/93] feat(Contacts): select billing contact for sales
 invoice

---
 erpnext/accounts/party.py                     | 32 +++++++++-
 .../erpnext_integrations/custom/contact.json  | 60 +++++++++++++++++++
 2 files changed, 89 insertions(+), 3 deletions(-)
 create mode 100644 erpnext/erpnext_integrations/custom/contact.json

diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 422ace64f5..e9c652ecba 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -46,7 +46,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
 	currency = party.default_currency if party.get("default_currency") else get_company_currency(company)
 
 	party_address, shipping_address = set_address_details(party_details, party, party_type, doctype, company, party_address, company_address, shipping_address)
-	set_contact_details(party_details, party, party_type)
+	set_contact_details(party_details, party, party_type, doctype)
 	set_other_values(party_details, party, party_type)
 	set_price_list(party_details, party, party_type, price_list, pos_profile)
 
@@ -115,8 +115,11 @@ def set_address_details(party_details, party, party_type, doctype=None, company=
 def get_regional_address_details(party_details, doctype, company):
 	pass
 
-def set_contact_details(party_details, party, party_type):
-	party_details.contact_person = get_default_contact(party_type, party.name)
+def set_contact_details(party_details, party, party_type, doctype=None):
+	if doctype == 'Sales Invoice':
+		party_details.contact_person = get_default_billing_contact(doctype, party.name)
+	else:
+		party_details.contact_person = get_default_contact(party_type, party.name)
 
 	if not party_details.contact_person:
 		party_details.update({
@@ -615,3 +618,26 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None):
 
 	if data:
 		return frappe._dict(data)
+
+def get_default_billing_contact(doctype, name):
+	""" 
+		Returns default contact for the given doctype and name.
+		Can be ordered by `contact_type` to either is_primary_contact or is_billing_contact.
+	"""
+	out = frappe.db.sql("""
+			SELECT dl.parent, c.is_primary_contact, c.is_billing_contact
+			FROM `tabDynamic Link` dl
+			INNER JOIN tabContact c ON c.name = dl.parent
+			WHERE 
+				dl.link_doctype=%s AND
+				dl.link_name=%s AND
+				dl.parenttype = "Contact"
+			ORDER BY is_billing_contact DESC, is_primary_contact DESC
+		""", (doctype, name))
+	if out:
+		try:
+			return out[0][0]
+		except:
+			return None
+	else:
+		return None
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/custom/contact.json b/erpnext/erpnext_integrations/custom/contact.json
new file mode 100644
index 0000000000..98a4bbc795
--- /dev/null
+++ b/erpnext/erpnext_integrations/custom/contact.json
@@ -0,0 +1,60 @@
+{
+ "custom_fields": [
+  {
+   "_assign": null,
+   "_comments": null,
+   "_liked_by": null,
+   "_user_tags": null,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "collapsible_depends_on": null,
+   "columns": 0,
+   "creation": "2019-12-02 11:00:03.432994",
+   "default": null,
+   "depends_on": null,
+   "description": null,
+   "docstatus": 0,
+   "dt": "Contact",
+   "fetch_from": null,
+   "fetch_if_empty": 0,
+   "fieldname": "is_billing_contact",
+   "fieldtype": "Check",
+   "hidden": 0,
+   "idx": 27,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 0,
+   "in_standard_filter": 0,
+   "insert_after": "is_primary_contact",
+   "label": "Is Billing Contact",
+   "length": 0,
+   "modified": "2019-12-02 11:00:03.432994",
+   "modified_by": "Administrator",
+   "name": "Contact-is_billing_contact",
+   "no_copy": 0,
+   "options": null,
+   "owner": "Administrator",
+   "parent": null,
+   "parentfield": null,
+   "parenttype": null,
+   "permlevel": 0,
+   "precision": "",
+   "print_hide": 0,
+   "print_hide_if_no_value": 0,
+   "print_width": null,
+   "read_only": 0,
+   "report_hide": 0,
+   "reqd": 0,
+   "search_index": 0,
+   "translatable": 0,
+   "unique": 0,
+   "width": null
+  }
+ ],
+ "custom_perms": [],
+ "doctype": "Contact",
+ "property_setters": [],
+ "sync_on_migrate": 1
+}
\ No newline at end of file

From ce90848161c842d07e72d7b9efbe61264accefb2 Mon Sep 17 00:00:00 2001
From: Rohan <rohan@bloomstack.com>
Date: Thu, 19 Mar 2020 13:10:26 +0530
Subject: [PATCH 13/93] fix: allow target warehouses to be changed for work
 order stock entries

---
 erpnext/stock/doctype/stock_entry/stock_entry.py | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 8b072c66ee..5880c408a1 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -294,13 +294,8 @@ class StockEntry(StockController):
 				if validate_for_manufacture:
 					if d.bom_no:
 						d.s_warehouse = None
-
 						if not d.t_warehouse:
 							frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
-
-						elif self.pro_doc and (cstr(d.t_warehouse) != self.pro_doc.fg_warehouse and cstr(d.t_warehouse) != self.pro_doc.scrap_warehouse):
-							frappe.throw(_("Target warehouse in row {0} must be same as Work Order").format(d.idx))
-
 					else:
 						d.t_warehouse = None
 						if not d.s_warehouse:

From 5e4e2dbd03107f00fbe76ece1bf27e05b9fa8d47 Mon Sep 17 00:00:00 2001
From: Rohit Waghchaure <rohitw1991@gmail.com>
Date: Thu, 19 Mar 2020 14:44:16 +0530
Subject: [PATCH 14/93] fix: stock entry qty must be positive

---
 erpnext/stock/doctype/stock_entry/stock_entry.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 8b072c66ee..8d746ba87c 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -177,6 +177,10 @@ class StockEntry(StockController):
 		stock_items = self.get_stock_items()
 		serialized_items = self.get_serialized_items()
 		for item in self.get("items"):
+			if item.qty and item.qty < 0:
+				frappe.throw(_("Row {0}: The item {1}, quantity must be positive number")
+					.format(item.idx, frappe.bold(item.item_code)))
+
 			if item.item_code not in stock_items:
 				frappe.throw(_("{0} is not a stock Item").format(item.item_code))
 

From 3cf0dc966c197341a91c87540088de348bcbc437 Mon Sep 17 00:00:00 2001
From: Suraj Shetty <surajshetty3416@gmail.com>
Date: Fri, 20 Mar 2020 12:51:16 +0530
Subject: [PATCH 15/93] fix: Add currency in options of Opportunity Amount - To
 show currect symbol in list view

---
 erpnext/crm/doctype/opportunity/opportunity.json | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json
index 0e2068a0a5..08be34e603 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.json
+++ b/erpnext/crm/doctype/opportunity/opportunity.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "allow_import": 1,
  "allow_rename": 1,
  "autoname": "naming_series:",
@@ -214,7 +215,8 @@
   {
    "fieldname": "opportunity_amount",
    "fieldtype": "Currency",
-   "label": "Opportunity Amount"
+   "label": "Opportunity Amount",
+   "options": "currency"
   },
   {
    "default": "0",
@@ -418,7 +420,8 @@
  ],
  "icon": "fa fa-info-sign",
  "idx": 195,
- "modified": "2019-09-30 12:58:37.385400",
+ "links": [],
+ "modified": "2020-03-20 12:28:45.228994",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Opportunity",

From 54cbff9e80ad0ed4ba42c9f6a58f51cd8262a76f Mon Sep 17 00:00:00 2001
From: sahil28297 <37302950+sahil28297@users.noreply.github.com>
Date: Fri, 20 Mar 2020 14:23:27 +0530
Subject: [PATCH 16/93] fix: develop version as 12.x.x instead of 12.2.0

---
 erpnext/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index f40b957563..288926bd55 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@ import frappe
 from erpnext.hooks import regional_overrides
 from frappe.utils import getdate
 
-__version__ = '12.2.0'
+__version__ = '12.x.x'
 
 def get_default_company(user=None):
 	'''Get default company for user'''

From fcdc2b1815a531dfc9cbf057546e34f3640c76d1 Mon Sep 17 00:00:00 2001
From: sahil28297 <37302950+sahil28297@users.noreply.github.com>
Date: Fri, 20 Mar 2020 15:20:28 +0530
Subject: [PATCH 17/93] fix: develop version as 12.0.0.dev

---
 erpnext/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 288926bd55..13a3b13c94 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@ import frappe
 from erpnext.hooks import regional_overrides
 from frappe.utils import getdate
 
-__version__ = '12.x.x'
+__version__ = '12.0.0.dev'
 
 def get_default_company(user=None):
 	'''Get default company for user'''

From 4c3679ba8a7dff5513bfa78b0a8fe671a6c3f1c8 Mon Sep 17 00:00:00 2001
From: sahil28297 <37302950+sahil28297@users.noreply.github.com>
Date: Fri, 20 Mar 2020 15:38:52 +0530
Subject: [PATCH 18/93] fix: develop version as 12.0.0-dev

---
 erpnext/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 13a3b13c94..786b9cfd16 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@ import frappe
 from erpnext.hooks import regional_overrides
 from frappe.utils import getdate
 
-__version__ = '12.0.0.dev'
+__version__ = '12.0.0-dev'
 
 def get_default_company(user=None):
 	'''Get default company for user'''

From 73c8d23a79ec8b2def31d61d19855e8e3d0d7228 Mon Sep 17 00:00:00 2001
From: marination <maricadsouza221197@gmail.com>
Date: Fri, 20 Mar 2020 17:49:59 +0530
Subject: [PATCH 19/93] fix: UOM fixes in Sales Order,Material Request &
 Production Plan

---
 .../doctype/production_plan/production_plan.py   |  2 +-
 .../selling/doctype/sales_order/sales_order.py   | 16 +++++-----------
 .../doctype/material_request/material_request.py |  2 +-
 3 files changed, 7 insertions(+), 13 deletions(-)

diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index a79ea0e14b..358a5429d9 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -144,7 +144,7 @@ class ProductionPlan(Document):
 			item_condition = " and mr_item.item_code ={0}".format(frappe.db.escape(self.item_code))
 
 		items = frappe.db.sql("""select distinct parent, name, item_code, warehouse, description,
-			(qty - ordered_qty) as pending_qty
+			(qty - ordered_qty) * conversion_factor as pending_qty
 			from `tabMaterial Request Item` mr_item
 			where parent in (%s) and docstatus = 1 and qty > ordered_qty
 			and exists (select name from `tabBOM` bom where bom.item=mr_item.item_code
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 13d2b1519f..ef2d19ac54 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -496,7 +496,7 @@ def close_or_unclose_sales_orders(names, status):
 
 def get_requested_item_qty(sales_order):
 	return frappe._dict(frappe.db.sql("""
-		select sales_order_item, sum(stock_qty)
+		select sales_order_item, sum(qty)
 		from `tabMaterial Request Item`
 		where docstatus = 1
 			and sales_order = %s
@@ -507,16 +507,12 @@ def get_requested_item_qty(sales_order):
 def make_material_request(source_name, target_doc=None):
 	requested_item_qty = get_requested_item_qty(source_name)
 
-	def postprocess(source, doc):
-		doc.material_request_type = "Purchase"
-
 	def update_item(source, target, source_parent):
 		# qty is for packed items, because packed items don't have stock_qty field
-		qty = source.get("stock_qty") or source.get("qty")
+		qty = source.get("qty")
 		target.project = source_parent.project
 		target.qty = qty - requested_item_qty.get(source.name, 0)
-		target.conversion_factor = 1
-		target.stock_qty = qty - requested_item_qty.get(source.name, 0)
+		target.stock_qty = flt(target.qty) * flt(target.conversion_factor)
 
 	doc = get_mapped_doc("Sales Order", source_name, {
 		"Sales Order": {
@@ -537,14 +533,12 @@ def make_material_request(source_name, target_doc=None):
 			"doctype": "Material Request Item",
 			"field_map": {
 				"name": "sales_order_item",
-				"parent": "sales_order",
-				"stock_uom": "uom",
-				"stock_qty": "qty"
+				"parent": "sales_order"
 			},
 			"condition": lambda doc: not frappe.db.exists('Product Bundle', doc.item_code) and doc.stock_qty > requested_item_qty.get(doc.name, 0),
 			"postprocess": update_item
 		}
-	}, target_doc, postprocess)
+	}, target_doc)
 
 	return doc
 
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 4542847016..285643d712 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -501,7 +501,7 @@ def raise_work_orders(material_request):
 				wo_order = frappe.new_doc("Work Order")
 				wo_order.update({
 					"production_item": d.item_code,
-					"qty": d.qty - d.ordered_qty,
+					"qty": d.stock_qty - d.ordered_qty,
 					"fg_warehouse": d.warehouse,
 					"wip_warehouse": default_wip_warehouse,
 					"description": d.description,

From 4464a591ef39d03d4441ee60fcbf36217f90e32f Mon Sep 17 00:00:00 2001
From: Rohan <rohan@bloomstack.com>
Date: Thu, 19 Mar 2020 18:02:35 +0530
Subject: [PATCH 20/93] fix: allow BOM to use same item as raw material

---
 erpnext/manufacturing/doctype/bom/bom.js | 3 +--
 erpnext/manufacturing/doctype/bom/bom.py | 5 -----
 2 files changed, 1 insertion(+), 7 deletions(-)

diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 3acaee4ffb..70c4c608e7 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -43,8 +43,7 @@ frappe.ui.form.on("BOM", {
 
 		frm.set_query("item_code", "items", function() {
 			return {
-				query: "erpnext.controllers.queries.item_query",
-				filters: [["Item", "name", "!=", cur_frm.doc.item]]
+				query: "erpnext.controllers.queries.item_query"
 			};
 		});
 
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index f6cdb2e57c..b3e602bdfa 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -114,10 +114,6 @@ class BOM(WebsiteGenerator):
 				child = self.append('operations', d)
 				child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2)
 
-	def validate_rm_item(self, item):
-		if (item[0]['name'] in [it.item_code for it in self.items]) and item[0]['name'] == self.item:
-			frappe.throw(_("BOM #{0}: Raw material cannot be same as main Item").format(self.name))
-
 	def set_bom_material_details(self):
 		for item in self.get("items"):
 			self.validate_bom_currecny(item)
@@ -147,7 +143,6 @@ class BOM(WebsiteGenerator):
 			args = json.loads(args)
 
 		item = self.get_item_det(args['item_code'])
-		self.validate_rm_item(item)
 
 		args['bom_no'] = args['bom_no'] or item and cstr(item[0]['default_bom']) or ''
 		args['transfer_for_manufacture'] = (cstr(args.get('include_item_in_manufacturing', '')) or

From b7d84725e873877f13acd23ed45f19d134a3b1b1 Mon Sep 17 00:00:00 2001
From: Rohan <rohan@bloomstack.com>
Date: Thu, 19 Mar 2020 18:44:27 +0530
Subject: [PATCH 21/93] fix: add warehouse check for FG item

---
 erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 8b072c66ee..d66a9fe9ce 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -234,7 +234,7 @@ class StockEntry(StockController):
 		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.qty != self.fg_completed_qty:
+				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))
 

From 75225316f02e4afb861ec3dec732e568e66fa5d1 Mon Sep 17 00:00:00 2001
From: Deepesh Garg <deepeshgarg6@gmail.com>
Date: Sun, 22 Mar 2020 21:50:22 +0530
Subject: [PATCH 22/93] fix: Remove quick entry from accouting dimensions

---
 .../accounting_dimension.js                     |  6 ------
 .../accounting_dimension.json                   | 17 +++++++++++++++--
 2 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js
index a36f4214d7..3c12f85f93 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js
@@ -48,12 +48,6 @@ frappe.ui.form.on('Accounting Dimension', {
 		frm.set_value('label', frm.doc.document_type);
 		frm.set_value('fieldname', frappe.model.scrub(frm.doc.document_type));
 
-		if (frm.is_new()){
-			let row = frappe.model.add_child(frm.doc, "Accounting Dimension Detail", "dimension_defaults");
-			row.reference_document = frm.doc.document_type;
-			frm.refresh_fields("dimension_defaults");
-		}
-
 		frappe.db.get_value('Accounting Dimension', {'document_type': frm.doc.document_type}, 'document_type', (r) => {
 			if (r && r.document_type) {
 				frm.set_df_property('document_type', 'description', "Document type is already set as dimension");
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json
index cf6dc7a8fa..cf55d554fb 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "autoname": "field:label",
  "creation": "2019-05-04 18:13:37.002352",
  "doctype": "DocType",
@@ -46,7 +47,8 @@
    "options": "Accounting Dimension Detail"
   }
  ],
- "modified": "2019-07-17 16:49:31.134385",
+ "links": [],
+ "modified": "2020-03-22 20:34:39.805728",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounting Dimension",
@@ -63,9 +65,20 @@
    "role": "System Manager",
    "share": 1,
    "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager",
+   "share": 1,
+   "write": 1
   }
  ],
- "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "ASC",
  "track_changes": 1

From 7ea79933b30bb3fc239513a532da04218e41b2cb Mon Sep 17 00:00:00 2001
From: Rohan <Alchez@users.noreply.github.com>
Date: Mon, 23 Mar 2020 14:54:33 +0530
Subject: [PATCH 23/93] fix: handle error handling (#20815)

---
 erpnext/stock/doctype/delivery_trip/delivery_trip.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
index a34db45114..28e9533186 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
@@ -238,7 +238,7 @@ class DeliveryTrip(Document):
 		try:
 			directions = maps_client.directions(**directions_data)
 		except Exception as e:
-			frappe.throw(_(e))
+			frappe.throw(_(str(e)))
 
 		return directions[0] if directions else False
 

From 5c1404e8786be8468203890a946421f41f60bb7f Mon Sep 17 00:00:00 2001
From: Rohit Waghchaure <rohitw1991@gmail.com>
Date: Tue, 24 Mar 2020 11:34:44 +0530
Subject: [PATCH 24/93] fix: batch selection popup not coming for stock entry

---
 erpnext/public/js/utils/serial_no_batch_selector.js | 6 +++++-
 erpnext/stock/doctype/stock_entry/stock_entry.js    | 5 +++--
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index a240e49b6a..02bced293f 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -313,11 +313,15 @@ erpnext.SerialNoBatchSelector = Class.extend({
 								frappe.throw(__(`Batch ${val} already selected.`));
 								return;
 							}
+
+							let batch_number = me.item.batch_no ||
+								this.grid_row.on_grid_fields_dict.batch_no.get_value();
+
 							if (me.warehouse_details.name) {
 								frappe.call({
 									method: 'erpnext.stock.doctype.batch.batch.get_batch_qty',
 									args: {
-										batch_no: me.item.batch_no,
+										batch_no: batch_number,
 										warehouse: me.warehouse_details.name,
 										item_code: me.item_code
 									},
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 2840a70dd0..3af3524423 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -60,7 +60,8 @@ frappe.ui.form.on('Stock Entry', {
 					}
 				}
 
-				if(item.s_warehouse) filters["warehouse"] = item.s_warehouse;
+				filters["warehouse"] = item.s_warehouse || item.t_warehouse;
+
 				return {
 					query : "erpnext.controllers.queries.get_batch_no",
 					filters: filters
@@ -964,7 +965,7 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => {
 		}
 	}
 
-	if(item && !item.has_serial_no && item.has_batch_no) return;
+	if(item && !item.has_serial_no && !item.has_batch_no) return;
 	if (frm.doc.purpose === 'Material Receipt') return;
 
 	frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {

From 95e35f9e81f6d0277977cbd6b4c9fbf318eade7b Mon Sep 17 00:00:00 2001
From: Anupam K <anupam@Anupams-MacBook-Air.local>
Date: Tue, 24 Mar 2020 11:52:34 +0530
Subject: [PATCH 25/93] Better error message

---
 erpnext/utilities/transaction_base.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index 2099810846..46db510569 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -123,8 +123,10 @@ class TransactionBase(StatusUpdater):
 					ref_rate = frappe.db.get_value(ref_dt + " Item", d.get(ref_link_field), "rate")
 
 					if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= .01:
-						frappe.throw(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ")
+						frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ")
 							.format(d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate))
+						frappe.throw(_("To allow different rates, disable the {0} checkbox in {1}.")
+							.format"<b>Maintain Same Rate Throughout Sales Cycle</b>",(get_link_to_form("Selling Settings","Selling Settings","<b>Selling Settings</b>")))
 
 	def get_link_filters(self, for_doctype):
 		if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):

From b14085eb0ee60c1aa78e9d4cb12362dba095283f Mon Sep 17 00:00:00 2001
From: Anupam K <anupam@Anupams-MacBook-Air.local>
Date: Tue, 24 Mar 2020 12:17:28 +0530
Subject: [PATCH 26/93] Better error message

---
 erpnext/utilities/transaction_base.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index 46db510569..c52c55b68e 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
 import frappe
 import frappe.share
 from frappe import _
-from frappe.utils import cstr, now_datetime, cint, flt, get_time
+from frappe.utils import cstr, now_datetime, cint, flt, get_time, get_link_to_form
 from erpnext.controllers.status_updater import StatusUpdater
 
 from six import string_types
@@ -126,7 +126,7 @@ class TransactionBase(StatusUpdater):
 						frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ")
 							.format(d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate))
 						frappe.throw(_("To allow different rates, disable the {0} checkbox in {1}.")
-							.format"<b>Maintain Same Rate Throughout Sales Cycle</b>",(get_link_to_form("Selling Settings","Selling Settings","<b>Selling Settings</b>")))
+							.format("<b>Maintain Same Rate Throughout Sales Cycle</b>", get_link_to_form("Selling Settings","Selling Settings","<b>Selling Settings</b>")))
 
 	def get_link_filters(self, for_doctype):
 		if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):

From 4e3fbf5a8bf018f903def6f315874b59f9cbf5b8 Mon Sep 17 00:00:00 2001
From: Deepesh Garg <deepeshgarg6@gmail.com>
Date: Tue, 24 Mar 2020 15:43:21 +0530
Subject: [PATCH 27/93] fix: Default dimension set code cleanup

---
 .../public/js/utils/dimension_tree_filter.js  | 69 +++++++++----------
 1 file changed, 31 insertions(+), 38 deletions(-)

diff --git a/erpnext/public/js/utils/dimension_tree_filter.js b/erpnext/public/js/utils/dimension_tree_filter.js
index 8b6de0f517..75c5a820b4 100644
--- a/erpnext/public/js/utils/dimension_tree_filter.js
+++ b/erpnext/public/js/utils/dimension_tree_filter.js
@@ -1,23 +1,25 @@
 frappe.provide('frappe.ui.form');
 
-erpnext.doctypes_with_dimensions = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset",
+let default_dimensions = {};
+
+let doctypes_with_dimensions = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset",
 	"Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Shipping Rule", "Loyalty Program",
 	"Fee Schedule", "Fee Structure", "Stock Reconciliation", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool",
 	"Subscription", "Purchase Order", "Journal Entry", "Material Request", "Purchase Receipt", "Landed Cost Item", "Asset"];
 
-erpnext.child_docs = ["Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account",
+let child_docs = ["Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account",
 	"Material Request Item", "Delivery Note Item", "Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction",
 	"Landed Cost Item", "Asset Value Adjustment", "Opening Invoice Creation Tool Item", "Subscription Plan"];
 
 frappe.call({
 	method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.get_dimension_filters",
-	callback: function(r){
+	callback: function(r) {
 		erpnext.dimension_filters = r.message[0];
-		erpnext.default_dimensions = r.message[1];
+		default_dimensions = r.message[1];
 	}
 });
 
-erpnext.doctypes_with_dimensions.forEach((doctype) => {
+doctypes_with_dimensions.forEach((doctype) => {
 	frappe.ui.form.on(doctype, {
 		onload: function(frm) {
 			erpnext.dimension_filters.forEach((dimension) => {
@@ -27,41 +29,40 @@ erpnext.doctypes_with_dimensions.forEach((doctype) => {
 							"is_group": 0
 						});
 					}
-
-					if (Object.keys(erpnext.default_dimensions).length > 0) {
-						if (frappe.meta.has_field(doctype, dimension['fieldname'])) {
-							if (frm.is_new() && frappe.meta.has_field(doctype, 'company') && frm.doc.company) {
-								frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]);
-							}
-						}
-
-						if (frm.doc.items && frm.doc.items.length && frm.doc.docstatus === 0
-							&& (!frm.doc.items[0][dimension['fieldname']])) {
-							frm.doc.items[0][dimension['fieldname']] = erpnext.default_dimensions[frm.doc.company][dimension['document_type']];
-						}
-
-						if (frm.doc.accounts && frm.doc.accounts.length && frm.doc.docstatus === 0
-							&& (!frm.doc.items[0][dimension['fieldname']])) {
-							frm.doc.accounts[0][dimension['fieldname']] = erpnext.default_dimensions[frm.doc.company][dimension['document_type']];
-						}
-					}
 				});
 			});
 		},
 
 		company: function(frm) {
-			if(frm.doc.company && (Object.keys(erpnext.default_dimensions).length > 0)) {
-				erpnext.dimension_filters.forEach((dimension) => {
-					if (frappe.meta.has_field(doctype, dimension['fieldname'])) {
-						frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]);
-					}
-				});
+			if(frm.doc.company && (Object.keys(default_dimensions || {}).length > 0)
+				&& default_dimensions[frm.doc.company]) {
+				frm.trigger('update_dimension');
 			}
 		},
+
+		update_dimension: function(frm) {
+			erpnext.dimension_filters.forEach((dimension) => {
+				if (frm.is_new()) {
+					if (frm.doc.company && Object.keys(default_dimensions || {}).length > 0
+						&& default_dimensions[frm.doc.company]) {
+
+						if (frappe.meta.has_field(doctype, dimension['fieldname'])) {
+							frm.set_value(dimension['fieldname'],
+								default_dimensions[frm.doc.company][dimension['document_type']]);
+						}
+
+						$.each(frm.doc.items || frm.doc.accounts || [], function(i, row) {
+							frappe.model.set_value(row.doctype, row.name, dimension['fieldname'],
+								default_dimensions[frm.doc.company][dimension['document_type']])
+						});
+					}
+				}
+			});
+		}
 	});
 });
 
-erpnext.child_docs.forEach((doctype) => {
+child_docs.forEach((doctype) => {
 	frappe.ui.form.on(doctype, {
 		items_add: function(frm, cdt, cdn) {
 			erpnext.dimension_filters.forEach((dimension) => {
@@ -77,14 +78,6 @@ erpnext.child_docs.forEach((doctype) => {
 			});
 		},
 
-		company: function(frm) {
-			if(frm.doc.company) {
-				erpnext.dimension_filters.forEach((dimension) => {
-					frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]);
-				});
-			}
-		},
-
 		items_add: function(frm, cdt, cdn) {
 			erpnext.dimension_filters.forEach((dimension) => {
 				var row = frappe.get_doc(cdt, cdn);

From 878e4fbcf869219e77d422756ad917cc5f29b010 Mon Sep 17 00:00:00 2001
From: Deepesh Garg <deepeshgarg6@gmail.com>
Date: Wed, 25 Mar 2020 16:52:07 +0530
Subject: [PATCH 28/93] fix: Place of supply validation in GSTR-1 report

---
 erpnext/regional/report/gstr_1/gstr_1.py | 27 ++++++++++++++++++------
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 2c5ab7cb91..86f193d3f4 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -54,8 +54,8 @@ class Gstr1Report(object):
 		return self.columns, self.data
 
 	def get_data(self):
-		if self.filters.get("type_of_business") ==  "B2C Small":
-			self.get_b2cs_data()
+		if self.filters.get("type_of_business") in  ("B2C Small", "B2C Large"):
+			self.get_b2c_data()
 		else:
 			for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
 				invoice_details = self.invoices.get(inv)
@@ -69,7 +69,7 @@ class Gstr1Report(object):
 					if taxable_value:
 						self.data.append(row)
 
-	def get_b2cs_data(self):
+	def get_b2c_data(self):
 		b2cs_output = {}
 
 		for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
@@ -84,7 +84,10 @@ class Gstr1Report(object):
 					"rate": "",
 					"taxable_value": 0,
 					"cess_amount": 0,
-					"type": ""
+					"type": "",
+					"invoice_number": invoice_details.get("invoice_number"),
+					"posting_date": invoice_details.get("posting_date"),
+					"invoice_value": invoice_details.get("base_grand_total"),
 				})
 
 				row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin))
@@ -139,7 +142,7 @@ class Gstr1Report(object):
 			where docstatus = 1 {where_conditions}
 			order by posting_date desc
 			""".format(select_columns=self.select_columns, doctype=self.doctype,
-				where_conditions=conditions), self.filters, as_dict=1)
+				where_conditions=conditions), self.filters, as_dict=1, debug=1)
 
 		for d in invoice_data:
 			self.invoices.setdefault(d.invoice_number, d)
@@ -164,7 +167,7 @@ class Gstr1Report(object):
 				frappe.throw(_("Please set B2C Limit in GST Settings."))
 
 		if self.filters.get("type_of_business") ==  "B2C Large":
-			conditions += """ and SUBSTR(place_of_supply, 1, 2) != SUBSTR(company_gstin, 1, 2)
+			conditions += """ and ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'')
 				and grand_total > {0} and is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit))
 
 		elif self.filters.get("type_of_business") ==  "B2C Small":
@@ -581,6 +584,11 @@ def get_b2b_json(res, gstin):
 		if not gst_in: continue
 
 		for number, invoice in iteritems(res[gst_in]):
+			if not invoice[0]["place_of_supply"]:
+				frappe.throw(_("""{0} not entered in Invoice {1}.
+					Please update and try again""").format(frappe.bold("Place Of Supply"),
+					frappe.bold(invoice[0]['invoice_number'])))
+
 			inv_item = get_basic_invoice_detail(invoice[0])
 			inv_item["pos"] = "%02d" % int(invoice[0]["place_of_supply"].split('-')[0])
 			inv_item["rchrg"] = invoice[0]["reverse_charge"]
@@ -606,6 +614,9 @@ def get_b2cs_json(data, gstin):
 
 	out = []
 	for d in data:
+		if not d.get("place_of_supply"):
+			frappe.throw(_("""{0} not entered in some invoices.
+				Please update and try again""").format(frappe.bold("Place Of Supply")))
 
 		pos = d.get('place_of_supply').split('-')[0]
 		tax_details = {}
@@ -642,6 +653,10 @@ def get_b2cs_json(data, gstin):
 def get_b2cl_json(res, gstin):
 	out = []
 	for pos in res:
+		if not pos:
+			frappe.throw(_("""{0} not entered in some invoices.
+				Please update and try again""").format(frappe.bold("Place Of Supply")))
+
 		b2cl_item, inv = {"pos": "%02d" % int(pos.split('-')[0]), "inv": []}, []
 
 		for row in res[pos]:

From 113c6c86b7c93b4618594c84eb0b719a90b0fb9a Mon Sep 17 00:00:00 2001
From: Deepesh Garg <deepeshgarg6@gmail.com>
Date: Wed, 25 Mar 2020 16:54:17 +0530
Subject: [PATCH 29/93] fix: Remove debug

---
 erpnext/regional/report/gstr_1/gstr_1.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 86f193d3f4..fd1cc58c20 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -142,7 +142,7 @@ class Gstr1Report(object):
 			where docstatus = 1 {where_conditions}
 			order by posting_date desc
 			""".format(select_columns=self.select_columns, doctype=self.doctype,
-				where_conditions=conditions), self.filters, as_dict=1, debug=1)
+				where_conditions=conditions), self.filters, as_dict=1)
 
 		for d in invoice_data:
 			self.invoices.setdefault(d.invoice_number, d)

From 10b7a51805c6cfcbf40674dcc24423726cf58ab3 Mon Sep 17 00:00:00 2001
From: Anupam K <anupam@Anupams-MacBook-Air.local>
Date: Wed, 25 Mar 2020 18:21:42 +0530
Subject: [PATCH 30/93] Better error message

---
 erpnext/utilities/transaction_base.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index c52c55b68e..779af4a2b1 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -126,7 +126,7 @@ class TransactionBase(StatusUpdater):
 						frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ")
 							.format(d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate))
 						frappe.throw(_("To allow different rates, disable the {0} checkbox in {1}.")
-							.format("<b>Maintain Same Rate Throughout Sales Cycle</b>", get_link_to_form("Selling Settings","Selling Settings","<b>Selling Settings</b>")))
+							.format(frappe.bold("Maintain Same Rate Throughout Sales Cycle"), get_link_to_form("Selling Settings","Selling Settings",frappe.bold("Selling Settings"))))
 
 	def get_link_filters(self, for_doctype):
 		if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):

From bdcfab3a0fcd86c03250a6c5328191ca5cb2a226 Mon Sep 17 00:00:00 2001
From: Deepesh Garg <deepeshgarg6@gmail.com>
Date: Wed, 25 Mar 2020 21:32:10 +0530
Subject: [PATCH 31/93] fix: Update patch and bank form cleanup

---
 erpnext/accounts/doctype/bank/bank.js         |  2 ++
 erpnext/accounts/doctype/bank/bank.json       | 27 +++----------------
 .../doctype/bank_account/bank_account.json    |  2 +-
 .../move_bank_account_swift_number_to_bank.py | 16 ++++-------
 4 files changed, 12 insertions(+), 35 deletions(-)

diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js
index e21c77e9ca..de9498e075 100644
--- a/erpnext/accounts/doctype/bank/bank.js
+++ b/erpnext/accounts/doctype/bank/bank.js
@@ -13,9 +13,11 @@ frappe.ui.form.on('Bank', {
 		frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
 
 		if (frm.doc.__islocal) {
+			frm.set_df_property('address_and_contact', 'hidden', 1);
 			frappe.contacts.clear_address_and_contact(frm);
 		}
 		else {
+			frm.set_df_property('address_and_contact', 'hidden', 0);
 			frappe.contacts.render_address_and_contact(frm);
 		}
 	},
diff --git a/erpnext/accounts/doctype/bank/bank.json b/erpnext/accounts/doctype/bank/bank.json
index fca171d392..99978e657d 100644
--- a/erpnext/accounts/doctype/bank/bank.json
+++ b/erpnext/accounts/doctype/bank/bank.json
@@ -9,14 +9,14 @@
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
-  "bank_name",
   "bank_details_section",
+  "bank_name",
   "swift_number",
   "column_break_1",
   "branch_code",
+  "website",
   "address_and_contact",
   "address_html",
-  "website",
   "column_break_13",
   "contact_html",
   "data_import_configuration_section",
@@ -44,7 +44,6 @@
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "SWIFT number",
-   "reqd": 0,
    "unique": 1
   },
   {
@@ -59,7 +58,6 @@
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Branch Code",
-   "reqd": 0,
    "unique": 1
   },
   {
@@ -112,40 +110,23 @@
    "read_only": 1
   }
  ],
- "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-11-27 16:12:13.938776",
+ "links": [],
+ "modified": "2020-03-25 21:22:33.496264",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Bank",
- "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
   }
  ],
diff --git a/erpnext/accounts/doctype/bank_account/bank_account.json b/erpnext/accounts/doctype/bank_account/bank_account.json
index ade379b11d..aa9c434db0 100644
--- a/erpnext/accounts/doctype/bank_account/bank_account.json
+++ b/erpnext/accounts/doctype/bank_account/bank_account.json
@@ -200,7 +200,7 @@
   }
  ],
  "links": [],
- "modified": "2020-01-29 20:42:26.458316",
+ "modified": "2020-01-30 20:42:26.458316",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Bank Account",
diff --git a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
index 2675b98480..4aad1420e3 100644
--- a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
+++ b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
@@ -4,17 +4,11 @@ import frappe
 def execute():
 	frappe.reload_doc('accounts', 'doctype', 'bank', force=1)
 
-	banks = frappe.get_all('Bank', 'name')
-	for bank in banks:
-		bank_accounts = frappe.get_all('Bank Account', filters={'bank': bank.name}, fields=['swift_number', 'branch_code'])
-		bank_account = ''
-		if len(bank_accounts):
-			bank_account = bank_accounts[0]
-			if bank_account and bank_account.swift_number:
-				bank.swift_number = bank_account.swift_number
-			if bank_account and bank_account.branch_code:
-				bank.branch_code = bank_account.branch_code
-			bank.save()
+	frappe.db.sql("""
+		UPDATE `tabBank` b, `tabBank Account` ba
+		SET b.swift_number = ba.swift_number, b.branch_code = ba.branch_code
+		WHERE b.name = ba.bank
+	""")
 
 	frappe.reload_doc('accounts', 'doctype', 'bank_account')
 	frappe.reload_doc('accounts', 'doctype', 'payment_request')
\ No newline at end of file

From 9725e43eedd219e4ea1deb2f0ef7b417c0a07fcb Mon Sep 17 00:00:00 2001
From: Anupam K <anupam@Anupams-MacBook-Air.local>
Date: Thu, 26 Mar 2020 00:17:09 +0530
Subject: [PATCH 32/93] fix: Adding proper error message

---
 erpnext/accounts/party.py | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 422ace64f5..86d85ec2d4 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -303,7 +303,13 @@ def validate_party_accounts(doc):
 			company_default_currency = frappe.db.get_value('Company', account.company, "default_currency")
 
 		if existing_gle_currency and party_account_currency != existing_gle_currency:
-			frappe.throw(_("Accounting entries have already been made in currency {0} for company {1}. Please select a receivable or payable account with currency {0}.").format(existing_gle_currency, account.company))
+			if doc.doctype == 'Customer':
+				error_msg = _("Customer {0} has Accounting entries in currency {1} for company {2}. Please select a receivable or payable account with currency {1}.").format(frappe.bold(doc.customer_name), frappe.bold(existing_gle_currency), frappe.bold(account.company))
+			elif doc.doctype == 'Supplier':
+				error_msg = _("Supplier {0} has Accounting entries in currency {1} for company {2}. Please select a receivable or payable account with currency {1}.").format(frappe.bold(doc.supplier_name), frappe.bold(existing_gle_currency), frappe.bold(account.company))
+			else:
+				error_msg = _("{0} has Accounting entries in currency {1} for company {2}. Please select a receivable or payable account with currency {1}.").format(frappe.bold(doc.name), frappe.bold(existing_gle_currency), frappe.bold(account.company))
+			frappe.throw(error_msg)
 
 		if doc.get("default_currency") and party_account_currency and company_default_currency:
 			if doc.default_currency != party_account_currency and doc.default_currency != company_default_currency:

From 9062ce5191064e76150fd15f364dca5a0f734fa7 Mon Sep 17 00:00:00 2001
From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
Date: Thu, 26 Mar 2020 11:56:03 +0530
Subject: [PATCH 33/93] fix: Spacing and indentations

---
 erpnext/utilities/transaction_base.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index 779af4a2b1..f88ffd44e3 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -126,7 +126,8 @@ class TransactionBase(StatusUpdater):
 						frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ")
 							.format(d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate))
 						frappe.throw(_("To allow different rates, disable the {0} checkbox in {1}.")
-							.format(frappe.bold("Maintain Same Rate Throughout Sales Cycle"), get_link_to_form("Selling Settings","Selling Settings",frappe.bold("Selling Settings"))))
+							.format(frappe.bold("Maintain Same Rate Throughout Sales Cycle"), 
+							get_link_to_form("Selling Settings", "Selling Settings", frappe.bold("Selling Settings"))))
 
 	def get_link_filters(self, for_doctype):
 		if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):

From ced6898fd72687827b3035f8aec8889b42759ff7 Mon Sep 17 00:00:00 2001
From: Nabin Hait <nabinhait@gmail.com>
Date: Fri, 20 Mar 2020 12:23:18 +0530
Subject: [PATCH 34/93] fix: Ignored user permission for parent_company and
 existing_company field in Company

---
 erpnext/setup/doctype/company/company.json | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 84810b6824..020a93ff6a 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -157,6 +157,7 @@
   {
    "fieldname": "parent_company",
    "fieldtype": "Link",
+   "ignore_user_permissions": 1,
    "in_list_view": 1,
    "label": "Parent Company",
    "options": "Company"
@@ -277,6 +278,7 @@
    "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Existing Company\"",
    "fieldname": "existing_company",
    "fieldtype": "Link",
+   "ignore_user_permissions": 1,
    "label": "Existing Company ",
    "no_copy": 1,
    "options": "Company"
@@ -728,7 +730,7 @@
  "image_field": "company_logo",
  "is_tree": 1,
  "links": [],
- "modified": "2020-03-18 18:09:53.534211",
+ "modified": "2020-03-21 18:09:53.534211",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Company",

From 1aa8c2ecc4d14312248fc805101e6a92ce5801f6 Mon Sep 17 00:00:00 2001
From: Nabin Hait <nabinhait@gmail.com>
Date: Thu, 26 Mar 2020 13:15:31 +0530
Subject: [PATCH 35/93] fix: delete contact email and phone along with contact

---
 erpnext/tests/utils.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py
index dfd3ed76bc..16ecd5180b 100644
--- a/erpnext/tests/utils.py
+++ b/erpnext/tests/utils.py
@@ -7,6 +7,8 @@ import frappe
 
 def create_test_contact_and_address():
 	frappe.db.sql('delete from tabContact')
+	frappe.db.sql('delete from `tabContact Email`')
+	frappe.db.sql('delete from `tabContact Phone`')
 	frappe.db.sql('delete from tabAddress')
 	frappe.db.sql('delete from `tabDynamic Link`')
 

From faea85451f1f37c7ca0bdc87eb47e0cfabf56258 Mon Sep 17 00:00:00 2001
From: Saqib <nextchamp.saqib@gmail.com>
Date: Thu, 26 Mar 2020 13:18:57 +0530
Subject: [PATCH 36/93] feat: auto set batch no on serial no selection (#20757)

* feat: auto set batch no on serial no selection

* fix: dialog not shown if set warehouse selected

* fix: typo

* fix: merge conflict

* fix: callback no getting called after serial no selected

* fix: available batch qty not fetched without set_warehouse selected

* fix: item batch not synced with dialog batch table
---
 erpnext/public/js/controllers/transaction.js  |  15 +-
 .../js/utils/serial_no_batch_selector.js      | 144 +++++++++++-------
 erpnext/selling/sales_common.js               |   9 +-
 erpnext/stock/doctype/serial_no/serial_no.py  |  11 +-
 4 files changed, 119 insertions(+), 60 deletions(-)

diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index fea2d5e700..1747932af6 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -4,7 +4,7 @@
 erpnext.TransactionController = erpnext.taxes_and_totals.extend({
 	setup: function() {
 		this._super();
-		frappe.flags.hide_serial_batch_dialog = false;
+		frappe.flags.hide_serial_batch_dialog = true;
 		frappe.ui.form.on(this.frm.doctype + " Item", "rate", function(frm, cdt, cdn) {
 			var item = frappe.get_doc(cdt, cdn);
 			var has_margin_field = frappe.meta.has_field(cdt, 'margin_type');
@@ -519,6 +519,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
 									}
 								},
 								() => me.toggle_conversion_factor(item),
+								() => {
+									if (show_batch_dialog)
+										return frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
+											.then((r) => {
+												if(r.message.has_batch_no || r.message.has_serial_no) {
+													frappe.flags.hide_serial_batch_dialog = false;
+												}
+											});
+								},
 								() => {
 									if(show_batch_dialog && !frappe.flags.hide_serial_batch_dialog) {
 										var d = locals[cdt][cdn];
@@ -528,7 +537,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
 
 										erpnext.show_serial_batch_selector(me.frm, d, (item) => {
 											me.frm.script_manager.trigger('qty', item.doctype, item.name);
-										});
+											if (!me.frm.doc.set_warehouse)
+												me.frm.script_manager.trigger('warehouse', item.doctype, item.name);
+										}, undefined, !frappe.flags.hide_serial_batch_dialog);
 									}
 								},
 								() => me.conversion_factor(doc, cdt, cdn, true),
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 02bced293f..d75633e5a9 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -5,14 +5,13 @@ erpnext.SerialNoBatchSelector = Class.extend({
 		this.show_dialog = show_dialog;
 		// frm, item, warehouse_details, has_batch, oldest
 		let d = this.item;
-		if (d && d.has_batch_no && (!d.batch_no || this.show_dialog)) {
-			this.has_batch = 1;
-			this.setup();
+		this.has_batch = 0; this.has_serial_no = 0;
+
+		if (d && d.has_batch_no && (!d.batch_no || this.show_dialog)) this.has_batch = 1;
 		// !(this.show_dialog == false) ensures that show_dialog is implictly true, even when undefined
-		} else if(d && d.has_serial_no && !(this.show_dialog == false)) {
-			this.has_batch = 0;
-			this.setup();
-		}
+		if(d && d.has_serial_no && !(this.show_dialog == false)) this.has_serial_no = 1;
+
+		this.setup();
 	},
 
 	setup: function() {
@@ -36,16 +35,16 @@ erpnext.SerialNoBatchSelector = Class.extend({
 				label: __('Item Code'),
 				default: me.item_code
 			},
-			{fieldtype:'Column Break'},
 			{
 				fieldname: 'warehouse',
 				fieldtype:'Link',
 				options: 'Warehouse',
+				reqd: me.has_batch && !me.has_serial_no ? 0 : 1,
 				label: __(me.warehouse_details.type),
-				default: me.warehouse_details.name,
+				default: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : '',
 				onchange: function(e) {
 
-					if(me.has_batch) {
+					if(me.has_batch && !me.has_serial_no) {
 						fields = fields.concat(me.get_batch_fields());
 					} else {
 						fields = fields.concat(me.get_serial_no_fields());
@@ -74,15 +73,16 @@ erpnext.SerialNoBatchSelector = Class.extend({
 			{
 				fieldname: 'qty',
 				fieldtype:'Float',
-				read_only: me.has_batch,
-				label: __(me.has_batch ? 'Total Qty' : 'Qty'),
+				read_only: me.has_batch && !me.has_serial_no,
+				label: __(me.has_batch && !me.has_serial_no ? 'Total Qty' : 'Qty'),
 				default: 0
 			},
 			{
 				fieldname: 'auto_fetch_button',
 				fieldtype:'Button',
-				hidden: me.has_batch,
-				label: __('Fetch based on FIFO'),
+				hidden: me.has_batch && !me.has_serial_no,
+				label: __('Auto Fetch'),
+				description: __('Fetch Serial Numbers based on FIFO'),
 				click: () => {
 					let qty = this.dialog.fields_dict.qty.get_value();
 					let numbers = frappe.call({
@@ -90,7 +90,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
 						args: {
 							qty: qty,
 							item_code: me.item_code,
-							warehouse: me.warehouse_details.name,
+							warehouse: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : '',
 							batch_no: me.item.batch_no || null
 						}
 					});
@@ -109,10 +109,12 @@ erpnext.SerialNoBatchSelector = Class.extend({
 			}
 		];
 
-		if (this.has_batch) {
+		if (this.has_batch && !this.has_serial_no) {
 			title = __("Select Batch Numbers");
 			fields = fields.concat(this.get_batch_fields());
 		} else {
+			// if only serial no OR
+			// if both batch_no & serial_no then only select serial_no and auto set batches nos
 			title = __("Select Serial Numbers");
 			fields = fields.concat(this.get_serial_no_fields());
 		}
@@ -122,25 +124,31 @@ erpnext.SerialNoBatchSelector = Class.extend({
 			fields: fields
 		});
 
-		if (this.item.serial_no) {
-			this.dialog.fields_dict.serial_no.set_value(this.item.serial_no);
-		}
-
 		this.dialog.set_primary_action(__('Insert'), function() {
 			me.values = me.dialog.get_values();
 			if(me.validate()) {
-				me.set_items();
-				me.dialog.hide();
+				frappe.run_serially([
+					() => me.update_batch_items(),
+					() => me.update_serial_no_item(),
+					() => me.update_batch_serial_no_items(),
+					() => {
+						refresh_field("items");
+						if (me.callback) {
+							return me.callback(me.item);
+						}
+					},
+					() => me.dialog.hide()
+				])
 			}
 		});
 
 		if(this.show_dialog) {
 			let d = this.item;
-			if (d.has_serial_no && d.serial_no) {
-				this.dialog.set_value('serial_no', d.serial_no);
+			if (this.item.serial_no) {
+				this.dialog.fields_dict.serial_no.set_value(this.item.serial_no);
 			}
-
-			if (d.has_batch_no && d.batch_no) {
+			
+			if (this.has_batch && !this.has_serial_no && d.batch_no) {
 				this.frm.doc.items.forEach(data => {
 					if(data.item_code == d.item_code) {
 						this.dialog.fields_dict.batches.df.data.push({
@@ -155,7 +163,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
 			}
 		}
 
-		if (this.has_batch) {
+		if (this.has_batch && !this.has_serial_no) {
 			this.update_total_qty();
 		}
 
@@ -174,7 +182,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
 			frappe.throw(__("Please select a warehouse"));
 			return false;
 		}
-		if(this.has_batch) {
+		if(this.has_batch && !this.has_serial_no) {
 			if(values.batches.length === 0 || !values.batches) {
 				frappe.throw(__("Please select batches for batched item "
 					+ values.item_code));
@@ -193,34 +201,23 @@ erpnext.SerialNoBatchSelector = Class.extend({
 		} else {
 			let serial_nos = values.serial_no || '';
 			if (!serial_nos || !serial_nos.replace(/\s/g, '').length) {
-				if (!this.show_dialog) {
-					frappe.throw(__("Please enter serial numbers for serialized item "
-						+ values.item_code));
-					return false;
-				}
+				frappe.throw(__("Please enter serial numbers for serialized item "
+					+ values.item_code));
+				return false;
 			}
 			return true;
 		}
 	},
 
-	set_items: function() {
-		var me = this;
-		if(this.has_batch) {
+	update_batch_items() {
+		// clones an items if muliple batches are selected.
+		if(this.has_batch && !this.has_serial_no) {
 			this.values.batches.map((batch, i) => {
 				let batch_no = batch.batch_no;
 				let row = '';
 
 				if (i !== 0 && !this.batch_exists(batch_no)) {
-					row = this.frm.add_child("items", {
-						'item_code': this.item.item_code,
-						'item_name': this.item.item_name,
-						'price_list_rate': this.item.price_list_rate,
-						'rate': this.item.rate,
-						'qty': batch.selected_qty,
-						'batch_no': batch_no,
-						'actual_qty': this.item.actual_qty,
-						'discount_percentage': this.item.discount_percentage
-					});
+					row = this.frm.add_child("items", { ...this.item });
 				} else {
 					row = this.frm.doc.items.find(i => i.batch_no === batch_no);
 				}
@@ -228,16 +225,59 @@ erpnext.SerialNoBatchSelector = Class.extend({
 				if (!row) {
 					row = this.item;
 				}
-
+				// this ensures that qty & batch no is set
 				this.map_row_values(row, batch, 'batch_no',
 					'selected_qty', this.values.warehouse);
 			});
-		} else {
+		} 
+	},
+
+	update_serial_no_item() {
+		// just updates serial no for the item
+		if(this.has_serial_no && !this.has_batch) {
 			this.map_row_values(this.item, this.values, 'serial_no', 'qty');
 		}
+	},
 
-		refresh_field("items");
-		this.callback && this.callback(this.item);
+	update_batch_serial_no_items() {
+		// if serial no selected is from different batches, adds new rows for each batch.
+		if(this.has_batch && this.has_serial_no) {
+			const selected_serial_nos = this.values.serial_no.split(/\n/g).filter(s => s);
+
+			return frappe.db.get_list("Serial No", {
+				filters: { 'name': ["in", selected_serial_nos]},
+				fields: ["batch_no", "name"]
+			}).then((data) => {
+				// data = [{batch_no: 'batch-1', name: "SR-001"}, 
+				// 	{batch_no: 'batch-2', name: "SR-003"}, {batch_no: 'batch-2', name: "SR-004"}]
+				const batch_serial_map = data.reduce((acc, d) => {
+					if (!acc[d['batch_no']]) acc[d['batch_no']] = [];
+					acc[d['batch_no']].push(d['name'])
+					return acc
+				}, {})
+				// batch_serial_map = { "batch-1": ['SR-001'], "batch-2": ["SR-003", "SR-004"]}
+				Object.keys(batch_serial_map).map((batch_no, i) => {
+					let row = '';
+					const serial_no = batch_serial_map[batch_no];
+					if (i == 0) {
+						row = this.item;
+						this.map_row_values(row, {qty: serial_no.length, batch_no: batch_no}, 'batch_no',
+							'qty', this.values.warehouse);
+					} else if (!this.batch_exists(batch_no)) {
+						row = this.frm.add_child("items", { ...this.item });
+						row.batch_no = batch_no;
+					} else {
+						row = this.frm.doc.items.find(i => i.batch_no === batch_no);
+					}
+					const values = {
+						'qty': serial_no.length,
+						'serial_no': serial_no.join('\n')
+					}
+					this.map_row_values(row, values, 'serial_no',
+						'qty', this.values.warehouse);
+				});
+			})
+		}
 	},
 
 	batch_exists: function(batch) {
@@ -287,7 +327,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
 							return {
 								filters: {
 									item_code: me.item_code,
-									warehouse: me.warehouse || me.warehouse_details.name
+									warehouse: me.warehouse || typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : ''
 								},
 								query: 'erpnext.controllers.queries.get_batch_no'
 							};
@@ -448,7 +488,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
 			{
 				fieldname: 'serial_no',
 				fieldtype: 'Small Text',
-				label: __(me.has_batch ? 'Selected Batch Numbers' : 'Selected Serial Numbers'),
+				label: __(me.has_batch && !me.has_serial_no ? 'Selected Batch Numbers' : 'Selected Serial Numbers'),
 				onchange: function() {
 					me.serial_list = this.get_value()
 						.replace(/\n/g, ' ').match(/\S+/g) || [];
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 8278745a80..af100692c6 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -413,15 +413,20 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
 	*/
 	set_batch_number: function(cdt, cdn) {
 		const doc = frappe.get_doc(cdt, cdn);
-		if (doc && doc.has_batch_no) {
+		if (doc && doc.has_batch_no && doc.warehouse) {
 			this._set_batch_number(doc);
 		}
 	},
 
 	_set_batch_number: function(doc) {
+		let args = {'item_code': doc.item_code, 'warehouse': doc.warehouse, 'qty': flt(doc.qty) * flt(doc.conversion_factor)};
+		if (doc.has_serial_no && doc.serial_no) {
+			args['serial_no'] = doc.serial_no
+		}
+		
 		return frappe.call({
 			method: 'erpnext.stock.doctype.batch.batch.get_batch_no',
-			args: {'item_code': doc.item_code, 'warehouse': doc.warehouse, 'qty': flt(doc.qty) * flt(doc.conversion_factor)},
+			args: args,
 			callback: function(r) {
 				if(r.message) {
 					frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message);
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 64d4c6c082..772ac58af6 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -523,12 +523,15 @@ def get_delivery_note_serial_no(item_code, qty, delivery_note):
 	return serial_nos
 
 @frappe.whitelist()
-def auto_fetch_serial_number(qty, item_code, warehouse, batch_no=None):
-	serial_numbers = frappe.get_list("Serial No", filters={
+def auto_fetch_serial_number(qty, item_code, warehouse, batch_nos=None):
+	import json
+	filters = {
 		"item_code": item_code,
 		"warehouse": warehouse,
-		"batch_no": batch_no,
 		"delivery_document_no": "",
 		"sales_invoice": ""
-	}, limit=qty, order_by="creation")
+	}
+	if batch_nos: filters["batch_no"] = ["in", json.loads(batch_nos)]
+
+	serial_numbers = frappe.get_list("Serial No", filters=filters, limit=qty, order_by="creation")
 	return [item['name'] for item in serial_numbers]

From 74864b3b6117ce7e52dcd841d71f6624182e53e8 Mon Sep 17 00:00:00 2001
From: Saqib <nextchamp.saqib@gmail.com>
Date: Thu, 26 Mar 2020 13:20:31 +0530
Subject: [PATCH 37/93] fix: currency not fetched on quotation creation
 (#20996)

---
 erpnext/selling/doctype/customer/customer.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 6571ecb404..02667e8c23 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -268,9 +268,11 @@ def make_quotation(source_name, target_doc=None):
 	target_doc.run_method("set_other_charges")
 	target_doc.run_method("calculate_taxes_and_totals")
 
-	price_list = frappe.get_value("Customer", source_name, 'default_price_list')
+	price_list, currency = frappe.db.get_value("Customer", {'name': source_name}, ['default_price_list', 'default_currency'])
 	if price_list:
 		target_doc.selling_price_list = price_list
+	if currency:
+		target_doc.currency = currency
 
 	return target_doc
 

From b856aae651cfd3832cbd980bb689243494660399 Mon Sep 17 00:00:00 2001
From: Marica <maricadsouza221197@gmail.com>
Date: Thu, 26 Mar 2020 13:23:49 +0530
Subject: [PATCH 38/93] fix: Project field in Stock Entry Detail should be
 editable (#20999)

---
 .../doctype/stock_entry_detail/stock_entry_detail.json     | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

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 d86e68b722..a848c80cf2 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "autoname": "hash",
  "creation": "2013-03-29 18:22:12",
  "doctype": "DocType",
@@ -479,8 +480,7 @@
    "fieldname": "project",
    "fieldtype": "Link",
    "label": "Project",
-   "options": "Project",
-   "read_only": 1
+   "options": "Project"
   },
   {
    "fieldname": "po_detail",
@@ -494,7 +494,8 @@
  ],
  "idx": 1,
  "istable": 1,
- "modified": "2019-08-20 14:01:02.319754",
+ "links": [],
+ "modified": "2020-03-19 12:34:09.836295",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry Detail",

From 89cac5af16cefa7ab0c570ea8e5ca0ddf8a29ee5 Mon Sep 17 00:00:00 2001
From: Raffael Meyer <raffael@alyf.de>
Date: Thu, 26 Mar 2020 08:55:58 +0100
Subject: [PATCH 39/93] fix(regional): encoding of DATEV report (#21059)

---
 erpnext/regional/report/datev/datev.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py
index 7ceaf50134..e9b42356a2 100644
--- a/erpnext/regional/report/datev/datev.py
+++ b/erpnext/regional/report/datev/datev.py
@@ -109,7 +109,7 @@ def get_transactions(filters, as_dict=1):
 		WHERE gl.company = %(company)s 
 		AND DATE(gl.posting_date) >= %(from_date)s
 		AND DATE(gl.posting_date) <= %(to_date)s
-		ORDER BY 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict, as_utf8=1)
+		ORDER BY 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict)
 
 	return gl_entries
 
@@ -160,7 +160,7 @@ def get_customers(filters):
 			and ccl.company = par.company
 
 		WHERE par.company = %(company)s
-		AND par.parenttype = 'Customer'""", filters, as_dict=1, as_utf8=1)
+		AND par.parenttype = 'Customer'""", filters, as_dict=1)
 
 
 def get_suppliers(filters):
@@ -217,7 +217,7 @@ def get_suppliers(filters):
 			and con.is_primary_contact = '1'
 
 		WHERE par.company = %(company)s
-		AND par.parenttype = 'Supplier'""", filters, as_dict=1, as_utf8=1)
+		AND par.parenttype = 'Supplier'""", filters, as_dict=1)
 
 
 def get_account_names(filters):

From 38cba153b5f1cb6011e2067dceb2200b11e8c244 Mon Sep 17 00:00:00 2001
From: Marica <maricadsouza221197@gmail.com>
Date: Thu, 26 Mar 2020 13:29:02 +0530
Subject: [PATCH 40/93] fix: Make shelf life mandatory in Batched Item if it
 has expiry date (#21044)

---
 erpnext/stock/doctype/batch/batch.py | 7 +++++--
 erpnext/stock/doctype/item/item.json | 5 +++--
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index 8ae978eaf0..9b7249e66b 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -122,8 +122,11 @@ class Batch(Document):
 			self.expiry_date = add_days(self.manufacturing_date, shelf_life_in_days)
 
 		if has_expiry_date and not self.expiry_date:
-			frappe.msgprint(_('Expiry date is mandatory for selected item.'))
-			frappe.throw(_("Set item's shelf life in days, to set expiry based on manufacturing date plus shelf-life."))
+			frappe.throw(msg=_("Please set {0} for Batched Item {1}, which is used to set {2} on Submit.") \
+				.format(frappe.bold("Shelf Life in Days"),
+					frappe.utils.get_link_to_form("Item", self.item),
+					frappe.bold("Batch Expiry Date")),
+				title=_("Expiry Date Mandatory"))
 
 	def get_name_from_naming_series(self):
 		"""
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 3503e7cc1c..aa6b2fedd7 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -343,7 +343,8 @@
   {
    "fieldname": "shelf_life_in_days",
    "fieldtype": "Int",
-   "label": "Shelf Life In Days"
+   "label": "Shelf Life In Days",
+   "mandatory_depends_on": "eval:doc.has_batch_no && doc.has_expiry_date"
   },
   {
    "default": "2099-12-31",
@@ -1045,7 +1046,7 @@
  "image_field": "image",
  "links": [],
  "max_attachments": 1,
- "modified": "2020-01-02 19:13:59.295963",
+ "modified": "2020-03-24 16:14:36.950677",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Item",

From fafba10219d88d58735744da3a8d83d7b11418cf Mon Sep 17 00:00:00 2001
From: Nabin Hait <nabinhait@gmail.com>
Date: Thu, 26 Mar 2020 13:36:40 +0530
Subject: [PATCH 41/93] fix: Ignored user permission for parent_company and
 existing_company field in Company (#21009)


From d74bcad7afc3869126f18a2fe6ef779fcf93b57c Mon Sep 17 00:00:00 2001
From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
Date: Thu, 26 Mar 2020 13:37:40 +0530
Subject: [PATCH 42/93] fix: Added hidden GL column in general ledger (#21021)

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
---
 erpnext/accounts/report/general_ledger/general_ledger.py | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 8750c2325d..898ac13e0e 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -131,7 +131,7 @@ def get_gl_entries(filters):
 	gl_entries = frappe.db.sql(
 		"""
 		select
-			posting_date, account, party_type, party,
+			name as gl_entry, posting_date, account, party_type, party,
 			voucher_type, voucher_no, cost_center, project,
 			against_voucher_type, against_voucher, account_currency,
 			remarks, against, is_opening {select_fields}
@@ -362,6 +362,12 @@ def get_columns(filters):
 			currency = get_company_currency(company)
 
 	columns = [
+		{
+			"fieldname": "gl_entry",
+			"fieldtype": "Link",
+			"options": "GL Entry",
+			"hidden": 1
+		},
 		{
 			"label": _("Posting Date"),
 			"fieldname": "posting_date",

From ea182051d3bd63f104cc47de5b19eb43bf3f8b7e Mon Sep 17 00:00:00 2001
From: Rohan <rohan@bloomstack.com>
Date: Thu, 26 Mar 2020 15:26:47 +0530
Subject: [PATCH 43/93] fix: consumed qty values in work order

---
 .../doctype/work_order/work_order.py          | 39 ++++++++++++-------
 1 file changed, 24 insertions(+), 15 deletions(-)

diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 98149aedef..e50dc69b4b 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -552,24 +552,33 @@ class WorkOrder(Document):
 			d.db_set('transferred_qty', flt(transferred_qty), update_modified = False)
 
 	def update_consumed_qty_for_required_items(self):
-		'''update consumed qty from submitted stock entries for that item against
-			the work order'''
+		'''
+			Update consumed qty from submitted stock entries
+			against a work order for each stock item
+		'''
 
-		for d in self.required_items:
-			consumed_qty = frappe.db.sql('''select sum(qty)
-				from `tabStock Entry` entry, `tabStock Entry Detail` detail
-				where
+		for item in self.required_items:
+			consumed_qty = frappe.db.sql('''
+				SELECT
+					SUM(qty)
+				FROM
+					`tabStock Entry` entry,
+					`tabStock Entry Detail` detail
+				WHERE
 					entry.work_order = %(name)s
-					and (entry.purpose = "Material Consumption for Manufacture"
-					or entry.purpose = "Manufacture")
-					and entry.docstatus = 1
-					and detail.parent = entry.name
-					and (detail.item_code = %(item)s or detail.original_item = %(item)s)''', {
-						'name': self.name,
-						'item': d.item_code
-					})[0][0]
+						AND (entry.purpose = "Material Consumption for Manufacture"
+							OR entry.purpose = "Manufacture")
+						AND entry.docstatus = 1
+						AND detail.parent = entry.name
+						AND detail.s_warehouse IS NOT null
+						AND (detail.item_code = %(item)s
+							OR detail.original_item = %(item)s)
+				''', {
+					'name': self.name,
+					'item': item.item_code
+				})[0][0]
 
-			d.db_set('consumed_qty', flt(consumed_qty), update_modified = False)
+			item.db_set('consumed_qty', flt(consumed_qty), update_modified=False)
 
 	def make_bom(self):
 		data = frappe.db.sql(""" select sed.item_code, sed.qty, sed.s_warehouse

From 7ca28fdab5058f06a31a20dfcac204ab9f4a777a Mon Sep 17 00:00:00 2001
From: Anupam K <anupam@Anupams-MacBook-Air.local>
Date: Thu, 26 Mar 2020 15:54:15 +0530
Subject: [PATCH 44/93] fix: Adding proper error message

---
 erpnext/accounts/party.py | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)

diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 86d85ec2d4..87bc468790 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -281,8 +281,9 @@ def validate_party_gle_currency(party_type, party, company, party_account_curren
 	existing_gle_currency = get_party_gle_currency(party_type, party, company)
 
 	if existing_gle_currency and party_account_currency != existing_gle_currency:
-		frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")
-			.format(party_type, party, existing_gle_currency), InvalidAccountCurrency)
+		frappe.throw(_("{0} : {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.")
+			.format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)))
+		
 
 def validate_party_accounts(doc):
 	companies = []
@@ -302,14 +303,12 @@ def validate_party_accounts(doc):
 		else:
 			company_default_currency = frappe.db.get_value('Company', account.company, "default_currency")
 
-		if existing_gle_currency and party_account_currency != existing_gle_currency:
-			if doc.doctype == 'Customer':
-				error_msg = _("Customer {0} has Accounting entries in currency {1} for company {2}. Please select a receivable or payable account with currency {1}.").format(frappe.bold(doc.customer_name), frappe.bold(existing_gle_currency), frappe.bold(account.company))
-			elif doc.doctype == 'Supplier':
-				error_msg = _("Supplier {0} has Accounting entries in currency {1} for company {2}. Please select a receivable or payable account with currency {1}.").format(frappe.bold(doc.supplier_name), frappe.bold(existing_gle_currency), frappe.bold(account.company))
-			else:
-				error_msg = _("{0} has Accounting entries in currency {1} for company {2}. Please select a receivable or payable account with currency {1}.").format(frappe.bold(doc.name), frappe.bold(existing_gle_currency), frappe.bold(account.company))
-			frappe.throw(error_msg)
+		if doc.doctype == 'Customer':
+			validate_party_gle_currency(doc.doctype, doc.customer_name, account.company,party_account_currency)
+		elif doc.doctype == 'Supplier':
+			validate_party_gle_currency(doc.doctype, doc.supplier_name, account.company,party_account_currency)
+		else:
+			validate_party_gle_currency(doc.doctype, doc.name, account.company,party_account_currency)
 
 		if doc.get("default_currency") and party_account_currency and company_default_currency:
 			if doc.default_currency != party_account_currency and doc.default_currency != company_default_currency:

From da54719b43fb4d003af33ff40913f44b7a0f6cf9 Mon Sep 17 00:00:00 2001
From: Rohan Bansal <rohan@digithinkit.com>
Date: Fri, 6 Mar 2020 20:55:02 +0530
Subject: [PATCH 45/93] feat: add status filter in issue web form

---
 erpnext/support/web_form/issues/issues.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/erpnext/support/web_form/issues/issues.json b/erpnext/support/web_form/issues/issues.json
index 9b904ad796..0f15e4737f 100644
--- a/erpnext/support/web_form/issues/issues.json
+++ b/erpnext/support/web_form/issues/issues.json
@@ -18,7 +18,7 @@
  "is_standard": 1,
  "login_required": 1,
  "max_attachment_size": 0,
- "modified": "2019-12-10 13:48:19.894186",
+ "modified": "2020-03-06 05:24:05.749664",
  "modified_by": "Administrator",
  "module": "Support",
  "name": "issues",
@@ -58,7 +58,7 @@
    "options": "Open\nReplied\nHold\nClosed",
    "read_only": 1,
    "reqd": 0,
-   "show_in_filter": 0
+   "show_in_filter": 1
   },
   {
    "allow_read_on_all_link_options": 0,

From 88898abe6cf176bc6c269da7762016375cc30047 Mon Sep 17 00:00:00 2001
From: Deepesh Garg <deepeshgarg6@gmail.com>
Date: Fri, 27 Mar 2020 10:27:15 +0530
Subject: [PATCH 46/93] fix: Randomly failing loan repayment test

---
 erpnext/loan_management/doctype/loan/test_loan.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index c2e5685ea7..759b0d8e09 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -273,11 +273,11 @@ class TestLoan(unittest.TestCase):
 
 		penalty_amount = (accrued_interest_amount * 4 * 25) / (100 * days_in_year(get_datetime(first_date).year))
 
-		lia = frappe.get_all("Loan Interest Accrual", fields=["is_paid"],
-			filters={"loan": loan.name}, order_by="posting_date")
+		lia1 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 1}, 'name')
+		lia2 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 0}, 'name')
 
-		self.assertTrue(lia[0].get('is_paid'))
-		self.assertFalse(lia[1].get('is_paid'))
+		self.assertTrue(lia1)
+		self.assertTrue(lia2)
 
 	def test_security_shortfall(self):
 		pledges = []

From a5357c2e27440cbf57cfdac03f2aadc0da94b1b0 Mon Sep 17 00:00:00 2001
From: Rohit Waghchaure <rohitw1991@gmail.com>
Date: Fri, 27 Mar 2020 12:54:01 +0530
Subject: [PATCH 47/93] fix: item not showing in popup while making batch

---
 erpnext/public/js/controllers/transaction.js | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index fea2d5e700..1d096b8751 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -165,6 +165,16 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
 				return (doc.rule_applied) ? "green" : "red";
 			});
 		}
+
+		let batch_no_field = this.frm.get_docfield("items", "batch_no");
+		if (batch_no_field) {
+			batch_no_field.get_route_options_for_new_doc = function(row) {
+				return {
+					"item": row.doc.item_code
+				}
+			};
+		}
+
 	},
 	onload: function() {
 		var me = this;

From cf6a814328920025ff8bc32bc12793e4543e5b1d Mon Sep 17 00:00:00 2001
From: Shivam Mishra <scm.mymail@gmail.com>
Date: Fri, 27 Mar 2020 19:15:59 +0530
Subject: [PATCH 48/93] fix: total currency formatting

---
 .../accounts_receivable.html                  | 20 +++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html
index 791f3f8008..bb0d0a132a 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html
@@ -218,15 +218,15 @@
 						<td></td>
 						<td style="text-align: right"><b>{%= __("Total") %}</b></td>
 						<td style="text-align: right">
-							{%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %}</td>
+							{%= format_currency(data[i]["invoiced"], data[0]["currency"] ) %}</td>
 
 						{% if(!filters.show_future_payments) { %}
 							<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>
+								{%= 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[i]["currency"]) %}</td>
+							{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td>
 
 						{% if(filters.show_future_payments) { %}
 							{% if(report.report_name === "Accounts Receivable") { %}
@@ -234,8 +234,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[i]["currency"]) %}</td>
-							<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</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>
 						{% } %}
 					{% } %}
 				{% } else { %}
@@ -256,10 +256,10 @@
 						{% } else { %}
 							<td><b>{%= __("Total") %}</b></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>
+						<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>
 					{% } %}
 				{% } %}
 				</tr>

From fe000e20e8224af594bc493ff0ce0e9455aaf76f Mon Sep 17 00:00:00 2001
From: Nabin Hait <nabinhait@gmail.com>
Date: Fri, 27 Mar 2020 19:24:17 +0530
Subject: [PATCH 49/93] fix: Add item defaults based on global settings only if
 default company and warehouse is mentioned

---
 erpnext/stock/doctype/item/item.py | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index f805a674ca..e2e84c4747 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -741,14 +741,12 @@ class Item(WebsiteGenerator):
 				defaults = frappe.defaults.get_defaults() or {}
 
 				# To check default warehouse is belong to the default company
-				if defaults.get("default_warehouse") and frappe.db.exists("Warehouse",
+				if defaults.get("default_warehouse") and defaults.company and frappe.db.exists("Warehouse",
 					{'name': defaults.default_warehouse, 'company': defaults.company}):
-					warehouse = defaults.default_warehouse
-
-				self.append("item_defaults", {
-					"company": defaults.get("company"),
-					"default_warehouse": warehouse
-				})
+						self.append("item_defaults", {
+							"company": defaults.get("company"),
+							"default_warehouse": defaults.default_warehouse
+						})
 
 	def update_variants(self):
 		if self.flags.dont_update_variants or \

From 71fb8e8b57bef071b403cac420a854205e01e0fa Mon Sep 17 00:00:00 2001
From: Shivam Mishra <scm.mymail@gmail.com>
Date: Fri, 27 Mar 2020 20:43:17 +0530
Subject: [PATCH 50/93] fix: use setup from Supplier Quotation Controller

---
 .../supplier_quotation/supplier_quotation.js   | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
index 39042b8b06..16061c61ba 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
@@ -4,15 +4,17 @@
 // attach required files
 {% include 'erpnext/public/js/controllers/buying.js' %};
 
-frappe.ui.form.on('Suppier Quotation', {
-	setup: function(frm) {
-		frm.custom_make_buttons = {
-			'Purchase Order': 'Purchase Order'
-		}
-	}
-});
-
 erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.extend({
+	setup: function() {
+		this.frm.custom_make_buttons = {
+			'Purchase Order': 'Purchase Order',
+			'Quotation': 'Quotation',
+			'Subscription': 'Subscription'
+		}
+
+		this._super();
+	},
+
 	refresh: function() {
 		var me = this;
 		this._super();

From 8488ef8eb16c5ec2a0853dee9e0c2f3bbe90e29c Mon Sep 17 00:00:00 2001
From: Saqib Ansari <nextchamp.saqib@gmail.com>
Date: Sat, 28 Mar 2020 14:07:09 +0530
Subject: [PATCH 51/93] fix: customer group price list not fetched in pos

---
 .../accounts/doctype/sales_invoice/sales_invoice.py   | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index ba1ceffd14..bd18d5799b 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -437,13 +437,16 @@ class SalesInvoice(SellingController):
 					if (not for_validate) or (for_validate and not self.get(fieldname)):
 						self.set(fieldname, pos.get(fieldname))
 
-			customer_price_list = frappe.get_value("Customer", self.customer, 'default_price_list')
-
 			if pos.get("company_address"):
 				self.company_address = pos.get("company_address")
 
-			if not customer_price_list:
-				self.set('selling_price_list', pos.get('selling_price_list'))
+			customer_price_list, customer_group = frappe.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
+
+			customer_group_price_list = frappe.get_value("Customer Group", customer_group, 'default_price_list')
+
+			selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list')
+
+			self.set('selling_price_list', selling_price_list)
 
 			if not for_validate:
 				self.update_stock = cint(pos.get("update_stock"))

From 85aae7de0b392b66198262fd729f464e94c04e7c Mon Sep 17 00:00:00 2001
From: Anupam K <anupam@Anupams-MacBook-Air.local>
Date: Sat, 28 Mar 2020 14:13:58 +0530
Subject: [PATCH 52/93] bug: Item-wise Sales History - Billed amount

---
 .../report/item_wise_sales_history/item_wise_sales_history.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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 1fc3663bed..405004ece5 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
@@ -121,8 +121,8 @@ def get_columns(filters):
 		},
 		{
 			"label": _("Billed Amount"),
-			"fieldname": "rate",
-			"options": "billed_amount",
+			"fieldtype": "currency",
+			"fieldname": "billed_amount",
 			"width": 120
 		},
 		{

From e7c45654839c86575f1140afb6d53c8b03ea61cc Mon Sep 17 00:00:00 2001
From: Rohit Waghchaure <rohitw1991@gmail.com>
Date: Sat, 28 Mar 2020 14:56:45 +0530
Subject: [PATCH 53/93] fix: item code showing as mandatory even if the 'Item
 Naming By' is set as Naming Series in stock settings

---
 erpnext/public/js/utils/item_quick_entry.js | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/erpnext/public/js/utils/item_quick_entry.js b/erpnext/public/js/utils/item_quick_entry.js
index 2947d5b98e..27ef107ace 100644
--- a/erpnext/public/js/utils/item_quick_entry.js
+++ b/erpnext/public/js/utils/item_quick_entry.js
@@ -8,12 +8,19 @@ frappe.ui.form.ItemQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({
 	render_dialog: function() {
 		this.mandatory = this.get_variant_fields().concat(this.mandatory);
 		this.mandatory = this.mandatory.concat(this.get_attributes_fields());
+		this.check_naming_series_based_on();
 		this._super();
 		this.init_post_render_dialog_operations();
 		this.preset_fields_for_template();
 		this.dialog.$wrapper.find('.edit-full').text(__('Edit in full page for more options like assets, serial nos, batches etc.'))
 	},
 
+	check_naming_series_based_on: function() {
+		if (frappe.defaults.get_default("item_naming_by") === "Naming Series") {
+			this.mandatory = this.mandatory.filter(d => d.fieldname !== "item_code");
+		}
+	},
+
 	init_post_render_dialog_operations: function() {
 		this.dialog.fields_dict.attribute_html.$wrapper.append(frappe.render_template("item_quick_entry"));
 		this.init_for_create_variant_trigger();

From c1678203f1273466640b8628c11139fff807a92b Mon Sep 17 00:00:00 2001
From: Saqib Ansari <nextchamp.saqib@gmail.com>
Date: Sat, 28 Mar 2020 16:39:22 +0530
Subject: [PATCH 54/93] fix: currency formatting in gl entry dr cr field

---
 erpnext/accounts/doctype/gl_entry/gl_entry.json | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json
index e64bc9e0c7..2214811d8b 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.json
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json
@@ -114,13 +114,13 @@
    "fieldname": "debit_in_account_currency",
    "fieldtype": "Currency",
    "label": "Debit Amount in Account Currency",
-   "options": "currency"
+   "options": "account_currency"
   },
   {
    "fieldname": "credit_in_account_currency",
    "fieldtype": "Currency",
    "label": "Credit Amount in Account Currency",
-   "options": "currency"
+   "options": "account_currency"
   },
   {
    "fieldname": "against",
@@ -250,7 +250,7 @@
  "icon": "fa fa-list",
  "idx": 1,
  "in_create": 1,
- "modified": "2020-02-10 04:54:57.777905",
+ "modified": "2020-03-28 16:22:33.766994",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "GL Entry",

From ed3553f795b94df0e06bef266c2964029fc9edd1 Mon Sep 17 00:00:00 2001
From: Saqib Ansari <nextchamp.saqib@gmail.com>
Date: Sat, 28 Mar 2020 19:12:59 +0530
Subject: [PATCH 55/93] chore: [ux] filter warehouse based on company

---
 erpnext/stock/report/stock_ledger/stock_ledger.js | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js
index 3d5cfdc274..9adfbf7cd0 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.js
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.js
@@ -29,7 +29,13 @@ frappe.query_reports["Stock Ledger"] = {
 			"fieldname":"warehouse",
 			"label": __("Warehouse"),
 			"fieldtype": "Link",
-			"options": "Warehouse"
+			"options": "Warehouse",
+			"get_query": function() {
+				const company = frappe.query_report.get_filter_value('company');
+				return { 
+					filters: { 'company': company }
+				}
+			}
 		},
 		{
 			"fieldname":"item_code",

From b345fe0fe9342e85f2b54d763a25f2973d1e1fdd Mon Sep 17 00:00:00 2001
From: Saqib Ansari <nextchamp.saqib@gmail.com>
Date: Sat, 28 Mar 2020 20:11:26 +0530
Subject: [PATCH 56/93] chore: hyperlinks in cannot cancel message

---
 erpnext/controllers/buying_controller.py               | 2 +-
 erpnext/manufacturing/doctype/work_order/work_order.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 8d3db8d534..88c8dba4c6 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -745,7 +745,7 @@ class BuyingController(StockController):
 							asset.supplier = None
 						if asset.docstatus == 1 and delete_asset:
 							frappe.throw(_('Cannot cancel this document as it is linked with submitted asset {0}.\
-								Please cancel the it to continue.').format(asset.name))
+								Please cancel the it to continue.').format(frappe.utils.get_link_to_form('Asset', asset.name)))
 
 					asset.flags.ignore_validate_update_after_submit = True
 					asset.flags.ignore_mandatory = True
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 98149aedef..71a62e48d2 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -314,7 +314,7 @@ class WorkOrder(Document):
 		stock_entry = frappe.db.sql("""select name from `tabStock Entry`
 			where work_order = %s and docstatus = 1""", self.name)
 		if stock_entry:
-			frappe.throw(_("Cannot cancel because submitted Stock Entry {0} exists").format(stock_entry[0][0]))
+			frappe.throw(_("Cannot cancel because submitted Stock Entry {0} exists").format(frappe.utils.get_link_to_form('Stock Entry', stock_entry[0][0])))
 
 	def update_planned_qty(self):
 		update_bin_qty(self.production_item, self.fg_warehouse, {

From c6b850eacb8f0dc42682781467a3112fe436d5f0 Mon Sep 17 00:00:00 2001
From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
Date: Sat, 28 Mar 2020 21:17:58 +0530
Subject: [PATCH 57/93] fix: Improve message

---
 erpnext/accounts/party.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 87bc468790..457b07b41f 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -281,7 +281,7 @@ def validate_party_gle_currency(party_type, party, company, party_account_curren
 	existing_gle_currency = get_party_gle_currency(party_type, party, company)
 
 	if existing_gle_currency and party_account_currency != existing_gle_currency:
-		frappe.throw(_("{0} : {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.")
+		frappe.throw(_("{0} {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.")
 			.format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)))
 		
 

From 1a2cf5cabe8c4faff6d8b204d0916bbcaf8c75c1 Mon Sep 17 00:00:00 2001
From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
Date: Sat, 28 Mar 2020 21:26:16 +0530
Subject: [PATCH 58/93] fix: get_party_gle_currency failing if customer_name or
 supplier_name is passed

---
 erpnext/accounts/party.py | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 457b07b41f..aed6e23052 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -296,19 +296,13 @@ def validate_party_accounts(doc):
 			companies.append(account.company)
 
 		party_account_currency = frappe.db.get_value("Account", account.account, "account_currency", cache=True)
-		existing_gle_currency = get_party_gle_currency(doc.doctype, doc.name, account.company)
 		if frappe.db.get_default("Company"):
 			company_default_currency = frappe.get_cached_value('Company',
 				frappe.db.get_default("Company"),  "default_currency")
 		else:
 			company_default_currency = frappe.db.get_value('Company', account.company, "default_currency")
 
-		if doc.doctype == 'Customer':
-			validate_party_gle_currency(doc.doctype, doc.customer_name, account.company,party_account_currency)
-		elif doc.doctype == 'Supplier':
-			validate_party_gle_currency(doc.doctype, doc.supplier_name, account.company,party_account_currency)
-		else:
-			validate_party_gle_currency(doc.doctype, doc.name, account.company,party_account_currency)
+		validate_party_gle_currency(doc.doctype, doc.name, account.company, party_account_currency)
 
 		if doc.get("default_currency") and party_account_currency and company_default_currency:
 			if doc.default_currency != party_account_currency and doc.default_currency != company_default_currency:

From 0f6d372182b98d5ecf7d9d5b311122bcf4803536 Mon Sep 17 00:00:00 2001
From: Saqib Ansari <nextchamp.saqib@gmail.com>
Date: Sun, 29 Mar 2020 12:52:16 +0530
Subject: [PATCH 59/93] fix: cannot view report for course doctype - permission
 problem

---
 erpnext/education/doctype/course/course.json | 26 +++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/erpnext/education/doctype/course/course.json b/erpnext/education/doctype/course/course.json
index 68426c36be..da10db1857 100644
--- a/erpnext/education/doctype/course/course.json
+++ b/erpnext/education/doctype/course/course.json
@@ -74,7 +74,7 @@
   }
  ],
  "image_field": "hero_image",
- "modified": "2019-06-12 12:34:23.748157",
+ "modified": "2020-03-29 12:50:27.677589",
  "modified_by": "Administrator",
  "module": "Education",
  "name": "Course",
@@ -103,6 +103,30 @@
    "role": "Instructor",
    "share": 1,
    "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Administrator",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Education Manager",
+   "share": 1,
+   "write": 1
   }
  ],
  "restrict_to_domain": "Education",

From cdf80eb4ad41a774119e8a0d83deb3e071cdebf6 Mon Sep 17 00:00:00 2001
From: Rohit Waghchaure <rohitw1991@gmail.com>
Date: Sun, 29 Mar 2020 16:33:30 +0530
Subject: [PATCH 60/93] fix: not able to add equity account in the chart of
 accounts

---
 erpnext/accounts/doctype/account/account.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index d5a36b8259..0a72d4fa4e 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -102,7 +102,7 @@ class Account(NestedSet):
 			if not frappe.db.get_value("Account",
 				{'account_name': self.account_name, 'company': ancestors[0]}, 'name'):
 				frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0]))
-		else:
+		elif self.parent_account:
 			descendants = get_descendants_of('Company', self.company)
 			if not descendants: return
 			parent_acc_name_map = {}

From 56bc36f9e250c70ad94c0ed25a310a0ab04e7ec7 Mon Sep 17 00:00:00 2001
From: Deepesh Garg <deepeshgarg6@gmail.com>
Date: Mon, 30 Mar 2020 15:05:20 +0530
Subject: [PATCH 61/93] fix: Test cases

---
 erpnext/accounts/party.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index aed6e23052..8567740ee7 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -282,8 +282,7 @@ def validate_party_gle_currency(party_type, party, company, party_account_curren
 
 	if existing_gle_currency and party_account_currency != existing_gle_currency:
 		frappe.throw(_("{0} {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.")
-			.format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)))
-		
+			.format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)), InvalidAccountCurrency)
 
 def validate_party_accounts(doc):
 	companies = []

From c33412b6ae0b5638127351c4692e9414afe30d66 Mon Sep 17 00:00:00 2001
From: Deepesh Garg <deepeshgarg6@gmail.com>
Date: Mon, 30 Mar 2020 22:44:13 +0530
Subject: [PATCH 62/93] feat: Link blanket order and quotation

---
 .../doctype/blanket_order/blanket_order.js    | 38 ++++++----
 .../doctype/blanket_order/blanket_order.py    | 70 +++++++++++--------
 .../blanket_order/blanket_order_dashboard.py  |  2 +-
 .../public/js/controllers/taxes_and_totals.js |  2 +-
 .../selling/doctype/quotation/quotation.py    |  5 ++
 .../quotation_item/quotation_item.json        | 31 +++++++-
 6 files changed, 100 insertions(+), 48 deletions(-)

diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js
index 1cd9446c8b..4c31bd0b7d 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js
@@ -14,27 +14,37 @@ frappe.ui.form.on('Blanket Order', {
 	refresh: function(frm) {
 		erpnext.hide_company();
 		if (frm.doc.customer && frm.doc.docstatus === 1) {
-			frm.add_custom_button(__('View Orders'), function() {
-				frappe.set_route('List', 'Sales Order', {blanket_order: frm.doc.name});
-			});
-			frm.add_custom_button(__("Create Sales Order"), function(){
+			frm.add_custom_button(__("Sales Order"), function() {
 				frappe.model.open_mapped_doc({
-					method: "erpnext.manufacturing.doctype.blanket_order.blanket_order.make_sales_order",
-					frm: frm
+					method: "erpnext.manufacturing.doctype.blanket_order.blanket_order.make_order",
+					frm: frm,
+					args: {
+						doctype: 'Sales Order'
+					}
 				});
-			}).addClass("btn-primary");
+			}, __('Create'));
+
+			frm.add_custom_button(__("Quotation"), function() {
+				frappe.model.open_mapped_doc({
+					method: "erpnext.manufacturing.doctype.blanket_order.blanket_order.make_order",
+					frm: frm,
+					args: {
+						doctype: 'Quotation'
+					}
+				});
+			}, __('Create'));
 		}
 
 		if (frm.doc.supplier && frm.doc.docstatus === 1) {
-			frm.add_custom_button(__('View Orders'), function() {
-				frappe.set_route('List', 'Purchase Order', {blanket_order: frm.doc.name});
-			});
-			frm.add_custom_button(__("Create Purchase Order"), function(){
+			frm.add_custom_button(__("Purchase Order"), function(){
 				frappe.model.open_mapped_doc({
-					method: "erpnext.manufacturing.doctype.blanket_order.blanket_order.make_purchase_order",
-					frm: frm
+					method: "erpnext.manufacturing.doctype.blanket_order.blanket_order.make_order",
+					frm: frm,
+					args: {
+						doctype: 'Purchase Order'
+					}
 				});
-			}).addClass("btn-primary");
+			}, __('Create'));
 		}
 	},
 
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
index 38118bd78d..5eb4c513b0 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
@@ -17,7 +17,7 @@ class BlanketOrder(Document):
 
 	def validate_dates(self):
 		if getdate(self.from_date) > getdate(self.to_date):
-			frappe.throw(_("From date cannot be greater than To date")) 
+			frappe.throw(_("From date cannot be greater than To date"))
 
 	def update_ordered_qty(self):
 		ref_doctype = "Sales Order" if self.blanket_order_type == "Selling" else "Purchase Order"
@@ -35,7 +35,14 @@ class BlanketOrder(Document):
 			d.db_set("ordered_qty", item_ordered_qty.get(d.item_code, 0))
 
 @frappe.whitelist()
-def make_sales_order(source_name):
+def make_order(source_name):
+	doctype = frappe.flags.args.doctype
+
+	def update_doc(source_doc, target_doc, source_parent):
+		if doctype == 'Quotation':
+			target_doc.quotation_to = 'Customer'
+			target_doc.party_name = source_doc.customer
+
 	def update_item(source, target, source_parent):
 		target_qty = source.get("qty") - source.get("ordered_qty")
 		target.qty = target_qty if not flt(target_qty) < 0 else 0
@@ -49,10 +56,11 @@ def make_sales_order(source_name):
 
 	target_doc = get_mapped_doc("Blanket Order", source_name, {
 		"Blanket Order": {
-			"doctype": "Sales Order"
+			"doctype": doctype,
+			"postprocess": update_doc
 		},
 		"Blanket Order Item": {
-			"doctype": "Sales Order Item",
+			"doctype": doctype + " Item",
 			"field_map": {
 				"rate": "blanket_order_rate",
 				"parent": "blanket_order"
@@ -62,31 +70,31 @@ def make_sales_order(source_name):
 	})
 	return target_doc
 
-@frappe.whitelist()
-def make_purchase_order(source_name):
-	def update_item(source, target, source_parent):
-		target_qty = source.get("qty") - source.get("ordered_qty")
-		target.qty = target_qty if not flt(target_qty) < 0 else 0
-		item = get_item_defaults(target.item_code, source_parent.company)
-		if item:
-			target.item_name = item.get("item_name")
-			target.description = item.get("description")
-			target.uom = item.get("stock_uom")
-			target.warehouse = item.get("default_warehouse")
-			target.against_blanket_order = 1
-			target.blanket_order = source_name
+# @frappe.whitelist()
+# def make_purchase_order(source_name):
+# 	def update_item(source, target, source_parent):
+# 		target_qty = source.get("qty") - source.get("ordered_qty")
+# 		target.qty = target_qty if not flt(target_qty) < 0 else 0
+# 		item = get_item_defaults(target.item_code, source_parent.company)
+# 		if item:
+# 			target.item_name = item.get("item_name")
+# 			target.description = item.get("description")
+# 			target.uom = item.get("stock_uom")
+# 			target.warehouse = item.get("default_warehouse")
+# 			target.against_blanket_order = 1
+# 			target.blanket_order = source_name
 
-	target_doc = get_mapped_doc("Blanket Order", source_name, {
-		"Blanket Order": {
-			"doctype": "Purchase Order"
-		},
-		"Blanket Order Item": {
-			"doctype": "Purchase Order Item",
-			"field_map": {
-				"rate": "blanket_order_rate",
-				"parent": "blanket_order"
-			},
-			"postprocess": update_item
-		}
-	})
-	return target_doc
\ No newline at end of file
+# 	target_doc = get_mapped_doc("Blanket Order", source_name, {
+# 		"Blanket Order": {
+# 			"doctype": "Purchase Order"
+# 		},
+# 		"Blanket Order Item": {
+# 			"doctype": "Purchase Order Item",
+# 			"field_map": {
+# 				"rate": "blanket_order_rate",
+# 				"parent": "blanket_order"
+# 			},
+# 			"postprocess": update_item
+# 		}
+# 	})
+# 	return target_doc
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py
index ed319a0cef..d9aa0ca49d 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py
@@ -6,7 +6,7 @@ def get_data():
 		'fieldname': 'blanket_order',
 		'transactions': [
 			{
-				'items': ['Purchase Order', 'Sales Order']
+				'items': ['Purchase Order', 'Sales Order', 'Quotation']
 			}
 		]
 	}
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 9a5b750e8c..dbe48ec654 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -6,7 +6,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
 
 	apply_pricing_rule_on_item: function(item){
 		let effective_item_rate = item.price_list_rate;
-		if (item.parenttype === "Sales Order" && item.blanket_order_rate) {
+		if (in_list(["Sales Order", "Quotation"], item.parenttype) && item.blanket_order_rate) {
 			effective_item_rate = item.blanket_order_rate;
 		}
 		if(item.margin_type == "Percentage"){
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index b79c91cef1..7c47b8ac51 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -155,6 +155,11 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
 	def update_item(obj, target, source_parent):
 		target.stock_qty = flt(obj.qty) * flt(obj.conversion_factor)
 
+		if obj.against_blanket_order:
+			target.against_blanket_order = obj.against_blanket_order
+			target.blanket_order = obj.blanket_order
+			target.blanket_order_rate = obj.blanket_order_rate
+
 	doclist = get_mapped_doc("Quotation", source_name, {
 			"Quotation": {
 				"doctype": "Sales Order",
diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json
index 3ff5555e82..d50397cfad 100644
--- a/erpnext/selling/doctype/quotation_item/quotation_item.json
+++ b/erpnext/selling/doctype/quotation_item/quotation_item.json
@@ -55,6 +55,9 @@
   "weight_uom",
   "reference",
   "warehouse",
+  "against_blanket_order",
+  "blanket_order",
+  "blanket_order_rate",
   "column_break_30",
   "prevdoc_doctype",
   "prevdoc_docname",
@@ -573,12 +576,38 @@
    "fieldname": "image_section",
    "fieldtype": "Section Break",
    "label": "Image"
+  },
+  {
+   "depends_on": "eval:doc.against_blanket_order",
+   "fieldname": "blanket_order",
+   "fieldtype": "Link",
+   "label": "Blanket Order",
+   "no_copy": 1,
+   "options": "Blanket Order",
+   "print_hide": 1
+  },
+  {
+   "depends_on": "eval:doc.against_blanket_order",
+   "fieldname": "blanket_order_rate",
+   "fieldtype": "Currency",
+   "label": "Blanket Order Rate",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "against_blanket_order",
+   "fieldtype": "Check",
+   "label": "Against Blanket Order",
+   "no_copy": 1,
+   "print_hide": 1
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-03-05 14:18:58.783751",
+ "modified": "2020-03-30 18:40:28.782720",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Quotation Item",

From c014c802dd15ee4cee3633489690fcf3365cb4fa Mon Sep 17 00:00:00 2001
From: Deepesh Garg <deepeshgarg6@gmail.com>
Date: Mon, 30 Mar 2020 22:50:29 +0530
Subject: [PATCH 63/93] fix: Remove comments

---
 .../doctype/blanket_order/blanket_order.py    | 31 +------------------
 1 file changed, 1 insertion(+), 30 deletions(-)

diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
index 5eb4c513b0..75b101fe24 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
@@ -68,33 +68,4 @@ def make_order(source_name):
 			"postprocess": update_item
 		}
 	})
-	return target_doc
-
-# @frappe.whitelist()
-# def make_purchase_order(source_name):
-# 	def update_item(source, target, source_parent):
-# 		target_qty = source.get("qty") - source.get("ordered_qty")
-# 		target.qty = target_qty if not flt(target_qty) < 0 else 0
-# 		item = get_item_defaults(target.item_code, source_parent.company)
-# 		if item:
-# 			target.item_name = item.get("item_name")
-# 			target.description = item.get("description")
-# 			target.uom = item.get("stock_uom")
-# 			target.warehouse = item.get("default_warehouse")
-# 			target.against_blanket_order = 1
-# 			target.blanket_order = source_name
-
-# 	target_doc = get_mapped_doc("Blanket Order", source_name, {
-# 		"Blanket Order": {
-# 			"doctype": "Purchase Order"
-# 		},
-# 		"Blanket Order Item": {
-# 			"doctype": "Purchase Order Item",
-# 			"field_map": {
-# 				"rate": "blanket_order_rate",
-# 				"parent": "blanket_order"
-# 			},
-# 			"postprocess": update_item
-# 		}
-# 	})
-# 	return target_doc
\ No newline at end of file
+	return target_doc
\ No newline at end of file

From 007548b79cc0efdbb5a4221ee8cc6f9ec003b247 Mon Sep 17 00:00:00 2001
From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
Date: Tue, 31 Mar 2020 10:07:51 +0530
Subject: [PATCH 64/93] fix: Bank Account patch (#21124)

---
 .../v12_0/move_bank_account_swift_number_to_bank.py   | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
index 4aad1420e3..3c9758eb84 100644
--- a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
+++ b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
@@ -4,11 +4,12 @@ import frappe
 def execute():
 	frappe.reload_doc('accounts', 'doctype', 'bank', force=1)
 
-	frappe.db.sql("""
-		UPDATE `tabBank` b, `tabBank Account` ba
-		SET b.swift_number = ba.swift_number, b.branch_code = ba.branch_code
-		WHERE b.name = ba.bank
-	""")
+	if frappe.db.table_exists('Bank') and frappe.db.table_exists('Bank Account'):
+		frappe.db.sql("""
+			UPDATE `tabBank` b, `tabBank Account` ba
+			SET b.swift_number = ba.swift_number, b.branch_code = ba.branch_code
+			WHERE b.name = ba.bank
+		""")
 
 	frappe.reload_doc('accounts', 'doctype', 'bank_account')
 	frappe.reload_doc('accounts', 'doctype', 'payment_request')
\ No newline at end of file

From 7806e5a052202ffc64f08c8b58d1322c6373bbb2 Mon Sep 17 00:00:00 2001
From: Shivam Mishra <scmmishra@users.noreply.github.com>
Date: Tue, 31 Mar 2020 10:30:20 +0530
Subject: [PATCH 65/93] feat: changes for desk pages (#21027)

* feat: rename Chart on Getting Started Page

* feat: add dashboard and leaderboard to getting started

* feat: remove communication

Co-authored-by: Prssanna Desai <prssud@gmail.com>
Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
---
 .../communication/communication.json          | 22 -------------------
 .../getting_started/getting_started.json      | 15 ++++++++++---
 2 files changed, 12 insertions(+), 25 deletions(-)
 delete mode 100644 erpnext/communication/desk_page/communication/communication.json

diff --git a/erpnext/communication/desk_page/communication/communication.json b/erpnext/communication/desk_page/communication/communication.json
deleted file mode 100644
index 59318fb8cd..0000000000
--- a/erpnext/communication/desk_page/communication/communication.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "cards": [],
- "charts": [],
- "creation": "2020-01-28 11:49:55.003637",
- "developer_mode_only": 0,
- "disable_user_customization": 0,
- "docstatus": 0,
- "doctype": "Desk Page",
- "extends_another_page": 0,
- "icon": "",
- "idx": 0,
- "is_standard": 1,
- "label": "Communication",
- "modified": "2020-03-12 16:30:40.534226",
- "modified_by": "Administrator",
- "module": "Communication",
- "name": "Communication",
- "owner": "Administrator",
- "pin_to_bottom": 0,
- "pin_to_top": 0,
- "shortcuts": []
-}
\ No newline at end of file
diff --git a/erpnext/setup/desk_page/getting_started/getting_started.json b/erpnext/setup/desk_page/getting_started/getting_started.json
index 00236ba5fe..b045b5d083 100644
--- a/erpnext/setup/desk_page/getting_started/getting_started.json
+++ b/erpnext/setup/desk_page/getting_started/getting_started.json
@@ -41,8 +41,7 @@
  "charts": [
   {
    "chart_name": "Bank Balance",
-   "label": "All Your Money",
-   "size": "Full"
+   "label": "Bank Balance"
   }
  ],
  "creation": "2020-01-23 13:46:38.833076",
@@ -55,7 +54,7 @@
  "idx": 0,
  "is_standard": 1,
  "label": "Getting Started",
- "modified": "2020-03-12 16:30:37.821762",
+ "modified": "2020-03-23 11:20:49.161823",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Getting Started",
@@ -82,6 +81,16 @@
    "is_query_report": 0,
    "link_to": "Sales Invoice",
    "type": "DocType"
+  },
+  {
+   "is_query_report": 0,
+   "link_to": "dashboard",
+   "type": "Page"
+  },
+  {
+   "is_query_report": 0,
+   "link_to": "leaderboard",
+   "type": "Page"
   }
  ]
 }
\ No newline at end of file

From 06b3f6f781d04f48b68246bfa02043483734adda Mon Sep 17 00:00:00 2001
From: Ahmad M Abdelrahman <ahmad18189@gmail.com>
Date: Tue, 31 Mar 2020 08:08:49 +0300
Subject: [PATCH 66/93] Remove Campagin restrection (#21024)

Lead Source Now is an independent doctype.
forcing the user to add Campaign is not valid
Solution
Show Campaign keeping it optional and keep the user to be free to add or remove data
---
 erpnext/crm/doctype/lead/lead.json | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json
index bc007b146f..5299368f42 100644
--- a/erpnext/crm/doctype/lead/lead.json
+++ b/erpnext/crm/doctype/lead/lead.json
@@ -171,7 +171,6 @@
    "options": "Customer"
   },
   {
-   "depends_on": "eval: doc.source==\"Campaign\"",
    "fieldname": "campaign_name",
    "fieldtype": "Link",
    "label": "Campaign Name",
@@ -512,4 +511,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "title_field": "title"
-}
\ No newline at end of file
+}

From b0ab3981ea51bdc5541b3bf2933b2e4c4814dfea Mon Sep 17 00:00:00 2001
From: Raffael Meyer <raffael@alyf.de>
Date: Tue, 31 Mar 2020 07:10:03 +0200
Subject: [PATCH 67/93] fix(regional): header row in DATEV report (#21113)

* fix(regional): encoding of DATEV report

* feat(regional): filter datev report by voucher type

* fix: creation time, coa used, is frozen

* add voucher types: Payroll Entry, Bank Reconciliation, Asset, Stock Entry

* fix indentation

* fix indentation

* fix indentation
---
 erpnext/regional/report/datev/datev.js        |  6 +++
 erpnext/regional/report/datev/datev.py        | 40 ++++++++++++-------
 .../regional/report/datev/datev_constants.py  | 20 ++++++++--
 3 files changed, 49 insertions(+), 17 deletions(-)

diff --git a/erpnext/regional/report/datev/datev.js b/erpnext/regional/report/datev/datev.js
index 1e000b673e..d8638ab05d 100644
--- a/erpnext/regional/report/datev/datev.js
+++ b/erpnext/regional/report/datev/datev.js
@@ -21,6 +21,12 @@ frappe.query_reports["DATEV"] = {
 			"default": frappe.datetime.now_date(),
 			"fieldtype": "Date",
 			"reqd": 1
+		},
+		{
+			"fieldname": "voucher_type",
+			"label": __("Voucher Type"),
+			"fieldtype": "Select",
+			"options": "\nSales Invoice\nPurchase Invoice\nPayment Entry\nExpense Claim\nPayroll Entry\nBank Reconciliation\nAsset\nStock Entry"
 		}
 	],
 	onload: function(query_report) {
diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py
index e9b42356a2..a6579121cb 100644
--- a/erpnext/regional/report/datev/datev.py
+++ b/erpnext/regional/report/datev/datev.py
@@ -62,6 +62,7 @@ def get_transactions(filters, as_dict=1):
 	filters -- dict of filters to be passed to the sql query
 	as_dict -- return as list of dicts [0,1]
 	"""
+	filter_by_voucher = 'AND gl.voucher_type = %(voucher_type)s' if filters.get('voucher_type') else ''
 	gl_entries = frappe.db.sql("""
 		SELECT
 
@@ -80,8 +81,10 @@ def get_transactions(filters, as_dict=1):
 			gl.posting_date as 'Belegdatum',
 			gl.voucher_no as 'Belegfeld 1',
 			gl.remarks as 'Buchungstext',
-			gl.against_voucher_type as 'Beleginfo - Art 1',
-			gl.against_voucher as 'Beleginfo - Inhalt 1'
+			gl.voucher_type as 'Beleginfo - Art 1',
+			gl.voucher_no as 'Beleginfo - Inhalt 1',
+			gl.against_voucher_type as 'Beleginfo - Art 2',
+			gl.against_voucher as 'Beleginfo - Inhalt 2'
 
 		FROM `tabGL Entry` gl
 
@@ -109,7 +112,8 @@ def get_transactions(filters, as_dict=1):
 		WHERE gl.company = %(company)s 
 		AND DATE(gl.posting_date) >= %(from_date)s
 		AND DATE(gl.posting_date) <= %(to_date)s
-		ORDER BY 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict)
+		{}
+		ORDER BY 'Belegdatum', gl.voucher_no""".format(filter_by_voucher), filters, as_dict=as_dict)
 
 	return gl_entries
 
@@ -281,24 +285,24 @@ def get_datev_csv(data, filters, csv_class):
 
 def get_header(filters, csv_class):
 	coa = frappe.get_value("Company", filters.get("company"), "chart_of_accounts")
-	coa_used = "SKR04" if "SKR04" in coa else ("SKR03" if "SKR03" in coa else "")
+	coa_used = "04" if "SKR04" in coa else ("03" if "SKR03" in coa else "")
 
 	header = [
 		# DATEV format
-		#   "DTVF" = created by DATEV software,
-		#   "EXTF" = created by other software
+		#	"DTVF" = created by DATEV software,
+		#	"EXTF" = created by other software
 		'"EXTF"',
 		# version of the DATEV format
-		#   141 = 1.41, 
-		#   510 = 5.10,
-		#   720 = 7.20
+		#	141 = 1.41, 
+		#	510 = 5.10,
+		#	720 = 7.20
 		'700',
 		csv_class.DATA_CATEGORY,
 		'"%s"' % csv_class.FORMAT_NAME,
 		# Format version (regarding format name)
 		csv_class.FORMAT_VERSION,
 		# Generated on
-		datetime.datetime.now().strftime("%Y%m%d%H%M%S"),
+		datetime.datetime.now().strftime("%Y%m%d%H%M%S") + '000',
 		# Imported on -- stays empty
 		'',
 		# Origin. Any two symbols, will be replaced by "SV" on import.
@@ -328,13 +332,21 @@ def get_header(filters, csv_class):
 		# R = Diktatkürzel
 		'',
 		# S = Buchungstyp
-		#   1 = Transaction batch (Finanzbuchführung),
-		#   2 = Annual financial statement (Jahresabschluss)
+		#	1 = Transaction batch (Finanzbuchführung),
+		#	2 = Annual financial statement (Jahresabschluss)
 		'1' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
 		# T = Rechnungslegungszweck
-		'',
+		#	0 oder leer = vom Rechnungslegungszweck unabhängig
+		#	50 = Handelsrecht
+		#	30 = Steuerrecht
+		#	64 = IFRS
+		#	40 = Kalkulatorik
+		#	11 = Reserviert
+		#	12 = Reserviert
+		'0',
 		# U = Festschreibung
-		'',
+		# TODO: Filter by Accounting Period. In export for closed Accounting Period, this will be "1"
+		'0',
 		# V = Default currency, for example, "EUR"
 		'"%s"' % frappe.get_value("Company", filters.get("company"), "default_currency"),
 		# reserviert
diff --git a/erpnext/regional/report/datev/datev_constants.py b/erpnext/regional/report/datev/datev_constants.py
index a4cd5fc10e..a059ed365a 100644
--- a/erpnext/regional/report/datev/datev_constants.py
+++ b/erpnext/regional/report/datev/datev_constants.py
@@ -498,13 +498,27 @@ QUERY_REPORT_COLUMNS = [
 	},
 	{
 		"label": "Beleginfo - Art 1",
-		"fieldname": "Beleginfo - Art 2",
-		"fieldtype": "Data",
+		"fieldname": "Beleginfo - Art 1",
+		"fieldtype": "Link",
+		"options": "DocType"
 	},
 	{
 		"label": "Beleginfo - Inhalt 1",
+		"fieldname": "Beleginfo - Inhalt 1",
+		"fieldtype": "Dynamic Link",
+		"options": "Beleginfo - Art 1"
+	},
+	{
+		"label": "Beleginfo - Art 2",
+		"fieldname": "Beleginfo - Art 2",
+		"fieldtype": "Link",
+		"options": "DocType"
+	},
+	{
+		"label": "Beleginfo - Inhalt 2",
 		"fieldname": "Beleginfo - Inhalt 2",
-		"fieldtype": "Data",
+		"fieldtype": "Dynamic Link",
+		"options": "Beleginfo - Art 2"
 	}
 ]
 

From 3fa03df1e37810aa48ee3f85e974a9b60ac5407f Mon Sep 17 00:00:00 2001
From: prafful1234 <43948551+prafful1234@users.noreply.github.com>
Date: Tue, 31 Mar 2020 10:40:42 +0530
Subject: [PATCH 68/93] fix(transaction): Add comment-by from frappe session
 (#20867)

Co-authored-by: prafful1234 <prafful@mntechnique.com>
---
 erpnext/buying/doctype/purchase_order/purchase_order.js | 3 ++-
 erpnext/selling/doctype/sales_order/sales_order.js      | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index a3264a4c0f..3111a3a7d5 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -499,7 +499,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
 						reference_doctype: me.frm.doctype,
 						reference_name: me.frm.docname,
 						content: __('Reason for hold: ')+data.reason_for_hold,
-						comment_email: frappe.session.user
+						comment_email: frappe.session.user,
+						comment_by: frappe.session.user_fullname
 					},
 					callback: function(r) {
 						if(!r.exc) {
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index fa765dfaad..61aa608dd3 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -664,7 +664,8 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
 						reference_doctype: me.frm.doctype,
 						reference_name: me.frm.docname,
 						content: __('Reason for hold: ')+data.reason_for_hold,
-						comment_email: frappe.session.user
+						comment_email: frappe.session.user,
+						comment_by: frappe.session.user_fullname
 					},
 					callback: function(r) {
 						if(!r.exc) {

From 5c54adec28c7fda2e290a1bd2bfd1ab7f3c751d0 Mon Sep 17 00:00:00 2001
From: Andy Zhu <andy007yan@gmail.com>
Date: Tue, 31 Mar 2020 18:13:00 +1300
Subject: [PATCH 69/93] fix: docfield of sales_order is not fetching route
 options for new doc (#21123)

Using the wrong method to get `so` as docfield.
frappe.meta changes will not effect the `sales_order` in `frm`.
---
 erpnext/projects/doctype/project/project.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js
index 3570a0f2be..5862963496 100644
--- a/erpnext/projects/doctype/project/project.js
+++ b/erpnext/projects/doctype/project/project.js
@@ -18,7 +18,7 @@ frappe.ui.form.on("Project", {
 		};
 	},
 	onload: function (frm) {
-		var so = frappe.meta.get_docfield("Project", "sales_order");
+		var so = frm.get_docfield("Project", "sales_order");
 		so.get_route_options_for_new_doc = function (field) {
 			if (frm.is_new()) return;
 			return {
@@ -135,4 +135,4 @@ function open_form(frm, doctype, child_doctype, parentfield) {
 		frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
 	});
 
-}
\ No newline at end of file
+}

From fbf6e56d860c041dff6ceb901e1ed129c1db37a2 Mon Sep 17 00:00:00 2001
From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
Date: Tue, 31 Mar 2020 10:45:32 +0530
Subject: [PATCH 70/93] fix: Expense account currency validation in Landed Cost
 voucher (#21073)

* fix: Expense account currency validation in Landed Cost voucher

* fix: Remove unused imports
---
 erpnext/controllers/queries.py                     | 14 +++++++++-----
 .../landed_cost_voucher/landed_cost_voucher.py     | 10 ++++++++++
 2 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index d18f8e54d8..163ef72ee1 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -3,6 +3,7 @@
 
 from __future__ import unicode_literals
 import frappe
+import erpnext
 from frappe.desk.reportview import get_match_cond, get_filters_cond
 from frappe.utils import nowdate, getdate
 from collections import defaultdict
@@ -129,23 +130,26 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
 		})
 
 def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
+	company_currency = erpnext.get_company_currency(filters.get('company'))
+
 	tax_accounts = frappe.db.sql("""select name, parent_account	from tabAccount
 		where tabAccount.docstatus!=2
 			and account_type in (%s)
 			and is_group = 0
 			and company = %s
+			and account_currency = %s
 			and `%s` LIKE %s
 		order by idx desc, name
 		limit %s, %s""" %
-		(", ".join(['%s']*len(filters.get("account_type"))), "%s", searchfield, "%s", "%s", "%s"),
-		tuple(filters.get("account_type") + [filters.get("company"), "%%%s%%" % txt,
+		(", ".join(['%s']*len(filters.get("account_type"))), "%s", "%s", searchfield, "%s", "%s", "%s"),
+		tuple(filters.get("account_type") + [filters.get("company"), company_currency, "%%%s%%" % txt,
 			start, page_len]))
 	if not tax_accounts:
 		tax_accounts = frappe.db.sql("""select name, parent_account	from tabAccount
 			where tabAccount.docstatus!=2 and is_group = 0
-				and company = %s and `%s` LIKE %s limit %s, %s"""
-			% ("%s", searchfield, "%s", "%s", "%s"),
-			(filters.get("company"), "%%%s%%" % txt, start, page_len))
+				and company = %s and account_currency = %s and `%s` LIKE %s limit %s, %s""" #nosec
+			% ("%s", "%s", searchfield, "%s", "%s", "%s"),
+			(filters.get("company"), company_currency, "%%%s%%" % txt, start, page_len))
 
 	return tax_accounts
 
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 d97b699a0f..5ad0e13db9 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -8,6 +8,7 @@ from frappe.utils import flt
 from frappe.model.meta import get_field_precision
 from frappe.model.document import Document
 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+from erpnext.accounts.doctype.account.account import get_account_currency
 
 class LandedCostVoucher(Document):
 	def get_items_from_purchase_receipts(self):
@@ -43,6 +44,7 @@ class LandedCostVoucher(Document):
 		else:
 			self.validate_applicable_charges_for_item()
 		self.validate_purchase_receipts()
+		self.validate_expense_accounts()
 		self.set_total_taxes_and_charges()
 
 	def check_mandatory(self):
@@ -71,6 +73,14 @@ class LandedCostVoucher(Document):
 				frappe.throw(_("Row {0}: Cost center is required for an item {1}")
 					.format(item.idx, item.item_code))
 
+	def validate_expense_accounts(self):
+		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"))
+
 	def set_total_taxes_and_charges(self):
 		self.total_taxes_and_charges = sum([flt(d.amount) for d in self.get("taxes")])
 

From bba78f038452fb2f58f63a58389f0a0e2d19dd14 Mon Sep 17 00:00:00 2001
From: Saqib <nextchamp.saqib@gmail.com>
Date: Tue, 31 Mar 2020 10:49:44 +0530
Subject: [PATCH 71/93] fix: auto created asset message (#21108)

* fix: auto created asset message

* Update erpnext/controllers/buying_controller.py

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
---
 erpnext/controllers/buying_controller.py | 33 +++++++++++++++++-------
 1 file changed, 24 insertions(+), 9 deletions(-)

diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 88c8dba4c6..fcc9098ba1 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -672,19 +672,32 @@ class BuyingController(StockController):
 					# If asset has to be auto created
 					# Check for asset naming series
 					if item_data.get('asset_naming_series'):
+						created_assets = []
+
 						for qty in range(cint(d.qty)):
-							self.make_asset(d)
-						is_plural = 's' if cint(d.qty) != 1 else ''
-						messages.append(_('{0} Asset{2} Created for <b>{1}</b>').format(cint(d.qty), d.item_code, is_plural))
+							asset = self.make_asset(d)
+							created_assets.append(asset)
+						
+						if len(created_assets) > 5:
+							# dont show asset form links if more than 5 assets are created
+							messages.append(_('{} Asset{} created for {}').format(len(created_assets), is_plural, frappe.bold(d.item_code)))
+						else:
+							assets_link = list(map(lambda d: frappe.utils.get_link_to_form('Asset', d), created_assets))
+							assets_link = frappe.bold(','.join(assets_link))
+
+							is_plural = 's' if len(created_assets) != 1 else ''
+							messages.append(
+								_('Asset{} {assets_link} created for {}').format(is_plural, frappe.bold(d.item_code), assets_link=assets_link)
+							)
 					else:
-						frappe.throw(_("Row {1}: Asset Naming Series is mandatory for the auto creation for item {0}")
-							.format(d.item_code, d.idx))
+						frappe.throw(_("Row {}: Asset Naming Series is mandatory for the auto creation for item {}")
+							.format(d.idx, frappe.bold(d.item_code)))
 				else:
-					messages.append(_("Assets not created for <b>{0}</b>. You will have to create asset manually.")
-						.format(d.item_code))
+					messages.append(_("Assets not created for {0}. You will have to create asset manually.")
+						.format(frappe.bold(d.item_code)))
 
 		for message in messages:
-			frappe.msgprint(message, title="Success")
+			frappe.msgprint(message, title="Success", indicator="green")
 
 	def make_asset(self, row):
 		if not row.asset_location:
@@ -716,6 +729,8 @@ class BuyingController(StockController):
 		asset.set_missing_values()
 		asset.insert()
 
+		return asset.name
+
 	def update_fixed_asset(self, field, delete_asset = False):
 		for d in self.get("items"):
 			if d.is_fixed_asset:
@@ -1026,4 +1041,4 @@ def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty
 			available_batches.append({'batch': batch, 'qty': available_qty})
 			required_qty -= available_qty
 
-	return available_batches
\ No newline at end of file
+	return available_batches

From ee0dec8776cbd821318cb7c32d5cdd3c86106a33 Mon Sep 17 00:00:00 2001
From: Anupam Kumar <anupamvns0099@gmail.com>
Date: Tue, 31 Mar 2020 10:51:09 +0530
Subject: [PATCH 72/93] fix: email_to, party_type and party are not set in
 payment request when order made from portal (#21084)

Co-authored-by: Anupam K <anupam@Anupams-MacBook-Air.local>
---
 erpnext/accounts/doctype/payment_request/payment_request.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 0fade8c456..7e9211af9f 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -317,13 +317,13 @@ def make_payment_request(**args):
 			"payment_request_type": args.get("payment_request_type"),
 			"currency": ref_doc.currency,
 			"grand_total": grand_total,
-			"email_to": args.recipient_id or "",
+			"email_to": args.recipient_id or ref_doc.owner,
 			"subject": _("Payment Request for {0}").format(args.dn),
 			"message": gateway_account.get("message") or get_dummy_message(ref_doc),
 			"reference_doctype": args.dt,
 			"reference_name": args.dn,
-			"party_type": args.get("party_type"),
-			"party": args.get("party"),
+			"party_type": args.get("party_type") or "Customer",
+			"party": args.get("party") or ref_doc.customer,
 			"bank_account": bank_account
 		})
 

From 4d9fe69ba59b75a8a7955fb4b2728cc9ec2a630c Mon Sep 17 00:00:00 2001
From: Deepesh Garg <deepeshgarg6@gmail.com>
Date: Tue, 31 Mar 2020 14:17:53 +0530
Subject: [PATCH 73/93] fix: Test cases

---
 .../blanket_order/test_blanket_order.py       | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
index 455ea06137..3171defdae 100644
--- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
@@ -7,13 +7,17 @@ import frappe
 import unittest
 from frappe.utils import add_months, today
 from erpnext import get_company_currency
-from .blanket_order import make_sales_order, make_purchase_order
+from .blanket_order import make_order
 
 class TestBlanketOrder(unittest.TestCase):
+	def setUp(self):
+		frappe.flags.args = frappe._dict()
+
 	def test_sales_order_creation(self):
 		bo = make_blanket_order(blanket_order_type="Selling")
 
-		so = make_sales_order(bo.name)
+		frappe.flags.args.doctype = 'Sales Order'
+		so = make_order(bo.name)
 		so.currency = get_company_currency(so.company)
 		so.delivery_date = today()
 		so.items[0].qty = 10
@@ -29,7 +33,8 @@ class TestBlanketOrder(unittest.TestCase):
 		self.assertEqual(so.items[0].qty, bo.items[0].ordered_qty)
 
 		# test the quantity
-		so1 = make_sales_order(bo.name)
+		frappe.flags.args.doctype = 'Sales Order'
+		so1 = make_order(bo.name)
 		so1.currency = get_company_currency(so1.company)
 		self.assertEqual(so1.items[0].qty, (bo.items[0].qty-bo.items[0].ordered_qty))
 
@@ -37,7 +42,8 @@ class TestBlanketOrder(unittest.TestCase):
 	def test_purchase_order_creation(self):
 		bo = make_blanket_order(blanket_order_type="Purchasing")
 
-		po = make_purchase_order(bo.name)
+		frappe.flags.args.doctype = 'Purchase Order'
+		po = make_order(bo.name)
 		po.currency = get_company_currency(po.company)
 		po.schedule_date = today()
 		po.items[0].qty = 10
@@ -53,7 +59,8 @@ class TestBlanketOrder(unittest.TestCase):
 		self.assertEqual(po.items[0].qty, bo.items[0].ordered_qty)
 
 		# test the quantity
-		po1 = make_sales_order(bo.name)
+		frappe.flags.args.doctype = 'Purchase Order'
+		po1 = make_order(bo.name)
 		po1.currency = get_company_currency(po1.company)
 		self.assertEqual(po1.items[0].qty, (bo.items[0].qty-bo.items[0].ordered_qty))
 
@@ -78,7 +85,7 @@ def make_blanket_order(**args):
 		"qty": args.quantity or 1000,
 		"rate": args.rate or 100
 	})
-	
+
 	bo.insert()
 	bo.submit()
 	return bo
\ No newline at end of file

From 86aff0de1f834353aee25fc14676806f58b801eb Mon Sep 17 00:00:00 2001
From: rohitwaghchaure <rohitw1991@gmail.com>
Date: Tue, 31 Mar 2020 15:26:45 +0530
Subject: [PATCH 74/93] fix: serial no scan not adding the serial nos in stock
 entry (#21083)

---
 erpnext/public/js/controllers/transaction.js    |  7 ++++++-
 .../stock/doctype/stock_entry/stock_entry.js    | 17 +++++++++++------
 2 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index fc4541a256..4397fe49c3 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -362,12 +362,17 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
 
 				['serial_no', 'batch_no', 'barcode'].forEach(field => {
 					if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) {
+
+						let value = (row_to_modify[field] && field === "serial_no")
+							? row_to_modify[field] + '\n' + data[field] : data[field];
+
 						frappe.model.set_value(row_to_modify.doctype,
-							row_to_modify.name, field, data[field]);
+							row_to_modify.name, field, value);
 					}
 				});
 
 				scan_barcode_field.set_value('');
+				refresh_field("items");
 			});
 		}
 		return false;
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 3af3524423..3bb941573b 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -310,12 +310,12 @@ frappe.ui.form.on('Stock Entry', {
 			method: "erpnext.stock.get_item_details.get_serial_no",
 			args: {"args": args},
 			callback: function(r) {
-				if (!r.exe){
+				if (!r.exe && r.message){
 					frappe.model.set_value(cdt, cdn, "serial_no", r.message);
-				}
 
-				if (callback) {
-					callback();
+					if (callback) {
+						callback();
+					}
 				}
 			}
 		});
@@ -623,10 +623,15 @@ frappe.ui.form.on('Stock Entry Detail', {
 					if(r.message) {
 						var d = locals[cdt][cdn];
 						$.each(r.message, function(k, v) {
-							frappe.model.set_value(cdt, cdn, k, v); // qty and it's subsequent fields weren't triggered
+							if (v) {
+								frappe.model.set_value(cdt, cdn, k, v); // qty and it's subsequent fields weren't triggered
+							}
 						});
 						refresh_field("items");
-						erpnext.stock.select_batch_and_serial_no(frm, d);
+
+						if (!d.serial_no) {
+							erpnext.stock.select_batch_and_serial_no(frm, d);
+						}
 					}
 				}
 			});

From d274923ae13d2bf76b1c281c87e23fa0df665e1d Mon Sep 17 00:00:00 2001
From: rohitwaghchaure <rohitw1991@gmail.com>
Date: Tue, 31 Mar 2020 15:28:31 +0530
Subject: [PATCH 75/93] fix: "Qty To Manufacture" field as non mandatory in job
 card (#21081)

---
 erpnext/manufacturing/doctype/bom/bom.js      |  1 +
 .../doctype/job_card/job_card.js              | 14 ++++---
 .../doctype/job_card/job_card.json            |  6 +--
 .../doctype/job_card/job_card.py              | 42 ++++++++++---------
 .../doctype/work_order/work_order.py          |  3 +-
 5 files changed, 38 insertions(+), 28 deletions(-)

diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 3acaee4ffb..4f08bbc3fc 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -135,6 +135,7 @@ frappe.ui.form.on("BOM", {
 			frappe.call({
 				method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order",
 				args: {
+					bom_no: frm.doc.name,
 					item: frm.doc.item,
 					qty: data.qty || 0.0,
 					project: frm.doc.project
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js
index bc8c22998a..8c7876d48d 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card.js
@@ -20,7 +20,7 @@ frappe.ui.form.on('Job Card', {
 			}
 		}
 
-		if (frm.doc.docstatus == 0 && frm.doc.for_quantity > frm.doc.total_completed_qty
+		if (frm.doc.docstatus == 0 && (frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity)
 			&& (!frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
 			frm.trigger("prepare_timer_buttons");
 		}
@@ -59,10 +59,14 @@ frappe.ui.form.on('Job Card', {
 				let completed_time = frappe.datetime.now_datetime();
 				frm.trigger("hide_timer");
 
-				frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'),
-					fieldname: 'qty', reqd: 1, default: frm.doc.for_quantity}, data => {
-					frm.events.complete_job(frm, completed_time, data.qty);
-				}, __("Enter Value"), __("Complete"));
+				if (frm.doc.for_quantity) {
+					frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'),
+						fieldname: 'qty', reqd: 1, default: frm.doc.for_quantity}, data => {
+							frm.events.complete_job(frm, completed_time, data.qty);
+						}, __("Enter Value"), __("Complete"));
+				} else {
+					frm.events.complete_job(frm, completed_time, 0);
+				}
 			}).addClass("btn-primary");
 		}
 	},
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json
index 156accee74..7661fffa86 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.json
+++ b/erpnext/manufacturing/doctype/job_card/job_card.json
@@ -99,8 +99,7 @@
    "fieldname": "for_quantity",
    "fieldtype": "Float",
    "in_list_view": 1,
-   "label": "Qty To Manufacture",
-   "reqd": 1
+   "label": "Qty To Manufacture"
   },
   {
    "fieldname": "wip_warehouse",
@@ -122,6 +121,7 @@
    "options": "Employee"
   },
   {
+   "allow_bulk_edit": 1,
    "fieldname": "time_logs",
    "fieldtype": "Table",
    "label": "Time Logs",
@@ -290,7 +290,7 @@
   }
  ],
  "is_submittable": 1,
- "modified": "2019-12-03 13:08:57.926201",
+ "modified": "2020-03-27 13:36:35.417502",
  "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 029db1cb4e..f8c60f2a11 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -191,12 +191,9 @@ class JobCard(Document):
 		if not self.time_logs:
 			frappe.throw(_("Time logs are required for job card {0}").format(self.name))
 
-		if self.total_completed_qty <= 0.0:
-			frappe.throw(_("Total completed qty must be greater than zero"))
-
-		if self.total_completed_qty != self.for_quantity:
-			frappe.throw(_("The total completed qty({0}) must be equal to qty to manufacture({1})")
-				.format(frappe.bold(self.total_completed_qty),frappe.bold(self.for_quantity)))
+		if self.for_quantity and self.total_completed_qty != self.for_quantity:
+			frappe.throw(_("The total completed qty({0}) must be equal to qty to manufacture({1})"
+				.format(frappe.bold(self.total_completed_qty),frappe.bold(self.for_quantity))))
 
 	def update_work_order(self):
 		if not self.work_order:
@@ -205,27 +202,34 @@ class JobCard(Document):
 		for_quantity, time_in_mins = 0, 0
 		from_time_list, to_time_list = [], []
 
-		for d in frappe.get_all('Job Card',
-			filters = {'docstatus': 1, 'operation_id': self.operation_id}):
-			doc = frappe.get_doc('Job Card', d.name)
 
-			for_quantity += doc.total_completed_qty
-			time_in_mins += doc.total_time_in_mins
-			for time_log in doc.time_logs:
-				if time_log.from_time:
-					from_time_list.append(time_log.from_time)
-				if time_log.to_time:
-					to_time_list.append(time_log.to_time)
+		data = frappe.get_all('Job Card',
+			fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
+			filters = {"docstatus": 1, "work_order": self.work_order,
+				"workstation": self.workstation, "operation": self.operation})
+
+		if data and len(data) > 0:
+			for_quantity = data[0].completed_qty
+			time_in_mins = data[0].time_in_mins
 
 		if for_quantity:
+			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.workstation = %s and jc.operation = %s and jc.docstatus = 1
+			""", (self.work_order, self.workstation, self.operation), as_dict=1)
+
 			wo = frappe.get_doc('Work Order', self.work_order)
 
 			for data in wo.operations:
-				if data.name == self.operation_id:
+				if data.workstation == self.workstation and data.operation == self.operation:
 					data.completed_qty = for_quantity
 					data.actual_operation_time = time_in_mins
-					data.actual_start_time = min(from_time_list) if from_time_list else None
-					data.actual_end_time = max(to_time_list) if to_time_list else None
+					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()
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 71a62e48d2..a124b1fcc5 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -648,7 +648,7 @@ def get_item_details(item, project = None):
 	return res
 
 @frappe.whitelist()
-def make_work_order(item, qty=0, project=None):
+def make_work_order(bom_no, item, qty=0, project=None):
 	if not frappe.has_permission("Work Order", "write"):
 		frappe.throw(_("Not permitted"), frappe.PermissionError)
 
@@ -657,6 +657,7 @@ def make_work_order(item, qty=0, project=None):
 	wo_doc = frappe.new_doc("Work Order")
 	wo_doc.production_item = item
 	wo_doc.update(item_details)
+	wo_doc.bom_no = bom_no
 
 	if flt(qty) > 0:
 		wo_doc.qty = flt(qty)

From c19330df634ef0bb2cc4d9027a7b25eb5fbdce39 Mon Sep 17 00:00:00 2001
From: Raffael Meyer <raffael@alyf.de>
Date: Tue, 31 Mar 2020 11:58:45 +0200
Subject: [PATCH 76/93] fix: account groups (#21070)

---
 ..._kontenplan_SKR04_with_account_number.json | 387 ++++++++----------
 1 file changed, 177 insertions(+), 210 deletions(-)

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 ff95c5ae19..3fc109bfd6 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
@@ -2433,29 +2433,26 @@
             "Erl\u00f6se aus Verk\u00e4ufen Sachanlageverm\u00f6gen (bei Buchgewinn)": {
                 "account_number": "4849"
             },
-            "Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn) (Gruppe)": {
-                "is_group": 1,
-                "Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn)": {
-                    "account_number": "4850"
-                },
-                "Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (bei Buchgewinn)": {
-                    "account_number": "4851"
-                },
-                "Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (inl\u00e4ndische Kap.Ges., bei Buchgewinn)": {
-                    "account_number": "4852"
-                },
-                "Anlagenabg\u00e4nge Sachanlagen (Restbuchwert bei Buchvergewinn)": {
-                    "account_number": "4855"
-                },
-                "Anlagenabg\u00e4nge immaterielle VG (Restbuchwert bei Buchgewinn)": {
-                    "account_number": "4856"
-                },
-                "Anlagenabg\u00e4nge Finanzanlagen (Restbuchwert bei Buchgewinn)": {
-                    "account_number": "4857"
-                },
-                "Anlagenabg\u00e4nge Finanzanlagen (inl\u00e4ndische Kap.Ges., Restbuchwert bei Buchgewinn)": {
-                    "account_number": "4858"
-                }
+            "Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn)": {
+                "account_number": "4850"
+            },
+            "Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (bei Buchgewinn)": {
+                "account_number": "4851"
+            },
+            "Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (inl\u00e4ndische Kap.Ges., bei Buchgewinn)": {
+                "account_number": "4852"
+            },
+            "Anlagenabg\u00e4nge Sachanlagen (Restbuchwert bei Buchvergewinn)": {
+                "account_number": "4855"
+            },
+            "Anlagenabg\u00e4nge immaterielle VG (Restbuchwert bei Buchgewinn)": {
+                "account_number": "4856"
+            },
+            "Anlagenabg\u00e4nge Finanzanlagen (Restbuchwert bei Buchgewinn)": {
+                "account_number": "4857"
+            },
+            "Anlagenabg\u00e4nge Finanzanlagen (inl\u00e4ndische Kap.Ges., Restbuchwert bei Buchgewinn)": {
+                "account_number": "4858"
             },
             "Ertr\u00e4ge aus Zuschreibungen des Sachanlageverm\u00f6gens": {
                 "account_number": "4910",
@@ -2578,20 +2575,17 @@
             "Entnahme von Gegenst\u00e4nden ohne USt": {
                 "account_number": "4605"
             },
-            "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt (Gruppe)": {
-                "is_group": 1,
-                "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt": {
-                    "account_number": "4630"
-                },
-                "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt": {
-                    "account_number": "4637"
-                },
-                "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternnehmens ohne USt (Telefon-Nutzung)": {
-                    "account_number": "4638"
-                },
-                "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt (Kfz-Nutzung)": {
-                    "account_number": "4639"
-                }
+            "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt": {
+                "account_number": "4630"
+            },
+            "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt": {
+                "account_number": "4637"
+            },
+            "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternnehmens ohne USt (Telefon-Nutzung)": {
+                "account_number": "4638"
+            },
+            "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt (Kfz-Nutzung)": {
+                "account_number": "4639"
             },
             "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 19 % USt (Gruppe)": {
                 "is_group": 1,
@@ -2629,14 +2623,11 @@
             "Unentgeltliche Zuwendung von Gegenst\u00e4nden ohne USt": {
                 "account_number": "4689"
             },
-            "Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze) (Gruppe)": {
-                "is_group": 1,
-                "Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze)": {
-                    "account_number": "4690"
-                },
-                "Umsatzsteuerverg\u00fctungen, z.B. nach \u00a7 24 UStG": {
-                    "account_number": "4695"
-                }
+            "Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze)": {
+                "account_number": "4690"
+            },
+            "Umsatzsteuerverg\u00fctungen, z.B. nach \u00a7 24 UStG": {
+                "account_number": "4695"
             },
             "Au\u00dferordentliche Ertr\u00e4ge (Gruppe)": {
                 "is_group": 1,
@@ -2646,41 +2637,35 @@
                 "Au\u00dferordentliche Ertr\u00e4ge finanzwirksam": {
                     "account_number": "7401"
                 },
-                "Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam (Gruppe)": {
-                    "is_group": 1,
-                    "Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam": {
-                        "account_number": "7450"
-                    },
-                    "Ertr\u00e4ge durch Verschmelzung und Umwandlung": {
-                        "account_number": "7451"
-                    },
-                    "Ertr\u00e4ge durch den Verkauf von bedeutenden Beteiligungen": {
-                        "account_number": "7452"
-                    },
-                    "Ert\u00e4ge durch den Verkauf von bedeutenden Grundst\u00fccken": {
-                        "account_number": "7453"
-                    },
-                    "Gewinn aus der Ver\u00e4u\u00dferung oder der Aufgabe von Gesch\u00e4ftsaktivit\u00e4ten nach Steuern": {
-                        "account_number": "7454"
-                    }
+                "Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam": {
+                    "account_number": "7450"
                 },
-                "Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften (Gruppe)": {
-                    "is_group": 1,
-                    "Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften": {
-                        "account_number": "7460"
-                    },
-                    "Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Sachanlageverm\u00f6gen": {
-                        "account_number": "7461"
-                    },
-                    "Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Finanzanlageverm\u00f6gen": {
-                        "account_number": "7462"
-                    },
-                    "Au\u00dferordentliche Ertr\u00e4ge: Wertpapiere im Umlaufverm\u00f6gen": {
-                        "account_number": "7463"
-                    },
-                    "Au\u00dferordentliche Ertr\u00e4ge: latente Steuern": {
-                        "account_number": "7464"
-                    }
+                "Ertr\u00e4ge durch Verschmelzung und Umwandlung": {
+                    "account_number": "7451"
+                },
+                "Ertr\u00e4ge durch den Verkauf von bedeutenden Beteiligungen": {
+                    "account_number": "7452"
+                },
+                "Ert\u00e4ge durch den Verkauf von bedeutenden Grundst\u00fccken": {
+                    "account_number": "7453"
+                },
+                "Gewinn aus der Ver\u00e4u\u00dferung oder der Aufgabe von Gesch\u00e4ftsaktivit\u00e4ten nach Steuern": {
+                    "account_number": "7454"
+                },
+                "Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften": {
+                    "account_number": "7460"
+                },
+                "Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Sachanlageverm\u00f6gen": {
+                    "account_number": "7461"
+                },
+                "Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Finanzanlageverm\u00f6gen": {
+                    "account_number": "7462"
+                },
+                "Au\u00dferordentliche Ertr\u00e4ge: Wertpapiere im Umlaufverm\u00f6gen": {
+                    "account_number": "7463"
+                },
+                "Au\u00dferordentliche Ertr\u00e4ge: latente Steuern": {
+                    "account_number": "7464"
                 }
             }
         },
@@ -2718,40 +2703,43 @@
                 },
                 "Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 16 % USt": {
                     "account_number": "4729"
+                }
+            },
+            "Gew\u00e4hrte Skonti (Gruppe)": {
+                "is_group": 1,
+                "Gew. Skonti": {
+                    "account_number": "4730"
                 },
-                "Gew\u00e4hrte Skonti (Gruppe)": {
-                    "is_group": 1,
-                    "Gew. Skonti": {
-                        "account_number": "4730"
-                    },
-                    "Gew. Skonti 7 % USt": {
-                        "account_number": "4731"
-                    },
-                    "Gew. Skonti 19 % USt": {
-                        "account_number": "4736"
-                    },
-                    "Gew. Skonti aus Lieferungen von Mobilfunkger./Schaltkr., f. die der Leistungsempf. die Ust. schuldet": {
-                        "account_number": "4738"
-                    },
-                    "Gew. Skonti aus Leistungen, f. die der Leistungsempf. die Umsatzsteuer nach \u00a7 13b UStG schuldet": {
-                        "account_number": "4741"
-                    },
-                    "Gew. Skonti aus Erl\u00f6sen aus im anderen EU-Land steuerpfl. Leistungen, f. die der Leistungsempf. die Ust. schuldet": {
-                        "account_number": "4742"
-                    },
-                    "Gew. Skonti aus steuerfreien innergem. Lieferungen \u00a7 4 Nr. 1b UStG": {
-                        "account_number": "4743"
-                    },
-                    "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen": {
-                        "account_number": "4745"
-                    },
-                    "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 7% USt": {
-                        "account_number": "4746"
-                    },
-                    "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 19% USt": {
-                        "account_number": "4748"
-                    }
+                "Gew. Skonti 7 % USt": {
+                    "account_number": "4731"
                 },
+                "Gew. Skonti 19 % USt": {
+                    "account_number": "4736"
+                },
+                "Gew. Skonti aus Lieferungen von Mobilfunkger./Schaltkr., f. die der Leistungsempf. die Ust. schuldet": {
+                    "account_number": "4738"
+                },
+                "Gew. Skonti aus Leistungen, f. die der Leistungsempf. die Umsatzsteuer nach \u00a7 13b UStG schuldet": {
+                    "account_number": "4741"
+                },
+                "Gew. Skonti aus Erl\u00f6sen aus im anderen EU-Land steuerpfl. Leistungen, f. die der Leistungsempf. die Ust. schuldet": {
+                    "account_number": "4742"
+                },
+                "Gew. Skonti aus steuerfreien innergem. Lieferungen \u00a7 4 Nr. 1b UStG": {
+                    "account_number": "4743"
+                },
+                "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen": {
+                    "account_number": "4745"
+                },
+                "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 7% USt": {
+                    "account_number": "4746"
+                },
+                "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 19% USt": {
+                    "account_number": "4748"
+                }
+            },
+            "Gew\u00e4hrte Boni (Gruppe)": {
+                "is_group": 1,
                 "Gew\u00e4hrte Boni 7 % USt": {
                     "account_number": "4750"
                 },
@@ -2864,103 +2852,79 @@
                     "account_number": "6398"
                 }
             },
-            "Versicherungen (Gruppe)": {
-                "is_group": 1,
-                "Versicherungen": {
-                    "account_number": "6400"
-                },
-                "Versicherungen f. Geb\u00e4ude, die zum Betriebsverm\u00f6gen geh\u00f6ren": {
-                    "account_number": "6405"
-                },
-                "Netto-Pr\u00e4mie f. R\u00fcckdeckung k\u00fcnftiger Versorgungsleistungen": {
-                    "account_number": "6410"
-                },
-                "Beitr\u00e4ge": {
-                    "account_number": "6420"
-                },
-                "Sonstige Abgaben": {
-                    "account_number": "6430"
-                },
-                "Steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
-                    "account_number": "6436"
-                },
-                "Steuerlich nicht abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
-                    "account_number": "6437"
-                },
-                "Ausgleichsabgabe i. S. d. Schwerbehindertengesetzes": {
-                    "account_number": "6440"
-                },
-                "Reparaturen und Instandhaltung von Bauten": {
-                    "account_number": "6450"
-                },
-                "Reparaturen und Instandhaltung von technischenAnlagen und Maschinen": {
-                    "account_number": "6460"
-                },
-                "Reparaturen und Instandhaltung von anderen Anlagen und Betriebs- und Gesch\u00e4ftsausstattung": {
-                    "account_number": "6470"
-                },
-                "Zuf\u00fchrung zu Aufwandsr\u00fcckstellungen": {
-                    "account_number": "6475"
-                },
-                "Reparaturen und Instandhaltung von anderen Anlagen": {
-                    "account_number": "6485"
-                },
-                "Sonstige Reparaturen und Instandhaltungen": {
-                    "account_number": "6490"
-                },
-                "Wartungskosten f. Hard- und Software": {
-                    "account_number": "6495"
-                },
-                "Mietleasing (bewegliche Wirtschaftsg\u00fcter)": {
-                    "account_number": "6498"
-                }
+            "Versicherungen": {
+                "account_number": "6400"
+            },
+            "Versicherungen f. Geb\u00e4ude, die zum Betriebsverm\u00f6gen geh\u00f6ren": {
+                "account_number": "6405"
+            },
+            "Netto-Pr\u00e4mie f. R\u00fcckdeckung k\u00fcnftiger Versorgungsleistungen": {
+                "account_number": "6410"
+            },
+            "Beitr\u00e4ge": {
+                "account_number": "6420"
+            },
+            "Sonstige Abgaben": {
+                "account_number": "6430"
+            },
+            "Steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
+                "account_number": "6436"
+            },
+            "Steuerlich nicht abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
+                "account_number": "6437"
+            },
+            "Ausgleichsabgabe i. S. d. Schwerbehindertengesetzes": {
+                "account_number": "6440"
+            },
+            "Reparaturen und Instandhaltung von Bauten": {
+                "account_number": "6450"
+            },
+            "Reparaturen und Instandhaltung von technischenAnlagen und Maschinen": {
+                "account_number": "6460"
+            },
+            "Reparaturen und Instandhaltung von anderen Anlagen und Betriebs- und Gesch\u00e4ftsausstattung": {
+                "account_number": "6470"
+            },
+            "Zuf\u00fchrung zu Aufwandsr\u00fcckstellungen": {
+                "account_number": "6475"
+            },
+            "Reparaturen und Instandhaltung von anderen Anlagen": {
+                "account_number": "6485"
+            },
+            "Sonstige Reparaturen und Instandhaltungen": {
+                "account_number": "6490"
+            },
+            "Wartungskosten f. Hard- und Software": {
+                "account_number": "6495"
+            },
+            "Mietleasing (bewegliche Wirtschaftsg\u00fcter)": {
+                "account_number": "6498"
             },
             "Fahrzeugkosten (Gruppe)": {
                 "is_group": 1,
                 "Fahrzeugkosten": {
                     "account_number": "6500"
                 },
-                "Kfz-Versicherungen (Gruppe)": {
-                    "is_group": 1,
-                    "Kfz-Versicherungen": {
-                        "account_number": "6520"
-                    }
+                "Kfz-Versicherungen": {
+                    "account_number": "6520"
                 },
-                "Laufende Kfz-Betriebskosten (Gruppe)": {
-                    "is_group": 1,
-                    "Laufende Kfz-Betriebskosten": {
-                        "account_number": "6530"
-                    }
+                "Laufende Kfz-Betriebskosten": {
+                    "account_number": "6530"
                 },
-                "Kfz-Reparaturen (Gruppe)": {
-                    "is_group": 1,
-                    "Kfz-Reparaturen": {
-                        "account_number": "6540"
-                    }
+                "Kfz-Reparaturen": {
+                    "account_number": "6540"
                 },
-                "Garagenmiete (Gruppe)": {
-                    "is_group": 1,
-                    "Garagenmiete": {
-                        "account_number": "6550"
-                    }
+                "Garagenmiete": {
+                    "account_number": "6550"
                 },
-                "Mietleasing Kfz (Gruppe)": {
-                    "is_group": 1,
-                    "Mietleasing Kfz": {
-                        "account_number": "6560"
-                    }
+                "Mietleasing Kfz": {
+                    "account_number": "6560"
                 },
-                "Sonstige Kfz-Kosten (Gruppe)": {
-                    "is_group": 1,
-                    "Sonstige Kfz-Kosten": {
-                        "account_number": "6570"
-                    }
+                "Sonstige Kfz-Kosten": {
+                    "account_number": "6570"
                 },
-                "Mautgeb\u00fchren (Gruppe)": {
-                    "is_group": 1,
-                    "Mautgeb\u00fchren": {
-                        "account_number": "6580"
-                    }
+                "Mautgeb\u00fchren": {
+                    "account_number": "6580"
                 },
                 "Kfz-Kosten f. betrieblich genutzte zum Privatverm\u00f6gen geh\u00f6rende Kraftfahrzeuge": {
                     "account_number": "6590"
@@ -3022,20 +2986,23 @@
                 "Nicht abzugsf\u00e4hige Betriebsausgaben aus Werbe- und Repr\u00e4sentationskosten": {
                     "account_number": "6645"
                 },
-                "Reisekosten Arbeitnehmer": {
-                    "account_number": "6650"
-                },
-                "Reisekosten Arbeitnehmer \u00dcbernachtungsaufwand": {
-                    "account_number": "6660"
-                },
-                "Reisekosten Arbeitnehmer Fahrtkosten": {
-                    "account_number": "6663"
-                },
-                "Reisekosten Arbeitnehmer Verpflegungsmehraufwand": {
-                    "account_number": "6664"
-                },
-                "Kilometergelderstattung Arbeitnehmer": {
-                    "account_number": "6668"
+                "Reisekosten Arbeitnehmer (Gruppe)": {
+                    "is_group": 1,
+                    "Reisekosten Arbeitnehmer": {
+                        "account_number": "6650"
+                    },
+                    "Reisekosten Arbeitnehmer \u00dcbernachtungsaufwand": {
+                        "account_number": "6660"
+                    },
+                    "Reisekosten Arbeitnehmer Fahrtkosten": {
+                        "account_number": "6663"
+                    },
+                    "Reisekosten Arbeitnehmer Verpflegungsmehraufwand": {
+                        "account_number": "6664"
+                    },
+                    "Kilometergelderstattung Arbeitnehmer": {
+                        "account_number": "6668"
+                    }
                 },
                 "Reisekosten Unternehmer (Gruppe)": {
                     "is_group": 1,

From 25846cccbdaa0302e94a436db86f66ea1328e10d Mon Sep 17 00:00:00 2001
From: Saqib <nextchamp.saqib@gmail.com>
Date: Tue, 31 Mar 2020 15:31:15 +0530
Subject: [PATCH 77/93] fix: warehouse_account_map not getting reset for diff
 company transac (#20995)

* fix: warehouse_account_map not getting reset for diff company transaction

* fix: potential key errors while fetching warehouse_account_map

* fix: travis

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
---
 erpnext/stock/__init__.py | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/erpnext/stock/__init__.py b/erpnext/stock/__init__.py
index a4d4cbd8ef..8d64efe41d 100644
--- a/erpnext/stock/__init__.py
+++ b/erpnext/stock/__init__.py
@@ -13,12 +13,16 @@ install_docs = [
 ]
 
 def get_warehouse_account_map(company=None):
-	if not frappe.flags.warehouse_account_map or frappe.flags.in_test:
+	company_warehouse_account_map = company and frappe.flags.setdefault('warehouse_account_map', {}).get(company)
+	warehouse_account_map = frappe.flags.warehouse_account_map
+
+	if not warehouse_account_map or not company_warehouse_account_map or frappe.flags.in_test:
 		warehouse_account = frappe._dict()
 
 		filters = {}
 		if company:
 			filters['company'] = company
+			frappe.flags.setdefault('warehouse_account_map', {}).setdefault(company, {})
 
 		for d in frappe.get_all('Warehouse',
 			fields = ["name", "account", "parent_warehouse", "company", "is_group"],
@@ -30,10 +34,12 @@ def get_warehouse_account_map(company=None):
 			if d.account:
 				d.account_currency = frappe.db.get_value('Account', d.account, 'account_currency', cache=True)
 				warehouse_account.setdefault(d.name, d)
-
-		frappe.flags.warehouse_account_map = warehouse_account
-
-	return frappe.flags.warehouse_account_map
+		if company:
+			frappe.flags.warehouse_account_map[company] = warehouse_account
+		else:
+			frappe.flags.warehouse_account_map = warehouse_account
+	
+	return frappe.flags.warehouse_account_map.get(company) or frappe.flags.warehouse_account_map
 
 def get_warehouse_account(warehouse, warehouse_account=None):
 	account = warehouse.account

From e567563aa17a92b35f993e0fae3efac2692c1afe Mon Sep 17 00:00:00 2001
From: Saurabh <saurabh6790@gmail.com>
Date: Wed, 4 Mar 2020 13:11:41 +0530
Subject: [PATCH 78/93] fix: if mandatory fields are missing do not create
 address against lead

---
 erpnext/crm/doctype/lead/lead.py | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index 73ef79b894..cefe32c88b 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -135,10 +135,14 @@ class Lead(SellingController):
 
 		# do not create an address if no fields are available,
 		# skipping country since the system auto-sets it from system defaults
-		if not any([self.get(field) for field in address_fields if field != "country"]):
+		address = frappe.new_doc("Address")
+
+		mandatory_fields = get_mandatory_fields(address)
+
+		if not all([self.get(field) for field in mandatory_fields]):
+			frappe.msgprint(_('Missing mandatory fields in address.'), alert=True, indicator='yellow')
 			return
 
-		address = frappe.new_doc("Address")
 		address.update({addr_field: self.get(addr_field) for addr_field in address_fields})
 		address.update({info_field: self.get(info_field) for info_field in info_fields})
 		address.insert()
@@ -370,3 +374,10 @@ def get_lead_with_phone_number(number):
 	lead = leads[0].name if leads else None
 
 	return lead
+
+def get_mandatory_fields(doc):
+	return [
+		df.fieldname
+		for df in doc.meta.fields
+		if df.reqd
+	]
\ No newline at end of file

From 6c8d63f3b92a11a36de2ec1a99e06a8909472baa Mon Sep 17 00:00:00 2001
From: Saurabh <saurabh6790@gmail.com>
Date: Mon, 9 Mar 2020 11:57:47 +0530
Subject: [PATCH 79/93] fix: provision to setup new address from lead if
 address creation break due to mandatory exception

---
 erpnext/crm/doctype/lead/lead.py | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index cefe32c88b..04f21ffb74 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -137,10 +137,13 @@ class Lead(SellingController):
 		# skipping country since the system auto-sets it from system defaults
 		address = frappe.new_doc("Address")
 
-		mandatory_fields = get_mandatory_fields(address)
+		mandatory_fields = [ df.fieldname for df in doc.meta.fields if df.reqd ]
 
 		if not all([self.get(field) for field in mandatory_fields]):
-			frappe.msgprint(_('Missing mandatory fields in address.'), alert=True, indicator='yellow')
+			frappe.msgprint(_('Missing mandatory fields in address. \
+				{0} to create address' ).format("<a href='desk#Form/Address/New Address 1' \
+				> Click here </a>"),
+				alert=True, indicator='yellow')
 			return
 
 		address.update({addr_field: self.get(addr_field) for addr_field in address_fields})
@@ -374,10 +377,3 @@ def get_lead_with_phone_number(number):
 	lead = leads[0].name if leads else None
 
 	return lead
-
-def get_mandatory_fields(doc):
-	return [
-		df.fieldname
-		for df in doc.meta.fields
-		if df.reqd
-	]
\ No newline at end of file

From 84b93414d0a61b4a8f535c3befcefa7b0061d4fa Mon Sep 17 00:00:00 2001
From: Saurabh <saurabh6790@gmail.com>
Date: Tue, 31 Mar 2020 15:57:39 +0530
Subject: [PATCH 80/93] fix: typo

---
 erpnext/crm/doctype/lead/lead.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index 04f21ffb74..eb9f86076c 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -137,7 +137,7 @@ class Lead(SellingController):
 		# skipping country since the system auto-sets it from system defaults
 		address = frappe.new_doc("Address")
 
-		mandatory_fields = [ df.fieldname for df in doc.meta.fields if df.reqd ]
+		mandatory_fields = [ df.fieldname for df in address.meta.fields if df.reqd ]
 
 		if not all([self.get(field) for field in mandatory_fields]):
 			frappe.msgprint(_('Missing mandatory fields in address. \

From cf9347d2f5e702cfb7f097836bb29afff1d22702 Mon Sep 17 00:00:00 2001
From: Deepesh Garg <deepeshgarg6@gmail.com>
Date: Tue, 31 Mar 2020 17:28:43 +0530
Subject: [PATCH 81/93] fix: Undo unneccessary changes

---
 erpnext/accounts/party.py | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)

diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 98b6d79626..4cfeb251d6 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -11,7 +11,7 @@ from frappe.utils import (add_days, getdate, formatdate, date_diff,
 	add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day)
 from frappe.contacts.doctype.address.address import (get_address_display,
 	get_default_address, get_company_address)
-from frappe.contacts.doctype.contact.contact import get_contact_details, get_default_contact
+from frappe.contacts.doctype.contact.contact import get_contact_details
 from erpnext.exceptions import PartyFrozen, PartyDisabled, InvalidAccountCurrency
 from erpnext.accounts.utils import get_fiscal_year
 from erpnext import get_company_currency
@@ -46,7 +46,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
 	currency = party.default_currency if party.get("default_currency") else get_company_currency(company)
 
 	party_address, shipping_address = set_address_details(party_details, party, party_type, doctype, company, party_address, company_address, shipping_address)
-	set_contact_details(party_details, party, party_type, doctype)
+	set_contact_details(party_details, party, party_type)
 	set_other_values(party_details, party, party_type)
 	set_price_list(party_details, party, party_type, price_list, pos_profile)
 
@@ -115,11 +115,8 @@ def set_address_details(party_details, party, party_type, doctype=None, company=
 def get_regional_address_details(party_details, doctype, company):
 	pass
 
-def set_contact_details(party_details, party, party_type, doctype=None):
-	if doctype == 'Sales Invoice':
-		party_details.contact_person = get_default_billing_contact(doctype, party.name)
-	else:
-		party_details.contact_person = get_default_contact(party_type, party.name)
+def set_contact_details(party_details, party, party_type):
+	party_details.contact_person = get_default_contact(party_type, party.name)
 
 	if not party_details.contact_person:
 		party_details.update({
@@ -617,8 +614,8 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None):
 	if data:
 		return frappe._dict(data)
 
-def get_default_billing_contact(doctype, name):
-	""" 
+def get_default_contact(doctype, name):
+	"""
 		Returns default contact for the given doctype and name.
 		Can be ordered by `contact_type` to either is_primary_contact or is_billing_contact.
 	"""
@@ -626,11 +623,11 @@ def get_default_billing_contact(doctype, name):
 			SELECT dl.parent, c.is_primary_contact, c.is_billing_contact
 			FROM `tabDynamic Link` dl
 			INNER JOIN tabContact c ON c.name = dl.parent
-			WHERE 
+			WHERE
 				dl.link_doctype=%s AND
 				dl.link_name=%s AND
 				dl.parenttype = "Contact"
-			ORDER BY is_billing_contact DESC, is_primary_contact DESC
+			ORDER BY is_primary_contact DESC, is_billing_contact DESC
 		""", (doctype, name))
 	if out:
 		try:

From 48aa2dd94bac306d634327d210558af3c455940f Mon Sep 17 00:00:00 2001
From: Poranut Chollavorn <pipech@gmail.com>
Date: Thu, 26 Mar 2020 13:46:36 +0000
Subject: [PATCH 82/93] fix(pos): fix pos display item instock

---
 .../page/point_of_sale/point_of_sale.py       | 46 +++++++++++--------
 1 file changed, 28 insertions(+), 18 deletions(-)

diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index 3425f8f2a5..17136e0472 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -64,30 +64,40 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
 		for d in item_prices_data:
 			item_prices[d.item_code] = d
 
-
+		# prepare filter for bin query
+		bin_filters = {'item_code': ['in', items]}
+		if warehouse:
+			bin_filters['warehouse'] = warehouse
 		if display_items_in_stock:
-			filters = {'actual_qty': [">", 0], 'item_code': ['in', items]}
+			bin_filters['actual_qty'] = [">", 0]
 
-			if warehouse:
-				filters['warehouse'] = warehouse
+		# query item bin
+		bin_data = frappe.get_all(
+			'Bin', fields=['item_code', 'sum(actual_qty) as actual_qty'],
+			filters=bin_filters, group_by='item_code'
+		)
 
-			bin_data = frappe._dict(
-				frappe.get_all("Bin", fields = ["item_code", "sum(actual_qty) as actual_qty"],
-				filters = filters, group_by = "item_code")
-			)
+		# convert list of dict into dict as {item_code: actual_qty}
+		bin_dict = {}
+		for b in bin_data:
+			bin_dict[b.get('item_code')] = b.get('actual_qty')
 
 		for item in items_data:
-			row = {}
+			item_code = item.item_code
+			item_price = item_prices.get(item_code) or {}
+			item_stock_qty = bin_dict.get(item_code)
 
-			row.update(item)
-			item_price = item_prices.get(item.item_code) or {}
-			row.update({
-				'price_list_rate': item_price.get('price_list_rate'),
-				'currency': item_price.get('currency'),
-				'actual_qty': bin_data.get('actual_qty')
-			})
-
-			result.append(row)
+			if display_items_in_stock and not item_stock_qty:
+				pass
+			else:
+				row = {}
+				row.update(item)
+				row.update({
+					'price_list_rate': item_price.get('price_list_rate'),
+					'currency': item_price.get('currency'),
+					'actual_qty': item_stock_qty,
+				})
+				result.append(row)
 
 	res = {
 		'items': result

From 9a7851096c607d3a90c18e3c633c930856ed049c Mon Sep 17 00:00:00 2001
From: Saqib Ansari <nextchamp.saqib@gmail.com>
Date: Tue, 31 Mar 2020 18:48:35 +0530
Subject: [PATCH 83/93] fix: check if selling price exists then set it

---
 erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index bd18d5799b..a42166241e 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -446,7 +446,8 @@ class SalesInvoice(SellingController):
 
 			selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list')
 
-			self.set('selling_price_list', selling_price_list)
+			if selling_price_list:
+				self.set('selling_price_list', selling_price_list)
 
 			if not for_validate:
 				self.update_stock = cint(pos.get("update_stock"))

From 6f0dc8257ccb4953e0aa84bb5c00cf478e854420 Mon Sep 17 00:00:00 2001
From: marination <maricadsouza221197@gmail.com>
Date: Wed, 1 Apr 2020 11:04:14 +0530
Subject: [PATCH 84/93] fix: Typo in stock level validation in Stock Ledger

---
 erpnext/stock/stock_ledger.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index b100f45327..7567a1ae75 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -428,7 +428,7 @@ class update_entries_after(object):
 				frappe.get_desk_link(self.exceptions[0]["voucher_type"], self.exceptions[0]["voucher_no"]))
 
 		if self.verbose:
-			frappe.throw(msg, NegativeStockError, title='Insufficent Stock')
+			frappe.throw(msg, NegativeStockError, title='Insufficient Stock')
 		else:
 			raise NegativeStockError(msg)
 

From e48bcb30e7ebd891a477f21362eb4cc73330ba91 Mon Sep 17 00:00:00 2001
From: Anupam K <anupamvns0099@gmail.com>
Date: Thu, 2 Apr 2020 01:04:01 +0530
Subject: [PATCH 85/93] Sum of years not needed.

---
 .../customer_acquisition_and_loyalty.py                         | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
index 4509904249..aabc503a1b 100644
--- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
+++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
@@ -48,7 +48,7 @@ def execute(filters=None):
 			new = new_customers_in.get(key, [0,0.0])
 			repeat = repeat_customers_in.get(key, [0,0.0])
 
-			out.append([year, calendar.month_name[month],
+			out.append([str(year), calendar.month_name[month],
 				new[0], repeat[0], new[0] + repeat[0],
 				new[1], repeat[1], new[1] + repeat[1]])
 

From 625acab057332db0af52f5a8e75755928db01323 Mon Sep 17 00:00:00 2001
From: Deepesh Garg <deepeshgarg6@gmail.com>
Date: Thu, 2 Apr 2020 11:18:22 +0530
Subject: [PATCH 86/93] fix: Add duplicate item validation

---
 .../manufacturing/doctype/blanket_order/blanket_order.py | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
index 75b101fe24..f2cb7f3bcc 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
@@ -19,6 +19,15 @@ class BlanketOrder(Document):
 		if getdate(self.from_date) > getdate(self.to_date):
 			frappe.throw(_("From date cannot be greater than To date"))
 
+		self.validate_duplicate_items()
+
+	def validate_duplicate_items(self):
+		item_list = []
+		for item in self.items:
+			if item.item_code in item_list:
+				frappe.throw(_("Note: Item {0} added multiple times").format(frappe.bold(item.item_code)))
+			item_list.append(item.item_code)
+
 	def update_ordered_qty(self):
 		ref_doctype = "Sales Order" if self.blanket_order_type == "Selling" else "Purchase Order"
 		item_ordered_qty = frappe._dict(frappe.db.sql("""

From b20dbff72627294ab6134bfe20178aef351de83e Mon Sep 17 00:00:00 2001
From: Anupam K <anupamvns0099@gmail.com>
Date: Thu, 2 Apr 2020 11:37:44 +0530
Subject: [PATCH 87/93] Sum of years not needed.

---
 .../customer_acquisition_and_loyalty.py                       | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
index aabc503a1b..28dd056407 100644
--- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
+++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
@@ -4,7 +4,7 @@
 from __future__ import unicode_literals
 import frappe
 from frappe import _
-from frappe.utils import getdate, cint
+from frappe.utils import getdate, cint, cstr
 import calendar
 
 def execute(filters=None):
@@ -48,7 +48,7 @@ def execute(filters=None):
 			new = new_customers_in.get(key, [0,0.0])
 			repeat = repeat_customers_in.get(key, [0,0.0])
 
-			out.append([str(year), calendar.month_name[month],
+			out.append([cstr(year), calendar.month_name[month],
 				new[0], repeat[0], new[0] + repeat[0],
 				new[1], repeat[1], new[1] + repeat[1]])
 

From 872fcb6caed285701c88512b56d578f339378b5a Mon Sep 17 00:00:00 2001
From: Deepesh Garg <deepeshgarg6@gmail.com>
Date: Thu, 2 Apr 2020 18:11:46 +0530
Subject: [PATCH 88/93] fix: Travis

---
 .../doctype/purchase_invoice_item/purchase_invoice_item.json  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index ef90b942b5..9c974260a2 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -761,7 +761,7 @@
    "depends_on": "is_fixed_asset",
    "fetch_from": "item_code.asset_category",
    "fieldname": "asset_category",
-   "fieldtype": "Data",
+   "fieldtype": "Link",
    "label": "Asset Category",
    "options": "Asset Category",
    "read_only": 1
@@ -777,7 +777,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-03-11 14:20:17.297284",
+ "modified": "2020-04-01 14:20:17.297284",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice Item",

From d9a17a9be170f52207e174ef6247577fd03691a9 Mon Sep 17 00:00:00 2001
From: Shivam Mishra <scm.mymail@gmail.com>
Date: Thu, 2 Apr 2020 19:57:11 +0530
Subject: [PATCH 89/93] fix (tests): reload doctype

---
 erpnext/patches.txt | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 8aec8bddb9..5f5df6c476 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -626,10 +626,11 @@ erpnext.patches.v12_0.update_ewaybill_field_position
 erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes
 erpnext.patches.v11_1.set_status_for_material_request_type_manufacture
 erpnext.patches.v12_0.move_plaid_settings_to_doctype
-execute:frappe.reload_doc('desk', 'doctype','dashboard_chart_link')
-execute:frappe.reload_doc('desk', 'doctype','dashboard')
-execute:frappe.reload_doc('desk', 'doctype','dashboard_chart_source')
-execute:frappe.reload_doc('desk', 'doctype','dashboard_chart')
+execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart_link')
+execute:frappe.reload_doc('desk', 'doctype', 'dashboard')
+execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart_source')
+execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart')
+execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart_field')
 erpnext.patches.v12_0.add_default_dashboards
 erpnext.patches.v12_0.remove_bank_remittance_custom_fields
 erpnext.patches.v12_0.generate_leave_ledger_entries

From d29dd58e894afa16889b71ab525d8e5fc1aed499 Mon Sep 17 00:00:00 2001
From: Deepesh Garg <deepeshgarg6@gmail.com>
Date: Thu, 2 Apr 2020 21:29:59 +0530
Subject: [PATCH 90/93] fix: Validation

---
 erpnext/manufacturing/doctype/blanket_order/blanket_order.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
index f2cb7f3bcc..d7556add80 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
@@ -14,13 +14,12 @@ from erpnext.stock.doctype.item.item import get_item_defaults
 class BlanketOrder(Document):
 	def validate(self):
 		self.validate_dates()
+		self.validate_duplicate_items()
 
 	def validate_dates(self):
 		if getdate(self.from_date) > getdate(self.to_date):
 			frappe.throw(_("From date cannot be greater than To date"))
 
-		self.validate_duplicate_items()
-
 	def validate_duplicate_items(self):
 		item_list = []
 		for item in self.items:

From 438e0433a3116520d91386f6e0dc726a55f05f83 Mon Sep 17 00:00:00 2001
From: Saqib <nextchamp.saqib@gmail.com>
Date: Fri, 3 Apr 2020 10:02:56 +0530
Subject: [PATCH 91/93] fix: cannot set warehouse on deleting all so items and
 updating them (#21078)

* fix: cannot set warehouse on deleting all so items and updating them

* fix: travis

* fix: docname is editable in update items dialog
---
 erpnext/controllers/accounts_controller.py | 15 +++---
 erpnext/public/js/utils.js                 |  3 +-
 erpnext/stock/get_item_details.py          | 60 ++++++++++++++--------
 3 files changed, 49 insertions(+), 29 deletions(-)

diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index d661bcbf34..76eb56f523 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -19,6 +19,7 @@ from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_t
 from erpnext.exceptions import InvalidCurrency
 from six import text_type
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
+from erpnext.stock.get_item_details import get_item_warehouse
 
 force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
 
@@ -1126,16 +1127,16 @@ def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname,
 	"""
 	Returns a Sales Order Item child item containing the default values
 	"""
-	p_doctype = frappe.get_doc(parent_doctype, parent_doctype_name)
-	child_item = frappe.new_doc('Sales Order Item', p_doctype, child_docname)
+	p_doc = frappe.get_doc(parent_doctype, parent_doctype_name)
+	child_item = frappe.new_doc('Sales Order Item', p_doc, child_docname)
 	item = frappe.get_doc("Item", item_code)
 	child_item.item_code = item.item_code
 	child_item.item_name = item.item_name
 	child_item.description = item.description
-	child_item.reqd_by_date = p_doctype.delivery_date
+	child_item.reqd_by_date = p_doc.delivery_date
 	child_item.uom = item.stock_uom
 	child_item.conversion_factor = get_conversion_factor(item_code, item.stock_uom).get("conversion_factor") or 1.0
-	child_item.warehouse = p_doctype.set_warehouse or p_doctype.items[0].warehouse
+	child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
 	return child_item
 
 
@@ -1143,13 +1144,13 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna
 	"""
 	Returns a Purchase Order Item child item containing the default values
 	"""
-	p_doctype = frappe.get_doc(parent_doctype, parent_doctype_name)
-	child_item = frappe.new_doc('Purchase Order Item', p_doctype, child_docname)
+	p_doc = frappe.get_doc(parent_doctype, parent_doctype_name)
+	child_item = frappe.new_doc('Purchase Order Item', p_doc, child_docname)
 	item = frappe.get_doc("Item", item_code)
 	child_item.item_code = item.item_code
 	child_item.item_name = item.item_name
 	child_item.description = item.description
-	child_item.schedule_date = p_doctype.schedule_date
+	child_item.schedule_date = p_doc.schedule_date
 	child_item.uom = item.stock_uom
 	child_item.conversion_factor = get_conversion_factor(item_code, item.stock_uom).get("conversion_factor") or 1.0
 	child_item.base_rate = 1 # Initiallize value will update in parent validation
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 4eb3175186..4d44eae086 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -453,7 +453,8 @@ erpnext.utils.update_child_items = function(opts) {
 				fields: [{
 					fieldtype:'Data',
 					fieldname:"docname",
-					hidden: 0,
+					read_only: 1,
+					hidden: 1,
 				}, {
 					fieldtype:'Link',
 					fieldname:"item_code",
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 53964f24c4..9c5a8e1be8 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -240,26 +240,13 @@ def get_basic_details(args, item, overwrite_warehouse=True):
 	item_group_defaults = get_item_group_defaults(item.name, args.company)
 	brand_defaults = get_brand_defaults(item.name, args.company)
 
-	if overwrite_warehouse or not args.warehouse:
-		warehouse = (
-			args.get("set_warehouse") or
-			item_defaults.get("default_warehouse") or
-			item_group_defaults.get("default_warehouse") or
-			brand_defaults.get("default_warehouse") or
-			args.warehouse
-		)
-
-		if not warehouse:
-			defaults = frappe.defaults.get_defaults() or {}
-			warehouse_exists = frappe.db.exists("Warehouse", {
-				'name': defaults.default_warehouse,
-				'company': args.company
-			})
-			if defaults.get("default_warehouse") and warehouse_exists:
-				warehouse = defaults.default_warehouse
-
-	else:
-		warehouse = args.warehouse
+	defaults = frappe._dict({
+		'item_defaults': item_defaults,
+		'item_group_defaults': item_group_defaults,
+		'brand_defaults': brand_defaults
+	})
+	
+	warehouse = get_item_warehouse(item, args, overwrite_warehouse, defaults)
 
 	if args.get('doctype') == "Material Request" and not args.get('material_request_type'):
 		args['material_request_type'] = frappe.db.get_value('Material Request',
@@ -272,7 +259,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
 		expense_account = get_asset_category_account(fieldname = "fixed_asset_account", item = args.item_code, company= args.company)
 
 	#Set the UOM to the Default Sales UOM or Default Purchase UOM if configured in the Item Master
-	if not args.uom:
+	if not args.get('uom'):
 		if args.get('doctype') in sales_doctypes:
 			args.uom = item.sales_uom if item.sales_uom else item.stock_uom
 		elif (args.get('doctype') in ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']) or \
@@ -362,6 +349,37 @@ def get_basic_details(args, item, overwrite_warehouse=True):
 
 	return out
 
+def get_item_warehouse(item, args, overwrite_warehouse, defaults={}):
+	if not defaults:
+		defaults = frappe._dict({
+			'item_defaults' : get_item_defaults(item.name, args.company),
+			'item_group_defaults' : get_item_group_defaults(item.name, args.company),
+			'brand_defaults' : get_brand_defaults(item.name, args.company)
+		})
+
+	if overwrite_warehouse or not args.warehouse:
+		warehouse = (
+			args.get("set_warehouse") or
+			defaults.item_defaults.get("default_warehouse") or
+			defaults.item_group_defaults.get("default_warehouse") or
+			defaults.brand_defaults.get("default_warehouse") or
+			args.get('warehouse')
+		)
+
+		if not warehouse:
+			defaults = frappe.defaults.get_defaults() or {}
+			warehouse_exists = frappe.db.exists("Warehouse", {
+				'name': defaults.default_warehouse,
+				'company': args.company
+			})
+			if defaults.get("default_warehouse") and warehouse_exists:
+				warehouse = defaults.default_warehouse
+
+	else:
+		warehouse = args.get('warehouse')
+	
+	return warehouse
+
 def update_barcode_value(out):
 	from erpnext.accounts.doctype.sales_invoice.pos import get_barcode_data
 	barcode_data = get_barcode_data([out])

From 66c4a087038e75bc0e53cf5744fcb960916e93a7 Mon Sep 17 00:00:00 2001
From: Marica <maricadsouza221197@gmail.com>
Date: Fri, 3 Apr 2020 10:07:06 +0530
Subject: [PATCH 92/93] fix: Update Received Qty in Material Request as per
 Stock UOM (#21054)

* fix: Update Received Qty in Material Request as per Stock UOM

* fix: Process each PR only once

* fix: minor suggested changes

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
---
 erpnext/patches.txt                           |  1 +
 ...ty_in_material_request_as_per_stock_uom.py | 30 +++++++++++++++++++
 .../purchase_receipt/purchase_receipt.py      |  8 ++---
 3 files changed, 35 insertions(+), 4 deletions(-)
 create mode 100644 erpnext/patches/v12_0/set_received_qty_in_material_request_as_per_stock_uom.py

diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 8aec8bddb9..bd50ff48f7 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -660,3 +660,4 @@ erpnext.patches.v12_0.set_job_offer_applicant_email
 erpnext.patches.v12_0.create_irs_1099_field_united_states
 erpnext.patches.v12_0.move_bank_account_swift_number_to_bank
 erpnext.patches.v12_0.rename_bank_reconciliation_fields # 2020-01-22
+erpnext.patches.v12_0.set_received_qty_in_material_request_as_per_stock_uom
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/set_received_qty_in_material_request_as_per_stock_uom.py b/erpnext/patches/v12_0/set_received_qty_in_material_request_as_per_stock_uom.py
new file mode 100644
index 0000000000..88c3e2e302
--- /dev/null
+++ b/erpnext/patches/v12_0/set_received_qty_in_material_request_as_per_stock_uom.py
@@ -0,0 +1,30 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	purchase_receipts = frappe.db.sql("""
+		SELECT
+			 parent from `tabPurchase Receipt Item`
+		WHERE
+			material_request is not null
+			AND docstatus=1
+		""",as_dict=1)
+
+	purchase_receipts = set([d.parent for d in purchase_receipts])
+
+	for pr in purchase_receipts:
+		doc = frappe.get_doc("Purchase Receipt", pr)
+		doc.status_updater = [
+			{
+			'source_dt': 'Purchase Receipt Item',
+			'target_dt': 'Material Request Item',
+			'join_field': 'material_request_item',
+			'target_field': 'received_qty',
+			'target_parent_dt': 'Material Request',
+			'target_parent_field': 'per_received',
+			'target_ref_field': 'stock_qty',
+			'source_field': 'stock_qty',
+			'percent_join_field': 'material_request'
+			}
+		]
+		doc.update_qty()
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 3b43690658..c2b38927f7 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -50,8 +50,8 @@ class PurchaseReceipt(BuyingController):
 			'target_field': 'received_qty',
 			'target_parent_dt': 'Material Request',
 			'target_parent_field': 'per_received',
-			'target_ref_field': 'qty',
-			'source_field': 'qty',
+			'target_ref_field': 'stock_qty',
+			'source_field': 'stock_qty',
 			'percent_join_field': 'material_request'
 		}]
 		if cint(self.is_return):
@@ -357,7 +357,7 @@ class PurchaseReceipt(BuyingController):
 		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):
@@ -628,7 +628,7 @@ def get_item_account_wise_additional_cost(purchase_document):
 
 	if not landed_cost_vouchers:
 		return
-	
+
 	item_account_wise_cost = {}
 
 	for lcv in landed_cost_vouchers:

From 3251e1304c9c46b5c4f74fd35765eda1ba090136 Mon Sep 17 00:00:00 2001
From: Shivam Mishra <scmmishra@users.noreply.github.com>
Date: Fri, 3 Apr 2020 10:08:19 +0530
Subject: [PATCH 93/93] fix: lms quiz type error (#21152)

---
 erpnext/public/js/education/lms/quiz.js | 2 +-
 erpnext/www/lms/content.html            | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js
index 52481291e0..91cbbf4d66 100644
--- a/erpnext/public/js/education/lms/quiz.js
+++ b/erpnext/public/js/education/lms/quiz.js
@@ -29,7 +29,7 @@ class Quiz {
 			this.questions.push(question)
 			this.wrapper.appendChild(question_wrapper);
 		})
-		if (data.activity.is_complete) {
+		if (data.activity && data.activity.is_complete) {
 			this.disable()
 			let indicator = 'red'
 			let message = 'Your are not allowed to attempt the quiz again.'
diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html
index 5607c0814d..cdc71412c4 100644
--- a/erpnext/www/lms/content.html
+++ b/erpnext/www/lms/content.html
@@ -63,7 +63,7 @@
 		</a>
 	</div>
 	<div>
-		<h1>{{ content.name }} <span class="small text-muted">({{ position + 1 }}/{{length}})</span></h1>
+		<h2>{{ content.name }} <span class="small text-muted">({{ position + 1 }}/{{length}})</span></h2>
 	</div>
 {% endmacro %}