Merge pull request #31219 from deepeshgarg007/group_filter_cost_center

fix: Parent dimension filters in orders
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/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index 89a9448..6c18a46 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -148,7 +148,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2022-04-14 15:56:42.340223",
+ "modified": "2022-05-31 19:40:26.103909",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Buying Settings",
@@ -162,6 +162,16 @@
    "role": "System Manager",
    "share": 1,
    "write": 1
+  },
+  {
+   "create": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "role": "Purchase Manager",
+   "share": 1,
+   "write": 1
   }
  ],
  "sort_field": "modified",
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/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
index 05ca2a8..9829a96 100644
--- a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
+++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
@@ -402,14 +402,15 @@
    "type": "Link"
   }
  ],
- "modified": "2022-01-13 17:40:09.474747",
+ "modified": "2022-05-31 22:08:19.408223",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Manufacturing",
  "owner": "Administrator",
  "parent_page": "",
  "public": 1,
- "restrict_to_domain": "Manufacturing",
+ "quick_lists": [],
+ "restrict_to_domain": "",
  "roles": [],
  "sequence_id": 17.0,
  "shortcuts": [
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 785e2ba..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
@@ -373,3 +372,4 @@
 erpnext.patches.v13_0.create_accounting_dimensions_in_orders
 erpnext.patches.v13_0.set_per_billed_in_return_delivery_note
 execute:frappe.delete_doc("DocType", "Naming Series")
+erpnext.patches.v13_0.set_payroll_entry_status
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/patches/v13_0/set_payroll_entry_status.py b/erpnext/patches/v13_0/set_payroll_entry_status.py
new file mode 100644
index 0000000..97adff9
--- /dev/null
+++ b/erpnext/patches/v13_0/set_payroll_entry_status.py
@@ -0,0 +1,16 @@
+import frappe
+from frappe.query_builder import Case
+
+
+def execute():
+	PayrollEntry = frappe.qb.DocType("Payroll Entry")
+
+	(
+		frappe.qb.update(PayrollEntry).set(
+			"status",
+			Case()
+			.when(PayrollEntry.docstatus == 0, "Draft")
+			.when(PayrollEntry.docstatus == 1, "Submitted")
+			.else_("Cancelled"),
+		)
+	).run()
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
index 62e183e..b06f350 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
@@ -40,30 +40,69 @@
 	},
 
 	refresh: function (frm) {
-		if (frm.doc.docstatus == 0) {
-			if (!frm.is_new()) {
+		if (frm.doc.docstatus === 0 && !frm.is_new()) {
+			frm.page.clear_primary_action();
+			frm.add_custom_button(__("Get Employees"),
+				function () {
+					frm.events.get_employee_details(frm);
+				}
+			).toggleClass("btn-primary", !(frm.doc.employees || []).length);
+		}
+
+		if (
+			(frm.doc.employees || []).length
+			&& !frappe.model.has_workflow(frm.doctype)
+			&& !cint(frm.doc.salary_slips_created)
+			&& (frm.doc.docstatus != 2)
+		) {
+			if (frm.doc.docstatus == 0) {
 				frm.page.clear_primary_action();
-				frm.add_custom_button(__("Get Employees"),
-					function () {
-						frm.events.get_employee_details(frm);
-					}
-				).toggleClass('btn-primary', !(frm.doc.employees || []).length);
-			}
-			if ((frm.doc.employees || []).length && !frappe.model.has_workflow(frm.doctype)) {
-				frm.page.clear_primary_action();
-				frm.page.set_primary_action(__('Create Salary Slips'), () => {
-					frm.save('Submit').then(() => {
+				frm.page.set_primary_action(__("Create Salary Slips"), () => {
+					frm.save("Submit").then(() => {
 						frm.page.clear_primary_action();
 						frm.refresh();
 						frm.events.refresh(frm);
 					});
 				});
+			} else if (frm.doc.docstatus == 1 && frm.doc.status == "Failed") {
+				frm.add_custom_button(__("Create Salary Slip"), function () {
+					frm.call("create_salary_slips", {}, () => {
+						frm.reload_doc();
+					});
+				}).addClass("btn-primary");
 			}
 		}
-		if (frm.doc.docstatus == 1) {
+
+		if (frm.doc.docstatus == 1 && frm.doc.status == "Submitted") {
 			if (frm.custom_buttons) frm.clear_custom_buttons();
 			frm.events.add_context_buttons(frm);
 		}
+
+		if (frm.doc.status == "Failed" && frm.doc.error_message) {
+			const issue = `<a id="jump_to_error" style="text-decoration: underline;">issue</a>`;
+			let process = (cint(frm.doc.salary_slips_created)) ? "submission" : "creation";
+
+			frm.dashboard.set_headline(
+				__("Salary Slip {0} failed. You can resolve the {1} and retry {0}.", [process, issue])
+			);
+
+			$("#jump_to_error").on("click", (e) => {
+				e.preventDefault();
+				frappe.utils.scroll_to(
+					frm.get_field("error_message").$wrapper,
+					true,
+					30
+				);
+			});
+		}
+
+		frappe.realtime.on("completed_salary_slip_creation", function() {
+			frm.reload_doc();
+		});
+
+		frappe.realtime.on("completed_salary_slip_submission", function() {
+			frm.reload_doc();
+		});
 	},
 
 	get_employee_details: function (frm) {
@@ -88,7 +127,7 @@
 			doc: frm.doc,
 			method: "create_salary_slips",
 			callback: function () {
-				frm.refresh();
+				frm.reload_doc();
 				frm.toolbar.refresh();
 			}
 		});
@@ -97,7 +136,7 @@
 	add_context_buttons: function (frm) {
 		if (frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) {
 			frm.events.add_bank_entry_button(frm);
-		} else if (frm.doc.salary_slips_created) {
+		} else if (frm.doc.salary_slips_created && frm.doc.status != 'Queued') {
 			frm.add_custom_button(__("Submit Salary Slip"), function () {
 				submit_salary_slip(frm);
 			}).addClass("btn-primary");
@@ -331,6 +370,7 @@
 				method: 'submit_salary_slips',
 				args: {},
 				callback: function () {
+					frm.reload_doc();
 					frm.events.refresh(frm);
 				},
 				doc: frm.doc,
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.json b/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
index 0444134..17882eb 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
@@ -8,11 +8,11 @@
  "engine": "InnoDB",
  "field_order": [
   "section_break0",
-  "column_break0",
   "posting_date",
   "payroll_frequency",
   "company",
   "column_break1",
+  "status",
   "currency",
   "exchange_rate",
   "payroll_payable_account",
@@ -41,11 +41,14 @@
   "cost_center",
   "account",
   "payment_account",
-  "amended_from",
   "column_break_33",
   "bank_account",
   "salary_slips_created",
-  "salary_slips_submitted"
+  "salary_slips_submitted",
+  "failure_details_section",
+  "error_message",
+  "section_break_41",
+  "amended_from"
  ],
  "fields": [
   {
@@ -54,11 +57,6 @@
    "label": "Select Employees"
   },
   {
-   "fieldname": "column_break0",
-   "fieldtype": "Column Break",
-   "width": "50%"
-  },
-  {
    "default": "Today",
    "fieldname": "posting_date",
    "fieldtype": "Date",
@@ -231,6 +229,7 @@
    "fieldtype": "Check",
    "hidden": 1,
    "label": "Salary Slips Created",
+   "no_copy": 1,
    "read_only": 1
   },
   {
@@ -239,6 +238,7 @@
    "fieldtype": "Check",
    "hidden": 1,
    "label": "Salary Slips Submitted",
+   "no_copy": 1,
    "read_only": 1
   },
   {
@@ -284,15 +284,44 @@
    "label": "Payroll Payable Account",
    "options": "Account",
    "reqd": 1
+  },
+  {
+   "collapsible": 1,
+   "collapsible_depends_on": "error_message",
+   "depends_on": "eval:doc.status=='Failed';",
+   "fieldname": "failure_details_section",
+   "fieldtype": "Section Break",
+   "label": "Failure Details"
+  },
+  {
+   "depends_on": "eval:doc.status=='Failed';",
+   "fieldname": "error_message",
+   "fieldtype": "Small Text",
+   "label": "Error Message",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "section_break_41",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "label": "Status",
+   "options": "Draft\nSubmitted\nCancelled\nQueued\nFailed",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "icon": "fa fa-cog",
  "is_submittable": 1,
  "links": [],
- "modified": "2020-12-17 15:13:17.766210",
+ "modified": "2022-03-16 12:45:21.662765",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Payroll Entry",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -308,5 +337,6 @@
   }
  ],
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index 473fb0d..620fcad 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -1,6 +1,7 @@
 # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 
+import json
 
 import frappe
 from dateutil.relativedelta import relativedelta
@@ -40,8 +41,10 @@
 
 	def validate(self):
 		self.number_of_employees = len(self.employees)
+		self.set_status()
 
 	def on_submit(self):
+		self.set_status(update=True, status="Submitted")
 		self.create_salary_slips()
 
 	def before_submit(self):
@@ -51,6 +54,15 @@
 			if self.validate_employee_attendance():
 				frappe.throw(_("Cannot Submit, Employees left to mark attendance"))
 
+	def set_status(self, status=None, update=False):
+		if not status:
+			status = {0: "Draft", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0]
+
+		if update:
+			self.db_set("status", status)
+		else:
+			self.status = status
+
 	def validate_employee_details(self):
 		emp_with_sal_slip = []
 		for employee_details in self.employees:
@@ -87,6 +99,8 @@
 		)
 		self.db_set("salary_slips_created", 0)
 		self.db_set("salary_slips_submitted", 0)
+		self.set_status(update=True, status="Cancelled")
+		self.db_set("error_message", "")
 
 	def get_emp_list(self):
 		"""
@@ -183,8 +197,20 @@
 					"currency": self.currency,
 				}
 			)
-			if len(employees) > 30:
-				frappe.enqueue(create_salary_slips_for_employees, timeout=600, employees=employees, args=args)
+			if len(employees) > 30 or frappe.flags.enqueue_payroll_entry:
+				self.db_set("status", "Queued")
+				frappe.enqueue(
+					create_salary_slips_for_employees,
+					timeout=600,
+					employees=employees,
+					args=args,
+					publish_progress=False,
+				)
+				frappe.msgprint(
+					_("Salary Slip creation is queued. It may take a few minutes"),
+					alert=True,
+					indicator="blue",
+				)
 			else:
 				create_salary_slips_for_employees(employees, args, publish_progress=False)
 				# since this method is called via frm.call this doc needs to be updated manually
@@ -214,13 +240,23 @@
 	@frappe.whitelist()
 	def submit_salary_slips(self):
 		self.check_permission("write")
-		ss_list = self.get_sal_slip_list(ss_status=0)
-		if len(ss_list) > 30:
+		salary_slips = self.get_sal_slip_list(ss_status=0)
+		if len(salary_slips) > 30 or frappe.flags.enqueue_payroll_entry:
+			self.db_set("status", "Queued")
 			frappe.enqueue(
-				submit_salary_slips_for_employees, timeout=600, payroll_entry=self, salary_slips=ss_list
+				submit_salary_slips_for_employees,
+				timeout=600,
+				payroll_entry=self,
+				salary_slips=salary_slips,
+				publish_progress=False,
+			)
+			frappe.msgprint(
+				_("Salary Slip submission is queued. It may take a few minutes"),
+				alert=True,
+				indicator="blue",
 			)
 		else:
-			submit_salary_slips_for_employees(self, ss_list, publish_progress=False)
+			submit_salary_slips_for_employees(self, salary_slips, publish_progress=False)
 
 	def email_salary_slip(self, submitted_ss):
 		if frappe.db.get_single_value("Payroll Settings", "email_salary_slip_to_employee"):
@@ -233,7 +269,11 @@
 		)
 
 		if not account:
-			frappe.throw(_("Please set account in Salary Component {0}").format(salary_component))
+			frappe.throw(
+				_("Please set account in Salary Component {0}").format(
+					get_link_to_form("Salary Component", salary_component)
+				)
+			)
 
 		return account
 
@@ -790,36 +830,80 @@
 	return response
 
 
+def log_payroll_failure(process, payroll_entry, error):
+	error_log = frappe.log_error(
+		title=_("Salary Slip {0} failed for Payroll Entry {1}").format(process, payroll_entry.name)
+	)
+	message_log = frappe.message_log.pop() if frappe.message_log else str(error)
+
+	try:
+		error_message = json.loads(message_log).get("message")
+	except Exception:
+		error_message = message_log
+
+	error_message += "\n" + _("Check Error Log {0} for more details.").format(
+		get_link_to_form("Error Log", error_log.name)
+	)
+
+	payroll_entry.db_set({"error_message": error_message, "status": "Failed"})
+
+
 def create_salary_slips_for_employees(employees, args, publish_progress=True):
-	salary_slips_exists_for = get_existing_salary_slips(employees, args)
-	count = 0
-	salary_slips_not_created = []
-	for emp in employees:
-		if emp not in salary_slips_exists_for:
-			args.update({"doctype": "Salary Slip", "employee": emp})
-			ss = frappe.get_doc(args)
-			ss.insert()
-			count += 1
-			if publish_progress:
-				frappe.publish_progress(
-					count * 100 / len(set(employees) - set(salary_slips_exists_for)),
-					title=_("Creating Salary Slips..."),
-				)
+	try:
+		payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry)
+		salary_slips_exist_for = get_existing_salary_slips(employees, args)
+		count = 0
 
-		else:
-			salary_slips_not_created.append(emp)
+		for emp in employees:
+			if emp not in salary_slips_exist_for:
+				args.update({"doctype": "Salary Slip", "employee": emp})
+				frappe.get_doc(args).insert()
 
-	payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry)
-	payroll_entry.db_set("salary_slips_created", 1)
-	payroll_entry.notify_update()
+				count += 1
+				if publish_progress:
+					frappe.publish_progress(
+						count * 100 / len(set(employees) - set(salary_slips_exist_for)),
+						title=_("Creating Salary Slips..."),
+					)
 
-	if salary_slips_not_created:
+		payroll_entry.db_set({"status": "Submitted", "salary_slips_created": 1, "error_message": ""})
+
+		if salary_slips_exist_for:
+			frappe.msgprint(
+				_(
+					"Salary Slips already exist for employees {}, and will not be processed by this payroll."
+				).format(frappe.bold(", ".join(emp for emp in salary_slips_exist_for))),
+				title=_("Message"),
+				indicator="orange",
+			)
+
+	except Exception as e:
+		frappe.db.rollback()
+		log_payroll_failure("creation", payroll_entry, e)
+
+	finally:
+		frappe.db.commit()  # nosemgrep
+		frappe.publish_realtime("completed_salary_slip_creation")
+
+
+def show_payroll_submission_status(submitted, unsubmitted, payroll_entry):
+	if not submitted and not unsubmitted:
 		frappe.msgprint(
 			_(
-				"Salary Slips already exists for employees {}, and will not be processed by this payroll."
-			).format(frappe.bold(", ".join([emp for emp in salary_slips_not_created]))),
-			title=_("Message"),
-			indicator="orange",
+				"No salary slip found to submit for the above selected criteria OR salary slip already submitted"
+			)
+		)
+	elif submitted and not unsubmitted:
+		frappe.msgprint(
+			_("Salary Slips submitted for period from {0} to {1}").format(
+				payroll_entry.start_date, payroll_entry.end_date
+			)
+		)
+	elif unsubmitted:
+		frappe.msgprint(
+			_("Could not submit some Salary Slips: {}").format(
+				", ".join(get_link_to_form("Salary Slip", entry) for entry in unsubmitted)
+			)
 		)
 
 
@@ -837,45 +921,41 @@
 
 
 def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progress=True):
-	submitted_ss = []
-	not_submitted_ss = []
-	frappe.flags.via_payroll_entry = True
+	try:
+		submitted = []
+		unsubmitted = []
+		frappe.flags.via_payroll_entry = True
+		count = 0
 
-	count = 0
-	for ss in salary_slips:
-		ss_obj = frappe.get_doc("Salary Slip", ss[0])
-		if ss_obj.net_pay < 0:
-			not_submitted_ss.append(ss[0])
-		else:
-			try:
-				ss_obj.submit()
-				submitted_ss.append(ss_obj)
-			except frappe.ValidationError:
-				not_submitted_ss.append(ss[0])
+		for entry in salary_slips:
+			salary_slip = frappe.get_doc("Salary Slip", entry[0])
+			if salary_slip.net_pay < 0:
+				unsubmitted.append(entry[0])
+			else:
+				try:
+					salary_slip.submit()
+					submitted.append(salary_slip)
+				except frappe.ValidationError:
+					unsubmitted.append(entry[0])
 
-		count += 1
-		if publish_progress:
-			frappe.publish_progress(count * 100 / len(salary_slips), title=_("Submitting Salary Slips..."))
-	if submitted_ss:
-		payroll_entry.make_accrual_jv_entry()
-		frappe.msgprint(
-			_("Salary Slip submitted for period from {0} to {1}").format(ss_obj.start_date, ss_obj.end_date)
-		)
+			count += 1
+			if publish_progress:
+				frappe.publish_progress(count * 100 / len(salary_slips), title=_("Submitting Salary Slips..."))
 
-		payroll_entry.email_salary_slip(submitted_ss)
+		if submitted:
+			payroll_entry.make_accrual_jv_entry()
+			payroll_entry.email_salary_slip(submitted)
+			payroll_entry.db_set({"salary_slips_submitted": 1, "status": "Submitted", "error_message": ""})
 
-		payroll_entry.db_set("salary_slips_submitted", 1)
-		payroll_entry.notify_update()
+		show_payroll_submission_status(submitted, unsubmitted, payroll_entry)
 
-	if not submitted_ss and not not_submitted_ss:
-		frappe.msgprint(
-			_(
-				"No salary slip found to submit for the above selected criteria OR salary slip already submitted"
-			)
-		)
+	except Exception as e:
+		frappe.db.rollback()
+		log_payroll_failure("submission", payroll_entry, e)
 
-	if not_submitted_ss:
-		frappe.msgprint(_("Could not submit some Salary Slips"))
+	finally:
+		frappe.db.commit()  # nosemgrep
+		frappe.publish_realtime("completed_salary_slip_submission")
 
 	frappe.flags.via_payroll_entry = False
 
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry_list.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry_list.js
new file mode 100644
index 0000000..56390b7
--- /dev/null
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry_list.js
@@ -0,0 +1,18 @@
+// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+// License: GNU General Public License v3. See license.txt
+
+// render
+frappe.listview_settings['Payroll Entry'] = {
+	has_indicator_for_draft: 1,
+	get_indicator: function(doc) {
+		var status_color = {
+			'Draft': 'red',
+			'Submitted': 'blue',
+			'Queued': 'orange',
+			'Failed': 'red',
+			'Cancelled': 'red'
+
+		};
+		return [__(doc.status), status_color[doc.status], 'status,=,'+doc.status];
+	}
+};
diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
index fda0fcf..0363a0c 100644
--- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
@@ -5,6 +5,7 @@
 
 import frappe
 from dateutil.relativedelta import relativedelta
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import add_months
 
 import erpnext
@@ -22,10 +23,9 @@
 from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_end_date, get_start_end_dates
 from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
 	create_account,
-	get_salary_component_account,
 	make_deduction_salary_component,
 	make_earning_salary_component,
-	make_employee_salary_slip,
+	set_salary_component_account,
 )
 from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
 	create_salary_structure_assignment,
@@ -35,13 +35,7 @@
 test_dependencies = ["Holiday List"]
 
 
-class TestPayrollEntry(unittest.TestCase):
-	@classmethod
-	def setUpClass(cls):
-		frappe.db.set_value(
-			"Company", erpnext.get_default_company(), "default_holiday_list", "_Test Holiday List"
-		)
-
+class TestPayrollEntry(FrappeTestCase):
 	def setUp(self):
 		for dt in [
 			"Salary Slip",
@@ -52,81 +46,72 @@
 			"Salary Structure Assignment",
 			"Payroll Employee Detail",
 			"Additional Salary",
+			"Loan",
 		]:
-			frappe.db.sql("delete from `tab%s`" % dt)
+			frappe.db.delete(dt)
 
 		make_earning_salary_component(setup=True, company_list=["_Test Company"])
 		make_deduction_salary_component(setup=True, test_tax=False, company_list=["_Test Company"])
 
+		frappe.db.set_value("Company", "_Test Company", "default_holiday_list", "_Test Holiday List")
 		frappe.db.set_value("Payroll Settings", None, "email_salary_slip_to_employee", 0)
 
-	def test_payroll_entry(self):  # pylint: disable=no-self-use
-		company = erpnext.get_default_company()
-		for data in frappe.get_all("Salary Component", fields=["name"]):
-			if not frappe.db.get_value(
-				"Salary Component Account", {"parent": data.name, "company": company}, "name"
-			):
-				get_salary_component_account(data.name)
-
-		employee = frappe.db.get_value("Employee", {"company": company})
-		company_doc = frappe.get_doc("Company", company)
-		make_salary_structure(
-			"_Test Salary Structure",
-			"Monthly",
-			employee,
-			company=company,
-			currency=company_doc.default_currency,
+		# set default payable account
+		default_account = frappe.db.get_value(
+			"Company", "_Test Company", "default_payroll_payable_account"
 		)
-		dates = get_start_end_dates("Monthly", nowdate())
-		if not frappe.db.get_value(
-			"Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}
-		):
-			make_payroll_entry(
-				start_date=dates.start_date,
-				end_date=dates.end_date,
-				payable_account=company_doc.default_payroll_payable_account,
-				currency=company_doc.default_currency,
+		if not default_account or default_account != "_Test Payroll Payable - _TC":
+			create_account(
+				account_name="_Test Payroll Payable",
+				company="_Test Company",
+				parent_account="Current Liabilities - _TC",
+				account_type="Payable",
+			)
+			frappe.db.set_value(
+				"Company", "_Test Company", "default_payroll_payable_account", "_Test Payroll Payable - _TC"
 			)
 
-	def test_multi_currency_payroll_entry(self):  # pylint: disable=no-self-use
-		company = erpnext.get_default_company()
-		employee = make_employee("test_muti_currency_employee@payroll.com", company=company)
-		for data in frappe.get_all("Salary Component", fields=["name"]):
-			if not frappe.db.get_value(
-				"Salary Component Account", {"parent": data.name, "company": company}, "name"
-			):
-				get_salary_component_account(data.name)
+	def test_payroll_entry(self):
+		company = frappe.get_doc("Company", "_Test Company")
+		employee = frappe.db.get_value("Employee", {"company": "_Test Company"})
+		setup_salary_structure(employee, company)
 
-		company_doc = frappe.get_doc("Company", company)
-		salary_structure = make_salary_structure(
-			"_Test Multi Currency Salary Structure", "Monthly", company=company, currency="USD"
+		dates = get_start_end_dates("Monthly", nowdate())
+		make_payroll_entry(
+			start_date=dates.start_date,
+			end_date=dates.end_date,
+			payable_account=company.default_payroll_payable_account,
+			currency=company.default_currency,
+			company=company.name,
 		)
-		create_salary_structure_assignment(
-			employee, salary_structure.name, company=company, currency="USD"
+
+	def test_multi_currency_payroll_entry(self):
+		company = frappe.get_doc("Company", "_Test Company")
+		employee = make_employee(
+			"test_muti_currency_employee@payroll.com", company=company.name, department="Accounts - _TC"
 		)
-		frappe.db.sql(
-			"""delete from `tabSalary Slip` where employee=%s""",
-			(frappe.db.get_value("Employee", {"user_id": "test_muti_currency_employee@payroll.com"})),
-		)
-		salary_slip = get_salary_slip(
-			"test_muti_currency_employee@payroll.com", "Monthly", "_Test Multi Currency Salary Structure"
-		)
+		salary_structure = "_Test Multi Currency Salary Structure"
+		setup_salary_structure(employee, company, "USD", salary_structure)
+
 		dates = get_start_end_dates("Monthly", nowdate())
 		payroll_entry = make_payroll_entry(
 			start_date=dates.start_date,
 			end_date=dates.end_date,
-			payable_account=company_doc.default_payroll_payable_account,
+			payable_account=company.default_payroll_payable_account,
 			currency="USD",
 			exchange_rate=70,
+			company=company.name,
+			cost_center="Main - _TC",
 		)
 		payroll_entry.make_payment_entry()
 
-		salary_slip.load_from_db()
+		salary_slip = frappe.db.get_value("Salary Slip", {"payroll_entry": payroll_entry.name}, "name")
+		salary_slip = frappe.get_doc("Salary Slip", salary_slip)
 
+		payroll_entry.reload()
 		payroll_je = salary_slip.journal_entry
 		if payroll_je:
 			payroll_je_doc = frappe.get_doc("Journal Entry", payroll_je)
-
 			self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit)
 			self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit)
 
@@ -139,27 +124,15 @@
 			(payroll_entry.name),
 			as_dict=1,
 		)
-
 		self.assertEqual(salary_slip.base_net_pay, payment_entry[0].total_debit)
 		self.assertEqual(salary_slip.base_net_pay, payment_entry[0].total_credit)
 
-	def test_payroll_entry_with_employee_cost_center(self):  # pylint: disable=no-self-use
-		for data in frappe.get_all("Salary Component", fields=["name"]):
-			if not frappe.db.get_value(
-				"Salary Component Account", {"parent": data.name, "company": "_Test Company"}, "name"
-			):
-				get_salary_component_account(data.name)
-
+	def test_payroll_entry_with_employee_cost_center(self):
 		if not frappe.db.exists("Department", "cc - _TC"):
 			frappe.get_doc(
 				{"doctype": "Department", "department_name": "cc", "company": "_Test Company"}
 			).insert()
 
-		frappe.db.sql("""delete from `tabEmployee` where employee_name='test_employee1@example.com' """)
-		frappe.db.sql("""delete from `tabEmployee` where employee_name='test_employee2@example.com' """)
-		frappe.db.sql("""delete from `tabSalary Structure` where name='_Test Salary Structure 1' """)
-		frappe.db.sql("""delete from `tabSalary Structure` where name='_Test Salary Structure 2' """)
-
 		employee1 = make_employee(
 			"test_employee1@example.com",
 			payroll_cost_center="_Test Cost Center - _TC",
@@ -170,38 +143,15 @@
 			"test_employee2@example.com", department="cc - _TC", company="_Test Company"
 		)
 
-		if not frappe.db.exists("Account", "_Test Payroll Payable - _TC"):
-			create_account(
-				account_name="_Test Payroll Payable",
-				company="_Test Company",
-				parent_account="Current Liabilities - _TC",
-				account_type="Payable",
-			)
+		company = frappe.get_doc("Company", "_Test Company")
+		setup_salary_structure(employee1, company)
 
-		if (
-			not frappe.db.get_value("Company", "_Test Company", "default_payroll_payable_account")
-			or frappe.db.get_value("Company", "_Test Company", "default_payroll_payable_account")
-			!= "_Test Payroll Payable - _TC"
-		):
-			frappe.db.set_value(
-				"Company", "_Test Company", "default_payroll_payable_account", "_Test Payroll Payable - _TC"
-			)
-		currency = frappe.db.get_value("Company", "_Test Company", "default_currency")
-
-		make_salary_structure(
-			"_Test Salary Structure 1",
-			"Monthly",
-			employee1,
-			company="_Test Company",
-			currency=currency,
-			test_tax=False,
-		)
 		ss = make_salary_structure(
 			"_Test Salary Structure 2",
 			"Monthly",
 			employee2,
 			company="_Test Company",
-			currency=currency,
+			currency=company.default_currency,
 			test_tax=False,
 		)
 
@@ -220,42 +170,38 @@
 		ssa_doc.append(
 			"payroll_cost_centers", {"cost_center": "_Test Cost Center 2 - _TC", "percentage": 40}
 		)
-
 		ssa_doc.save()
 
 		dates = get_start_end_dates("Monthly", nowdate())
-		if not frappe.db.get_value(
-			"Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}
-		):
-			pe = make_payroll_entry(
-				start_date=dates.start_date,
-				end_date=dates.end_date,
-				payable_account="_Test Payroll Payable - _TC",
-				currency=frappe.db.get_value("Company", "_Test Company", "default_currency"),
-				department="cc - _TC",
-				company="_Test Company",
-				payment_account="Cash - _TC",
-				cost_center="Main - _TC",
-			)
-			je = frappe.db.get_value("Salary Slip", {"payroll_entry": pe.name}, "journal_entry")
-			je_entries = frappe.db.sql(
-				"""
-				select account, cost_center, debit, credit
-				from `tabJournal Entry Account`
-				where parent=%s
-				order by account, cost_center
-			""",
-				je,
-			)
-			expected_je = (
-				("_Test Payroll Payable - _TC", "Main - _TC", 0.0, 155600.0),
-				("Salary - _TC", "_Test Cost Center - _TC", 124800.0, 0.0),
-				("Salary - _TC", "_Test Cost Center 2 - _TC", 31200.0, 0.0),
-				("Salary Deductions - _TC", "_Test Cost Center - _TC", 0.0, 320.0),
-				("Salary Deductions - _TC", "_Test Cost Center 2 - _TC", 0.0, 80.0),
-			)
+		pe = make_payroll_entry(
+			start_date=dates.start_date,
+			end_date=dates.end_date,
+			payable_account="_Test Payroll Payable - _TC",
+			currency=frappe.db.get_value("Company", "_Test Company", "default_currency"),
+			department="cc - _TC",
+			company="_Test Company",
+			payment_account="Cash - _TC",
+			cost_center="Main - _TC",
+		)
+		je = frappe.db.get_value("Salary Slip", {"payroll_entry": pe.name}, "journal_entry")
+		je_entries = frappe.db.sql(
+			"""
+			select account, cost_center, debit, credit
+			from `tabJournal Entry Account`
+			where parent=%s
+			order by account, cost_center
+		""",
+			je,
+		)
+		expected_je = (
+			("_Test Payroll Payable - _TC", "Main - _TC", 0.0, 155600.0),
+			("Salary - _TC", "_Test Cost Center - _TC", 124800.0, 0.0),
+			("Salary - _TC", "_Test Cost Center 2 - _TC", 31200.0, 0.0),
+			("Salary Deductions - _TC", "_Test Cost Center - _TC", 0.0, 320.0),
+			("Salary Deductions - _TC", "_Test Cost Center 2 - _TC", 0.0, 80.0),
+		)
 
-			self.assertEqual(je_entries, expected_je)
+		self.assertEqual(je_entries, expected_je)
 
 	def test_get_end_date(self):
 		self.assertEqual(get_end_date("2017-01-01", "monthly"), {"end_date": "2017-01-31"})
@@ -268,31 +214,22 @@
 		self.assertEqual(get_end_date("2017-02-15", "daily"), {"end_date": "2017-02-15"})
 
 	def test_loan(self):
-		branch = "Test Employee Branch"
-		applicant = make_employee("test_employee@loan.com", company="_Test Company")
 		company = "_Test Company"
-		holiday_list = make_holiday("test holiday for loan")
-
-		company_doc = frappe.get_doc("Company", company)
-		if not company_doc.default_payroll_payable_account:
-			company_doc.default_payroll_payable_account = frappe.db.get_value(
-				"Account", {"company": company, "root_type": "Liability", "account_type": ""}, "name"
-			)
-			company_doc.save()
+		branch = "Test Employee Branch"
 
 		if not frappe.db.exists("Branch", branch):
 			frappe.get_doc({"doctype": "Branch", "branch": branch}).insert()
+		holiday_list = make_holiday("test holiday for loan")
 
-		employee_doc = frappe.get_doc("Employee", applicant)
-		employee_doc.branch = branch
-		employee_doc.holiday_list = holiday_list
-		employee_doc.save()
+		applicant = make_employee(
+			"test_employee@loan.com", company="_Test Company", branch=branch, holiday_list=holiday_list
+		)
+		company_doc = frappe.get_doc("Company", company)
 
-		salary_structure = "Test Salary Structure for Loan"
 		make_salary_structure(
-			salary_structure,
+			"Test Salary Structure for Loan",
 			"Monthly",
-			employee=employee_doc.name,
+			employee=applicant,
 			company="_Test Company",
 			currency=company_doc.default_currency,
 		)
@@ -353,11 +290,110 @@
 				self.assertEqual(row.principal_amount, principal_amount)
 				self.assertEqual(row.total_payment, interest_amount + principal_amount)
 
-		if salary_slip.docstatus == 0:
-			frappe.delete_doc("Salary Slip", name)
+	def test_salary_slip_operation_queueing(self):
+		company = "_Test Company"
+		company_doc = frappe.get_doc("Company", company)
+		employee = make_employee("test_employee@payroll.com", company=company)
+		setup_salary_structure(employee, company_doc)
+
+		# enqueue salary slip creation via payroll entry
+		# Payroll Entry status should change to Queued
+		dates = get_start_end_dates("Monthly", nowdate())
+		payroll_entry = get_payroll_entry(
+			start_date=dates.start_date,
+			end_date=dates.end_date,
+			payable_account=company_doc.default_payroll_payable_account,
+			currency=company_doc.default_currency,
+			company=company_doc.name,
+			cost_center="Main - _TC",
+		)
+		frappe.flags.enqueue_payroll_entry = True
+		payroll_entry.submit()
+		payroll_entry.reload()
+
+		self.assertEqual(payroll_entry.status, "Queued")
+		frappe.flags.enqueue_payroll_entry = False
+
+	def test_salary_slip_operation_failure(self):
+		company = "_Test Company"
+		company_doc = frappe.get_doc("Company", company)
+		employee = make_employee("test_employee@payroll.com", company=company)
+
+		salary_structure = make_salary_structure(
+			"_Test Salary Structure",
+			"Monthly",
+			employee,
+			company=company,
+			currency=company_doc.default_currency,
+		)
+
+		# reset account in component to test submission failure
+		component = frappe.get_doc("Salary Component", salary_structure.earnings[0].salary_component)
+		component.accounts = []
+		component.save()
+
+		# salary slip submission via payroll entry
+		# Payroll Entry status should change to Failed because of the missing account setup
+		dates = get_start_end_dates("Monthly", nowdate())
+		payroll_entry = get_payroll_entry(
+			start_date=dates.start_date,
+			end_date=dates.end_date,
+			payable_account=company_doc.default_payroll_payable_account,
+			currency=company_doc.default_currency,
+			company=company_doc.name,
+			cost_center="Main - _TC",
+		)
+
+		# set employee as Inactive to check creation failure
+		frappe.db.set_value("Employee", employee, "status", "Inactive")
+		payroll_entry.submit()
+		payroll_entry.reload()
+		self.assertEqual(payroll_entry.status, "Failed")
+		self.assertIsNotNone(payroll_entry.error_message)
+
+		frappe.db.set_value("Employee", employee, "status", "Active")
+		payroll_entry.submit()
+		payroll_entry.submit_salary_slips()
+
+		payroll_entry.reload()
+		self.assertEqual(payroll_entry.status, "Failed")
+		self.assertIsNotNone(payroll_entry.error_message)
+
+		# set accounts
+		for data in frappe.get_all("Salary Component", pluck="name"):
+			set_salary_component_account(data, company_list=[company])
+
+		# Payroll Entry successful, status should change to Submitted
+		payroll_entry.submit_salary_slips()
+		payroll_entry.reload()
+
+		self.assertEqual(payroll_entry.status, "Submitted")
+		self.assertEqual(payroll_entry.error_message, "")
+
+	def test_payroll_entry_status(self):
+		company = "_Test Company"
+		company_doc = frappe.get_doc("Company", company)
+		employee = make_employee("test_employee@payroll.com", company=company)
+
+		setup_salary_structure(employee, company_doc)
+
+		dates = get_start_end_dates("Monthly", nowdate())
+		payroll_entry = get_payroll_entry(
+			start_date=dates.start_date,
+			end_date=dates.end_date,
+			payable_account=company_doc.default_payroll_payable_account,
+			currency=company_doc.default_currency,
+			company=company_doc.name,
+			cost_center="Main - _TC",
+		)
+		payroll_entry.submit()
+		self.assertEqual(payroll_entry.status, "Submitted")
+
+		payroll_entry.cancel()
+		self.assertEqual(payroll_entry.status, "Cancelled")
 
 
-def make_payroll_entry(**args):
+def get_payroll_entry(**args):
 	args = frappe._dict(args)
 
 	payroll_entry = frappe.new_doc("Payroll Entry")
@@ -380,8 +416,17 @@
 		payroll_entry.payment_account = args.payment_account
 
 	payroll_entry.fill_employee_details()
-	payroll_entry.save()
-	payroll_entry.create_salary_slips()
+	payroll_entry.insert()
+
+	# Commit so that the first salary slip creation failure does not rollback the Payroll Entry insert.
+	frappe.db.commit()  # nosemgrep
+
+	return payroll_entry
+
+
+def make_payroll_entry(**args):
+	payroll_entry = get_payroll_entry(**args)
+	payroll_entry.submit()
 	payroll_entry.submit_salary_slips()
 	if payroll_entry.get_sal_slip_list(ss_status=1):
 		payroll_entry.make_payment_entry()
@@ -423,10 +468,17 @@
 	return holiday_list_name
 
 
-def get_salary_slip(user, period, salary_structure):
-	salary_slip = make_employee_salary_slip(user, period, salary_structure)
-	salary_slip.exchange_rate = 70
-	salary_slip.calculate_net_pay()
-	salary_slip.db_update()
+def setup_salary_structure(employee, company_doc, currency=None, salary_structure=None):
+	for data in frappe.get_all("Salary Component", pluck="name"):
+		if not frappe.db.get_value(
+			"Salary Component Account", {"parent": data, "company": company_doc.name}, "name"
+		):
+			set_salary_component_account(data)
 
-	return salary_slip
+	make_salary_structure(
+		salary_structure or "_Test Salary Structure",
+		"Monthly",
+		employee,
+		company=company_doc.name,
+		currency=(currency or company_doc.default_currency),
+	)
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 60ba2d9..5e3814b 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -1050,10 +1050,10 @@
 		doc.update(salary_component)
 		doc.insert()
 
-		get_salary_component_account(doc, company_list)
+		set_salary_component_account(doc, company_list)
 
 
-def get_salary_component_account(sal_comp, company_list=None):
+def set_salary_component_account(sal_comp, company_list=None):
 	company = erpnext.get_default_company()
 
 	if company_list and company not in company_list:
diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
index 5c78e8f..8cc2ea3 100644
--- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
@@ -169,9 +169,6 @@
 	payroll_period=None,
 	include_flexi_benefits=False,
 ):
-	if test_tax:
-		frappe.db.sql("""delete from `tabSalary Structure` where name=%s""", (salary_structure))
-
 	if frappe.db.exists("Salary Structure", salary_structure):
 		frappe.db.delete("Salary Structure", salary_structure)
 
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/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 0bdbe56..6cbc12c 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -1155,8 +1155,11 @@
 		.inner_join(links)
 		.on(address.name == links.parent)
 		.select(address.gstin)
+		.distinct()
 		.where(links.link_doctype == "Company")
 		.where(links.link_name == company)
+		.where(address.gstin.isnotnull())
+		.where(address.gstin != "")
 		.run(as_dict=1)
 	)
 
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index acae37f..96308f0 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -783,6 +783,7 @@
 
 	def test_auto_insert_price(self):
 		make_item("_Test Item for Auto Price List", {"is_stock_item": 0})
+		make_item("_Test Item for Auto Price List with Discount Percentage", {"is_stock_item": 0})
 		frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 1)
 
 		item_price = frappe.db.get_value(
@@ -804,6 +805,25 @@
 			100,
 		)
 
+		make_sales_order(
+			item_code="_Test Item for Auto Price List with Discount Percentage",
+			selling_price_list="_Test Price List",
+			price_list_rate=200,
+			discount_percentage=20,
+		)
+
+		self.assertEqual(
+			frappe.db.get_value(
+				"Item Price",
+				{
+					"price_list": "_Test Price List",
+					"item_code": "_Test Item for Auto Price List with Discount Percentage",
+				},
+				"price_list_rate",
+			),
+			200,
+		)
+
 		# do not update price list
 		frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 0)
 
@@ -1659,7 +1679,9 @@
 				"warehouse": args.warehouse,
 				"qty": args.qty or 10,
 				"uom": args.uom or None,
-				"rate": args.rate or 100,
+				"price_list_rate": args.price_list_rate or None,
+				"discount_percentage": args.discount_percentage or None,
+				"rate": args.rate or (None if args.price_list_rate else 100),
 				"against_blanket_order": args.against_blanket_order,
 			},
 		)
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json
index 005e24c..2abb169 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.json
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.json
@@ -179,7 +179,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2022-04-14 16:01:29.405642",
+ "modified": "2022-05-31 19:39:48.398738",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Selling Settings",
@@ -193,6 +193,15 @@
    "role": "System Manager",
    "share": 1,
    "write": 1
+  },
+  {
+   "create": 1,
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "role": "Sales Manager",
+   "share": 1,
+   "write": 1
   }
  ],
  "sort_field": "modified",
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/get_item_details.py b/erpnext/stock/get_item_details.py
index c6241f8..c8d9f54 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -353,6 +353,7 @@
 			"has_batch_no": item.has_batch_no,
 			"batch_no": args.get("batch_no"),
 			"uom": args.uom,
+			"stock_uom": item.stock_uom,
 			"min_order_qty": flt(item.min_order_qty) if args.doctype == "Material Request" else "",
 			"qty": flt(args.qty) or 1.0,
 			"stock_qty": flt(args.qty) or 1.0,
@@ -365,7 +366,7 @@
 			"net_rate": 0.0,
 			"net_amount": 0.0,
 			"discount_percentage": 0.0,
-			"discount_amount": 0.0,
+			"discount_amount": flt(args.discount_amount) or 0.0,
 			"supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults),
 			"update_stock": args.get("update_stock")
 			if args.get("doctype") in ["Sales Invoice", "Purchase Invoice"]
@@ -823,7 +824,9 @@
 	):
 		if frappe.has_permission("Item Price", "write"):
 			price_list_rate = (
-				args.rate / args.get("conversion_factor") if args.get("conversion_factor") else args.rate
+				(args.rate + args.discount_amount) / args.get("conversion_factor")
+				if args.get("conversion_factor")
+				else (args.rate + args.discount_amount)
 			)
 
 			item_price = frappe.db.get_value(
@@ -849,6 +852,7 @@
 						"item_code": args.item_code,
 						"currency": args.currency,
 						"price_list_rate": price_list_rate,
+						"uom": args.stock_uom,
 					}
 				)
 				item_price.insert()
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: