Merge branch 'develop' into fix-salary-slip-bg-job
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index 69749c9..f0f83b0 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -11,7 +11,7 @@
 
 cd ~ || exit
 
-sudo apt-get install redis-server libcups2-dev
+sudo apt update && sudo apt install redis-server libcups2-dev
 
 pip install frappe-bench
 
diff --git a/cypress.json b/cypress.json
deleted file mode 100644
index 02b10d8..0000000
--- a/cypress.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-  "baseUrl": "http://test_site:8000/",
-  "projectId": "da59y9",
-  "adminPassword": "admin",
-  "defaultCommandTimeout": 20000,
-  "pageLoadTimeout": 15000,
-  "retries": {
-    "runMode": 2,
-    "openMode": 2
-  }
-}
diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json
deleted file mode 100644
index da18d93..0000000
--- a/cypress/fixtures/example.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "name": "Using fixtures to represent data",
-  "email": "hello@cypress.io",
-  "body": "Fixtures are a great way to mock data for responses to routes"
-}
\ No newline at end of file
diff --git a/cypress/integration/test_bulk_transaction_processing.js b/cypress/integration/test_bulk_transaction_processing.js
deleted file mode 100644
index 428ec51..0000000
--- a/cypress/integration/test_bulk_transaction_processing.js
+++ /dev/null
@@ -1,44 +0,0 @@
-describe("Bulk Transaction Processing", () => {
-	before(() => {
-		cy.login();
-		cy.visit("/app/website");
-	});
-
-	it("Creates To Sales Order", () => {
-		cy.visit("/app/sales-order");
-		cy.url().should("include", "/sales-order");
-		cy.window()
-			.its("frappe.csrf_token")
-			.then((csrf_token) => {
-				return cy
-					.request({
-						url: "/api/method/erpnext.tests.ui_test_bulk_transaction_processing.create_records",
-						method: "POST",
-						headers: {
-							Accept: "application/json",
-							"Content-Type": "application/json",
-							"X-Frappe-CSRF-Token": csrf_token,
-						},
-						timeout: 60000,
-					})
-					.then((res) => {
-						expect(res.status).eq(200);
-					});
-			});
-		cy.wait(5000);
-		cy.get(
-			".list-row-head > .list-header-subject > .list-row-col > .list-check-all"
-		).check({ force: true });
-		cy.wait(3000);
-		cy.get(".actions-btn-group > .btn-primary").click({ force: true });
-		cy.wait(3000);
-		cy.get(".dropdown-menu-right > .user-action > .dropdown-item")
-			.contains("Sales Invoice")
-			.click({ force: true });
-		cy.wait(3000);
-		cy.get(".modal-content > .modal-footer > .standard-actions")
-			.contains("Yes")
-			.click({ force: true });
-		cy.contains("Creation of Sales Invoice successful");
-	});
-});
diff --git a/cypress/integration/test_customer.js b/cypress/integration/test_customer.js
deleted file mode 100644
index 3d6ed5d..0000000
--- a/cypress/integration/test_customer.js
+++ /dev/null
@@ -1,13 +0,0 @@
-
-context('Customer', () => {
-	before(() => {
-		cy.login();
-	});
-	it('Check Customer Group', () => {
-		cy.visit(`app/customer/`);
-		cy.get('.primary-action').click();
-		cy.wait(500);
-		cy.get('.custom-actions > .btn').click();
-		cy.get_field('customer_group', 'Link').should('have.value', 'All Customer Groups');
-	});
-});
diff --git a/cypress/integration/test_item.js b/cypress/integration/test_item.js
deleted file mode 100644
index fcb7533..0000000
--- a/cypress/integration/test_item.js
+++ /dev/null
@@ -1,44 +0,0 @@
-describe("Test Item Dashboard", () => {
-	before(() => {
-		cy.login();
-		cy.visit("/app/item");
-		cy.insert_doc(
-			"Item",
-			{
-				item_code: "e2e_test_item",
-				item_group: "All Item Groups",
-				opening_stock: 42,
-				valuation_rate: 100,
-			},
-			true
-		);
-		cy.go_to_doc("item", "e2e_test_item");
-	});
-
-	it("should show dashboard with correct data on first load", () => {
-		cy.get(".stock-levels").contains("Stock Levels").should("be.visible");
-		cy.get(".stock-levels").contains("e2e_test_item").should("exist");
-
-		// reserved and available qty
-		cy.get(".stock-levels .inline-graph-count")
-			.eq(0)
-			.contains("0")
-			.should("exist");
-		cy.get(".stock-levels .inline-graph-count")
-			.eq(1)
-			.contains("42")
-			.should("exist");
-	});
-
-	it("should persist on field change", () => {
-		cy.get('input[data-fieldname="disabled"]').check();
-		cy.wait(500);
-		cy.get(".stock-levels").contains("Stock Levels").should("be.visible");
-		cy.get(".stock-levels").should("have.length", 1);
-	});
-
-	it("should persist on reload", () => {
-		cy.reload();
-		cy.get(".stock-levels").contains("Stock Levels").should("be.visible");
-	});
-});
diff --git a/cypress/integration/test_organizational_chart_desktop.js b/cypress/integration/test_organizational_chart_desktop.js
deleted file mode 100644
index 464cce4..0000000
--- a/cypress/integration/test_organizational_chart_desktop.js
+++ /dev/null
@@ -1,116 +0,0 @@
-context('Organizational Chart', () => {
-	before(() => {
-		cy.login();
-		cy.visit('/app/website');
-	});
-
-	it('navigates to org chart', () => {
-		cy.visit('/app');
-		cy.visit('/app/organizational-chart');
-		cy.url().should('include', '/organizational-chart');
-
-		cy.window().its('frappe.csrf_token').then(csrf_token => {
-			return cy.request({
-				url: `/api/method/erpnext.tests.ui_test_helpers.create_employee_records`,
-				method: 'POST',
-				headers: {
-					Accept: 'application/json',
-					'Content-Type': 'application/json',
-					'X-Frappe-CSRF-Token': csrf_token
-				},
-				timeout: 60000
-			}).then(res => {
-				expect(res.status).eq(200);
-				cy.get('.frappe-control[data-fieldname=company] input').focus().as('input');
-				cy.get('@input')
-					.clear({ force: true })
-					.type('Test Org Chart{downarrow}{enter}', { force: true })
-					.blur({ force: true });
-			});
-		});
-	});
-
-	it('renders root nodes and loads children for the first expandable node', () => {
-		// check rendered root nodes and the node name, title, connections
-		cy.get('.hierarchy').find('.root-level ul.node-children').children()
-			.should('have.length', 2)
-			.first()
-			.as('first-child');
-
-		cy.get('@first-child').get('.node-name').contains('Test Employee 1');
-		cy.get('@first-child').get('.node-info').find('.node-title').contains('CEO');
-		cy.get('@first-child').get('.node-info').find('.node-connections').contains('· 2 Connections');
-
-		cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
-			// children of 1st root visible
-			cy.get(`div[data-parent="${employee_records.message[0]}"]`).as('child-node');
-			cy.get('@child-node')
-				.should('have.length', 1)
-				.should('be.visible');
-			cy.get('@child-node').get('.node-name').contains('Test Employee 3');
-
-			// connectors between first root node and immediate child
-			cy.get(`path[data-parent="${employee_records.message[0]}"]`)
-				.should('be.visible')
-				.invoke('attr', 'data-child')
-				.should('equal', employee_records.message[2]);
-		});
-	});
-
-	it('hides active nodes children and connectors on expanding sibling node', () => {
-		cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
-			// click sibling
-			cy.get(`#${employee_records.message[1]}`)
-				.click()
-				.should('have.class', 'active');
-
-			// child nodes and connectors hidden
-			cy.get(`[data-parent="${employee_records.message[0]}"]`).should('not.be.visible');
-			cy.get(`path[data-parent="${employee_records.message[0]}"]`).should('not.be.visible');
-		});
-	});
-
-	it('collapses previous level nodes and refreshes connectors on expanding child node', () => {
-		cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
-			// click child node
-			cy.get(`#${employee_records.message[3]}`)
-				.click()
-				.should('have.class', 'active');
-
-			// previous level nodes: parent should be on active-path; other nodes should be collapsed
-			cy.get(`#${employee_records.message[0]}`).should('have.class', 'collapsed');
-			cy.get(`#${employee_records.message[1]}`).should('have.class', 'active-path');
-
-			// previous level connectors refreshed
-			cy.get(`path[data-parent="${employee_records.message[1]}"]`)
-				.should('have.class', 'collapsed-connector');
-
-			// child node's children and connectors rendered
-			cy.get(`[data-parent="${employee_records.message[3]}"]`).should('be.visible');
-			cy.get(`path[data-parent="${employee_records.message[3]}"]`).should('be.visible');
-		});
-	});
-
-	it('expands previous level nodes', () => {
-		cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
-			cy.get(`#${employee_records.message[0]}`)
-				.click()
-				.should('have.class', 'active');
-
-			cy.get(`[data-parent="${employee_records.message[0]}"]`)
-				.should('be.visible');
-
-			cy.get('ul.hierarchy').children().should('have.length', 2);
-			cy.get(`#connectors`).children().should('have.length', 1);
-		});
-	});
-
-	it('edit node navigates to employee master', () => {
-		cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
-			cy.get(`#${employee_records.message[0]}`).find('.btn-edit-node')
-				.click();
-
-			cy.url().should('include', `/employee/${employee_records.message[0]}`);
-		});
-	});
-});
diff --git a/cypress/integration/test_organizational_chart_mobile.js b/cypress/integration/test_organizational_chart_mobile.js
deleted file mode 100644
index 971ac6d..0000000
--- a/cypress/integration/test_organizational_chart_mobile.js
+++ /dev/null
@@ -1,195 +0,0 @@
-context('Organizational Chart Mobile', () => {
-	before(() => {
-		cy.login();
-		cy.visit('/app/website');
-	});
-
-	it('navigates to org chart', () => {
-		cy.viewport(375, 667);
-		cy.visit('/app');
-		cy.visit('/app/organizational-chart');
-		cy.url().should('include', '/organizational-chart');
-
-		cy.window().its('frappe.csrf_token').then(csrf_token => {
-			return cy.request({
-				url: `/api/method/erpnext.tests.ui_test_helpers.create_employee_records`,
-				method: 'POST',
-				headers: {
-					Accept: 'application/json',
-					'Content-Type': 'application/json',
-					'X-Frappe-CSRF-Token': csrf_token
-				},
-				timeout: 60000
-			}).then(res => {
-				expect(res.status).eq(200);
-				cy.get('.frappe-control[data-fieldname=company] input').focus().as('input');
-				cy.get('@input')
-					.clear({ force: true })
-					.type('Test Org Chart{downarrow}{enter}', { force: true })
-					.blur({ force: true });
-			});
-		});
-	});
-
-	it('renders root nodes', () => {
-		// check rendered root nodes and the node name, title, connections
-		cy.get('.hierarchy-mobile').find('.root-level').children()
-			.should('have.length', 2)
-			.first()
-			.as('first-child');
-
-		cy.get('@first-child').get('.node-name').contains('Test Employee 1');
-		cy.get('@first-child').get('.node-info').find('.node-title').contains('CEO');
-		cy.get('@first-child').get('.node-info').find('.node-connections').contains('· 2');
-	});
-
-	it('expands root node', () => {
-		cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
-			cy.get(`#${employee_records.message[1]}`)
-				.click()
-				.should('have.class', 'active');
-
-			// other root node removed
-			cy.get(`#${employee_records.message[0]}`).should('not.exist');
-
-			// children of active root node
-			cy.get('.hierarchy-mobile').find('.level').first().find('ul.node-children').children()
-				.should('have.length', 2);
-
-			cy.get(`div[data-parent="${employee_records.message[1]}"]`).first().as('child-node');
-			cy.get('@child-node').should('be.visible');
-
-			cy.get('@child-node')
-				.get('.node-name')
-				.contains('Test Employee 4');
-
-			// connectors between root node and immediate children
-			cy.get(`path[data-parent="${employee_records.message[1]}"]`).as('connectors');
-			cy.get('@connectors')
-				.should('have.length', 2)
-				.should('be.visible');
-
-			cy.get('@connectors')
-				.first()
-				.invoke('attr', 'data-child')
-				.should('eq', employee_records.message[3]);
-		});
-	});
-
-	it('expands child node', () => {
-		cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
-			cy.get(`#${employee_records.message[3]}`)
-				.click()
-				.should('have.class', 'active')
-				.as('expanded_node');
-
-			// 2 levels on screen; 1 on active path; 1 collapsed
-			cy.get('.hierarchy-mobile').children().should('have.length', 2);
-			cy.get(`#${employee_records.message[1]}`).should('have.class', 'active-path');
-
-			// children of expanded node visible
-			cy.get('@expanded_node')
-				.next()
-				.should('have.class', 'node-children')
-				.as('node-children');
-
-			cy.get('@node-children').children().should('have.length', 1);
-			cy.get('@node-children')
-				.first()
-				.get('.node-card')
-				.should('have.class', 'active-child')
-				.contains('Test Employee 7');
-
-			// orphan connectors removed
-			cy.get(`#connectors`).children().should('have.length', 2);
-		});
-	});
-
-	it('renders sibling group', () => {
-		cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
-			// sibling group visible for parent
-			cy.get(`#${employee_records.message[1]}`)
-				.next()
-				.as('sibling_group');
-
-			cy.get('@sibling_group')
-				.should('have.attr', 'data-parent', 'undefined')
-				.should('have.class', 'node-group')
-				.and('have.class', 'collapsed');
-
-			cy.get('@sibling_group').get('.avatar-group').children().as('siblings');
-			cy.get('@siblings').should('have.length', 1);
-			cy.get('@siblings')
-				.first()
-				.should('have.attr', 'title', 'Test Employee 1');
-
-		});
-	});
-
-	it('expands previous level nodes', () => {
-		cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
-			cy.get(`#${employee_records.message[6]}`)
-				.click()
-				.should('have.class', 'active');
-
-			// clicking on previous level node should remove all the nodes ahead
-			// and expand that node
-			cy.get(`#${employee_records.message[3]}`).click();
-			cy.get(`#${employee_records.message[3]}`)
-				.should('have.class', 'active')
-				.should('not.have.class', 'active-path');
-
-			cy.get(`#${employee_records.message[6]}`).should('have.class', 'active-child');
-			cy.get('.hierarchy-mobile').children().should('have.length', 2);
-			cy.get(`#connectors`).children().should('have.length', 2);
-		});
-	});
-
-	it('expands sibling group', () => {
-		cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
-			// sibling group visible for parent
-			cy.get(`#${employee_records.message[6]}`).click();
-
-			cy.get(`#${employee_records.message[3]}`)
-				.next()
-				.click();
-
-			// siblings of parent should be visible
-			cy.get('.hierarchy-mobile').prev().as('sibling_group');
-			cy.get('@sibling_group')
-				.should('exist')
-				.should('have.class', 'sibling-group')
-				.should('not.have.class', 'collapsed');
-
-			cy.get(`#${employee_records.message[1]}`)
-				.should('be.visible')
-				.should('have.class', 'active');
-
-			cy.get(`[data-parent="${employee_records.message[1]}"]`)
-				.should('be.visible')
-				.should('have.length', 2)
-				.should('have.class', 'active-child');
-		});
-	});
-
-	it('goes to the respective level after clicking on non-collapsed sibling group', () => {
-		cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(() => {
-			// click on non-collapsed sibling group
-			cy.get('.hierarchy-mobile')
-				.prev()
-				.click();
-
-			// should take you to that level
-			cy.get('.hierarchy-mobile').find('li.level .node-card').should('have.length', 2);
-		});
-	});
-
-	it('edit node navigates to employee master', () => {
-		cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
-			cy.get(`#${employee_records.message[0]}`).find('.btn-edit-node')
-				.click();
-
-			cy.url().should('include', `/employee/${employee_records.message[0]}`);
-		});
-	});
-});
diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js
deleted file mode 100644
index 07d9804..0000000
--- a/cypress/plugins/index.js
+++ /dev/null
@@ -1,17 +0,0 @@
-// ***********************************************************
-// This example plugins/index.js can be used to load plugins
-//
-// You can change the location of this file or turn off loading
-// the plugins file with the 'pluginsFile' configuration option.
-//
-// You can read more here:
-// https://on.cypress.io/plugins-guide
-// ***********************************************************
-
-// This function is called when a project is opened or re-opened (e.g. due to
-// the project's config changing)
-
-module.exports = () => {
-	// `on` is used to hook into various events Cypress emits
-	// `config` is the resolved Cypress config
-};
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
deleted file mode 100644
index 7ddc80a..0000000
--- a/cypress/support/commands.js
+++ /dev/null
@@ -1,31 +0,0 @@
-// ***********************************************
-// This example commands.js shows you how to
-// create various custom commands and overwrite
-// existing commands.
-//
-// For more comprehensive examples of custom
-// commands please read more here:
-// https://on.cypress.io/custom-commands
-// ***********************************************
-//
-//
-// -- This is a parent command --
-// Cypress.Commands.add("login", (email, password) => { ... });
-//
-//
-// -- This is a child command --
-// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... });
-//
-//
-// -- This is a dual command --
-// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... });
-//
-//
-// -- This is will overwrite an existing command --
-// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... });
-
-const slug = (name) => name.toLowerCase().replace(" ", "-");
-
-Cypress.Commands.add("go_to_doc", (doctype, name) => {
-	cy.visit(`/app/${slug(doctype)}/${encodeURIComponent(name)}`);
-});
diff --git a/cypress/support/index.js b/cypress/support/index.js
deleted file mode 100644
index 72070cc..0000000
--- a/cypress/support/index.js
+++ /dev/null
@@ -1,26 +0,0 @@
-// ***********************************************************
-// This example support/index.js is processed and
-// loaded automatically before your test files.
-//
-// This is a great place to put global configuration and
-// behavior that modifies Cypress.
-//
-// You can change the location of this file or turn off
-// automatically serving support files with the
-// 'supportFile' configuration option.
-//
-// You can read more here:
-// https://on.cypress.io/configuration
-// ***********************************************************
-
-// Import commands.js using ES2015 syntax:
-import './commands';
-import '../../../frappe/cypress/support/commands' // eslint-disable-line
-
-
-// Alternatively you can use CommonJS syntax:
-// require('./commands')
-
-Cypress.Cookies.defaults({
-	preserve: 'sid'
-});
diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json
deleted file mode 100644
index d90ebf6..0000000
--- a/cypress/tsconfig.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-    "compilerOptions": {
-        "allowJs": true,
-        "baseUrl": "../node_modules",
-        "types": [
-            "cypress"
-        ]
-    },
-    "include": [
-        "**/*.*"
-    ]
-}
\ No newline at end of file
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 056084b..bebfa6c 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1866,7 +1866,7 @@
 def get_taxes_and_charges(master_doctype, master_name):
 	if not master_name:
 		return
-	from frappe.model import default_fields
+	from frappe.model import child_table_fields, default_fields
 
 	tax_master = frappe.get_doc(master_doctype, master_name)
 
@@ -1874,7 +1874,7 @@
 	for i, tax in enumerate(tax_master.get("taxes")):
 		tax = tax.as_dict()
 
-		for fieldname in default_fields:
+		for fieldname in default_fields + child_table_fields:
 			if fieldname in tax:
 				del tax[fieldname]
 
@@ -2661,7 +2661,8 @@
 			parent.update_reserved_qty_for_subcontract()
 			parent.create_raw_materials_supplied("supplied_items")
 			parent.save()
-	else:
+	else:  # Sales Order
+		parent.validate_warehouse()
 		parent.update_reserved_qty()
 		parent.update_project()
 		parent.update_prevdoc_status("submit")
diff --git a/erpnext/hr/doctype/attendance/test_attendance.py b/erpnext/hr/doctype/attendance/test_attendance.py
index 762d0f7..c85ec65 100644
--- a/erpnext/hr/doctype/attendance/test_attendance.py
+++ b/erpnext/hr/doctype/attendance/test_attendance.py
@@ -3,7 +3,15 @@
 
 import frappe
 from frappe.tests.utils import FrappeTestCase
-from frappe.utils import add_days, get_year_ending, get_year_start, getdate, now_datetime, nowdate
+from frappe.utils import (
+	add_days,
+	add_months,
+	get_last_day,
+	get_year_ending,
+	get_year_start,
+	getdate,
+	nowdate,
+)
 
 from erpnext.hr.doctype.attendance.attendance import (
 	DuplicateAttendanceError,
@@ -138,69 +146,70 @@
 		self.assertEqual(attendance, fetch_attendance)
 
 	def test_unmarked_days(self):
-		now = now_datetime()
-		previous_month = now.month - 1
-		first_day = now.replace(day=1).replace(month=previous_month).date()
+		first_sunday = get_first_sunday(
+			self.holiday_list, for_date=get_last_day(add_months(getdate(), -1))
+		)
+		attendance_date = add_days(first_sunday, 1)
 
 		employee = make_employee(
-			"test_unmarked_days@example.com", date_of_joining=add_days(first_day, -1)
+			"test_unmarked_days@example.com", date_of_joining=add_days(attendance_date, -1)
 		)
 		frappe.db.set_value("Employee", employee, "holiday_list", self.holiday_list)
 
-		first_sunday = get_first_sunday(self.holiday_list, for_date=first_day)
-		mark_attendance(employee, first_day, "Present")
-		month_name = get_month_name(first_day)
+		mark_attendance(employee, attendance_date, "Present")
+		month_name = get_month_name(attendance_date)
 
 		unmarked_days = get_unmarked_days(employee, month_name)
 		unmarked_days = [getdate(date) for date in unmarked_days]
 
 		# attendance already marked for the day
-		self.assertNotIn(first_day, unmarked_days)
+		self.assertNotIn(attendance_date, unmarked_days)
 		# attendance unmarked
-		self.assertIn(getdate(add_days(first_day, 1)), unmarked_days)
+		self.assertIn(getdate(add_days(attendance_date, 1)), unmarked_days)
 		# holiday considered in unmarked days
 		self.assertIn(first_sunday, unmarked_days)
 
 	def test_unmarked_days_excluding_holidays(self):
-		now = now_datetime()
-		previous_month = now.month - 1
-		first_day = now.replace(day=1).replace(month=previous_month).date()
+		first_sunday = get_first_sunday(
+			self.holiday_list, for_date=get_last_day(add_months(getdate(), -1))
+		)
+		attendance_date = add_days(first_sunday, 1)
 
 		employee = make_employee(
-			"test_unmarked_days@example.com", date_of_joining=add_days(first_day, -1)
+			"test_unmarked_days@example.com", date_of_joining=add_days(attendance_date, -1)
 		)
 		frappe.db.set_value("Employee", employee, "holiday_list", self.holiday_list)
 
-		first_sunday = get_first_sunday(self.holiday_list, for_date=first_day)
-		mark_attendance(employee, first_day, "Present")
-		month_name = get_month_name(first_day)
+		mark_attendance(employee, attendance_date, "Present")
+		month_name = get_month_name(attendance_date)
 
 		unmarked_days = get_unmarked_days(employee, month_name, exclude_holidays=True)
 		unmarked_days = [getdate(date) for date in unmarked_days]
 
 		# attendance already marked for the day
-		self.assertNotIn(first_day, unmarked_days)
+		self.assertNotIn(attendance_date, unmarked_days)
 		# attendance unmarked
-		self.assertIn(getdate(add_days(first_day, 1)), unmarked_days)
+		self.assertIn(getdate(add_days(attendance_date, 1)), unmarked_days)
 		# holidays not considered in unmarked days
 		self.assertNotIn(first_sunday, unmarked_days)
 
 	def test_unmarked_days_as_per_joining_and_relieving_dates(self):
-		now = now_datetime()
-		previous_month = now.month - 1
-		first_day = now.replace(day=1).replace(month=previous_month).date()
+		first_sunday = get_first_sunday(
+			self.holiday_list, for_date=get_last_day(add_months(getdate(), -1))
+		)
+		date = add_days(first_sunday, 1)
 
-		doj = add_days(first_day, 1)
-		relieving_date = add_days(first_day, 5)
+		doj = add_days(date, 1)
+		relieving_date = add_days(date, 5)
 		employee = make_employee(
 			"test_unmarked_days_as_per_doj@example.com", date_of_joining=doj, relieving_date=relieving_date
 		)
 
 		frappe.db.set_value("Employee", employee, "holiday_list", self.holiday_list)
 
-		attendance_date = add_days(first_day, 2)
+		attendance_date = add_days(date, 2)
 		mark_attendance(employee, attendance_date, "Present")
-		month_name = get_month_name(first_day)
+		month_name = get_month_name(attendance_date)
 
 		unmarked_days = get_unmarked_days(employee, month_name)
 		unmarked_days = [getdate(date) for date in unmarked_days]
diff --git a/erpnext/hr/doctype/employee/employee_reminders.py b/erpnext/hr/doctype/employee/employee_reminders.py
index 1829bc4..f09d7ff 100644
--- a/erpnext/hr/doctype/employee/employee_reminders.py
+++ b/erpnext/hr/doctype/employee/employee_reminders.py
@@ -230,7 +230,7 @@
 		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} year(s)"
+		anniversary_person += f" completed {get_pluralized_years(completed_years)}"
 	else:
 		person_names_with_years = []
 		names = []
@@ -239,7 +239,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} year(s)"
+			person_text += f" completed {get_pluralized_years(completed_years)}"
 			person_names_with_years.append(person_text)
 
 		# converts ["Jim", "Rim", "Dim"] to Jim, Rim & Dim
@@ -254,6 +254,12 @@
 	return reminder_text, message
 
 
+def get_pluralized_years(years):
+	if years == 1:
+		return "1 year"
+	return f"{years} years"
+
+
 def send_work_anniversary_reminder(recipients, reminder_text, anniversary_persons, message):
 	frappe.sendmail(
 		recipients=recipients,
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index a9659c3..8594ebb 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -231,7 +231,6 @@
 erpnext.patches.v13_0.update_reason_for_resignation_in_employee
 execute:frappe.delete_doc("Report", "Quoted Item Comparison")
 erpnext.patches.v13_0.update_member_email_address
-erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
 erpnext.patches.v13_0.update_pos_closing_entry_in_merge_log
 erpnext.patches.v13_0.add_po_to_global_search
 erpnext.patches.v13_0.update_returned_qty_in_pr_dn
diff --git a/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py b/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py
deleted file mode 100644
index 59b17ee..0000000
--- a/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# Copyright (c) 2019, Frappe and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import frappe
-
-
-def execute():
-	frappe.reload_doc("hr", "doctype", "leave_policy_assignment")
-	frappe.reload_doc("hr", "doctype", "employee_grade")
-	employee_with_assignment = []
-	leave_policy = []
-
-	if "leave_policy" in frappe.db.get_table_columns("Employee"):
-		employees_with_leave_policy = frappe.db.sql(
-			"SELECT name, leave_policy FROM `tabEmployee` WHERE leave_policy IS NOT NULL and leave_policy != ''",
-			as_dict=1,
-		)
-
-		for employee in employees_with_leave_policy:
-			alloc = frappe.db.exists(
-				"Leave Allocation",
-				{"employee": employee.name, "leave_policy": employee.leave_policy, "docstatus": 1},
-			)
-			if not alloc:
-				create_assignment(employee.name, employee.leave_policy)
-
-			employee_with_assignment.append(employee.name)
-			leave_policy.append(employee.leave_policy)
-
-	if "default_leave_policy" in frappe.db.get_table_columns("Employee Grade"):
-		employee_grade_with_leave_policy = frappe.db.sql(
-			"SELECT name, default_leave_policy FROM `tabEmployee Grade` WHERE default_leave_policy IS NOT NULL and default_leave_policy!=''",
-			as_dict=1,
-		)
-
-		# for whole employee Grade
-		for grade in employee_grade_with_leave_policy:
-			employees = get_employee_with_grade(grade.name)
-			for employee in employees:
-
-				if employee not in employee_with_assignment:  # Will ensure no duplicate
-					alloc = frappe.db.exists(
-						"Leave Allocation",
-						{"employee": employee.name, "leave_policy": grade.default_leave_policy, "docstatus": 1},
-					)
-					if not alloc:
-						create_assignment(employee.name, grade.default_leave_policy)
-					leave_policy.append(grade.default_leave_policy)
-
-	# for old Leave allocation and leave policy from allocation, which may got updated in employee grade.
-	leave_allocations = frappe.db.sql(
-		"SELECT leave_policy, leave_period, employee FROM `tabLeave Allocation` WHERE leave_policy IS NOT NULL and leave_policy != '' and docstatus = 1 ",
-		as_dict=1,
-	)
-
-	for allocation in leave_allocations:
-		if allocation.leave_policy not in leave_policy:
-			create_assignment(
-				allocation.employee,
-				allocation.leave_policy,
-				leave_period=allocation.leave_period,
-				allocation_exists=True,
-			)
-
-
-def create_assignment(employee, leave_policy, leave_period=None, allocation_exists=False):
-	if frappe.db.get_value("Leave Policy", leave_policy, "docstatus") == 2:
-		return
-
-	filters = {"employee": employee, "leave_policy": leave_policy}
-	if leave_period:
-		filters["leave_period"] = leave_period
-
-	if not frappe.db.exists("Leave Policy Assignment", filters):
-		lpa = frappe.new_doc("Leave Policy Assignment")
-		lpa.employee = employee
-		lpa.leave_policy = leave_policy
-
-		lpa.flags.ignore_mandatory = True
-		if allocation_exists:
-			lpa.assignment_based_on = "Leave Period"
-			lpa.leave_period = leave_period
-			lpa.leaves_allocated = 1
-
-		lpa.save()
-		if allocation_exists:
-			lpa.submit()
-			# Updating old Leave Allocation
-			frappe.db.sql("Update `tabLeave Allocation` set leave_policy_assignment = %s", lpa.name)
-
-
-def get_employee_with_grade(grade):
-	return frappe.get_list("Employee", filters={"grade": grade})
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index edc4b06..de93c82 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -423,7 +423,7 @@
 		item.barcode = null;
 
 
-		if(item.item_code || item.barcode || item.serial_no) {
+		if(item.item_code || item.serial_no) {
 			if(!this.validate_company_and_party()) {
 				this.frm.fields_dict["items"].grid.grid_rows[item.idx - 1].remove();
 			} else {
@@ -463,6 +463,7 @@
 							stock_qty: item.stock_qty,
 							conversion_factor: item.conversion_factor,
 							weight_per_unit: item.weight_per_unit,
+							uom: item.uom,
 							weight_uom: item.weight_uom,
 							manufacturer: item.manufacturer,
 							stock_uom: item.stock_uom,
diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js
index 943db07..a6bff2c 100644
--- a/erpnext/public/js/utils/barcode_scanner.js
+++ b/erpnext/public/js/utils/barcode_scanner.js
@@ -9,6 +9,7 @@
 		this.barcode_field = opts.barcode_field || "barcode";
 		this.serial_no_field = opts.serial_no_field || "serial_no";
 		this.batch_no_field = opts.batch_no_field || "batch_no";
+		this.uom_field = opts.uom_field || "uom";
 		this.qty_field = opts.qty_field || "qty";
 		// field name on row which defines max quantity to be scanned e.g. picklist
 		this.max_qty_field = opts.max_qty_field;
@@ -26,6 +27,7 @@
 		//     bar_code: "123456", // present if barcode was scanned
 		//     batch_no: "LOT12", // present if batch was scanned
 		//     serial_no: "987XYZ", // present if serial no was scanned
+		//     uom: "Kg", // present if barcode UOM is different from default
 		// }
 		this.scan_api = opts.scan_api || "erpnext.stock.utils.scan_barcode";
 	}
@@ -67,9 +69,9 @@
 		return new Promise(resolve => {
 			let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
 
-			const {item_code, barcode, batch_no, serial_no} = data;
+			const {item_code, barcode, batch_no, serial_no, uom} = data;
 
-			let row = this.get_row_to_modify_on_scan(item_code, batch_no);
+			let row = this.get_row_to_modify_on_scan(item_code, batch_no, uom);
 
 			if (!row) {
 				if (this.dont_allow_new_row) {
@@ -90,10 +92,11 @@
 			}
 
 			frappe.run_serially([
-				() => this.set_selector_trigger_flag(row, data),
+				() => this.set_selector_trigger_flag(data),
 				() => this.set_item(row, item_code).then(qty => {
 					this.show_scan_message(row.idx, row.item_code, qty);
 				}),
+				() => this.set_barcode_uom(row, uom),
 				() => this.set_serial_no(row, serial_no),
 				() => this.set_batch_no(row, batch_no),
 				() => this.set_barcode(row, barcode),
@@ -106,7 +109,7 @@
 
 	// batch and serial selector is reduandant when all info can be added by scan
 	// this flag on item row is used by transaction.js to avoid triggering selector
-	set_selector_trigger_flag(row, data) {
+	set_selector_trigger_flag(data) {
 		const {batch_no, serial_no, has_batch_no, has_serial_no} = data;
 
 		const require_selecting_batch = has_batch_no && !batch_no;
@@ -154,6 +157,12 @@
 		}
 	}
 
+	async set_barcode_uom(row, uom) {
+		if (uom && frappe.meta.has_field(row.doctype, this.uom_field)) {
+			await frappe.model.set_value(row.doctype, row.name, this.uom_field, uom);
+		}
+	}
+
 	async set_batch_no(row, batch_no) {
 		if (batch_no && frappe.meta.has_field(row.doctype, this.batch_no_field)) {
 			await frappe.model.set_value(row.doctype, row.name, this.batch_no_field, batch_no);
@@ -184,7 +193,7 @@
 		return is_duplicate;
 	}
 
-	get_row_to_modify_on_scan(item_code, batch_no) {
+	get_row_to_modify_on_scan(item_code, batch_no, uom) {
 		let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
 
 		// Check if batch is scanned and table has batch no field
@@ -193,10 +202,12 @@
 
 		const matching_row = (row) => {
 			const item_match = row.item_code == item_code;
-			const batch_match = row.batch_no == batch_no;
+			const batch_match = row[this.batch_no_field] == batch_no;
+			const uom_match = !uom || row[this.uom_field] == uom;
 			const qty_in_limit = flt(row[this.qty_field]) < flt(row[this.max_qty_field]);
 
 			return item_match
+				&& uom_match
 				&& (!is_batch_no_scan || batch_match)
 				&& (!check_max_qty || qty_in_limit)
 		}
diff --git a/erpnext/stock/doctype/item_barcode/item_barcode.json b/erpnext/stock/doctype/item_barcode/item_barcode.json
index eef70c9..56832f3 100644
--- a/erpnext/stock/doctype/item_barcode/item_barcode.json
+++ b/erpnext/stock/doctype/item_barcode/item_barcode.json
@@ -6,7 +6,8 @@
  "engine": "InnoDB",
  "field_order": [
   "barcode",
-  "barcode_type"
+  "barcode_type",
+  "uom"
  ],
  "fields": [
   {
@@ -24,11 +25,18 @@
    "in_list_view": 1,
    "label": "Barcode Type",
    "options": "\nEAN\nUPC-A"
+  },
+  {
+   "fieldname": "uom",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "UOM",
+   "options": "UOM"
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2022-04-01 05:54:27.314030",
+ "modified": "2022-06-01 06:24:33.969534",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Item Barcode",
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 2120304..6d8fdaa 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -558,7 +558,7 @@
 	barcode_data = frappe.db.get_value(
 		"Item Barcode",
 		{"barcode": search_value},
-		["barcode", "parent as item_code"],
+		["barcode", "parent as item_code", "uom"],
 		as_dict=True,
 	)
 	if barcode_data: