Merge branch 'develop' into e-commerce-refactor-develop
diff --git a/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json
index 4b871ae..7e50962 100644
--- a/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json
+++ b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json
@@ -1,7 +1,7 @@
 {
  "actions": [],
  "allow_rename": 1,
- "creation": "2022-01-03 18:10:11.697198",
+ "creation": "2022-01-13 20:07:30.096306",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
@@ -20,7 +20,7 @@
   },
   {
    "fieldname": "percentage",
-   "fieldtype": "Int",
+   "fieldtype": "Percent",
    "in_list_view": 1,
    "label": "Percentage (%)",
    "reqd": 1
@@ -29,7 +29,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-01-03 18:10:20.029821",
+ "modified": "2022-02-01 22:22:31.589523",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Cost Center Allocation Percentage",
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index f66abdc..97d34e0 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -42,7 +42,6 @@
 		self.validate_serialised_or_batched_item()
 		self.validate_stock_availablility()
 		self.validate_return_items_qty()
-		self.validate_non_stock_items()
 		self.set_status()
 		self.set_account_for_mode_of_payment()
 		self.validate_pos()
@@ -175,9 +174,11 @@
 	def validate_stock_availablility(self):
 		if self.is_return or self.docstatus != 1:
 			return
-
 		allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
 		for d in self.get('items'):
+			is_service_item = not (frappe.db.get_value('Item', d.get('item_code'), 'is_stock_item'))
+			if is_service_item:
+				return
 			if d.serial_no:
 				self.validate_pos_reserved_serial_nos(d)
 				self.validate_delivered_serial_nos(d)
@@ -188,7 +189,7 @@
 				if allow_negative_stock:
 					return
 
-				available_stock = get_stock_availability(d.item_code, d.warehouse)
+				available_stock, is_stock_item = 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:
@@ -259,14 +260,6 @@
 							.format(d.idx, bold_serial_no, bold_return_against)
 						)
 
-	def validate_non_stock_items(self):
-		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:
-				if not frappe.db.exists('Product Bundle', d.item_code):
-					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:
 			frappe.throw(_("At least one mode of payment is required for POS invoice."))
@@ -506,12 +499,18 @@
 @frappe.whitelist()
 def get_stock_availability(item_code, warehouse):
 	if frappe.db.get_value('Item', item_code, 'is_stock_item'):
+		is_stock_item = True
 		bin_qty = get_bin_qty(item_code, warehouse)
 		pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
-		return bin_qty - pos_sales_qty
+		return bin_qty - pos_sales_qty, is_stock_item
 	else:
+		is_stock_item = False
 		if frappe.db.exists('Product Bundle', item_code):
-			return get_bundle_availability(item_code, warehouse)
+			return get_bundle_availability(item_code, warehouse), is_stock_item
+		else:
+			# Is a service item
+			return 0, is_stock_item
+
 
 def get_bundle_availability(bundle_item_code, warehouse):
 	product_bundle = frappe.get_doc('Product Bundle', bundle_item_code)
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index b364218..279557a 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -548,6 +548,10 @@
 		exchange_rate_map, net_rate_map = get_purchase_document_details(self)
 
 		enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
+		provisional_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, \
+			'enable_provisional_accounting_for_non_stock_items'))
+
+		purchase_receipt_doc_map = {}
 
 		for item in self.get("items"):
 			if flt(item.base_net_amount):
@@ -643,19 +647,23 @@
 					else:
 						amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
 
-					auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items'))
-
-					if auto_accounting_for_non_stock_items:
-						service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed")
-
+					if provisional_accounting_for_non_stock_items:
 						if item.purchase_receipt:
+							provisional_account = self.get_company_default("default_provisional_account")
+							purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt)
+
+							if not purchase_receipt_doc:
+								purchase_receipt_doc = frappe.get_doc("Purchase Receipt", item.purchase_receipt)
+								purchase_receipt_doc_map[item.purchase_receipt] = purchase_receipt_doc
+
 							# Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt
 							expense_booked_in_pr = frappe.db.get_value('GL Entry', {'is_cancelled': 0,
 								'voucher_type': 'Purchase Receipt', 'voucher_no': item.purchase_receipt, 'voucher_detail_no': item.pr_detail,
-								'account':service_received_but_not_billed_account}, ['name'])
+								'account':provisional_account}, ['name'])
 
 							if expense_booked_in_pr:
-								expense_account = service_received_but_not_billed_account
+								# Intentionally passing purchase invoice item to handle partial billing
+								purchase_receipt_doc.add_provisional_gl_entry(item, gl_entries, self.posting_date, reverse=1)
 
 					if not self.is_internal_transfer():
 						gl_entries.append(self.get_gl_dict({
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 21846bb..d51a008 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -11,12 +11,17 @@
 import erpnext
 from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
 from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice
+from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
 from erpnext.buying.doctype.supplier.test_supplier import create_supplier
 from erpnext.controllers.accounts_controller import get_payment_terms
 from erpnext.controllers.buying_controller import QtyMismatchError
 from erpnext.exceptions import InvalidCurrency
 from erpnext.projects.doctype.project.test_project import make_project
 from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
+	make_purchase_invoice as create_purchase_invoice_from_receipt,
+)
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
 	get_taxes,
 	make_purchase_receipt,
@@ -1147,8 +1152,6 @@
 
 	def test_purchase_invoice_advance_taxes(self):
 		from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
-		from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice
-		from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
 
 		# create a new supplier to test
 		supplier = create_supplier(supplier_name = '_Test TDS Advance Supplier',
@@ -1221,6 +1224,45 @@
 		payment_entry.load_from_db()
 		self.assertEqual(payment_entry.taxes[0].allocated_amount, 0)
 
+	def test_provisional_accounting_entry(self):
+		item = create_item("_Test Non Stock Item", is_stock_item=0)
+		provisional_account = create_account(account_name="Provision Account",
+			parent_account="Current Liabilities - _TC", company="_Test Company")
+
+		company = frappe.get_doc('Company', '_Test Company')
+		company.enable_provisional_accounting_for_non_stock_items = 1
+		company.default_provisional_account = provisional_account
+		company.save()
+
+		pr = make_purchase_receipt(item_code="_Test Non Stock Item", posting_date=add_days(nowdate(), -2))
+
+		pi = create_purchase_invoice_from_receipt(pr.name)
+		pi.set_posting_time = 1
+		pi.posting_date = add_days(pr.posting_date, -1)
+		pi.items[0].expense_account = 'Cost of Goods Sold - _TC'
+		pi.save()
+		pi.submit()
+
+		# Check GLE for Purchase Invoice
+		expected_gle = [
+			['Cost of Goods Sold - _TC', 250, 0, add_days(pr.posting_date, -1)],
+			['Creditors - _TC', 0, 250, add_days(pr.posting_date, -1)]
+		]
+
+		check_gl_entries(self, pi.name, expected_gle, pi.posting_date)
+
+		expected_gle_for_purchase_receipt = [
+			["Provision Account - _TC", 250, 0, pr.posting_date],
+			["_Test Account Cost for Goods Sold - _TC", 0, 250, pr.posting_date],
+			["Provision Account - _TC", 0, 250, pi.posting_date],
+			["_Test Account Cost for Goods Sold - _TC", 250, 0, pi.posting_date]
+		]
+
+		check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date)
+
+		company.enable_provisional_accounting_for_non_stock_items = 0
+		company.save()
+
 def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
 	gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
 		from `tabGL Entry`
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 4ff851d..75fcaee 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -204,7 +204,7 @@
 		valuation_rate_map = {}
 
 		for item in self.items:
-			if not item.item_code:
+			if not item.item_code or item.is_free_item:
 				continue
 
 			last_purchase_rate, is_stock_item = frappe.get_cached_value(
@@ -251,7 +251,7 @@
 			valuation_rate_map[(rate.item_code, rate.warehouse)] = rate.valuation_rate
 
 		for item in self.items:
-			if not item.item_code:
+			if not item.item_code or item.is_free_item:
 				continue
 
 			last_valuation_rate = valuation_rate_map.get(
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 2912d3e..8d17683 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -40,7 +40,10 @@
 		if self.docstatus == 2:
 			make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
 
-		if cint(erpnext.is_perpetual_inventory_enabled(self.company)):
+		provisional_accounting_for_non_stock_items = \
+			cint(frappe.db.get_value('Company', self.company, 'enable_provisional_accounting_for_non_stock_items'))
+
+		if cint(erpnext.is_perpetual_inventory_enabled(self.company)) or provisional_accounting_for_non_stock_items:
 			warehouse_account = get_warehouse_account_map(self.company)
 
 			if self.docstatus==1:
diff --git a/erpnext/hr/doctype/employee/employee_reminders.py b/erpnext/hr/doctype/employee/employee_reminders.py
index 559bd39..0bb6637 100644
--- a/erpnext/hr/doctype/employee/employee_reminders.py
+++ b/erpnext/hr/doctype/employee/employee_reminders.py
@@ -20,6 +20,7 @@
 
 	send_advance_holiday_reminders("Weekly")
 
+
 def send_reminders_in_advance_monthly():
 	to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders"))
 	frequency = frappe.db.get_single_value("HR Settings", "frequency")
@@ -28,6 +29,7 @@
 
 	send_advance_holiday_reminders("Monthly")
 
+
 def send_advance_holiday_reminders(frequency):
 	"""Send Holiday Reminders in Advance to Employees
 	`frequency` (str): 'Weekly' or 'Monthly'
@@ -42,7 +44,7 @@
 	else:
 		return
 
-	employees = frappe.db.get_all('Employee', pluck='name')
+	employees = frappe.db.get_all('Employee', filters={'status': 'Active'}, pluck='name')
 	for employee in employees:
 		holidays = get_holidays_for_employee(
 			employee,
@@ -51,10 +53,13 @@
 			raise_exception=False
 		)
 
-		if not (holidays is None):
-			send_holidays_reminder_in_advance(employee, holidays)
+		send_holidays_reminder_in_advance(employee, holidays)
+
 
 def send_holidays_reminder_in_advance(employee, holidays):
+	if not holidays:
+		return
+
 	employee_doc = frappe.get_doc('Employee', employee)
 	employee_email = get_employee_email(employee_doc)
 	frequency = frappe.db.get_single_value("HR Settings", "frequency")
@@ -101,6 +106,7 @@
 				reminder_text, message = get_birthday_reminder_text_and_message(others)
 				send_birthday_reminder(person_email, reminder_text, others, message)
 
+
 def get_birthday_reminder_text_and_message(birthday_persons):
 	if len(birthday_persons) == 1:
 		birthday_person_text = birthday_persons[0]['name']
@@ -116,6 +122,7 @@
 
 	return reminder_text, message
 
+
 def send_birthday_reminder(recipients, reminder_text, birthday_persons, message):
 	frappe.sendmail(
 		recipients=recipients,
@@ -129,10 +136,12 @@
 		header=_("Birthday Reminder 🎂")
 	)
 
+
 def get_employees_who_are_born_today():
 	"""Get all employee born today & group them based on their company"""
 	return get_employees_having_an_event_today("birthday")
 
+
 def get_employees_having_an_event_today(event_type):
 	"""Get all employee who have `event_type` today
 	& group them based on their company. `event_type`
@@ -210,13 +219,14 @@
 				reminder_text, message = get_work_anniversary_reminder_text_and_message(others)
 				send_work_anniversary_reminder(person_email, reminder_text, others, message)
 
+
 def get_work_anniversary_reminder_text_and_message(anniversary_persons):
 	if len(anniversary_persons) == 1:
 		anniversary_person = anniversary_persons[0]['name']
 		persons_name = anniversary_person
 		# Number of years completed at the company
 		completed_years = getdate().year - anniversary_persons[0]['date_of_joining'].year
-		anniversary_person += f" completed {completed_years} years"
+		anniversary_person += f" completed {completed_years} year(s)"
 	else:
 		person_names_with_years = []
 		names = []
@@ -225,7 +235,7 @@
 			names.append(person_text)
 			# Number of years completed at the company
 			completed_years = getdate().year - person['date_of_joining'].year
-			person_text += f" completed {completed_years} years"
+			person_text += f" completed {completed_years} year(s)"
 			person_names_with_years.append(person_text)
 
 		# converts ["Jim", "Rim", "Dim"] to Jim, Rim & Dim
@@ -239,6 +249,7 @@
 
 	return reminder_text, message
 
+
 def send_work_anniversary_reminder(recipients, reminder_text, anniversary_persons, message):
 	frappe.sendmail(
 		recipients=recipients,
@@ -249,5 +260,5 @@
 			anniversary_persons=anniversary_persons,
 			message=message,
 		),
-		header=_("🎊️🎊️ Work Anniversary Reminder 🎊️🎊️")
+		header=_("Work Anniversary Reminder")
 	)
diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py
index 8a2da08..67cbea6 100644
--- a/erpnext/hr/doctype/employee/test_employee.py
+++ b/erpnext/hr/doctype/employee/test_employee.py
@@ -36,7 +36,7 @@
 		employee_doc.reload()
 
 		make_holiday_list()
-		frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List")
+		frappe.db.set_value("Company", employee_doc.company, "default_holiday_list", "Salary Slip Test Holiday List")
 
 		frappe.db.sql("""delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'""")
 		salary_structure = make_salary_structure("Test Inactive Employee Salary Slip", "Monthly",
diff --git a/erpnext/hr/doctype/employee/test_employee_reminders.py b/erpnext/hr/doctype/employee/test_employee_reminders.py
index 52c0098..bdb51b0 100644
--- a/erpnext/hr/doctype/employee/test_employee_reminders.py
+++ b/erpnext/hr/doctype/employee/test_employee_reminders.py
@@ -5,10 +5,12 @@
 from datetime import timedelta
 
 import frappe
-from frappe.utils import getdate
+from frappe.utils import add_months, getdate
 
+from erpnext.hr.doctype.employee.employee_reminders import send_holidays_reminder_in_advance
 from erpnext.hr.doctype.employee.test_employee import make_employee
 from erpnext.hr.doctype.hr_settings.hr_settings import set_proceed_with_frequency_change
+from erpnext.hr.utils import get_holidays_for_employee
 
 
 class TestEmployeeReminders(unittest.TestCase):
@@ -46,6 +48,24 @@
 		cls.test_employee = test_employee
 		cls.test_holiday_dates = test_holiday_dates
 
+		# Employee without holidays in this month/week
+		test_employee_2 = make_employee('test@empwithoutholiday.io', company="_Test Company")
+		test_employee_2 = frappe.get_doc('Employee', test_employee_2)
+
+		test_holiday_list = make_holiday_list(
+			'TestHolidayRemindersList2',
+			holiday_dates=[
+				{'holiday_date': add_months(getdate(), 1), 'description': 'test holiday1'},
+			],
+			from_date=add_months(getdate(), -2),
+			to_date=add_months(getdate(), 2)
+		)
+		test_employee_2.holiday_list = test_holiday_list.name
+		test_employee_2.save()
+
+		cls.test_employee_2 = test_employee_2
+		cls.holiday_list_2 = test_holiday_list
+
 	@classmethod
 	def get_test_holiday_dates(cls):
 		today_date = getdate()
@@ -61,6 +81,7 @@
 	def setUp(self):
 		# Clear Email Queue
 		frappe.db.sql("delete from `tabEmail Queue`")
+		frappe.db.sql("delete from `tabEmail Queue Recipient`")
 
 	def test_is_holiday(self):
 		from erpnext.hr.doctype.employee.employee import is_holiday
@@ -103,11 +124,10 @@
 		self.assertTrue("Subject: Birthday Reminder" in email_queue[0].message)
 
 	def test_work_anniversary_reminders(self):
-		employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
-		employee.date_of_joining = "1998" + frappe.utils.nowdate()[4:]
-		employee.company_email = "test@example.com"
-		employee.company = "_Test Company"
-		employee.save()
+		make_employee("test_work_anniversary@gmail.com",
+			date_of_joining="1998" + frappe.utils.nowdate()[4:],
+			company="_Test Company",
+		)
 
 		from erpnext.hr.doctype.employee.employee_reminders import (
 			get_employees_having_an_event_today,
@@ -115,7 +135,12 @@
 		)
 
 		employees_having_work_anniversary = get_employees_having_an_event_today('work_anniversary')
-		self.assertTrue(employees_having_work_anniversary.get("_Test Company"))
+		employees = employees_having_work_anniversary.get("_Test Company") or []
+		user_ids = []
+		for entry in employees:
+			user_ids.append(entry.user_id)
+
+		self.assertTrue("test_work_anniversary@gmail.com" in user_ids)
 
 		hr_settings = frappe.get_doc("HR Settings", "HR Settings")
 		hr_settings.send_work_anniversary_reminders = 1
@@ -126,16 +151,24 @@
 		email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
 		self.assertTrue("Subject: Work Anniversary Reminder" in email_queue[0].message)
 
-	def test_send_holidays_reminder_in_advance(self):
-		from erpnext.hr.doctype.employee.employee_reminders import send_holidays_reminder_in_advance
-		from erpnext.hr.utils import get_holidays_for_employee
+	def test_work_anniversary_reminder_not_sent_for_0_years(self):
+		make_employee("test_work_anniversary_2@gmail.com",
+			date_of_joining=getdate(),
+			company="_Test Company",
+		)
 
-		# Get HR settings and enable advance holiday reminders
-		hr_settings = frappe.get_doc("HR Settings", "HR Settings")
-		hr_settings.send_holiday_reminders = 1
-		set_proceed_with_frequency_change()
-		hr_settings.frequency = 'Weekly'
-		hr_settings.save()
+		from erpnext.hr.doctype.employee.employee_reminders import get_employees_having_an_event_today
+
+		employees_having_work_anniversary = get_employees_having_an_event_today('work_anniversary')
+		employees = employees_having_work_anniversary.get("_Test Company") or []
+		user_ids = []
+		for entry in employees:
+			user_ids.append(entry.user_id)
+
+		self.assertTrue("test_work_anniversary_2@gmail.com" not in user_ids)
+
+	def test_send_holidays_reminder_in_advance(self):
+		setup_hr_settings('Weekly')
 
 		holidays = get_holidays_for_employee(
 					self.test_employee.get('name'),
@@ -151,32 +184,80 @@
 
 		email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
 		self.assertEqual(len(email_queue), 1)
+		self.assertTrue("Holidays this Week." in email_queue[0].message)
 
 	def test_advance_holiday_reminders_monthly(self):
 		from erpnext.hr.doctype.employee.employee_reminders import send_reminders_in_advance_monthly
 
-		# Get HR settings and enable advance holiday reminders
-		hr_settings = frappe.get_doc("HR Settings", "HR Settings")
-		hr_settings.send_holiday_reminders = 1
-		set_proceed_with_frequency_change()
-		hr_settings.frequency = 'Monthly'
-		hr_settings.save()
+		setup_hr_settings('Monthly')
+
+		# disable emp 2, set same holiday list
+		frappe.db.set_value('Employee', self.test_employee_2.name, {
+			'status': 'Left',
+			'holiday_list': self.test_employee.holiday_list
+		})
 
 		send_reminders_in_advance_monthly()
-
 		email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
 		self.assertTrue(len(email_queue) > 0)
 
+		# even though emp 2 has holiday, non-active employees should not be recipients
+		recipients = frappe.db.get_all('Email Queue Recipient', pluck='recipient')
+		self.assertTrue(self.test_employee_2.user_id not in recipients)
+
+		# teardown: enable emp 2
+		frappe.db.set_value('Employee', self.test_employee_2.name, {
+			'status': 'Left',
+			'holiday_list': self.holiday_list_2
+		})
+
 	def test_advance_holiday_reminders_weekly(self):
 		from erpnext.hr.doctype.employee.employee_reminders import send_reminders_in_advance_weekly
 
-		# Get HR settings and enable advance holiday reminders
-		hr_settings = frappe.get_doc("HR Settings", "HR Settings")
-		hr_settings.send_holiday_reminders = 1
-		hr_settings.frequency = 'Weekly'
-		hr_settings.save()
+		setup_hr_settings('Weekly')
+
+		# disable emp 2, set same holiday list
+		frappe.db.set_value('Employee', self.test_employee_2.name, {
+			'status': 'Left',
+			'holiday_list': self.test_employee.holiday_list
+		})
 
 		send_reminders_in_advance_weekly()
-
 		email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
 		self.assertTrue(len(email_queue) > 0)
+
+		# even though emp 2 has holiday, non-active employees should not be recipients
+		recipients = frappe.db.get_all('Email Queue Recipient', pluck='recipient')
+		self.assertTrue(self.test_employee_2.user_id not in recipients)
+
+		# teardown: enable emp 2
+		frappe.db.set_value('Employee', self.test_employee_2.name, {
+			'status': 'Left',
+			'holiday_list': self.holiday_list_2
+		})
+
+	def test_reminder_not_sent_if_no_holdays(self):
+		setup_hr_settings('Monthly')
+
+		# reminder not sent if there are no holidays
+		holidays = get_holidays_for_employee(
+			self.test_employee_2.get('name'),
+			getdate(), getdate() + timedelta(days=3),
+			only_non_weekly=True,
+			raise_exception=False
+		)
+		send_holidays_reminder_in_advance(
+			self.test_employee_2.get('name'),
+			holidays
+		)
+		email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
+		self.assertEqual(len(email_queue), 0)
+
+
+def setup_hr_settings(frequency=None):
+	# Get HR settings and enable advance holiday reminders
+	hr_settings = frappe.get_doc("HR Settings", "HR Settings")
+	hr_settings.send_holiday_reminders = 1
+	set_proceed_with_frequency_change()
+	hr_settings.frequency = frequency or 'Weekly'
+	hr_settings.save()
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index 75e99f8..6b85927 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -75,10 +75,8 @@
 			frappe.db.sql("DELETE FROM `tab%s`" % dt) #nosec
 
 		frappe.set_user("Administrator")
-
-	@classmethod
-	def setUpClass(cls):
 		set_leave_approver()
+
 		frappe.db.sql("delete from tabAttendance where employee='_T-Employee-00001'")
 
 	def tearDown(self):
@@ -134,10 +132,11 @@
 		make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date))
 
 		holiday_list = make_holiday_list()
-		frappe.db.set_value("Company", "_Test Company", "default_holiday_list", holiday_list)
+		employee = get_employee()
+		frappe.db.set_value("Company", employee.company, "default_holiday_list", holiday_list)
 		first_sunday = get_first_sunday(holiday_list)
 
-		leave_application = make_leave_application("_T-Employee-00001", first_sunday, add_days(first_sunday, 3), leave_type.name)
+		leave_application = make_leave_application(employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name)
 		leave_application.reload()
 		self.assertEqual(leave_application.total_leave_days, 4)
 		self.assertEqual(frappe.db.count('Attendance', {'leave_application': leave_application.name}), 4)
@@ -157,25 +156,28 @@
 		make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date))
 
 		holiday_list = make_holiday_list()
-		frappe.db.set_value("Company", "_Test Company", "default_holiday_list", holiday_list)
+		employee = get_employee()
+		frappe.db.set_value("Company", employee.company, "default_holiday_list", holiday_list)
 		first_sunday = get_first_sunday(holiday_list)
 
 		# already marked attendance on a holiday should be deleted in this case
 		config = {
 			"doctype": "Attendance",
-			"employee": "_T-Employee-00001",
+			"employee": employee.name,
 			"status": "Present"
 		}
 		attendance_on_holiday = frappe.get_doc(config)
 		attendance_on_holiday.attendance_date = first_sunday
+		attendance_on_holiday.flags.ignore_validate = True
 		attendance_on_holiday.save()
 
 		# already marked attendance on a non-holiday should be updated
 		attendance = frappe.get_doc(config)
 		attendance.attendance_date = add_days(first_sunday, 3)
+		attendance.flags.ignore_validate = True
 		attendance.save()
 
-		leave_application = make_leave_application("_T-Employee-00001", first_sunday, add_days(first_sunday, 3), leave_type.name)
+		leave_application = make_leave_application(employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name)
 		leave_application.reload()
 		# holiday should be excluded while marking attendance
 		self.assertEqual(leave_application.total_leave_days, 3)
@@ -325,7 +327,7 @@
 		employee = get_employee()
 
 		default_holiday_list = make_holiday_list()
-		frappe.db.set_value("Company", "_Test Company", "default_holiday_list", default_holiday_list)
+		frappe.db.set_value("Company", employee.company, "default_holiday_list", default_holiday_list)
 		first_sunday = get_first_sunday(default_holiday_list)
 
 		optional_leave_date = add_days(first_sunday, 1)
diff --git a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
index 3d07081..b7b20d9 100644
--- a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
+++ b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
@@ -70,7 +70,6 @@
   {
    "fieldname": "loan_repayment_entry",
    "fieldtype": "Link",
-   "hidden": 1,
    "label": "Loan Repayment Entry",
    "no_copy": 1,
    "options": "Loan Repayment",
@@ -88,7 +87,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-03-14 20:47:11.725818",
+ "modified": "2022-01-31 14:50:14.823213",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Salary Slip Loan",
@@ -97,5 +96,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index fc3b971..8a7634e 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -93,7 +93,7 @@
 			});
 		}
 
-		if(frm.doc.docstatus!=0) {
+		if(frm.doc.docstatus==1) {
 			frm.add_custom_button(__("Work Order"), function() {
 				frm.trigger("make_work_order");
 			}, __("Create"));
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 7d5486f..d6710dd 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -332,6 +332,7 @@
 erpnext.patches.v13_0.update_exchange_rate_settings
 erpnext.patches.v13_0.update_asset_quantity_field
 erpnext.patches.v13_0.delete_bank_reconciliation_detail
+erpnext.patches.v13_0.enable_provisional_accounting
 
 [post_model_sync]
 erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
diff --git a/erpnext/patches/v13_0/enable_provisional_accounting.py b/erpnext/patches/v13_0/enable_provisional_accounting.py
new file mode 100644
index 0000000..8e22270
--- /dev/null
+++ b/erpnext/patches/v13_0/enable_provisional_accounting.py
@@ -0,0 +1,19 @@
+import frappe
+
+
+def execute():
+	frappe.reload_doc("setup", "doctype", "company")
+
+	company = frappe.qb.DocType("Company")
+
+	frappe.qb.update(
+		company
+	).set(
+		company.enable_provisional_accounting_for_non_stock_items, company.enable_perpetual_inventory_for_non_stock_items
+	).set(
+		company.default_provisional_account, company.service_received_but_not_billed
+	).where(
+		company.enable_perpetual_inventory_for_non_stock_items == 1
+	).where(
+		company.service_received_but_not_billed.isnotnull()
+	).run()
\ No newline at end of file
diff --git a/erpnext/patches/v14_0/migrate_cost_center_allocations.py b/erpnext/patches/v14_0/migrate_cost_center_allocations.py
index 3d217d8..c4f097f 100644
--- a/erpnext/patches/v14_0/migrate_cost_center_allocations.py
+++ b/erpnext/patches/v14_0/migrate_cost_center_allocations.py
@@ -27,7 +27,7 @@
 		cca.submit()
 
 def get_existing_cost_center_allocations():
-	if not frappe.get_meta("Cost Center").has_field("enable_distributed_cost_center"):
+	if not frappe.db.exists("DocType", "Distributed Cost Center"):
 		return
 
 	par = frappe.qb.DocType("Cost Center")
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index f33443d..f727ff4 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -746,11 +746,12 @@
 		previous_total_paid_taxes = self.get_tax_paid_in_period(payroll_period.start_date, self.start_date, tax_component)
 
 		# get taxable_earnings for current period (all days)
-		current_taxable_earnings = self.get_taxable_earnings(tax_slab.allow_tax_exemption)
+		current_taxable_earnings = self.get_taxable_earnings(tax_slab.allow_tax_exemption, payroll_period=payroll_period)
 		future_structured_taxable_earnings = current_taxable_earnings.taxable_earnings * (math.ceil(remaining_sub_periods) - 1)
 
 		# get taxable_earnings, addition_earnings for current actual payment days
-		current_taxable_earnings_for_payment_days = self.get_taxable_earnings(tax_slab.allow_tax_exemption, based_on_payment_days=1)
+		current_taxable_earnings_for_payment_days = self.get_taxable_earnings(tax_slab.allow_tax_exemption,
+			based_on_payment_days=1, payroll_period=payroll_period)
 		current_structured_taxable_earnings = current_taxable_earnings_for_payment_days.taxable_earnings
 		current_additional_earnings = current_taxable_earnings_for_payment_days.additional_income
 		current_additional_earnings_with_full_tax = current_taxable_earnings_for_payment_days.additional_income_with_full_tax
@@ -876,7 +877,7 @@
 
 		return total_tax_paid
 
-	def get_taxable_earnings(self, allow_tax_exemption=False, based_on_payment_days=0):
+	def get_taxable_earnings(self, allow_tax_exemption=False, based_on_payment_days=0, payroll_period=None):
 		joining_date, relieving_date = self.get_joining_and_relieving_dates()
 
 		taxable_earnings = 0
@@ -903,7 +904,7 @@
 					# Get additional amount based on future recurring additional salary
 					if additional_amount and earning.is_recurring_additional_salary:
 						additional_income += self.get_future_recurring_additional_amount(earning.additional_salary,
-							earning.additional_amount) # Used earning.additional_amount to consider the amount for the full month
+							earning.additional_amount, payroll_period) # Used earning.additional_amount to consider the amount for the full month
 
 					if earning.deduct_full_tax_on_selected_payroll_date:
 						additional_income_with_full_tax += additional_amount
@@ -920,7 +921,7 @@
 
 					if additional_amount and ded.is_recurring_additional_salary:
 						additional_income -= self.get_future_recurring_additional_amount(ded.additional_salary,
-							ded.additional_amount) # Used ded.additional_amount to consider the amount for the full month
+							ded.additional_amount, payroll_period) # Used ded.additional_amount to consider the amount for the full month
 
 		return frappe._dict({
 			"taxable_earnings": taxable_earnings,
@@ -929,12 +930,18 @@
 			"flexi_benefits": flexi_benefits
 		})
 
-	def get_future_recurring_additional_amount(self, additional_salary, monthly_additional_amount):
+	def get_future_recurring_additional_amount(self, additional_salary, monthly_additional_amount, payroll_period):
 		future_recurring_additional_amount = 0
 		to_date = frappe.db.get_value("Additional Salary", additional_salary, 'to_date')
 
 		# future month count excluding current
 		from_date, to_date = getdate(self.start_date), getdate(to_date)
+
+		# If recurring period end date is beyond the payroll period,
+		# last day of payroll period should be considered for recurring period calculation
+		if getdate(to_date) > getdate(payroll_period.end_date):
+			to_date = getdate(payroll_period.end_date)
+
 		future_recurring_period = ((to_date.year - from_date.year) * 12) + (to_date.month - from_date.month)
 
 		if future_recurring_period > 0:
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index bcf981b..597fd5a 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -147,7 +147,7 @@
 		# Payroll based on attendance
 		frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
 
-		emp = make_employee("test_employee_timesheet@salary.com", company="_Test Company")
+		emp = make_employee("test_employee_timesheet@salary.com", company="_Test Company", holiday_list="Salary Slip Test Holiday List")
 		frappe.db.set_value("Employee", emp, {"relieving_date": None, "status": "Active"})
 
 		# mark attendance
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index db5b20e..993c61d 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -24,7 +24,7 @@
 			["name as item_code", "item_name", "description", "stock_uom", "image as item_image", "is_stock_item"],
 			as_dict=1)
 
-		item_stock_qty = get_stock_availability(item_code, warehouse)
+		item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse)
 		price_list_rate, currency = frappe.db.get_value('Item Price', {
 			'price_list': price_list,
 			'item_code': item_code
@@ -99,7 +99,6 @@
 		), {'warehouse': warehouse}, as_dict=1)
 
 	if items_data:
-		items_data = filter_service_items(items_data)
 		items = [d.item_code for d in items_data]
 		item_prices_data = frappe.get_all("Item Price",
 			fields = ["item_code", "price_list_rate", "currency"],
@@ -112,7 +111,7 @@
 		for item in items_data:
 			item_code = item.item_code
 			item_price = item_prices.get(item_code) or {}
-			item_stock_qty = get_stock_availability(item_code, warehouse)
+			item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse)
 
 			row = {}
 			row.update(item)
@@ -144,14 +143,6 @@
 
 	return {}
 
-def filter_service_items(items):
-	for item in items:
-		if not item['is_stock_item']:
-			if not frappe.db.exists('Product Bundle', item['item_code']):
-				items.remove(item)
-
-	return items
-
 def get_conditions(search_term):
 	condition = "("
 	condition += """item.name like {search_term}
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index ce74f6d..56aa24f 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -630,18 +630,24 @@
 	}
 
 	async check_stock_availability(item_row, qty_needed, warehouse) {
-		const available_qty = (await this.get_available_stock(item_row.item_code, warehouse)).message;
+		const resp = (await this.get_available_stock(item_row.item_code, warehouse)).message;
+		const available_qty = resp[0];
+		const is_stock_item = resp[1];
 
 		frappe.dom.unfreeze();
 		const bold_item_code = item_row.item_code.bold();
 		const bold_warehouse = warehouse.bold();
 		const bold_available_qty = available_qty.toString().bold()
 		if (!(available_qty > 0)) {
-			frappe.model.clear_doc(item_row.doctype, item_row.name);
-			frappe.throw({
-				title: __("Not Available"),
-				message: __('Item Code: {0} is not available under warehouse {1}.', [bold_item_code, bold_warehouse])
-			})
+			if (is_stock_item) {
+				frappe.model.clear_doc(item_row.doctype, item_row.name);
+				frappe.throw({
+					title: __("Not Available"),
+					message: __('Item Code: {0} is not available under warehouse {1}.', [bold_item_code, bold_warehouse])
+				});
+			} else {
+				return;
+			}
 		} else if (available_qty < qty_needed) {
 			frappe.throw({
 				message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]),
@@ -675,8 +681,8 @@
 			},
 			callback(res) {
 				if (!me.item_stock_map[item_code])
-					me.item_stock_map[item_code] = {}
-				me.item_stock_map[item_code][warehouse] = res.message;
+					me.item_stock_map[item_code] = {};
+				me.item_stock_map[item_code][warehouse] = res.message[0];
 			}
 		});
 	}
diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js
index a30bcd7..1177615 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_selector.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -79,14 +79,20 @@
 		const me = this;
 		// eslint-disable-next-line no-unused-vars
 		const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom, price_list_rate } = item;
-		const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange";
 		const precision = flt(price_list_rate, 2) % 1 != 0 ? 2 : 0;
-
+		let indicator_color;
 		let qty_to_display = actual_qty;
 
-		if (Math.round(qty_to_display) > 999) {
-			qty_to_display = Math.round(qty_to_display)/1000;
-			qty_to_display = qty_to_display.toFixed(1) + 'K';
+		if (item.is_stock_item) {
+			indicator_color = (actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange");
+
+			if (Math.round(qty_to_display) > 999) {
+				qty_to_display = Math.round(qty_to_display)/1000;
+				qty_to_display = qty_to_display.toFixed(1) + 'K';
+			}
+		} else {
+			indicator_color = '';
+			qty_to_display = '';
 		}
 
 		function get_item_image_html() {
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 63d96bf..370a327 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -3,7 +3,7 @@
  "allow_import": 1,
  "allow_rename": 1,
  "autoname": "field:company_name",
- "creation": "2013-04-10 08:35:39",
+ "creation": "2022-01-25 10:29:55.938239",
  "description": "Legal Entity / Subsidiary with a separate Chart of Accounts belonging to the Organization.",
  "doctype": "DocType",
  "document_type": "Setup",
@@ -77,13 +77,13 @@
   "default_finance_book",
   "auto_accounting_for_stock_settings",
   "enable_perpetual_inventory",
-  "enable_perpetual_inventory_for_non_stock_items",
+  "enable_provisional_accounting_for_non_stock_items",
   "default_inventory_account",
   "stock_adjustment_account",
   "default_in_transit_warehouse",
   "column_break_32",
   "stock_received_but_not_billed",
-  "service_received_but_not_billed",
+  "default_provisional_account",
   "expenses_included_in_valuation",
   "fixed_asset_defaults",
   "accumulated_depreciation_account",
@@ -685,20 +685,6 @@
    "options": "Terms and Conditions"
   },
   {
-   "fieldname": "service_received_but_not_billed",
-   "fieldtype": "Link",
-   "ignore_user_permissions": 1,
-   "label": "Service Received But Not Billed",
-   "no_copy": 1,
-   "options": "Account"
-  },
-  {
-   "default": "0",
-   "fieldname": "enable_perpetual_inventory_for_non_stock_items",
-   "fieldtype": "Check",
-   "label": "Enable Perpetual Inventory For Non Stock Items"
-  },
-  {
    "fieldname": "default_in_transit_warehouse",
    "fieldtype": "Link",
    "label": "Default In-Transit Warehouse",
@@ -741,6 +727,20 @@
    "fieldname": "section_break_28",
    "fieldtype": "Section Break",
    "label": "Chart of Accounts"
+  },
+  {
+   "default": "0",
+   "fieldname": "enable_provisional_accounting_for_non_stock_items",
+   "fieldtype": "Check",
+   "label": "Enable Provisional Accounting For Non Stock Items"
+  },
+  {
+   "fieldname": "default_provisional_account",
+   "fieldtype": "Link",
+   "ignore_user_permissions": 1,
+   "label": "Default Provisional Account",
+   "no_copy": 1,
+   "options": "Account"
   }
  ],
  "icon": "fa fa-building",
@@ -748,7 +748,7 @@
  "image_field": "company_logo",
  "is_tree": 1,
  "links": [],
- "modified": "2021-10-04 12:09:25.833133",
+ "modified": "2022-01-25 10:33:16.826067",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Company",
@@ -809,5 +809,6 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "ASC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 0a02bcd..95b1e8b 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -10,6 +10,7 @@
 from frappe import _
 from frappe.cache_manager import clear_defaults_cache
 from frappe.contacts.address_and_contact import load_address_and_contact
+from frappe.custom.doctype.property_setter.property_setter import make_property_setter
 from frappe.utils import cint, formatdate, get_timestamp, today
 from frappe.utils.nestedset import NestedSet
 
@@ -45,7 +46,7 @@
 		self.validate_currency()
 		self.validate_coa_input()
 		self.validate_perpetual_inventory()
-		self.validate_perpetual_inventory_for_non_stock_items()
+		self.validate_provisional_account_for_non_stock_items()
 		self.check_country_change()
 		self.check_parent_changed()
 		self.set_chart_of_accounts()
@@ -187,11 +188,14 @@
 				frappe.msgprint(_("Set default inventory account for perpetual inventory"),
 					alert=True, indicator='orange')
 
-	def validate_perpetual_inventory_for_non_stock_items(self):
+	def validate_provisional_account_for_non_stock_items(self):
 		if not self.get("__islocal"):
-			if cint(self.enable_perpetual_inventory_for_non_stock_items) == 1 and not self.service_received_but_not_billed:
-				frappe.throw(_("Set default {0} account for perpetual inventory for non stock items").format(
-					frappe.bold('Service Received But Not Billed')))
+			if cint(self.enable_provisional_accounting_for_non_stock_items) == 1 and not self.default_provisional_account:
+				frappe.throw(_("Set default {0} account for non stock items").format(
+					frappe.bold('Provisional Account')))
+
+			make_property_setter("Purchase Receipt", "provisional_expense_account", "hidden",
+				not self.enable_provisional_accounting_for_non_stock_items, "Check", validate_fields_for_doctype=False)
 
 	def check_country_change(self):
 		frappe.flags.country_change = False
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index 112dded..b54a90e 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -106,6 +106,8 @@
   "terms",
   "bill_no",
   "bill_date",
+  "accounting_details_section",
+  "provisional_expense_account",
   "more_info",
   "project",
   "status",
@@ -1144,16 +1146,30 @@
    "label": "Represents Company",
    "options": "Company",
    "read_only": 1
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "accounting_details_section",
+   "fieldtype": "Section Break",
+   "label": "Accounting Details"
+  },
+  {
+   "fieldname": "provisional_expense_account",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "label": "Provisional Expense Account",
+   "options": "Account"
   }
  ],
  "icon": "fa fa-truck",
  "idx": 261,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-09-28 13:11:10.181328",
+ "modified": "2022-02-01 11:40:52.690984",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt",
+ "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
   {
@@ -1214,6 +1230,7 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "timeline_field": "supplier",
  "title_field": "title",
  "track_changes": 1
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index c97b306..1257057 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -8,6 +8,7 @@
 from frappe.model.mapper import get_mapped_doc
 from frappe.utils import cint, flt, getdate, nowdate
 
+import erpnext
 from erpnext.accounts.utils import get_account_currency
 from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
 from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
@@ -112,6 +113,7 @@
 		self.validate_uom_is_integer("uom", ["qty", "received_qty"])
 		self.validate_uom_is_integer("stock_uom", "stock_qty")
 		self.validate_cwip_accounts()
+		self.validate_provisional_expense_account()
 
 		self.check_on_hold_or_closed_status()
 
@@ -133,6 +135,15 @@
 					company = self.company)
 				break
 
+	def validate_provisional_expense_account(self):
+		provisional_accounting_for_non_stock_items = \
+			cint(frappe.db.get_value('Company', self.company, 'enable_provisional_accounting_for_non_stock_items'))
+
+		if provisional_accounting_for_non_stock_items:
+			default_provisional_account = self.get_company_default("default_provisional_account")
+			if not self.provisional_expense_account:
+				self.provisional_expense_account = default_provisional_account
+
 	def validate_with_previous_doc(self):
 		super(PurchaseReceipt, self).validate_with_previous_doc({
 			"Purchase Order": {
@@ -258,13 +269,15 @@
 			get_purchase_document_details,
 		)
 
-		stock_rbnb = self.get_company_default("stock_received_but_not_billed")
-		landed_cost_entries = get_item_account_wise_additional_cost(self.name)
-		expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
-		auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items'))
+		if erpnext.is_perpetual_inventory_enabled(self.company):
+			stock_rbnb = self.get_company_default("stock_received_but_not_billed")
+			landed_cost_entries = get_item_account_wise_additional_cost(self.name)
+			expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
 
 		warehouse_with_no_account = []
 		stock_items = self.get_stock_items()
+		provisional_accounting_for_non_stock_items = \
+				cint(frappe.db.get_value('Company', self.company, 'enable_provisional_accounting_for_non_stock_items'))
 
 		exchange_rate_map, net_rate_map = get_purchase_document_details(self)
 
@@ -422,43 +435,58 @@
 				elif d.warehouse not in warehouse_with_no_account or \
 					d.rejected_warehouse not in warehouse_with_no_account:
 						warehouse_with_no_account.append(d.warehouse)
-			elif d.item_code not in stock_items and not d.is_fixed_asset and flt(d.qty) and auto_accounting_for_non_stock_items:
-				service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed")
-				credit_currency = get_account_currency(service_received_but_not_billed_account)
-				debit_currency = get_account_currency(d.expense_account)
-				remarks = self.get("remarks") or _("Accounting Entry for Service")
-
-				self.add_gl_entry(
-					gl_entries=gl_entries,
-					account=service_received_but_not_billed_account,
-					cost_center=d.cost_center,
-					debit=0.0,
-					credit=d.amount,
-					remarks=remarks,
-					against_account=d.expense_account,
-					account_currency=credit_currency,
-					project=d.project,
-					voucher_detail_no=d.name, item=d)
-
-				self.add_gl_entry(
-					gl_entries=gl_entries,
-					account=d.expense_account,
-					cost_center=d.cost_center,
-					debit=d.amount,
-					credit=0.0,
-					remarks=remarks,
-					against_account=service_received_but_not_billed_account,
-					account_currency = debit_currency,
-					project=d.project,
-					voucher_detail_no=d.name,
-					item=d)
+			elif d.item_code not in stock_items and not d.is_fixed_asset and flt(d.qty) and provisional_accounting_for_non_stock_items:
+				self.add_provisional_gl_entry(d, gl_entries, self.posting_date)
 
 		if warehouse_with_no_account:
 			frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
 				"\n".join(warehouse_with_no_account))
 
+	def add_provisional_gl_entry(self, item, gl_entries, posting_date, reverse=0):
+		provisional_expense_account = self.get('provisional_expense_account')
+		credit_currency = get_account_currency(provisional_expense_account)
+		debit_currency = get_account_currency(item.expense_account)
+		expense_account = item.expense_account
+		remarks = self.get("remarks") or _("Accounting Entry for Service")
+		multiplication_factor = 1
+
+		if reverse:
+			multiplication_factor = -1
+			expense_account = frappe.db.get_value('Purchase Receipt Item', {'name': item.get('pr_detail')}, ['expense_account'])
+
+		self.add_gl_entry(
+			gl_entries=gl_entries,
+			account=provisional_expense_account,
+			cost_center=item.cost_center,
+			debit=0.0,
+			credit=multiplication_factor * item.amount,
+			remarks=remarks,
+			against_account=expense_account,
+			account_currency=credit_currency,
+			project=item.project,
+			voucher_detail_no=item.name,
+			item=item,
+			posting_date=posting_date)
+
+		self.add_gl_entry(
+			gl_entries=gl_entries,
+			account=expense_account,
+			cost_center=item.cost_center,
+			debit=multiplication_factor * item.amount,
+			credit=0.0,
+			remarks=remarks,
+			against_account=provisional_expense_account,
+			account_currency = debit_currency,
+			project=item.project,
+			voucher_detail_no=item.name,
+			item=item,
+			posting_date=posting_date)
+
 	def make_tax_gl_entries(self, gl_entries):
-		expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
+
+		if erpnext.is_perpetual_inventory_enabled(self.company):
+			expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
+
 		negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get('items')])
 		# Cost center-wise amount breakup for other charges included for valuation
 		valuation_tax = {}
@@ -515,7 +543,8 @@
 
 	def add_gl_entry(self, gl_entries, account, cost_center, debit, credit, remarks, against_account,
 		debit_in_account_currency=None, credit_in_account_currency=None, account_currency=None,
-		project=None, voucher_detail_no=None, item=None):
+		project=None, voucher_detail_no=None, item=None, posting_date=None):
+
 		gl_entry = {
 			"account": account,
 			"cost_center": cost_center,
@@ -534,6 +563,9 @@
 		if credit_in_account_currency:
 			gl_entry.update({"credit_in_account_currency": credit_in_account_currency})
 
+		if posting_date:
+			gl_entry.update({"posting_date": posting_date})
+
 		gl_entries.append(self.get_gl_dict(gl_entry, item=item))
 
 	def get_asset_gl_entry(self, gl_entries):
@@ -562,6 +594,7 @@
 		# debit cwip account
 		debit_in_account_currency = (base_asset_amount
 			if cwip_account_currency == self.company_currency else asset_amount)
+
 		self.add_gl_entry(
 			gl_entries=gl_entries,
 			account=cwip_account,
@@ -577,6 +610,7 @@
 		# credit arbnb account
 		credit_in_account_currency = (base_asset_amount
 			if asset_rbnb_currency == self.company_currency else asset_amount)
+
 		self.add_gl_entry(
 			gl_entries=gl_entries,
 			account=arbnb_account,
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 2909a2d..b87d920 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -1312,58 +1312,6 @@
 		self.assertEqual(pr.status, "To Bill")
 		self.assertAlmostEqual(pr.per_billed, 50.0, places=2)
 
-	def test_service_item_purchase_with_perpetual_inventory(self):
-		company = '_Test Company with perpetual inventory'
-		service_item = '_Test Non Stock Item'
-
-		before_test_value = frappe.db.get_value(
-			'Company', company, 'enable_perpetual_inventory_for_non_stock_items'
-		)
-		frappe.db.set_value(
-			'Company', company,
-			'enable_perpetual_inventory_for_non_stock_items', 1
-		)
-		srbnb_account = 'Stock Received But Not Billed - TCP1'
-		frappe.db.set_value(
-			'Company', company,
-			'service_received_but_not_billed', srbnb_account
-		)
-
-		pr = make_purchase_receipt(
-			company=company, item=service_item,
-			warehouse='Finished Goods - TCP1', do_not_save=1
-		)
-		item_row_with_diff_rate = frappe.copy_doc(pr.items[0])
-		item_row_with_diff_rate.rate = 100
-		pr.append('items', item_row_with_diff_rate)
-
-		pr.save()
-		pr.submit()
-
-		item_one_gl_entry = frappe.db.get_all("GL Entry", {
-			'voucher_type': pr.doctype,
-			'voucher_no': pr.name,
-			'account': srbnb_account,
-			'voucher_detail_no': pr.items[0].name
-		}, pluck="name")
-
-		item_two_gl_entry = frappe.db.get_all("GL Entry", {
-			'voucher_type': pr.doctype,
-			'voucher_no': pr.name,
-			'account': srbnb_account,
-			'voucher_detail_no': pr.items[1].name
-		}, pluck="name")
-
-		# check if the entries are not merged into one
-		# seperate entries should be made since voucher_detail_no is different
-		self.assertEqual(len(item_one_gl_entry), 1)
-		self.assertEqual(len(item_two_gl_entry), 1)
-
-		frappe.db.set_value(
-			'Company', company,
-			'enable_perpetual_inventory_for_non_stock_items', before_test_value
-		)
-
 	def test_purchase_receipt_with_exchange_rate_difference(self):
 		from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import (
 			make_purchase_receipt as create_purchase_receipt,
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 30ea1c3..e5994b2 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -976,7 +976,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-11-15 15:46:10.591600",
+ "modified": "2022-02-01 11:32:27.980524",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt Item",
@@ -985,5 +985,6 @@
  "permissions": [],
  "quick_entry": 1,
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/tests/test_point_of_sale.py b/erpnext/tests/test_point_of_sale.py
new file mode 100644
index 0000000..df2dc8b
--- /dev/null
+++ b/erpnext/tests/test_point_of_sale.py
@@ -0,0 +1,53 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+
+from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
+from erpnext.selling.page.point_of_sale.point_of_sale import get_items
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+from erpnext.tests.utils import ERPNextTestCase
+
+
+class TestPointOfSale(ERPNextTestCase):
+	def test_item_search(self):
+		"""
+		Test Stock and Service Item Search.
+		"""
+
+		pos_profile = make_pos_profile()
+		item1 = make_item("Test Search Stock Item", {"is_stock_item": 1})
+		make_stock_entry(
+			item_code="Test Search Stock Item",
+			qty=10,
+			to_warehouse="_Test Warehouse - _TC",
+			rate=500,
+		)
+
+		result = get_items(
+			start=0,
+			page_length=20,
+			price_list=None,
+			item_group=item1.item_group,
+			pos_profile=pos_profile.name,
+			search_term="Test Search Stock Item",
+		)
+		filtered_items = result.get("items")
+
+		self.assertEqual(len(filtered_items), 1)
+		self.assertEqual(filtered_items[0]["item_code"], item1.item_code)
+		self.assertEqual(filtered_items[0]["actual_qty"], 10)
+
+		item2 = make_item("Test Search Service Item", {"is_stock_item": 0})
+		result = get_items(
+			start=0,
+			page_length=20,
+			price_list=None,
+			item_group=item2.item_group,
+			pos_profile=pos_profile.name,
+			search_term="Test Search Service Item",
+		)
+		filtered_items = result.get("items")
+
+		self.assertEqual(len(filtered_items), 1)
+		self.assertEqual(filtered_items[0]["item_code"], item2.item_code)