Merge branch 'develop' into fix-salary-slip-bg-job
This commit is contained in:
commit
956a803be7
2
.github/helper/install.sh
vendored
2
.github/helper/install.sh
vendored
@ -11,7 +11,7 @@ fi
|
|||||||
|
|
||||||
cd ~ || exit
|
cd ~ || exit
|
||||||
|
|
||||||
sudo apt-get install redis-server libcups2-dev
|
sudo apt update && sudo apt install redis-server libcups2-dev
|
||||||
|
|
||||||
pip install frappe-bench
|
pip install frappe-bench
|
||||||
|
|
||||||
|
11
cypress.json
11
cypress.json
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"baseUrl": "http://test_site:8000/",
|
|
||||||
"projectId": "da59y9",
|
|
||||||
"adminPassword": "admin",
|
|
||||||
"defaultCommandTimeout": 20000,
|
|
||||||
"pageLoadTimeout": 15000,
|
|
||||||
"retries": {
|
|
||||||
"runMode": 2,
|
|
||||||
"openMode": 2
|
|
||||||
}
|
|
||||||
}
|
|
@ -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"
|
|
||||||
}
|
|
@ -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");
|
|
||||||
});
|
|
||||||
});
|
|
@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
@ -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");
|
|
||||||
});
|
|
||||||
});
|
|
@ -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]}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -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]}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -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
|
|
||||||
};
|
|
@ -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)}`);
|
|
||||||
});
|
|
@ -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'
|
|
||||||
});
|
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"allowJs": true,
|
|
||||||
"baseUrl": "../node_modules",
|
|
||||||
"types": [
|
|
||||||
"cypress"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"**/*.*"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1866,7 +1866,7 @@ def get_default_taxes_and_charges(master_doctype, tax_template=None, company=Non
|
|||||||
def get_taxes_and_charges(master_doctype, master_name):
|
def get_taxes_and_charges(master_doctype, master_name):
|
||||||
if not master_name:
|
if not master_name:
|
||||||
return
|
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)
|
tax_master = frappe.get_doc(master_doctype, master_name)
|
||||||
|
|
||||||
@ -1874,7 +1874,7 @@ def get_taxes_and_charges(master_doctype, master_name):
|
|||||||
for i, tax in enumerate(tax_master.get("taxes")):
|
for i, tax in enumerate(tax_master.get("taxes")):
|
||||||
tax = tax.as_dict()
|
tax = tax.as_dict()
|
||||||
|
|
||||||
for fieldname in default_fields:
|
for fieldname in default_fields + child_table_fields:
|
||||||
if fieldname in tax:
|
if fieldname in tax:
|
||||||
del tax[fieldname]
|
del tax[fieldname]
|
||||||
|
|
||||||
@ -2661,7 +2661,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
parent.update_reserved_qty_for_subcontract()
|
parent.update_reserved_qty_for_subcontract()
|
||||||
parent.create_raw_materials_supplied("supplied_items")
|
parent.create_raw_materials_supplied("supplied_items")
|
||||||
parent.save()
|
parent.save()
|
||||||
else:
|
else: # Sales Order
|
||||||
|
parent.validate_warehouse()
|
||||||
parent.update_reserved_qty()
|
parent.update_reserved_qty()
|
||||||
parent.update_project()
|
parent.update_project()
|
||||||
parent.update_prevdoc_status("submit")
|
parent.update_prevdoc_status("submit")
|
||||||
|
@ -3,7 +3,15 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
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 (
|
from erpnext.hr.doctype.attendance.attendance import (
|
||||||
DuplicateAttendanceError,
|
DuplicateAttendanceError,
|
||||||
@ -138,69 +146,70 @@ class TestAttendance(FrappeTestCase):
|
|||||||
self.assertEqual(attendance, fetch_attendance)
|
self.assertEqual(attendance, fetch_attendance)
|
||||||
|
|
||||||
def test_unmarked_days(self):
|
def test_unmarked_days(self):
|
||||||
now = now_datetime()
|
first_sunday = get_first_sunday(
|
||||||
previous_month = now.month - 1
|
self.holiday_list, for_date=get_last_day(add_months(getdate(), -1))
|
||||||
first_day = now.replace(day=1).replace(month=previous_month).date()
|
)
|
||||||
|
attendance_date = add_days(first_sunday, 1)
|
||||||
|
|
||||||
employee = make_employee(
|
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)
|
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, attendance_date, "Present")
|
||||||
mark_attendance(employee, first_day, "Present")
|
month_name = get_month_name(attendance_date)
|
||||||
month_name = get_month_name(first_day)
|
|
||||||
|
|
||||||
unmarked_days = get_unmarked_days(employee, month_name)
|
unmarked_days = get_unmarked_days(employee, month_name)
|
||||||
unmarked_days = [getdate(date) for date in unmarked_days]
|
unmarked_days = [getdate(date) for date in unmarked_days]
|
||||||
|
|
||||||
# attendance already marked for the day
|
# attendance already marked for the day
|
||||||
self.assertNotIn(first_day, unmarked_days)
|
self.assertNotIn(attendance_date, unmarked_days)
|
||||||
# attendance unmarked
|
# 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
|
# holiday considered in unmarked days
|
||||||
self.assertIn(first_sunday, unmarked_days)
|
self.assertIn(first_sunday, unmarked_days)
|
||||||
|
|
||||||
def test_unmarked_days_excluding_holidays(self):
|
def test_unmarked_days_excluding_holidays(self):
|
||||||
now = now_datetime()
|
first_sunday = get_first_sunday(
|
||||||
previous_month = now.month - 1
|
self.holiday_list, for_date=get_last_day(add_months(getdate(), -1))
|
||||||
first_day = now.replace(day=1).replace(month=previous_month).date()
|
)
|
||||||
|
attendance_date = add_days(first_sunday, 1)
|
||||||
|
|
||||||
employee = make_employee(
|
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)
|
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, attendance_date, "Present")
|
||||||
mark_attendance(employee, first_day, "Present")
|
month_name = get_month_name(attendance_date)
|
||||||
month_name = get_month_name(first_day)
|
|
||||||
|
|
||||||
unmarked_days = get_unmarked_days(employee, month_name, exclude_holidays=True)
|
unmarked_days = get_unmarked_days(employee, month_name, exclude_holidays=True)
|
||||||
unmarked_days = [getdate(date) for date in unmarked_days]
|
unmarked_days = [getdate(date) for date in unmarked_days]
|
||||||
|
|
||||||
# attendance already marked for the day
|
# attendance already marked for the day
|
||||||
self.assertNotIn(first_day, unmarked_days)
|
self.assertNotIn(attendance_date, unmarked_days)
|
||||||
# attendance unmarked
|
# 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
|
# holidays not considered in unmarked days
|
||||||
self.assertNotIn(first_sunday, unmarked_days)
|
self.assertNotIn(first_sunday, unmarked_days)
|
||||||
|
|
||||||
def test_unmarked_days_as_per_joining_and_relieving_dates(self):
|
def test_unmarked_days_as_per_joining_and_relieving_dates(self):
|
||||||
now = now_datetime()
|
first_sunday = get_first_sunday(
|
||||||
previous_month = now.month - 1
|
self.holiday_list, for_date=get_last_day(add_months(getdate(), -1))
|
||||||
first_day = now.replace(day=1).replace(month=previous_month).date()
|
)
|
||||||
|
date = add_days(first_sunday, 1)
|
||||||
|
|
||||||
doj = add_days(first_day, 1)
|
doj = add_days(date, 1)
|
||||||
relieving_date = add_days(first_day, 5)
|
relieving_date = add_days(date, 5)
|
||||||
employee = make_employee(
|
employee = make_employee(
|
||||||
"test_unmarked_days_as_per_doj@example.com", date_of_joining=doj, relieving_date=relieving_date
|
"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)
|
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")
|
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 = get_unmarked_days(employee, month_name)
|
||||||
unmarked_days = [getdate(date) for date in unmarked_days]
|
unmarked_days = [getdate(date) for date in unmarked_days]
|
||||||
|
@ -230,7 +230,7 @@ def get_work_anniversary_reminder_text_and_message(anniversary_persons):
|
|||||||
persons_name = anniversary_person
|
persons_name = anniversary_person
|
||||||
# Number of years completed at the company
|
# Number of years completed at the company
|
||||||
completed_years = getdate().year - anniversary_persons[0]["date_of_joining"].year
|
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:
|
else:
|
||||||
person_names_with_years = []
|
person_names_with_years = []
|
||||||
names = []
|
names = []
|
||||||
@ -239,7 +239,7 @@ def get_work_anniversary_reminder_text_and_message(anniversary_persons):
|
|||||||
names.append(person_text)
|
names.append(person_text)
|
||||||
# Number of years completed at the company
|
# Number of years completed at the company
|
||||||
completed_years = getdate().year - person["date_of_joining"].year
|
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)
|
person_names_with_years.append(person_text)
|
||||||
|
|
||||||
# converts ["Jim", "Rim", "Dim"] to Jim, Rim & Dim
|
# converts ["Jim", "Rim", "Dim"] to Jim, Rim & Dim
|
||||||
@ -254,6 +254,12 @@ def get_work_anniversary_reminder_text_and_message(anniversary_persons):
|
|||||||
return reminder_text, message
|
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):
|
def send_work_anniversary_reminder(recipients, reminder_text, anniversary_persons, message):
|
||||||
frappe.sendmail(
|
frappe.sendmail(
|
||||||
recipients=recipients,
|
recipients=recipients,
|
||||||
|
@ -231,7 +231,6 @@ erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
|||||||
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
|
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
|
||||||
execute:frappe.delete_doc("Report", "Quoted Item Comparison")
|
execute:frappe.delete_doc("Report", "Quoted Item Comparison")
|
||||||
erpnext.patches.v13_0.update_member_email_address
|
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.update_pos_closing_entry_in_merge_log
|
||||||
erpnext.patches.v13_0.add_po_to_global_search
|
erpnext.patches.v13_0.add_po_to_global_search
|
||||||
erpnext.patches.v13_0.update_returned_qty_in_pr_dn
|
erpnext.patches.v13_0.update_returned_qty_in_pr_dn
|
||||||
|
@ -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})
|
|
@ -423,7 +423,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
item.barcode = null;
|
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()) {
|
if(!this.validate_company_and_party()) {
|
||||||
this.frm.fields_dict["items"].grid.grid_rows[item.idx - 1].remove();
|
this.frm.fields_dict["items"].grid.grid_rows[item.idx - 1].remove();
|
||||||
} else {
|
} else {
|
||||||
@ -463,6 +463,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
stock_qty: item.stock_qty,
|
stock_qty: item.stock_qty,
|
||||||
conversion_factor: item.conversion_factor,
|
conversion_factor: item.conversion_factor,
|
||||||
weight_per_unit: item.weight_per_unit,
|
weight_per_unit: item.weight_per_unit,
|
||||||
|
uom: item.uom,
|
||||||
weight_uom: item.weight_uom,
|
weight_uom: item.weight_uom,
|
||||||
manufacturer: item.manufacturer,
|
manufacturer: item.manufacturer,
|
||||||
stock_uom: item.stock_uom,
|
stock_uom: item.stock_uom,
|
||||||
|
@ -9,6 +9,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
this.barcode_field = opts.barcode_field || "barcode";
|
this.barcode_field = opts.barcode_field || "barcode";
|
||||||
this.serial_no_field = opts.serial_no_field || "serial_no";
|
this.serial_no_field = opts.serial_no_field || "serial_no";
|
||||||
this.batch_no_field = opts.batch_no_field || "batch_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";
|
this.qty_field = opts.qty_field || "qty";
|
||||||
// field name on row which defines max quantity to be scanned e.g. picklist
|
// field name on row which defines max quantity to be scanned e.g. picklist
|
||||||
this.max_qty_field = opts.max_qty_field;
|
this.max_qty_field = opts.max_qty_field;
|
||||||
@ -26,6 +27,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
// bar_code: "123456", // present if barcode was scanned
|
// bar_code: "123456", // present if barcode was scanned
|
||||||
// batch_no: "LOT12", // present if batch was scanned
|
// batch_no: "LOT12", // present if batch was scanned
|
||||||
// serial_no: "987XYZ", // present if serial no 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";
|
this.scan_api = opts.scan_api || "erpnext.stock.utils.scan_barcode";
|
||||||
}
|
}
|
||||||
@ -67,9 +69,9 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
|
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 (!row) {
|
||||||
if (this.dont_allow_new_row) {
|
if (this.dont_allow_new_row) {
|
||||||
@ -90,10 +92,11 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
frappe.run_serially([
|
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.set_item(row, item_code).then(qty => {
|
||||||
this.show_scan_message(row.idx, row.item_code, 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_serial_no(row, serial_no),
|
||||||
() => this.set_batch_no(row, batch_no),
|
() => this.set_batch_no(row, batch_no),
|
||||||
() => this.set_barcode(row, barcode),
|
() => this.set_barcode(row, barcode),
|
||||||
@ -106,7 +109,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
|
|
||||||
// batch and serial selector is reduandant when all info can be added by scan
|
// 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
|
// 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 {batch_no, serial_no, has_batch_no, has_serial_no} = data;
|
||||||
|
|
||||||
const require_selecting_batch = has_batch_no && !batch_no;
|
const require_selecting_batch = has_batch_no && !batch_no;
|
||||||
@ -154,6 +157,12 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
async set_batch_no(row, batch_no) {
|
||||||
if (batch_no && frappe.meta.has_field(row.doctype, this.batch_no_field)) {
|
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);
|
await frappe.model.set_value(row.doctype, row.name, this.batch_no_field, batch_no);
|
||||||
@ -184,7 +193,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
return is_duplicate;
|
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;
|
let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
|
||||||
|
|
||||||
// Check if batch is scanned and table has batch no field
|
// Check if batch is scanned and table has batch no field
|
||||||
@ -193,10 +202,12 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
|
|
||||||
const matching_row = (row) => {
|
const matching_row = (row) => {
|
||||||
const item_match = row.item_code == item_code;
|
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]);
|
const qty_in_limit = flt(row[this.qty_field]) < flt(row[this.max_qty_field]);
|
||||||
|
|
||||||
return item_match
|
return item_match
|
||||||
|
&& uom_match
|
||||||
&& (!is_batch_no_scan || batch_match)
|
&& (!is_batch_no_scan || batch_match)
|
||||||
&& (!check_max_qty || qty_in_limit)
|
&& (!check_max_qty || qty_in_limit)
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"barcode",
|
"barcode",
|
||||||
"barcode_type"
|
"barcode_type",
|
||||||
|
"uom"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -24,11 +25,18 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Barcode Type",
|
"label": "Barcode Type",
|
||||||
"options": "\nEAN\nUPC-A"
|
"options": "\nEAN\nUPC-A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "uom",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "UOM",
|
||||||
|
"options": "UOM"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-04-01 05:54:27.314030",
|
"modified": "2022-06-01 06:24:33.969534",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Item Barcode",
|
"name": "Item Barcode",
|
||||||
|
@ -558,7 +558,7 @@ def scan_barcode(search_value: str) -> Dict[str, Optional[str]]:
|
|||||||
barcode_data = frappe.db.get_value(
|
barcode_data = frappe.db.get_value(
|
||||||
"Item Barcode",
|
"Item Barcode",
|
||||||
{"barcode": search_value},
|
{"barcode": search_value},
|
||||||
["barcode", "parent as item_code"],
|
["barcode", "parent as item_code", "uom"],
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
if barcode_data:
|
if barcode_data:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user