Merge branch 'develop' into employee-util-report
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index 78c2f5a..824b74e 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -85,10 +85,9 @@
         run: |
           cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
           cd ${GITHUB_WORKSPACE}
-          pip install coveralls==2.2.0
-          pip install coverage==4.5.4
-          coveralls
+          pip install coveralls==3.0.1
+          pip install coverage==5.5
+          coveralls --service=github
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
-
diff --git a/README.md b/README.md
index bb592ae..0a556f5 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,10 @@
 
 ---
 
+### Containerized Installation
+
+Use docker to deploy ERPNext in production or for development of [Frappe](https://github.com/frappe/frappe) apps. See https://github.com/frappe/frappe_docker for more details.
+
 ### Full Install
 
 The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details.
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 199a183..4da0605 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
 from erpnext.hooks import regional_overrides
 from frappe.utils import getdate
 
-__version__ = '13.0.0-dev'
+__version__ = '13.1.0'
 
 def get_default_company(user=None):
 	'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
index 10f660a..f7d471b 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
@@ -78,8 +78,7 @@
 				if (
 					frm.doc.bank_account &&
 					frm.doc.bank_statement_from_date &&
-					frm.doc.bank_statement_to_date &&
-					frm.doc.bank_statement_closing_balance
+					frm.doc.bank_statement_to_date
 				) {
 					frm.trigger("render_chart");
 					frm.trigger("render");
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json
index 4837db3..b643e6e 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json
@@ -39,13 +39,13 @@
    "depends_on": "eval: doc.bank_account",
    "fieldname": "bank_statement_from_date",
    "fieldtype": "Date",
-   "label": "Bank Statement From Date"
+   "label": "From Date"
   },
   {
    "depends_on": "eval: doc.bank_statement_from_date",
    "fieldname": "bank_statement_to_date",
    "fieldtype": "Date",
-   "label": "Bank Statement To Date"
+   "label": "To Date"
   },
   {
    "fieldname": "column_break_2",
@@ -63,11 +63,10 @@
    "depends_on": "eval: doc.bank_statement_to_date",
    "fieldname": "bank_statement_closing_balance",
    "fieldtype": "Currency",
-   "label": "Bank Statement Closing Balance",
+   "label": "Closing Balance",
    "options": "Currency"
   },
   {
-   "depends_on": "eval: doc.bank_statement_closing_balance",
    "fieldname": "section_break_1",
    "fieldtype": "Section Break",
    "label": "Reconcile"
@@ -90,7 +89,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-02-02 01:35:53.043578",
+ "modified": "2021-04-21 11:13:49.831769",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Bank Reconciliation Tool",
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js
index 1092f4c..b7b6020 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js
@@ -21,21 +21,17 @@
 
 	refresh: function(frm) {
 		if(frm.doc.docstatus==1) {
-			frappe.db.get_value("Journal Entry Account", {
-				'reference_type': 'Exchange Rate Revaluation',
-				'reference_name': frm.doc.name,
-				'docstatus': 1
-			}, "sum(debit) as sum", (r) =>{
-				let total_amt = 0;
-				frm.doc.accounts.forEach(d=> {
-					total_amt = total_amt + d['new_balance_in_base_currency'];
-				});
-				if(total_amt !== r.sum) {
-					frm.add_custom_button(__('Journal Entry'), function() {
-						return frm.events.make_jv(frm);
-					}, __('Create'));
+			frappe.call({
+				method: 'check_journal_entry_condition',
+				doc: frm.doc,
+				callback: function(r) {
+					if (r.message) {
+						frm.add_custom_button(__('Journal Entry'), function() {
+							return frm.events.make_jv(frm);
+						}, __('Create'));
+					}
 				}
-			}, 'Journal Entry');
+			});			
 		}
 	},
 
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
index c1b8ba7..5619321 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -28,6 +28,23 @@
 			frappe.throw(_("Please select Company and Posting Date to getting entries"))
 
 	@frappe.whitelist()
+	def check_journal_entry_condition(self):
+		total_debit = frappe.db.get_value("Journal Entry Account", {
+			'reference_type': 'Exchange Rate Revaluation',
+			'reference_name': self.name,
+			'docstatus': 1
+		}, "sum(debit) as sum")
+
+		total_amt = 0
+		for d in self.accounts:
+			total_amt = total_amt + d.new_balance_in_base_currency
+
+		if total_amt != total_debit:
+			return True
+		
+		return False
+
+	@frappe.whitelist()
 	def get_accounts_data(self, account=None):
 		accounts = []
 		self.validate_mandatory()
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index ff2c8c2..fefab82 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -592,6 +592,7 @@
 
 			self.validate_total_debit_and_credit()
 
+	@frappe.whitelist()
 	def get_outstanding_invoices(self):
 		self.set('accounts', [])
 		total = 0
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
index a05e598..1065168 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
@@ -16,28 +16,8 @@
 		if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
 			frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
 
-		self.validate_pos_closing()
 		self.validate_pos_invoices()
 
-	def validate_pos_closing(self):
-		user = frappe.db.sql("""
-			SELECT name FROM `tabPOS Closing Entry`
-			WHERE
-				user = %(user)s AND docstatus = 1 AND pos_profile = %(profile)s AND
-				(period_start_date between %(start)s and %(end)s OR period_end_date between %(start)s and %(end)s)
-			""", {
-				'user': self.user,
-				'profile': self.pos_profile,
-				'start': self.period_start_date,
-				'end': self.period_end_date
-			})
-
-		if user:
-			bold_already_exists = frappe.bold(_("already exists"))
-			bold_user = frappe.bold(self.user)
-			frappe.throw(_("POS Closing Entry {} against {} between selected period")
-				.format(bold_already_exists, bold_user), title=_("Invalid Period"))
-
 	def validate_pos_invoices(self):
 		invalid_rows = []
 		for d in self.pos_transactions:
@@ -89,8 +69,8 @@
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
 def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
-	cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'])
-	return [c['user'] for c in cashiers_list]
+	cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'], as_list=1)
+	return [c for c in cashiers_list]
 
 @frappe.whitelist()
 def get_pos_invoices(start, end, pos_profile, user):
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index e614459..1e6a3d1 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -96,30 +96,45 @@
 				if paid_amt and pay.amount != paid_amt:
 					return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
 
+	def validate_pos_reserved_serial_nos(self, item):
+		serial_nos = get_serial_nos(item.serial_no)
+		filters = {"item_code": item.item_code, "warehouse": item.warehouse}
+		if item.batch_no:
+			filters["batch_no"] = item.batch_no
+
+		reserved_serial_nos = get_pos_reserved_serial_nos(filters)
+		invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos]
+
+		bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos))
+		if len(invalid_serial_nos) == 1:
+			frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.")
+						.format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable"))
+		elif invalid_serial_nos:
+			frappe.throw(_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.")
+						.format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable"))
+
+	def validate_delivered_serial_nos(self, item):
+		serial_nos = get_serial_nos(item.serial_no)
+		delivered_serial_nos = frappe.db.get_list('Serial No', {
+			'item_code': item.item_code,
+			'name': ['in', serial_nos],
+			'sales_invoice': ['is', 'set']
+		}, pluck='name')
+
+		if delivered_serial_nos:
+			bold_delivered_serial_nos = frappe.bold(', '.join(delivered_serial_nos))
+			frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another Sales Invoice. Please select valid serial no.")
+						.format(item.idx, bold_delivered_serial_nos), title=_("Item Unavailable"))
+
 	def validate_stock_availablility(self):
 		if self.is_return:
 			return
 
-		allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
-		error_msg = []
+		allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
 		for d in self.get('items'):
-			msg = ""
 			if d.serial_no:
-				filters = { "item_code": d.item_code, "warehouse": d.warehouse }
-				if d.batch_no:
-					filters["batch_no"] = d.batch_no
-				reserved_serial_nos = get_pos_reserved_serial_nos(filters)
-				serial_nos = get_serial_nos(d.serial_no)
-				invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos]
-
-				bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos))
-				if len(invalid_serial_nos) == 1:
-					msg = (_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.")
-								.format(d.idx, bold_invalid_serial_nos))
-				elif invalid_serial_nos:
-					msg = (_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.")
-								.format(d.idx, bold_invalid_serial_nos))
-
+				self.validate_pos_reserved_serial_nos(d)
+				self.validate_delivered_serial_nos(d)
 			else:
 				if allow_negative_stock:
 					return
@@ -127,15 +142,11 @@
 				available_stock = get_stock_availability(d.item_code, d.warehouse)
 				item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
 				if flt(available_stock) <= 0:
-					msg = (_('Row #{}: Item Code: {} is not available under warehouse {}.').format(d.idx, item_code, warehouse))
+					frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.')
+								.format(d.idx, item_code, warehouse), title=_("Item Unavailable"))
 				elif flt(available_stock) < flt(d.qty):
-					msg = (_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.')
-								.format(d.idx, item_code, warehouse, qty))
-			if msg:
-				error_msg.append(msg)
-
-		if error_msg:
-			frappe.throw(error_msg, title=_("Item Unavailable"), as_list=True)
+					frappe.throw(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.')
+								.format(d.idx, item_code, warehouse, available_stock), title=_("Item Unavailable"))
 
 	def validate_serialised_or_batched_item(self):
 		error_msg = []
@@ -202,9 +213,8 @@
 		for d in self.get("items"):
 			is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
 			if not is_stock_item:
-				frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ").format(
-					d.idx, frappe.bold(d.item_code)
-				), title=_("Invalid Item"))
+				frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ")
+					.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
 
 	def validate_mode_of_payment(self):
 		if len(self.payments) == 0:
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index 6d388c4..6172796 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -10,10 +10,12 @@
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
 from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
 
 class TestPOSInvoice(unittest.TestCase):
 	@classmethod
 	def setUpClass(cls):
+		make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100)
 		frappe.db.sql("delete from `tabTax Rule`")
 
 	def tearDown(self):
@@ -320,6 +322,34 @@
 
 		self.assertRaises(frappe.ValidationError, pos2.insert)
 
+	def test_delivered_serialized_item_transaction(self):
+		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
+		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+		se = make_serialized_item(company='_Test Company',
+			target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC')
+
+		serial_nos = get_serial_nos(se.get("items")[0].serial_no)
+
+		si = create_sales_invoice(company='_Test Company', debit_to='Debtors - _TC',
+			account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
+			expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
+			item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
+
+		si.get("items")[0].serial_no = serial_nos[0]
+		si.insert()
+		si.submit()
+
+		pos2 = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
+			account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
+			expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
+			item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
+
+		pos2.get("items")[0].serial_no = serial_nos[0]
+		pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
+
+		self.assertRaises(frappe.ValidationError, pos2.insert)
+
 	def test_loyalty_points(self):
 		from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records
 		from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index b91a7a5..d23b952 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -173,7 +173,7 @@
 		if parenttype in ["Customer Group", "Item Group", "Territory"]:
 			parent_field = "parent_{0}".format(frappe.scrub(parenttype))
 			root_name = frappe.db.get_list(parenttype,
-				{"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1)
+				{"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1, ignore_permissions=True)
 
 			if root_name and root_name[0][0]:
 				parent_groups.append(root_name[0][0])
diff --git a/erpnext/change_log/v13/v13_1_0.md b/erpnext/change_log/v13/v13_1_0.md
new file mode 100644
index 0000000..d991034
--- /dev/null
+++ b/erpnext/change_log/v13/v13_1_0.md
@@ -0,0 +1,129 @@
+# Version 13.1.0 Release Notes
+
+### Features
+
+- Recursive pricing rule ([#24922](https://github.com/frappe/erpnext/pull/24922))
+- Discount configuration on early payments ([#24586](https://github.com/frappe/erpnext/pull/24586))
+- Bulk e-invoice generation ([#24969](https://github.com/frappe/erpnext/pull/24969))
+- Employee Self Service ([#24408](https://github.com/frappe/erpnext/pull/24408))
+- Share doc with employee approvers if they don't have access ([#25190](https://github.com/frappe/erpnext/pull/25190))
+- Price margin in buying ([#24685](https://github.com/frappe/erpnext/pull/24685))
+- Allow changing Work Stations in Work Order & Job Card ([#24897](https://github.com/frappe/erpnext/pull/24897))
+- Add document type field for e-invoicing (Italy) ([#25256](https://github.com/frappe/erpnext/pull/25256))
+- Add checkbox for disabling leave notification in HR Settings ([#24877](https://github.com/frappe/erpnext/pull/24877))
+- Enhancements in Material Request Plan Item in Production Plan ([#25025](https://github.com/frappe/erpnext/pull/25025))
+
+
+### Fixes and Enhancements
+- Mode of payments disappear on loading draft pos invoice ([#24917](https://github.com/frappe/erpnext/pull/24917))
+- Sales order not saving due type mismatch in promo scheme (#24748) ([#25222](https://github.com/frappe/erpnext/pull/25222))
+- Zero amount completed delivery notes being shown in Sales Invoice get items ([#25317](https://github.com/frappe/erpnext/pull/25317))
+- Incorrect status creating PR from PO after creating PI ([#25109](https://github.com/frappe/erpnext/pull/25109))
+- Precision and formatted document for stock level in item dashboard. ([#24921](https://github.com/frappe/erpnext/pull/24921))
+- Precision issues while allocating advance amount ([#25086](https://github.com/frappe/erpnext/pull/25086))
+- Round off final tax amount instead of current tax amount ([#25188](https://github.com/frappe/erpnext/pull/25188))
+- Redesign fixes ([#24896](https://github.com/frappe/erpnext/pull/24896))
+- TDS check getting checked after reload ([#24972](https://github.com/frappe/erpnext/pull/24972))
+- Github Action not failing when tests fail ([#24867](https://github.com/frappe/erpnext/pull/24867))
+- Calculate 80g certificate amount on validate for memberships ([#24925](https://github.com/frappe/erpnext/pull/24925))
+- Purchase from registered composition dealer ([#25040](https://github.com/frappe/erpnext/pull/25040))
+- Reduce number of queries for checking if future SL entry exists ([#24881](https://github.com/frappe/erpnext/pull/24881))
+- Remove unwanted parameter in calculate_rate_and_amount ([#24883](https://github.com/frappe/erpnext/pull/24883))
+- Membership renewal validation ([#24963](https://github.com/frappe/erpnext/pull/24963))
+- Not able to save material request ([#25112](https://github.com/frappe/erpnext/pull/25112))
+- POS print receipt ([#25330](https://github.com/frappe/erpnext/pull/25330))
+- Supplier was not able to Submit RFQ due to insufficient permission ([#24622](https://github.com/frappe/erpnext/pull/24622))
+- Unequal debit and credit issue on RCM Invoice ([#24836](https://github.com/frappe/erpnext/pull/24836))
+- Picked Qty conversion from Stock Qty to Qty while creating DN from Pick List ([#25105](https://github.com/frappe/erpnext/pull/25105))
+- Salary Structure object has no attribute set_totals ([#25113](https://github.com/frappe/erpnext/pull/25113))
+- Incorrect Nil Exempt and Non GST amount in GSTR3B report ([#24916](https://github.com/frappe/erpnext/pull/24916))
+- Add method for regional round off account back ([#24893](https://github.com/frappe/erpnext/pull/24893))
+- Employee profile pic upload access for erpnext user ([#25022](https://github.com/frappe/erpnext/pull/25022))
+- Make filters for payroll entry ([#25386](https://github.com/frappe/erpnext/pull/25386))
+- Fix dynamically changing grid properties ([#25310](https://github.com/frappe/erpnext/pull/25310))
+- Consider paid repayment entries in subsequent loan repayments ([#25271](https://github.com/frappe/erpnext/pull/25271))
+- Allow duplicate additional salaries ([#24842](https://github.com/frappe/erpnext/pull/24842))
+- Object referencing the same address issue ([#25159](https://github.com/frappe/erpnext/pull/25159))
+- Validating party currency with doc currency ([#24318](https://github.com/frappe/erpnext/pull/24318))
+- Non Profit fixes ([#25060](https://github.com/frappe/erpnext/pull/25060))
+- Additional Salary component amount not getting set ([#25356](https://github.com/frappe/erpnext/pull/25356))
+- Allow user to update exchange rate in Multi-currency LCV ([#24912](https://github.com/frappe/erpnext/pull/24912))
+- Allow creating stock entry based on work order for customer provided items ([#24885](https://github.com/frappe/erpnext/pull/24885))
+- Create property setters for shorter naming series on setup ([#25128](https://github.com/frappe/erpnext/pull/25128))
+- Add GST category field in Delivery Note ([#25053](https://github.com/frappe/erpnext/pull/25053))
+- Ignore Permission for Leave Ledger Entry ([#25172](https://github.com/frappe/erpnext/pull/25172))
+- Pending shortfall update  on processing loan security shortfall ([#24971](https://github.com/frappe/erpnext/pull/24971))
+- Added flag for dont_fetch_price_list_rate in transaction ([#25041](https://github.com/frappe/erpnext/pull/25041))
+- Exchange Rate not getting set in Salary Slip ([#25004](https://github.com/frappe/erpnext/pull/25004))
+- Repost not completed backdated transactions ([#24980](https://github.com/frappe/erpnext/pull/24980))
+- frappe.whitelist for doc methods ([#25230](https://github.com/frappe/erpnext/pull/25230))
+- Opportunity-quotation mapping order status ([#25001](https://github.com/frappe/erpnext/pull/25001))
+- GST on freight charge in e-invoicing ([#25000](https://github.com/frappe/erpnext/pull/25000))
+- Role to override maintain same rate check in transactions ([#25193](https://github.com/frappe/erpnext/pull/25193))
+- Added blank option for status in report related to issue ([#25082](https://github.com/frappe/erpnext/pull/25082))
+- Cashier query in POS Opening/Closing Entry ([#25399](https://github.com/frappe/erpnext/pull/25399))
+- Lead Source's module ([#24583](https://github.com/frappe/erpnext/pull/24583))
+- Hide alt tag if item is not shown in website ([#24937](https://github.com/frappe/erpnext/pull/24937))
+- Ignore Customer Group Perm on All Products page ([#25397](https://github.com/frappe/erpnext/pull/25397))
+- Give first preference to loan security on repayment ([#25212](https://github.com/frappe/erpnext/pull/25212))
+- Add shortfall ratio in Loan Security Shortfall ([#25138](https://github.com/frappe/erpnext/pull/25138))
+- Condition for SLA status banner ([#25261](https://github.com/frappe/erpnext/pull/25261))
+- Component amount calculation based on formula with abbr not working ([#25117](https://github.com/frappe/erpnext/pull/25117))
+- Remove gst name validation for purchase Invoice ([#25235](https://github.com/frappe/erpnext/pull/25235))
+- Do not fetch stopped MR in production plan ([#25063](https://github.com/frappe/erpnext/pull/25063))
+- Backport missing commits to develop branch ([#25305](https://github.com/frappe/erpnext/pull/25305))
+- UOM length unit in global setup list is empty ([#24855](https://github.com/frappe/erpnext/pull/24855))
+- Round total quantity in job card ([#25240](https://github.com/frappe/erpnext/pull/25240))
+- Default total_estimated_cost to zero ([#24939](https://github.com/frappe/erpnext/pull/24939))
+- Serial no refresh issue ([#25127](https://github.com/frappe/erpnext/pull/25127))
+- Correct calculation for discount amount when margin is set ([#25179](https://github.com/frappe/erpnext/pull/25179))
+- Get correct holiday list when calculating dates; test fixes ([#24901](https://github.com/frappe/erpnext/pull/24901))
+- POS print receipt ([#24924](https://github.com/frappe/erpnext/pull/24924))
+- Condition for setting agreement status ([#25255](https://github.com/frappe/erpnext/pull/25255))
+- Loan Repayment entry cancellation on salary slip cancel ([#24879](https://github.com/frappe/erpnext/pull/24879))
+- Add company validation for e-invoicing ([#25349](https://github.com/frappe/erpnext/pull/25349))
+- Query values incorrectly escaped while back updating Quality Inspection ([#25118](https://github.com/frappe/erpnext/pull/25118))
+- Update Bin via Update Item on Purchase/Sales Order  ([#23509](https://github.com/frappe/erpnext/pull/23509))
+- Declare data before assigning ([#25287](https://github.com/frappe/erpnext/pull/25287))
+- Do not set standard link in Sales Invoice as custom ([#25096](https://github.com/frappe/erpnext/pull/25096))
+- Hide serial and batch selector in Stock Entry ([#25107](https://github.com/frappe/erpnext/pull/25107))
+- Taxable value including Freight and Forwarding charges in GSTR-1 Report ([#25290](https://github.com/frappe/erpnext/pull/25290))
+- Remove nonexistent method from pick list ([#25279](https://github.com/frappe/erpnext/pull/25279))
+- Allow zero valuation in stock reconciliation ([#24888](https://github.com/frappe/erpnext/pull/24888))
+- Place of supply of e-invoicing ([#25148](https://github.com/frappe/erpnext/pull/25148))
+- Delivery note print error ([#25080](https://github.com/frappe/erpnext/pull/25080))
+- Fix Payment references from disappearing on adding Cost Center in Payment Entry ([#24831](https://github.com/frappe/erpnext/pull/24831))
+- Company field in Warehouse ([#25196](https://github.com/frappe/erpnext/pull/25196))
+- Available employee for selection ([#25378](https://github.com/frappe/erpnext/pull/25378))
+- Cannot set qty to less than zero ([#25258](https://github.com/frappe/erpnext/pull/25258))
+- Don't delete mode of payment account details while deleting comp… ([#25217](https://github.com/frappe/erpnext/pull/25217))
+- Exclude current doc while validation. ([#24914](https://github.com/frappe/erpnext/pull/24914))
+- POS Opening Entry with empty balance detail rows ([#24876](https://github.com/frappe/erpnext/pull/24876))
+- Unable to submit stock entry ([#25033](https://github.com/frappe/erpnext/pull/25033))
+- BOM cost test case ([#25242](https://github.com/frappe/erpnext/pull/25242))
+- Filter for employees in salary slip ([#25361](https://github.com/frappe/erpnext/pull/25361))
+- Added correct path in hooks ([#24862](https://github.com/frappe/erpnext/pull/24862))
+- Patch regional fields for old companies ([#24988](https://github.com/frappe/erpnext/pull/24988))
+- consolidated sales invoice posting date ([#25119](https://github.com/frappe/erpnext/pull/25119))
+- Don't set "Company:company:default_currency" as default for currency link fields ([#25095](https://github.com/frappe/erpnext/pull/25095))
+- Healthcare lab module rename fields ([#25276](https://github.com/frappe/erpnext/pull/25276))
+- Error message compensatory leave request ([#25206](https://github.com/frappe/erpnext/pull/25206))
+- Adding company link to e invoice settings patch condition ([#25301](https://github.com/frappe/erpnext/pull/25301))
+- Membership and Donation API fixes ([#24900](https://github.com/frappe/erpnext/pull/24900))
+- Set correct ack no. on irn generation ([#25251](https://github.com/frappe/erpnext/pull/25251))
+- Report Issue Summary fix for zero issues ([#24934](https://github.com/frappe/erpnext/pull/24934))
+- Validation msg for TransDocNo e-invoicing ([#25121](https://github.com/frappe/erpnext/pull/25121))
+- Correct state code for 'Other Territory' ([#24993](https://github.com/frappe/erpnext/pull/24993))
+- Commit individual SLE rename for large datasets (develop) ([#25084](https://github.com/frappe/erpnext/pull/25084))
+- Remove shipping address GSTIN validation for e-invoice ([#25153](https://github.com/frappe/erpnext/pull/25153))
+- Period list for exponential smoothing forecasting report ([#24982](https://github.com/frappe/erpnext/pull/24982))
+- Customer creation from shopping cart ([#25136](https://github.com/frappe/erpnext/pull/25136))
+- Simplified logic for additional salary ([#24824](https://github.com/frappe/erpnext/pull/24824))
+- Item wise tax rate for consolidated POS invoice ([#25029](https://github.com/frappe/erpnext/pull/25029))
+- Column width in Recruitment analytics report ([#25003](https://github.com/frappe/erpnext/pull/25003))
+- Filter Bank Account drop-down list in Bank Reconciliation Tool ([#24873](https://github.com/frappe/erpnext/pull/24873))
+- Payroll issues ([#24540](https://github.com/frappe/erpnext/pull/24540))
+- PO not created against all selected suppliers (drop shipping) ([#24863](https://github.com/frappe/erpnext/pull/24863))
+- Can't multiply sequence by non-int of type 'float' ([#25092](https://github.com/frappe/erpnext/pull/25092))
+- Make Discharge Schedule Date as Datetime ([#24940](https://github.com/frappe/erpnext/pull/24940))
+- Serial no trim issue ([#24949](https://github.com/frappe/erpnext/pull/24949))
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py
index 899b7ff..9c59840 100644
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py
+++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py
@@ -17,10 +17,12 @@
 		else:
 			self.enable_sync = 0
 
+	@frappe.whitelist()
 	def get_products_details(self):
 		if self.enable_amazon == 1:
 			frappe.enqueue('erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_methods.get_products_details')
 
+	@frappe.whitelist()
 	def get_order_details(self):
 		if self.enable_amazon == 1:
 			after_date = dateutil.parser.parse(self.after_date).strftime("%Y-%m-%d")
@@ -40,4 +42,4 @@
 			fieldtype='Data', insert_after='title', read_only=1, print_hide=1)]
 	}
 
-	create_custom_fields(custom_fields)
\ No newline at end of file
+	create_custom_fields(custom_fields)
diff --git a/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json
index 818f125..3fa98b6 100644
--- a/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json
+++ b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json
@@ -1,206 +1,64 @@
 {
- "allow_copy": 0, 
- "allow_events_in_timeline": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2018-07-12 12:07:36.932333", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "creation": "2018-07-12 12:07:36.932333",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "service_unit",
+  "check_in",
+  "left",
+  "check_out",
+  "invoiced"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "service_unit", 
-   "fieldtype": "Link", 
-   "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": "Healthcare Service Unit", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Healthcare Service Unit", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "service_unit",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Healthcare Service Unit",
+   "options": "Healthcare Service Unit",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "check_in", 
-   "fieldtype": "Datetime", 
-   "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": "Check In", 
-   "length": 0, 
-   "no_copy": 0, 
-   "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": "check_in",
+   "fieldtype": "Datetime",
+   "in_list_view": 1,
+   "label": "Check In"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "left", 
-   "fieldtype": "Check", 
-   "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": "Left", 
-   "length": 0, 
-   "no_copy": 0, 
-   "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": 1, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "default": "0",
+   "fieldname": "left",
+   "fieldtype": "Check",
+   "label": "Left",
+   "read_only": 1,
+   "search_index": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "check_out", 
-   "fieldtype": "Datetime", 
-   "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": "Check Out", 
-   "length": 0, 
-   "no_copy": 0, 
-   "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": "check_out",
+   "fieldtype": "Datetime",
+   "label": "Check Out"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "0", 
-   "fieldname": "invoiced", 
-   "fieldtype": "Check", 
-   "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": "Invoiced", 
-   "length": 0, 
-   "no_copy": 0, 
-   "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
+   "default": "0",
+   "fieldname": "invoiced",
+   "fieldtype": "Check",
+   "label": "Invoiced",
+   "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": 1, 
- "max_attachments": 0, 
- "modified": "2018-11-04 03:33:26.958713", 
- "modified_by": "Administrator", 
- "module": "Healthcare", 
- "name": "Inpatient Occupancy", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Healthcare", 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0, 
- "track_views": 0
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-03-18 15:08:54.634132",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Inpatient Occupancy",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "restrict_to_domain": "Healthcare",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
index aaf0e85..0e1c2ba 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
@@ -185,7 +185,7 @@
    "fieldtype": "Datetime",
    "in_list_view": 1,
    "label": "Admitted Datetime",
-   "read_only": 1
+   "permlevel": 2
   },
   {
    "depends_on": "eval:(doc.expected_length_of_stay > 0)",
@@ -312,7 +312,7 @@
    "fieldname": "inpatient_occupancies",
    "fieldtype": "Table",
    "options": "Inpatient Occupancy",
-   "read_only": 1
+   "permlevel": 2
   },
   {
    "fieldname": "btn_transfer",
@@ -407,12 +407,12 @@
    "fieldname": "discharge_datetime",
    "fieldtype": "Datetime",
    "label": "Discharge Date",
-   "read_only": 1
+   "permlevel": 2
   }
  ],
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2021-03-18 14:44:11.689956",
+ "modified": "2021-03-18 15:59:17.318988",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Inpatient Record",
@@ -465,6 +465,37 @@
    "read": 1,
    "report": 1,
    "role": "Nursing User"
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "permlevel": 2,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Healthcare Administrator",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "permlevel": 2,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Physician",
+   "share": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "permlevel": 2,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Nursing User",
+   "share": 1
   }
  ],
  "restrict_to_domain": "Healthcare",
diff --git a/erpnext/healthcare/doctype/therapy_type/therapy_type.py b/erpnext/healthcare/doctype/therapy_type/therapy_type.py
index 6c825b8..3f6a36a 100644
--- a/erpnext/healthcare/doctype/therapy_type/therapy_type.py
+++ b/erpnext/healthcare/doctype/therapy_type/therapy_type.py
@@ -50,6 +50,7 @@
 
 		self.db_set('change_in_item', 0)
 
+	@frappe.whitelist()
 	def add_exercises(self):
 		exercises = self.get_exercises_for_body_parts()
 		last_idx = max([cint(d.idx) for d in self.get('exercises')] or [0,])
diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py
index 6df7bc8..8af8cea 100644
--- a/erpnext/hr/doctype/holiday_list/holiday_list.py
+++ b/erpnext/hr/doctype/holiday_list/holiday_list.py
@@ -16,6 +16,7 @@
 		self.validate_days()
 		self.total_holidays = len(self.holidays)
 
+	@frappe.whitelist()
 	def get_weekly_off_dates(self):
 		self.validate_values()
 		date_list = self.get_weekly_off_date_list(self.from_date, self.to_date)
@@ -61,6 +62,7 @@
 
 		return date_list
 
+	@frappe.whitelist()
 	def clear_table(self):
 		self.set('holidays', [])
 
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json
index 09666c5..3553229 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.json
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.json
@@ -10,6 +10,7 @@
   "retirement_age",
   "emp_created_by",
   "column_break_4",
+  "standard_working_hours",
   "stop_birthday_reminders",
   "expense_approver_mandatory_in_expense_claim",
   "leave_settings",
@@ -143,13 +144,19 @@
    "fieldname": "send_leave_notification",
    "fieldtype": "Check",
    "label": "Send Leave Notification"
+  },
+  {
+   "default": "8",
+   "fieldname": "standard_working_hours",
+   "fieldtype": "Int",
+   "label": "Standard Working Hours"
   }
  ],
  "icon": "fa fa-cog",
  "idx": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-03-14 02:04:22.907159",
+ "modified": "2021-04-16 15:45:18.467699",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "HR Settings",
diff --git a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py
index 57e61b5..7401402 100644
--- a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py
+++ b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py
@@ -29,6 +29,7 @@
 				frappe.throw(_("{0} is required").format(self.meta.get_label(f)))
 		self.validate_from_to_dates('from_date', 'to_date')
 
+	@frappe.whitelist()
 	def allocate_leave(self):
 		self.validate_values()
 		leave_allocated_for = []
diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json
index 4f8ceb0..c9f23ca 100644
--- a/erpnext/loan_management/doctype/loan/loan.json
+++ b/erpnext/loan_management/doctype/loan/loan.json
@@ -360,13 +360,14 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-10 09:28:21.946972",
+ "modified": "2021-04-19 18:10:32.360818",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.json b/erpnext/loan_management/doctype/loan_application/loan_application.json
index a353a77..f91fa07 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.json
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.json
@@ -212,15 +212,17 @@
    "read_only": 1
   }
  ],
+ "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-03-01 10:21:44.413353",
+ "modified": "2021-04-19 18:24:40.119647",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Application",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
@@ -235,6 +237,7 @@
    "write": 1
   },
   {
+   "amend": 1,
    "create": 1,
    "delete": 1,
    "email": 1,
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
index 662c626..7811d56 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
@@ -154,13 +154,14 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-10 10:03:41.502210",
+ "modified": "2021-04-19 18:09:32.175355",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Disbursement",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
@@ -175,6 +176,7 @@
    "write": 1
   },
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json
index 185bf7a..30e2328 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json
@@ -185,13 +185,14 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-01-10 00:15:21.544140",
+ "modified": "2021-04-19 18:26:38.871889",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Interest Accrual",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
@@ -206,6 +207,7 @@
    "write": 1
   },
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
index 8fbf233..6479853 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
@@ -248,13 +248,14 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-10 10:00:31.859076",
+ "modified": "2021-04-19 18:10:00.935364",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Repayment",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
@@ -269,6 +270,7 @@
    "write": 1
   },
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
index 7dd5725..18bd4ae 100644
--- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
+++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
@@ -160,13 +160,14 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-09-04 22:38:19.894488",
+ "modified": "2021-04-19 18:23:16.953305",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Security Pledge",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
@@ -181,6 +182,7 @@
    "write": 1
   },
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json
index 2e2b251..92923bb 100644
--- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json
+++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json
@@ -126,13 +126,14 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-09-04 22:39:57.756146",
+ "modified": "2021-04-19 18:12:01.401744",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Security Unpledge",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
@@ -147,6 +148,7 @@
    "write": 1
   },
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json
index 3ef5304..c0a5d2c 100644
--- a/erpnext/loan_management/doctype/loan_type/loan_type.json
+++ b/erpnext/loan_management/doctype/loan_type/loan_type.json
@@ -154,13 +154,14 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-01-17 06:51:26.082879",
+ "modified": "2021-04-19 18:10:57.368490",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Type",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json
index 4617a62..4ca9ef1 100644
--- a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json
+++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json
@@ -116,13 +116,14 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-26 07:13:43.663924",
+ "modified": "2021-04-19 18:11:27.759862",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Write Off",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
@@ -137,6 +138,7 @@
    "write": 1
   },
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py
index a78f802..9af0a8d 100644
--- a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py
+++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py
@@ -38,16 +38,37 @@
 			""".format(doctype), {'parentfield': parentfield})
 
 		# copy renamed child table fields (fields were already renamed in old doctype json, hence sql)
-		frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_name = test_name""")
-		frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_event = test_event""")
-		frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_uom = test_uom""")
-		frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_comment = test_comment""")
-		frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""")
-		frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""")
-		frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""")
-		frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_template = test_template""")
-		frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_description = test_description""")
-		frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_rate = test_rate""")
+		rename_fields = {
+			'lab_test_name': 'test_name',
+			'lab_test_event': 'test_event',
+			'lab_test_uom': 'test_uom',
+			'lab_test_comment': 'test_comment'
+		}
+
+		for new, old in rename_fields.items():
+			if frappe.db.has_column('Normal Test Result', old):
+				frappe.db.sql("""UPDATE `tabNormal Test Result` SET {} = {}"""
+					.format(new, old))
+
+		if frappe.db.has_column('Normal Test Template', 'test_event'):
+			frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""")
+
+		if frappe.db.has_column('Normal Test Template', 'test_uom'):
+			frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""")
+
+		if frappe.db.has_column('Descriptive Test Result', 'test_particulars'):
+			frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""")
+
+		rename_fields = {
+			'lab_test_template': 'test_template',
+			'lab_test_description': 'test_description',
+			'lab_test_rate': 'test_rate'
+		}
+
+		for new, old in rename_fields.items():
+			if frappe.db.has_column('Lab Test Group Template', old):
+				frappe.db.sql("""UPDATE `tabLab Test Group Template` SET {} = {}"""
+					.format(new, old))
 
 		# rename field
 		frappe.reload_doc('healthcare', 'doctype', 'lab_test')
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js
index b539b1b..e00bd87 100755
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.js
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js
@@ -133,8 +133,6 @@
 			title: __("Assign to Employees"),
 			fields: [
 				{fieldname: "sec_break", fieldtype: "Section Break", label: __("Filter Employees By (Optional)")},
-				{fieldname: "company", fieldtype: "Link", options: "Company", label: __("Company"), default: frm.doc.company, read_only:1},
-				{fieldname: "currency", fieldtype: "Link", options: "Currency", label: __("Currency"), default: frm.doc.currency, read_only:1},
 				{fieldname: "grade", fieldtype: "Link", options: "Employee Grade", label: __("Employee Grade")},
 				{fieldname:'department', fieldtype:'Link', options: 'Department', label: __('Department')},
 				{fieldname:'designation', fieldtype:'Link', options: 'Designation', label: __('Designation')},
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.py b/erpnext/payroll/doctype/salary_structure/salary_structure.py
index 352c180..58c445f 100644
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.py
@@ -88,7 +88,7 @@
 		return employees
 
 	@frappe.whitelist()
-	def assign_salary_structure(self, grade=None, department=None, designation=None,employee=None,
+	def assign_salary_structure(self, grade=None, department=None, designation=None, employee=None,
 			payroll_payable_account=None, from_date=None, base=None, variable=None, income_tax_slab=None):
 		employees = self.get_employees(company= self.company, grade= grade,department= department,designation= designation,name=employee)
 
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index f7c764e..d21ac0f 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -151,11 +151,11 @@
 		settings.save()
 
 
-def make_salary_structure_for_timesheet(employee):
+def make_salary_structure_for_timesheet(employee, company=None):
 	salary_structure_name = "Timesheet Salary Structure Test"
 	frequency = "Monthly"
 
-	salary_structure = make_salary_structure(salary_structure_name, frequency, dont_submit=True)
+	salary_structure = make_salary_structure(salary_structure_name, frequency, company=company, dont_submit=True)
 	salary_structure.salary_component = "Timesheet Component"
 	salary_structure.salary_slip_based_on_timesheet = 1
 	salary_structure.hour_rate = 50.0
diff --git a/erpnext/projects/report/project_profitability/__init__.py b/erpnext/projects/report/project_profitability/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/projects/report/project_profitability/__init__.py
diff --git a/erpnext/projects/report/project_profitability/project_profitability.js b/erpnext/projects/report/project_profitability/project_profitability.js
new file mode 100644
index 0000000..13ae19b
--- /dev/null
+++ b/erpnext/projects/report/project_profitability/project_profitability.js
@@ -0,0 +1,48 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Project Profitability"] = {
+	"filters": [
+		{
+			"fieldname": "company",
+			"label": __("Company"),
+			"fieldtype": "Link",
+			"options": "Company",
+			"default": frappe.defaults.get_user_default("Company"),
+			"reqd": 1
+		},
+		{
+			"fieldname": "start_date",
+			"label": __("Start Date"),
+			"fieldtype": "Date",
+			"reqd": 1,
+			"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1)
+		},
+		{
+			"fieldname": "end_date",
+			"label": __("End Date"),
+			"fieldtype": "Date",
+			"reqd": 1,
+			"default": frappe.datetime.now_date()
+		},
+		{
+			"fieldname": "customer_name",
+			"label": __("Customer"),
+			"fieldtype": "Link",
+			"options": "Customer"
+		},
+		{
+			"fieldname": "employee",
+			"label": __("Employee"),
+			"fieldtype": "Link",
+			"options": "Employee"
+		},
+		{
+			"fieldname": "project",
+			"label": __("Project"),
+			"fieldtype": "Link",
+			"options": "Project"
+		}
+	]
+};
diff --git a/erpnext/projects/report/project_profitability/project_profitability.json b/erpnext/projects/report/project_profitability/project_profitability.json
new file mode 100644
index 0000000..0b092cd
--- /dev/null
+++ b/erpnext/projects/report/project_profitability/project_profitability.json
@@ -0,0 +1,44 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-04-16 15:50:28.914872",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-04-16 15:50:48.490866",
+ "modified_by": "Administrator",
+ "module": "Projects",
+ "name": "Project Profitability",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Timesheet",
+ "report_name": "Project Profitability",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "HR User"
+  },
+  {
+   "role": "Accounts User"
+  },
+  {
+   "role": "Employee"
+  },
+  {
+   "role": "Projects User"
+  },
+  {
+   "role": "Manufacturing User"
+  },
+  {
+   "role": "Employee Self Service"
+  },
+  {
+   "role": "HR Manager"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py
new file mode 100644
index 0000000..5ad2d85
--- /dev/null
+++ b/erpnext/projects/report/project_profitability/project_profitability.py
@@ -0,0 +1,210 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+	columns, data = [], []
+	data = get_data(filters)
+	columns = get_columns()
+	charts = get_chart_data(data)
+	return columns, data, None, charts
+
+def get_data(filters):
+	data = get_rows(filters)
+	data = calculate_cost_and_profit(data)
+	return data
+
+def get_rows(filters):
+	conditions = get_conditions(filters)
+	standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours")
+	if not standard_working_hours:
+		msg = _("The metrics for this report are calculated based on the Standard Working Hours. Please set {0} in {1}.").format(
+			frappe.bold("Standard Working Hours"), frappe.utils.get_link_to_form("HR Settings", "HR Settings"))
+
+		frappe.msgprint(msg)
+		return []
+
+	sql = """
+			SELECT
+				*
+			FROM
+				(SELECT
+					si.customer_name,si.base_grand_total,
+					si.name as voucher_no,tabTimesheet.employee,
+					tabTimesheet.title as employee_name,tabTimesheet.parent_project as project,
+					tabTimesheet.start_date,tabTimesheet.end_date,
+					tabTimesheet.total_billed_hours,tabTimesheet.name as timesheet,
+					ss.base_gross_pay,ss.total_working_days,
+					tabTimesheet.total_billed_hours/(ss.total_working_days * {0}) as utilization
+					FROM
+						`tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet
+						join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name
+						join `tabSales Invoice` as si on si.name = sit.parent and si.status != "Cancelled"
+						join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """.format(standard_working_hours)
+	if conditions:
+		sql += """
+				WHERE
+					{0}) as t""".format(conditions)
+	return frappe.db.sql(sql,filters, as_dict=True)
+
+def calculate_cost_and_profit(data):
+	for row in data:
+		row.fractional_cost = row.base_gross_pay * row.utilization
+		row.profit = row.base_grand_total - row.base_gross_pay * row.utilization
+	return data
+
+def get_conditions(filters):
+	conditions = []
+
+	if filters.get("company"):
+		conditions.append("tabTimesheet.company={0}".format(frappe.db.escape(filters.get("company"))))
+
+	if filters.get("start_date"):
+		conditions.append("tabTimesheet.start_date>='{0}'".format(filters.get("start_date")))
+
+	if filters.get("end_date"):
+		conditions.append("tabTimesheet.end_date<='{0}'".format(filters.get("end_date")))
+
+	if filters.get("customer_name"):
+		conditions.append("si.customer_name={0}".format(frappe.db.escape(filters.get("customer_name"))))
+
+	if filters.get("employee"):
+		conditions.append("tabTimesheet.employee={0}".format(frappe.db.escape(filters.get("employee"))))
+
+	if filters.get("project"):
+		conditions.append("tabTimesheet.parent_project={0}".format(frappe.db.escape(filters.get("project"))))
+
+	conditions = " and ".join(conditions)
+	return conditions
+
+def get_chart_data(data):
+	if not data:
+		return None
+
+	labels = []
+	utilization = []
+
+	for entry in data:
+		labels.append(entry.get("employee_name") + " - " + str(entry.get("end_date")))
+		utilization.append(entry.get("utilization"))
+
+	charts = {
+		"data": {
+			"labels": labels,
+			"datasets": [
+				{
+					"name": "Utilization",
+					"values": utilization
+				}
+			]
+		},
+		"type": "bar",
+		"colors": ["#84BDD5"]
+	}
+	return charts
+
+def get_columns():
+	return [
+		{
+			"fieldname": "customer_name",
+			"label": _("Customer"),
+			"fieldtype": "Link",
+			"options": "Customer",
+			"width": 150
+		},
+		{
+			"fieldname": "employee",
+			"label": _("Employee"),
+			"fieldtype": "Link",
+			"options": "Employee",
+			"width": 130
+		},
+		{
+			"fieldname": "employee_name",
+			"label": _("Employee Name"),
+			"fieldtype": "Data",
+			"width": 120
+		},
+		{
+			"fieldname": "voucher_no",
+			"label": _("Sales Invoice"),
+			"fieldtype": "Link",
+			"options": "Sales Invoice",
+			"width": 120
+		},
+		{
+			"fieldname": "timesheet",
+			"label": _("Timesheet"),
+			"fieldtype": "Link",
+			"options": "Timesheet",
+			"width": 120
+		},
+		{
+			"fieldname": "project",
+			"label": _("Project"),
+			"fieldtype": "Link",
+			"options": "Project",
+			"width": 100
+		},
+		{
+			"fieldname": "base_grand_total",
+			"label": _("Bill Amount"),
+			"fieldtype": "Currency",
+			"options": "currency",
+			"width": 100
+		},
+		{
+			"fieldname": "base_gross_pay",
+			"label": _("Cost"),
+			"fieldtype": "Currency",
+			"options": "currency",
+			"width": 100
+		},
+		{
+			"fieldname": "profit",
+			"label": _("Profit"),
+			"fieldtype": "Currency",
+			"options": "currency",
+			"width": 100
+		},
+		{
+			"fieldname": "utilization",
+			"label": _("Utilization"),
+			"fieldtype": "Percentage",
+			"width": 100
+		},
+		{
+			"fieldname": "fractional_cost",
+			"label": _("Fractional Cost"),
+			"fieldtype": "Int",
+			"width": 120
+		},
+		{
+			"fieldname": "total_billed_hours",
+			"label": _("Total Billed Hours"),
+			"fieldtype": "Int",
+			"width": 150
+		},
+		{
+			"fieldname": "start_date",
+			"label": _("Start Date"),
+			"fieldtype": "Date",
+			"width": 100
+		},
+		{
+			"fieldname": "end_date",
+			"label": _("End Date"),
+			"fieldtype": "Date",
+			"width": 100
+		},
+		{
+			"label": _("Currency"),
+			"fieldname": "currency",
+			"fieldtype": "Link",
+			"options": "Currency",
+			"width": 80
+		}
+	]
\ No newline at end of file
diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py
new file mode 100644
index 0000000..251b71d
--- /dev/null
+++ b/erpnext/projects/report/project_profitability/test_project_profitability.py
@@ -0,0 +1,56 @@
+from __future__ import unicode_literals
+import unittest
+import frappe
+from frappe.utils import getdate, nowdate
+from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet 
+from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice
+from erpnext.projects.report.project_profitability.project_profitability import execute
+
+class TestProjectProfitability(unittest.TestCase):
+	@classmethod
+	def setUp(self):
+		emp = make_employee('test_employee_9@salary.com', company='_Test Company')
+		if not frappe.db.exists('Salary Component', 'Timesheet Component'):
+			frappe.get_doc({'doctype': 'Salary Component', 'salary_component': 'Timesheet Component'}).insert()
+		make_salary_structure_for_timesheet(emp, company='_Test Company')
+		self.timesheet = make_timesheet(emp, simulate = True, billable=1)
+		self.salary_slip = make_salary_slip(self.timesheet.name)
+		self.salary_slip.submit()
+		self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer')
+		self.sales_invoice.due_date = nowdate()
+		self.sales_invoice.submit()
+
+	def test_project_profitability(self):
+		filters = {
+			'company': '_Test Company',
+			'start_date': getdate(),
+			'end_date': getdate()
+		}
+
+		report = execute(filters)
+
+		row = report[1][0]
+		timesheet = frappe.get_doc("Timesheet", self.timesheet.name)
+
+		self.assertEqual(self.sales_invoice.customer, row.customer_name)
+		self.assertEqual(timesheet.title, row.employee_name)
+		self.assertEqual(self.sales_invoice.base_grand_total, row.base_grand_total)
+		self.assertEqual(self.salary_slip.base_gross_pay, row.base_gross_pay)
+		self.assertEqual(timesheet.total_billed_hours, row.total_billed_hours)
+		self.assertEqual(self.salary_slip.total_working_days, row.total_working_days)
+
+		standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours")
+		utilization = timesheet.total_billed_hours/(self.salary_slip.total_working_days * standard_working_hours)
+		self.assertEqual(utilization, row.utilization)
+		
+		profit = self.sales_invoice.base_grand_total - self.salary_slip.base_gross_pay * utilization
+		self.assertEqual(profit, row.profit)
+
+		fractional_cost = self.salary_slip.base_gross_pay * utilization
+		self.assertEqual(fractional_cost, row.fractional_cost)
+
+	def tearDown(self):
+		frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel()
+		frappe.get_doc("Salary Slip", self.salary_slip.name).cancel()
+		frappe.get_doc("Timesheet", self.timesheet.name).cancel()
\ No newline at end of file
diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json
index 62ea3b6..c023a73 100644
--- a/erpnext/projects/workspace/projects/projects.json
+++ b/erpnext/projects/workspace/projects/projects.json
@@ -141,6 +141,16 @@
    "type": "Link"
   },
   {
+   "dependencies": "Timesheet, Sales Invoice, Salary Slip",
+   "hidden": 0,
+   "is_query_report": 1,
+   "label": "Project Profitability",
+   "link_to": "Project Profitability",
+   "link_type": "Report",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
    "dependencies": "Project",
    "hidden": 0,
    "is_query_report": 1,
@@ -171,7 +181,7 @@
    "type": "Link"
   }
  ],
- "modified": "2021-04-19 16:17:30.142545",
+ "modified": "2021-04-25 16:27:16.548780",
  "modified_by": "Administrator",
  "module": "Projects",
  "name": "Projects",
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index fd98f17..19c9073 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -712,7 +712,7 @@
 }
 
 frappe.form.link_formatters['Item'] = function(value, doc) {
-	if (doc && value && doc.item_name && doc.item_name !== value) {
+	if (doc && value && doc.item_name && doc.item_name !== value && doc.item_code === value) {
 		return value + ': ' + doc.item_name;
 	} else if (!value && doc.doctype && doc.item_name) {
 		// format blank value in child table
diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
index 31a7545..cc6b907 100644
--- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
+++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
@@ -124,6 +124,7 @@
 					if disc_line.find("Percentuale"):
 						invoices_args["total_discount"] += flt((flt(disc_line.Percentuale.text) / 100) * (rate * qty))
 
+	@frappe.whitelist()
 	def process_file_data(self):
 		self.status = "Processing File Data"
 		self.save()
@@ -400,4 +401,4 @@
 	elif not self.file_url:
 		frappe.throw(_("There is some problem with the file url: {0}").format(file_path))
 
-	return file_path
\ No newline at end of file
+	return file_path
diff --git a/erpnext/selling/doctype/sms_center/sms_center.py b/erpnext/selling/doctype/sms_center/sms_center.py
index bb6ba1f..d142d16 100644
--- a/erpnext/selling/doctype/sms_center/sms_center.py
+++ b/erpnext/selling/doctype/sms_center/sms_center.py
@@ -12,6 +12,7 @@
 from frappe.core.doctype.sms_settings.sms_settings import send_sms
 
 class SMSCenter(Document):
+	@frappe.whitelist()
 	def create_receiver_list(self):
 		rec, where_clause = '', ''
 		if self.send_to == 'All Customer Contact':
@@ -73,6 +74,7 @@
 
 		return receiver_nos
 
+	@frappe.whitelist()
 	def send_sms(self):
 		receiver_list = []
 		if not self.message:
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_list.js b/erpnext/selling/page/point_of_sale/pos_past_order_list.js
index ec39231..70c7dc2 100644
--- a/erpnext/selling/page/point_of_sale/pos_past_order_list.js
+++ b/erpnext/selling/page/point_of_sale/pos_past_order_list.js
@@ -105,7 +105,7 @@
 						<svg class="mr-2" width="12" height="12" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
 							<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
 						</svg>
-						${invoice.customer}
+						${frappe.ellipsis(invoice.customer, 20)}
 					</div>
 				</div>
 				<div class="invoice-total-status">
diff --git a/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json b/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json
index 9094a07b..9d1b196 100644
--- a/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json
+++ b/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json
@@ -1,4 +1,5 @@
 {
+ "absolute_value": 0,
  "align_labels_right": 0,
  "creation": "2017-08-08 12:33:04.773099",
  "custom_format": 1,
@@ -7,10 +8,10 @@
  "docstatus": 0,
  "doctype": "Print Format",
  "font": "Default",
- "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Tahoma, sans-serif;\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n    {{ letter_head }}\n{% endif %}\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t<b>{{ _(\"GSTIN\") }}:</b>{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"<br>GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t<br>\n\t{% if doc.docstatus == 0 %}\n\t\t<b>{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}</b><br>\n\t{% else %}\n\t\t<b>{{ doc.select_print_heading or _(\"Invoice\") }}</b><br>\n\t{% endif %}\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t<b>{{ _(\"Customer\") }}:</b><br>\n\t\t{{ doc.customer_name }}<br>\n\t\t{{ customer_address }}\n\t{% endif %}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t<br><b>{{ _(\"HSN/SAC\") }}:</b> {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"SR.No\") }}:</b><br>\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.rate }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t  {%- if (not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) and row.tax_amount != 0 -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{% if '%' in row.description %}\n\t\t\t\t\t    {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t    {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t  {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- if doc.change_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- endif -%}\n\t</tbody>\n</table>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
+ "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n    {{ letter_head }}\n{% endif %}\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t<b>{{ _(\"GSTIN\") }}:</b>{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"<br>GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t<br>\n\t{% if doc.docstatus == 0 %}\n\t\t<b>{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}</b><br>\n\t{% else %}\n\t\t<b>{{ doc.select_print_heading or _(\"Invoice\") }}</b><br>\n\t{% endif %}\n</p>\n\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Cashier\") }}:</b> {{ doc.owner }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t<b>{{ _(\"Time\") }}:</b> {{  doc.get_formatted(\"posting_time\") }}<br>\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t<b>{{ _(\"Customer\") }}:</b><br>\n\t\t{{ doc.customer_name }}<br>\n\t\t{{ customer_address }}\n\t{% endif %}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t<br><b>{{ _(\"HSN/SAC\") }}:</b> {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"SR.No\") }}:</b><br>\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.rate }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t  {%- if (not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) and row.tax_amount != 0 -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{% if '%' in row.description %}\n\t\t\t\t\t    {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t    {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t  {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t{%- for row in doc.payments -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t    {{ row.mode_of_payment }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t{%- endfor -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- if doc.change_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- endif -%}\n\t</tbody>\n</table>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
  "idx": 0,
  "line_breaks": 0,
- "modified": "2020-04-29 16:47:02.743246",
+ "modified": "2021-04-15 15:26:04.396169",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "GST POS Invoice",
diff --git a/erpnext/selling/print_format/pos_invoice/pos_invoice.json b/erpnext/selling/print_format/pos_invoice/pos_invoice.json
index 99094ed..6c01e26 100644
--- a/erpnext/selling/print_format/pos_invoice/pos_invoice.json
+++ b/erpnext/selling/print_format/pos_invoice/pos_invoice.json
@@ -1,4 +1,5 @@
 {
+ "absolute_value": 0,
  "align_labels_right": 0,
  "creation": "2011-12-21 11:08:55",
  "custom_format": 1,
@@ -6,10 +7,10 @@
  "doc_type": "POS Invoice",
  "docstatus": 0,
  "doctype": "Print Format",
- "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Tahoma, sans-serif;\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n    {{ letter_head }}\n{% endif %}\n\n<p class=\"text-center\" style=\"margin-bottom: 1rem\">\n\t{{ doc.company }}<br>\n\t{{ doc.select_print_heading or _(\"Invoice\") }}<br>\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t<b>{{ _(\"Customer\") }}:</b> {{ doc.customer_name }}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"SR.No\") }}:</b><br>\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.get_formatted(\"rate\") }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t  {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t    {% if '%' in row.description %}\n\t\t\t\t\t    {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t    {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t  {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.change_amount -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t{%- endif -%}\n\t</tbody>\n</table>\n<hr>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
+ "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n    {{ letter_head }}\n{% endif %}\n\n<p class=\"text-center\" style=\"margin-bottom: 1rem\">\n\t{{ doc.company }}<br>\n\t<b>{{ doc.select_print_heading or _(\"Invoice\") }}</b><br>\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Cashier\") }}:</b> {{ doc.owner }}<br>\n\t<b>{{ _(\"Customer\") }}:</b> {{ doc.customer_name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t<b>{{ _(\"Time\") }}:</b> {{  doc.get_formatted(\"posting_time\") }}<br>\n</p>\n\n<hr>\n<table class=\"table table-condensed\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"SR.No\") }}:</b><br>\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.get_formatted(\"rate\") }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t  {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t    {% if '%' in row.description %}\n\t\t\t\t\t    {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t    {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t  {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t{%- for row in doc.payments -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t    {{ row.mode_of_payment }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t{%- endfor -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.change_amount -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t{%- endif -%}\n\t</tbody>\n</table>\n<hr>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
  "idx": 1,
  "line_breaks": 0,
- "modified": "2020-04-29 16:45:58.942375",
+ "modified": "2021-04-15 15:23:28.867135",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "POS Invoice",
diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py
index c4f1de1..373b0a5 100644
--- a/erpnext/setup/doctype/naming_series/naming_series.py
+++ b/erpnext/setup/doctype/naming_series/naming_series.py
@@ -159,6 +159,7 @@
 		if frappe.db.get_value('Series', series, 'name', order_by="name") == None:
 			frappe.db.sql("insert into tabSeries (name, current) values (%s, 0)", (series))
 
+	@frappe.whitelist()
 	def update_series_start(self):
 		if self.prefix:
 			prefix = self.parse_naming_series()
diff --git a/erpnext/setup/workspace/home/home.json b/erpnext/setup/workspace/home/home.json
index 305456b..1576d5a 100644
--- a/erpnext/setup/workspace/home/home.json
+++ b/erpnext/setup/workspace/home/home.json
@@ -248,177 +248,9 @@
    "link_type": "DocType",
    "onboard": 1,
    "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Healthcare",
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Patient",
-   "link_to": "Patient",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Diagnosis",
-   "link_to": "Diagnosis",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Education",
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Student",
-   "link_to": "Student",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Instructor",
-   "link_to": "Instructor",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Course",
-   "link_to": "Course",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Room",
-   "link_to": "Room",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Non Profit",
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Donor",
-   "link_to": "Donor",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Member",
-   "link_to": "Member",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Volunteer",
-   "link_to": "Volunteer",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Chapter",
-   "link_to": "Chapter",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Agriculture",
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Location",
-   "link_to": "Location",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Crop",
-   "link_to": "Crop",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Crop Cycle",
-   "link_to": "Crop Cycle",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Fertilizer",
-   "link_to": "Fertilizer",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
   }
  ],
- "modified": "2021-03-16 15:59:58.416154",
+ "modified": "2021-04-19 15:48:44.089927",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Home",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 5d7597b..d8d8310 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -221,6 +221,7 @@
 		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
 		self.delete_auto_created_batches()
 
+	@frappe.whitelist()
 	def get_current_stock(self):
 		for d in self.get('supplied_items'):
 			if self.supplier_warehouse:
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index f8ac400..48cfa51 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -398,8 +398,12 @@
 					and item_code = %s
 					and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0]
 			if fg_qty_already_entered and fg_qty_already_entered >= qty:
-				frappe.throw(_("Stock Entries already created for Work Order ")
-					+ self.work_order + ":" + ", ".join(other_ste), DuplicateEntryForWorkOrderError)
+				frappe.throw(
+					_("Stock Entries already created for Work Order {0}: {1}").format(
+						self.work_order, ", ".join(other_ste)
+					),
+					DuplicateEntryForWorkOrderError,
+				)
 
 	def set_actual_qty(self):
 		allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock"))
@@ -435,6 +439,7 @@
 			if transferred_serial_no:
 				d.serial_no = transferred_serial_no
 
+	@frappe.whitelist()
 	def get_stock_and_rate(self):
 		"""
 			Updates rate and availability of all the items.
diff --git a/requirements.txt b/requirements.txt
index 5a35236..377fd7d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,15 +1,13 @@
-braintree==3.57.1
 frappe
-gocardless-pro==1.11.0
-googlemaps==3.1.1
-pandas>=1.0.5
-plaid-python>=7.0.0
-pycountry==19.8.18
-PyGithub==1.44.1
-python-stdnum==1.12
-python-youtube==0.6.0
-taxjar==1.9.0
-tweepy==3.8.0
-Unidecode==1.1.1
-WooCommerce==2.1.1
-pycryptodome==3.9.8
+gocardless-pro~=1.22.0
+googlemaps  # used in ERPNext, but dependency is defined in Frappe
+pandas~=1.1.5
+plaid-python~=7.2.1
+pycountry~=20.7.3
+PyGithub~=1.54.1
+python-stdnum~=1.16
+python-youtube~=0.8.0
+taxjar~=1.9.2
+tweepy~=3.10.0
+Unidecode~=1.2.0
+WooCommerce~=3.0.0