Merge pull request #31194 from ruthra-kumar/bug_sales_order_analysis_report

fix: incorrect billed_qty in sales order analysis report when multiple delivery notes for item
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/.github/stale.yml b/.github/stale.yml
index fbf6447..cdce0ae 100644
--- a/.github/stale.yml
+++ b/.github/stale.yml
@@ -23,15 +23,3 @@
     activity occurs, but it only takes a comment to keep a contribution alive
     :) Also, even if it is closed, you can always reopen the PR when you're
     ready. Thank you for contributing.
-
-issues:
-  daysUntilStale: 90
-  daysUntilClose: 7
-  exemptLabels:
-    - valid
-    - to-validate
-    - QA
-  markComment: >
-    This issue has been automatically marked as inactive because it has not had
-    recent activity and it wasn't validated by maintainer team. It will be
-    closed within a week if no further activity occurs.
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/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 42917f8..7e3597e 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -45,8 +45,6 @@
 		if (this.frm.doc.supplier && this.frm.doc.__islocal) {
 			this.frm.trigger('supplier');
 		}
-
-		erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
 	}
 
 	refresh(doc) {
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 9dde85f..aefa9a5 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -52,7 +52,6 @@
 			me.frm.refresh_fields();
 		}
 		erpnext.queries.setup_warehouse_query(this.frm);
-		erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
 	}
 
 	refresh(doc, dt, dn) {
diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
index 2e7213f..ac70666 100644
--- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
+++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
@@ -443,12 +443,6 @@
 	]  # nosec
 
 
-def get_deducted_taxes():
-	return frappe.db.sql_list(
-		"select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'"
-	)
-
-
 def get_tax_accounts(
 	item_list,
 	columns,
@@ -462,6 +456,7 @@
 	tax_columns = []
 	invoice_item_row = {}
 	itemised_tax = {}
+	add_deduct_tax = "charge_type"
 
 	tax_amount_precision = (
 		get_field_precision(
@@ -477,13 +472,13 @@
 	conditions = ""
 	if doctype == "Purchase Invoice":
 		conditions = " and category in ('Total', 'Valuation and Total') and base_tax_amount_after_discount_amount != 0"
+		add_deduct_tax = "add_deduct_tax"
 
-	deducted_tax = get_deducted_taxes()
 	tax_details = frappe.db.sql(
 		"""
 		select
 			name, parent, description, item_wise_tax_detail,
-			charge_type, base_tax_amount_after_discount_amount
+			charge_type, {add_deduct_tax}, base_tax_amount_after_discount_amount
 		from `tab%s`
 		where
 			parenttype = %s and docstatus = 1
@@ -491,12 +486,22 @@
 			and parent in (%s)
 			%s
 		order by description
-	"""
+	""".format(
+			add_deduct_tax=add_deduct_tax
+		)
 		% (tax_doctype, "%s", ", ".join(["%s"] * len(invoice_item_row)), conditions),
 		tuple([doctype] + list(invoice_item_row)),
 	)
 
-	for name, parent, description, item_wise_tax_detail, charge_type, tax_amount in tax_details:
+	for (
+		name,
+		parent,
+		description,
+		item_wise_tax_detail,
+		charge_type,
+		add_deduct_tax,
+		tax_amount,
+	) in tax_details:
 		description = handle_html(description)
 		if description not in tax_columns and tax_amount:
 			# as description is text editor earlier and markup can break the column convention in reports
@@ -529,7 +534,9 @@
 						if item_tax_amount:
 							tax_value = flt(item_tax_amount, tax_amount_precision)
 							tax_value = (
-								tax_value * -1 if (doctype == "Purchase Invoice" and name in deducted_tax) else tax_value
+								tax_value * -1
+								if (doctype == "Purchase Invoice" and add_deduct_tax == "Deduct")
+								else tax_value
 							)
 
 							itemised_tax.setdefault(d.name, {})[description] = frappe._dict(
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index c9e6798..da45610 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -43,8 +43,6 @@
 		erpnext.queries.setup_queries(frm, "Warehouse", function() {
 			return erpnext.queries.warehouse(frm.doc);
 		});
-
-		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 	},
 
 	apply_tds: function(frm) {
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 056084b..854c0d0 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1465,8 +1465,8 @@
 
 		if not party_gle_currency and (party_account_currency != self.currency):
 			frappe.throw(
-				_("Party Account {0} currency and document currency should be same").format(
-					frappe.bold(party_account)
+				_("Party Account {0} currency ({1}) and document currency ({2}) should be same").format(
+					frappe.bold(party_account), party_account_currency, self.currency
 				)
 			)
 
@@ -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/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index cd6b168..53c5df4 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -88,7 +88,7 @@
 		share_doc_with_approver(self, self.leave_approver)
 
 	def on_submit(self):
-		if self.status == "Open":
+		if self.status in ["Open", "Cancelled"]:
 			frappe.throw(
 				_("Only Leave Applications with status 'Approved' and 'Rejected' can be submitted")
 			)
@@ -1117,7 +1117,7 @@
 	WHERE
 		from_date <= %(end)s AND to_date >= %(start)s <= to_date
 		AND docstatus < 2
-		AND status != 'Rejected'
+		AND status in ('Approved', 'Open')
 	"""
 
 	if conditions:
@@ -1201,24 +1201,33 @@
 
 
 def get_approved_leaves_for_period(employee, leave_type, from_date, to_date):
-	query = """
-		select employee, leave_type, from_date, to_date, total_leave_days
-		from `tabLeave Application`
-		where employee=%(employee)s
-			and docstatus=1
-			and (from_date between %(from_date)s and %(to_date)s
-				or to_date between %(from_date)s and %(to_date)s
-				or (from_date < %(from_date)s and to_date > %(to_date)s))
-	"""
-	if leave_type:
-		query += "and leave_type=%(leave_type)s"
-
-	leave_applications = frappe.db.sql(
-		query,
-		{"from_date": from_date, "to_date": to_date, "employee": employee, "leave_type": leave_type},
-		as_dict=1,
+	LeaveApplication = frappe.qb.DocType("Leave Application")
+	query = (
+		frappe.qb.from_(LeaveApplication)
+		.select(
+			LeaveApplication.employee,
+			LeaveApplication.leave_type,
+			LeaveApplication.from_date,
+			LeaveApplication.to_date,
+			LeaveApplication.total_leave_days,
+		)
+		.where(
+			(LeaveApplication.employee == employee)
+			& (LeaveApplication.docstatus == 1)
+			& (LeaveApplication.status == "Approved")
+			& (
+				(LeaveApplication.from_date.between(from_date, to_date))
+				| (LeaveApplication.to_date.between(from_date, to_date))
+				| ((LeaveApplication.from_date < from_date) & (LeaveApplication.to_date > to_date))
+			)
+		)
 	)
 
+	if leave_type:
+		query = query.where(LeaveApplication.leave_type == leave_type)
+
+	leave_applications = query.run(as_dict=True)
+
 	leave_days = 0
 	for leave_app in leave_applications:
 		if leave_app.from_date >= getdate(from_date) and leave_app.to_date <= getdate(to_date):
diff --git a/erpnext/hr/doctype/leave_application/leave_application_list.js b/erpnext/hr/doctype/leave_application/leave_application_list.js
index a3c03b1..157271a 100644
--- a/erpnext/hr/doctype/leave_application/leave_application_list.js
+++ b/erpnext/hr/doctype/leave_application/leave_application_list.js
@@ -1,13 +1,14 @@
-frappe.listview_settings['Leave Application'] = {
+frappe.listview_settings["Leave Application"] = {
 	add_fields: ["leave_type", "employee", "employee_name", "total_leave_days", "from_date", "to_date"],
 	has_indicator_for_draft: 1,
 	get_indicator: function (doc) {
-		if (doc.status === "Approved") {
-			return [__("Approved"), "green", "status,=,Approved"];
-		} else if (doc.status === "Rejected") {
-			return [__("Rejected"), "red", "status,=,Rejected"];
-		} else {
-			return [__("Open"), "red", "status,=,Open"];
-		}
+		let status_color = {
+			"Approved": "green",
+			"Rejected": "red",
+			"Open": "orange",
+			"Cancelled": "red",
+			"Submitted": "blue"
+		};
+		return [__(doc.status), status_color[doc.status], "status,=," + doc.status];
 	}
 };
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 0a9fd8a..0199a5c 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -621,7 +621,7 @@
 		self.set_status(update_status)
 
 	def set_status(self, update_status=False):
-		if self.status == "On Hold":
+		if self.status == "On Hold" and self.docstatus == 0:
 			return
 
 		self.status = {0: "Open", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0]
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..5a98463 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,5 @@
 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
+erpnext.patches.v13_0.job_card_status_on_hold
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/job_card_status_on_hold.py b/erpnext/patches/v13_0/job_card_status_on_hold.py
new file mode 100644
index 0000000..8c67c3c
--- /dev/null
+++ b/erpnext/patches/v13_0/job_card_status_on_hold.py
@@ -0,0 +1,19 @@
+import frappe
+
+
+def execute():
+	job_cards = frappe.get_all(
+		"Job Card",
+		{"status": "On Hold", "docstatus": ("!=", 0)},
+		pluck="name",
+	)
+
+	for idx, job_card in enumerate(job_cards):
+		try:
+			doc = frappe.get_doc("Job Card", job_card)
+			doc.set_status()
+			doc.db_set("status", doc.status, update_modified=False)
+			if idx % 100 == 0:
+				frappe.db.commit()
+		except Exception:
+			continue
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/employee_benefit_application/employee_benefit_application.py b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
index 0acd447..8df1bb6 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
@@ -5,7 +5,7 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
-from frappe.utils import add_days, cint, cstr, date_diff, getdate, rounded
+from frappe.utils import add_days, cstr, date_diff, flt, getdate, rounded
 
 from erpnext.hr.utils import (
 	get_holiday_dates_for_employee,
@@ -27,11 +27,14 @@
 		validate_active_employee(self.employee)
 		self.validate_duplicate_on_payroll_period()
 		if not self.max_benefits:
-			self.max_benefits = get_max_benefits_remaining(self.employee, self.date, self.payroll_period)
+			self.max_benefits = flt(
+				get_max_benefits_remaining(self.employee, self.date, self.payroll_period),
+				self.precision("max_benefits"),
+			)
 		if self.max_benefits and self.max_benefits > 0:
 			self.validate_max_benefit_for_component()
 			self.validate_prev_benefit_claim()
-			if self.remaining_benefit > 0:
+			if self.remaining_benefit and self.remaining_benefit > 0:
 				self.validate_remaining_benefit_amount()
 		else:
 			frappe.throw(
@@ -110,7 +113,7 @@
 			max_benefit_amount = 0
 			for employee_benefit in self.employee_benefits:
 				self.validate_max_benefit(employee_benefit.earning_component)
-				max_benefit_amount += employee_benefit.amount
+				max_benefit_amount += flt(employee_benefit.amount)
 			if max_benefit_amount > self.max_benefits:
 				frappe.throw(
 					_("Maximum benefit amount of employee {0} exceeds {1}").format(
@@ -125,7 +128,8 @@
 		benefit_amount = 0
 		for employee_benefit in self.employee_benefits:
 			if employee_benefit.earning_component == earning_component_name:
-				benefit_amount += employee_benefit.amount
+				benefit_amount += flt(employee_benefit.amount)
+
 		prev_sal_slip_flexi_amount = get_sal_slip_total_benefit_given(
 			self.employee, frappe.get_doc("Payroll Period", self.payroll_period), earning_component_name
 		)
@@ -207,26 +211,47 @@
 def calculate_lwp(employee, start_date, holidays, working_days):
 	lwp = 0
 	holidays = "','".join(holidays)
+
 	for d in range(working_days):
-		dt = add_days(cstr(getdate(start_date)), d)
-		leave = frappe.db.sql(
-			"""
-			select t1.name, t1.half_day
-			from `tabLeave Application` t1, `tabLeave Type` t2
-			where t2.name = t1.leave_type
-			and t2.is_lwp = 1
-			and t1.docstatus = 1
-			and t1.employee = %(employee)s
-			and CASE WHEN t2.include_holiday != 1 THEN %(dt)s not in ('{0}') and %(dt)s between from_date and to_date
-			WHEN t2.include_holiday THEN %(dt)s between from_date and to_date
-			END
-			""".format(
-				holidays
-			),
-			{"employee": employee, "dt": dt},
+		date = add_days(cstr(getdate(start_date)), d)
+
+		LeaveApplication = frappe.qb.DocType("Leave Application")
+		LeaveType = frappe.qb.DocType("Leave Type")
+
+		is_half_day = (
+			frappe.qb.terms.Case()
+			.when(
+				(
+					(LeaveApplication.half_day_date == date)
+					| (LeaveApplication.from_date == LeaveApplication.to_date)
+				),
+				LeaveApplication.half_day,
+			)
+			.else_(0)
+		).as_("is_half_day")
+
+		query = (
+			frappe.qb.from_(LeaveApplication)
+			.inner_join(LeaveType)
+			.on((LeaveType.name == LeaveApplication.leave_type))
+			.select(LeaveApplication.name, is_half_day)
+			.where(
+				(LeaveType.is_lwp == 1)
+				& (LeaveApplication.docstatus == 1)
+				& (LeaveApplication.status == "Approved")
+				& (LeaveApplication.employee == employee)
+				& ((LeaveApplication.from_date <= date) & (date <= LeaveApplication.to_date))
+			)
 		)
-		if leave:
-			lwp = cint(leave[0][1]) and (lwp + 0.5) or (lwp + 1)
+
+		# if it's a holiday only include if leave type has "include holiday" enabled
+		if date in holidays:
+			query = query.where((LeaveType.include_holiday == "1"))
+		leaves = query.run(as_dict=True)
+
+		if leaves:
+			lwp += 0.5 if leaves[0].is_half_day else 1
+
 	return lwp
 
 
diff --git a/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.py b/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.py
index 02149ad..de8f9b6 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.py
+++ b/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.py
@@ -3,6 +3,82 @@
 
 import unittest
 
+import frappe
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import add_days, date_diff, get_year_ending, get_year_start, getdate
 
-class TestEmployeeBenefitApplication(unittest.TestCase):
-	pass
+from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.hr.doctype.holiday_list.test_holiday_list import set_holiday_list
+from erpnext.hr.doctype.leave_application.test_leave_application import get_first_sunday
+from erpnext.hr.utils import get_holiday_dates_for_employee
+from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import (
+	calculate_lwp,
+)
+from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration import (
+	create_payroll_period,
+)
+from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
+	make_holiday_list,
+	make_leave_application,
+)
+from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
+from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
+
+
+class TestEmployeeBenefitApplication(FrappeTestCase):
+	def setUp(self):
+		date = getdate()
+		make_holiday_list(from_date=get_year_start(date), to_date=get_year_ending(date))
+
+	@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
+	def test_employee_benefit_application(self):
+		payroll_period = create_payroll_period(name="_Test Payroll Period 1", company="_Test Company")
+		employee = make_employee("test_employee_benefits@salary.com", company="_Test Company")
+		first_sunday = get_first_sunday("Salary Slip Test Holiday List")
+
+		leave_application = make_leave_application(
+			employee,
+			add_days(first_sunday, 1),
+			add_days(first_sunday, 3),
+			"Leave Without Pay",
+			half_day=1,
+			half_day_date=add_days(first_sunday, 1),
+			submit=True,
+		)
+
+		frappe.db.set_value("Leave Type", "Leave Without Pay", "include_holiday", 0)
+		salary_structure = make_salary_structure(
+			"Test Employee Benefits",
+			"Monthly",
+			other_details={"max_benefits": 100000},
+			include_flexi_benefits=True,
+			employee=employee,
+			payroll_period=payroll_period,
+		)
+		salary_slip = make_salary_slip(salary_structure.name, employee=employee, posting_date=getdate())
+		salary_slip.insert()
+		salary_slip.submit()
+
+		application = make_employee_benefit_application(
+			employee, payroll_period.name, date=leave_application.to_date
+		)
+		self.assertEqual(application.employee_benefits[0].max_benefit_amount, 15000)
+
+		holidays = get_holiday_dates_for_employee(employee, payroll_period.start_date, application.date)
+		working_days = date_diff(application.date, payroll_period.start_date) + 1
+		lwp = calculate_lwp(employee, payroll_period.start_date, holidays, working_days)
+		self.assertEqual(lwp, 2.5)
+
+
+def make_employee_benefit_application(employee, payroll_period, date):
+	frappe.db.delete("Employee Benefit Application")
+
+	return frappe.get_doc(
+		{
+			"doctype": "Employee Benefit Application",
+			"employee": employee,
+			"date": date,
+			"payroll_period": payroll_period,
+			"employee_benefits": [{"earning_component": "Medical Allowance", "amount": 1500}],
+		}
+	).insert()
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/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index 4c5fea1..6a35985 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -465,37 +465,14 @@
 		)
 
 		for d in range(working_days):
-			dt = add_days(cstr(getdate(self.start_date)), d)
-			leave = frappe.db.sql(
-				"""
-				SELECT t1.name,
-					CASE WHEN (t1.half_day_date = %(dt)s or t1.to_date = t1.from_date)
-					THEN t1.half_day else 0 END,
-					t2.is_ppl,
-					t2.fraction_of_daily_salary_per_leave
-				FROM `tabLeave Application` t1, `tabLeave Type` t2
-				WHERE t2.name = t1.leave_type
-				AND (t2.is_lwp = 1 or t2.is_ppl = 1)
-				AND t1.docstatus = 1
-				AND t1.employee = %(employee)s
-				AND ifnull(t1.salary_slip, '') = ''
-				AND CASE
-					WHEN t2.include_holiday != 1
-						THEN %(dt)s not in ('{0}') and %(dt)s between from_date and to_date
-					WHEN t2.include_holiday
-						THEN %(dt)s between from_date and to_date
-					END
-				""".format(
-					holidays
-				),
-				{"employee": self.employee, "dt": dt},
-			)
+			date = add_days(cstr(getdate(self.start_date)), d)
+			leave = get_lwp_or_ppl_for_date(date, self.employee, holidays)
 
 			if leave:
 				equivalent_lwp_count = 0
-				is_half_day_leave = cint(leave[0][1])
-				is_partially_paid_leave = cint(leave[0][2])
-				fraction_of_daily_salary_per_leave = flt(leave[0][3])
+				is_half_day_leave = cint(leave[0].is_half_day)
+				is_partially_paid_leave = cint(leave[0].is_ppl)
+				fraction_of_daily_salary_per_leave = flt(leave[0].fraction_of_daily_salary_per_leave)
 
 				equivalent_lwp_count = (1 - daily_wages_fraction_for_half_day) if is_half_day_leave else 1
 
@@ -1742,3 +1719,46 @@
 	except Exception as e:
 		frappe.throw(_("Error in formula or condition: {0} in Income Tax Slab").format(e))
 		raise
+
+
+def get_lwp_or_ppl_for_date(date, employee, holidays):
+	LeaveApplication = frappe.qb.DocType("Leave Application")
+	LeaveType = frappe.qb.DocType("Leave Type")
+
+	is_half_day = (
+		frappe.qb.terms.Case()
+		.when(
+			(
+				(LeaveApplication.half_day_date == date)
+				| (LeaveApplication.from_date == LeaveApplication.to_date)
+			),
+			LeaveApplication.half_day,
+		)
+		.else_(0)
+	).as_("is_half_day")
+
+	query = (
+		frappe.qb.from_(LeaveApplication)
+		.inner_join(LeaveType)
+		.on((LeaveType.name == LeaveApplication.leave_type))
+		.select(
+			LeaveApplication.name,
+			LeaveType.is_ppl,
+			LeaveType.fraction_of_daily_salary_per_leave,
+			(is_half_day),
+		)
+		.where(
+			(((LeaveType.is_lwp == 1) | (LeaveType.is_ppl == 1)))
+			& (LeaveApplication.docstatus == 1)
+			& (LeaveApplication.status == "Approved")
+			& (LeaveApplication.employee == employee)
+			& ((LeaveApplication.salary_slip.isnull()) | (LeaveApplication.salary_slip == ""))
+			& ((LeaveApplication.from_date <= date) & (date <= LeaveApplication.to_date))
+		)
+	)
+
+	# if it's a holiday only include if leave type has "include holiday" enabled
+	if date in holidays:
+		query = query.where((LeaveType.include_holiday == "1"))
+
+	return query.run(as_dict=True)
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 60ba2d9..a8b6bb5 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -49,7 +49,7 @@
 		"Payroll Settings", {"payroll_based_on": "Attendance", "daily_wages_fraction_for_half_day": 0.75}
 	)
 	def test_payment_days_based_on_attendance(self):
-		no_of_days = self.get_no_of_days()
+		no_of_days = get_no_of_days()
 
 		emp_id = make_employee("test_payment_days_based_on_attendance@salary.com")
 		frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"})
@@ -128,7 +128,7 @@
 		},
 	)
 	def test_payment_days_for_mid_joinee_including_holidays(self):
-		no_of_days = self.get_no_of_days()
+		no_of_days = get_no_of_days()
 		month_start_date, month_end_date = get_first_day(nowdate()), get_last_day(nowdate())
 
 		new_emp_id = make_employee("test_payment_days_based_on_joining_date@salary.com")
@@ -196,7 +196,7 @@
 		# tests mid month joining and relieving along with unmarked days
 		from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
 
-		no_of_days = self.get_no_of_days()
+		no_of_days = get_no_of_days()
 		month_start_date, month_end_date = get_first_day(nowdate()), get_last_day(nowdate())
 
 		new_emp_id = make_employee("test_payment_days_based_on_joining_date@salary.com")
@@ -236,7 +236,7 @@
 	def test_payment_days_for_mid_joinee_excluding_holidays(self):
 		from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
 
-		no_of_days = self.get_no_of_days()
+		no_of_days = get_no_of_days()
 		month_start_date, month_end_date = get_first_day(nowdate()), get_last_day(nowdate())
 
 		new_emp_id = make_employee("test_payment_days_based_on_joining_date@salary.com")
@@ -267,7 +267,7 @@
 
 	@change_settings("Payroll Settings", {"payroll_based_on": "Leave"})
 	def test_payment_days_based_on_leave_application(self):
-		no_of_days = self.get_no_of_days()
+		no_of_days = get_no_of_days()
 
 		emp_id = make_employee("test_payment_days_based_on_leave_application@salary.com")
 		frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"})
@@ -366,7 +366,7 @@
 		salary_slip.submit()
 		salary_slip.reload()
 
-		no_of_days = self.get_no_of_days()
+		no_of_days = get_no_of_days()
 		days_in_month = no_of_days[0]
 		no_of_holidays = no_of_days[1]
 
@@ -441,7 +441,7 @@
 
 	@change_settings("Payroll Settings", {"include_holidays_in_total_working_days": 1})
 	def test_salary_slip_with_holidays_included(self):
-		no_of_days = self.get_no_of_days()
+		no_of_days = get_no_of_days()
 		make_employee("test_salary_slip_with_holidays_included@salary.com")
 		frappe.db.set_value(
 			"Employee",
@@ -473,7 +473,7 @@
 
 	@change_settings("Payroll Settings", {"include_holidays_in_total_working_days": 0})
 	def test_salary_slip_with_holidays_excluded(self):
-		no_of_days = self.get_no_of_days()
+		no_of_days = get_no_of_days()
 		make_employee("test_salary_slip_with_holidays_excluded@salary.com")
 		frappe.db.set_value(
 			"Employee",
@@ -510,7 +510,7 @@
 			create_salary_structure_assignment,
 		)
 
-		no_of_days = self.get_no_of_days()
+		no_of_days = get_no_of_days()
 
 		# set joinng date in the same month
 		employee = make_employee("test_payment_days@salary.com")
@@ -984,17 +984,18 @@
 		activity_type.wage_rate = 25
 		activity_type.save()
 
-	def get_no_of_days(self):
-		no_of_days_in_month = calendar.monthrange(getdate(nowdate()).year, getdate(nowdate()).month)
-		no_of_holidays_in_month = len(
-			[
-				1
-				for i in calendar.monthcalendar(getdate(nowdate()).year, getdate(nowdate()).month)
-				if i[6] != 0
-			]
-		)
 
-		return [no_of_days_in_month[1], no_of_holidays_in_month]
+def get_no_of_days():
+	no_of_days_in_month = calendar.monthrange(getdate(nowdate()).year, getdate(nowdate()).month)
+	no_of_holidays_in_month = len(
+		[
+			1
+			for i in calendar.monthcalendar(getdate(nowdate()).year, getdate(nowdate()).month)
+			if i[6] != 0
+		]
+	)
+
+	return [no_of_days_in_month[1], no_of_holidays_in_month]
 
 
 def make_employee_salary_slip(user, payroll_frequency, salary_structure=None, posting_date=None):
@@ -1050,10 +1051,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:
@@ -1136,6 +1137,7 @@
 					"pay_against_benefit_claim": 0,
 					"type": "Earning",
 					"max_benefit_amount": 15000,
+					"depends_on_payment_days": 1,
 				},
 			]
 		)
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/buying.js b/erpnext/public/js/controllers/buying.js
index 58eb891..a5b7699 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -74,6 +74,7 @@
 		me.frm.set_query('supplier_address', erpnext.queries.address_query);
 
 		me.frm.set_query('billing_address', erpnext.queries.company_address_query);
+		erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype);
 
 		if(this.frm.fields_dict.supplier) {
 			this.frm.set_query("supplier", function() {
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/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
index d6210ab..91fccfa 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -148,7 +148,6 @@
 			FROM `tabPurchase Invoice` p , `tabPurchase Invoice Item` i
 			WHERE p.docstatus = 1 and p.name = i.parent
 			and p.is_opening = 'No'
-			and p.gst_category != 'Registered Composition'
 			and (i.is_nil_exempt = 1 or i.is_non_gst = 1 or p.gst_category = 'Registered Composition') and
 			month(p.posting_date) = %s and year(p.posting_date) = %s
 			and p.company = %s and p.company_gstin = %s
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/sales_common.js b/erpnext/selling/sales_common.js
index 0954de4..6cb53c3 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -43,6 +43,7 @@
 		me.frm.set_query('shipping_address_name', erpnext.queries.address_query);
 		me.frm.set_query('dispatch_address_name', erpnext.queries.dispatch_address_query);
 
+		erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype);
 
 		if(this.frm.fields_dict.selling_price_list) {
 			this.frm.set_query("selling_price_list", function() {
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 706ca36..ea3cf19 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -77,8 +77,6 @@
 			}
 		});
 
-		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
-
 		frm.set_df_property('packed_items', 'cannot_add_rows', true);
 		frm.set_df_property('packed_items', 'cannot_delete_rows', true);
 	},
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/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index 51ec598..754404b 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -46,8 +46,6 @@
 		erpnext.queries.setup_queries(frm, "Warehouse", function() {
 			return erpnext.queries.warehouse(frm.doc);
 		});
-
-		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 	},
 
 	refresh: function(frm) {
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: