diff --git a/.eslintrc b/.eslintrc index 3b6ab7498d9..46fb354c11c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -147,10 +147,15 @@ "Chart": true, "Cypress": true, "cy": true, + "describe": true, + "expect": true, "it": true, "context": true, "before": true, "beforeEach": true, - "onScan": true + "onScan": true, + "html2canvas": true, + "extend_cscript": true, + "localforage": true } } diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index be425ec2d9d..6cc5e3547ba 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -10,3 +10,6 @@ # This commit just changes spaces to tabs for indentation in some files 5f473611bd6ed57703716244a054d3fb5ba9cd23 + +# Whitespace trimming throughout codebase +9bb69e711a5da43aaf8c8ecb5601aeffd89dbe5a diff --git a/.github/helper/documentation.py b/.github/helper/documentation.py index 9cc4663c394..b4a4ba1bbdd 100644 --- a/.github/helper/documentation.py +++ b/.github/helper/documentation.py @@ -32,11 +32,15 @@ if __name__ == "__main__": if response.ok: payload = response.json() - title = payload.get("title", "").lower() + title = payload.get("title", "").lower().strip() head_sha = payload.get("head", {}).get("sha") body = payload.get("body", "").lower() - if title.startswith("feat") and head_sha and "no-docs" not in body: + if (title.startswith("feat") + and head_sha + and "no-docs" not in body + and "backport" not in body + ): if docs_link_exists(body): print("Documentation Link Found. You're Awesome! 🎉") diff --git a/.github/helper/install.sh b/.github/helper/install.sh index 7b0f944c669..a6a6069d358 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -42,5 +42,5 @@ sed -i 's/socketio:/# socketio:/g' Procfile sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile bench get-app erpnext "${GITHUB_WORKSPACE}" -bench start & +bench start &> bench_run_logs.txt & bench --site test_site reinstall --yes diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml index faab3344a62..d9603e89aa4 100644 --- a/.github/helper/semgrep_rules/frappe_correctness.yml +++ b/.github/helper/semgrep_rules/frappe_correctness.yml @@ -98,8 +98,6 @@ rules: languages: [python] severity: WARNING paths: - exclude: - - test_*.py include: - "*/**/doctype/*" diff --git a/.github/helper/semgrep_rules/security.yml b/.github/helper/semgrep_rules/security.yml index 5a5098bf506..8b219792080 100644 --- a/.github/helper/semgrep_rules/security.yml +++ b/.github/helper/semgrep_rules/security.yml @@ -8,18 +8,3 @@ rules: dynamic content. Avoid it or use safe_eval(). languages: [python] severity: ERROR - -- id: frappe-sqli-format-strings - patterns: - - pattern-inside: | - @frappe.whitelist() - def $FUNC(...): - ... - - pattern-either: - - pattern: frappe.db.sql("..." % ...) - - pattern: frappe.db.sql(f"...", ...) - - pattern: frappe.db.sql("...".format(...), ...) - message: | - Detected use of raw string formatting for SQL queries. This can lead to sql injection vulnerabilities. Refer security guidelines - https://github.com/frappe/erpnext/wiki/Code-Security-Guidelines - languages: [python] - severity: WARNING diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 7c6b8432b87..1d180f251e1 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -1,16 +1,25 @@ name: Backport on: - pull_request: + pull_request_target: types: - closed - labeled jobs: - backport: - runs-on: ubuntu-18.04 - name: Backport + main: + runs-on: ubuntu-latest steps: - - name: Backport - uses: tibdex/backport@v1 + - name: Checkout Actions + uses: actions/checkout@v2 with: - github_token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + repository: "frappe/backport" + path: ./actions + ref: develop + - name: Install Actions + run: npm install --production --prefix ./actions + - name: Run backport + uses: ./actions/backport + with: + token: ${{secrets.BACKPORT_BOT_TOKEN}} + labelsToAdd: "backport" + title: "{{originalTitle}}" diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index b96a3d6bbed..0f28838d2bf 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -1,6 +1,12 @@ name: Patch -on: [pull_request, workflow_dispatch] +on: + pull_request: + paths-ignore: + - '**.js' + - '**.md' + workflow_dispatch: + jobs: test: diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 389524e9684..e27b406df05 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -1,34 +1,18 @@ name: Semgrep on: - pull_request: - branches: - - develop - - version-13-hotfix - - version-13-pre-release + pull_request: { } + jobs: semgrep: name: Frappe Linter runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Setup python3 - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - - name: Setup semgrep - run: | - python -m pip install -q semgrep - git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q - - - name: Semgrep errors - run: | - files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) - [[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files - semgrep --config="r/python.lang.correctness" --quiet --error $files - - - name: Semgrep warnings - run: | - files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) - [[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files + - uses: actions/checkout@v2 + - uses: returntocorp/semgrep-action@v1 + env: + SEMGREP_TIMEOUT: 120 + with: + config: >- + r/python.lang.correctness + .github/helper/semgrep_rules diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 92685e2177d..124ed7ad3e9 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -1,6 +1,16 @@ name: Server -on: [pull_request, workflow_dispatch] +on: + pull_request: + paths-ignore: + - '**.js' + - '**.md' + workflow_dispatch: + push: + branches: [ develop ] + paths-ignore: + - '**.js' + - '**.md' jobs: test: diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml new file mode 100644 index 00000000000..e3158ea97ee --- /dev/null +++ b/.github/workflows/ui-tests.yml @@ -0,0 +1,110 @@ +name: UI + +on: + pull_request: + paths-ignore: + - '**.md' + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-18.04 + + strategy: + fail-fast: false + + name: UI Tests (Cypress) + + services: + mysql: + image: mariadb:10.3 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: YES + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + + steps: + - name: Clone + uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: 3.7 + + - uses: actions/setup-node@v2 + with: + node-version: 14 + check-latest: true + + - name: Add to Hosts + run: | + echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts + + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Cache cypress binary + uses: actions/cache@v2 + with: + path: ~/.cache + key: ${{ runner.os }}-cypress- + restore-keys: | + ${{ runner.os }}-cypress- + ${{ runner.os }}- + + - name: Install + run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh + env: + DB: mariadb + TYPE: ui + + - name: Site Setup + run: cd ~/frappe-bench/ && bench --site test_site execute erpnext.setup.utils.before_tests + + - name: cypress pre-requisites + run: cd ~/frappe-bench/apps/frappe && yarn add cypress-file-upload@^5 --no-lockfile + + + - name: Build Assets + run: cd ~/frappe-bench/ && bench build + + - name: UI Tests + run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests erpnext --headless + env: + CYPRESS_RECORD_KEY: 60a8e3bf-08f5-45b1-9269-2b207d7d30cd + + - name: Show bench console if tests failed + if: ${{ failure() }} + run: cat ~/frappe-bench/bench_run_logs.txt diff --git a/CODEOWNERS b/CODEOWNERS index 7cf65a7a732..a4a14de1b8e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,16 +3,33 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, -manufacturing/ @rohitwaghchaure @marination -accounts/ @deepeshgarg007 @nextchamp-saqib -loan_management/ @deepeshgarg007 @rohitwaghchaure -pos* @nextchamp-saqib @rohitwaghchaure -assets/ @nextchamp-saqib @deepeshgarg007 -stock/ @marination @rohitwaghchaure -buying/ @marination @deepeshgarg007 -hr/ @Anurag810 @rohitwaghchaure -projects/ @hrwX @nextchamp-saqib -support/ @hrwX @marination -healthcare/ @ruchamahabal @marination -erpnext_integrations/ @Mangesh-Khairnar @nextchamp-saqib -requirements.txt @gavindsouza +erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 +erpnext/assets/ @nextchamp-saqib @deepeshgarg007 +erpnext/erpnext_integrations/ @nextchamp-saqib +erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007 +erpnext/regional @nextchamp-saqib @deepeshgarg007 +erpnext/selling @nextchamp-saqib @deepeshgarg007 +erpnext/support/ @nextchamp-saqib @deepeshgarg007 +pos* @nextchamp-saqib + +erpnext/buying/ @marination @rohitwaghchaure @ankush +erpnext/e_commerce/ @marination +erpnext/maintenance/ @marination @rohitwaghchaure +erpnext/manufacturing/ @marination @rohitwaghchaure @ankush +erpnext/portal/ @marination +erpnext/quality_management/ @marination @rohitwaghchaure +erpnext/shopping_cart/ @marination +erpnext/stock/ @marination @rohitwaghchaure @ankush + +erpnext/crm/ @ruchamahabal @pateljannat +erpnext/education/ @ruchamahabal @pateljannat +erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand +erpnext/hr/ @ruchamahabal @pateljannat +erpnext/non_profit/ @ruchamahabal +erpnext/payroll @ruchamahabal @pateljannat +erpnext/projects/ @ruchamahabal @pateljannat + +erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination + +.github/ @surajshetty3416 @ankush +requirements.txt @gavindsouza diff --git a/cypress.json b/cypress.json new file mode 100644 index 00000000000..afcd657c53d --- /dev/null +++ b/cypress.json @@ -0,0 +1,11 @@ +{ + "baseUrl": "http://test_site:8000", + "projectId": "da59y9", + "adminPassword": "admin", + "defaultCommandTimeout": 20000, + "pageLoadTimeout": 15000, + "retries": { + "runMode": 2, + "openMode": 2 + } +} \ No newline at end of file diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 00000000000..da18d9352a1 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "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_customer.js b/cypress/integration/test_customer.js new file mode 100644 index 00000000000..3d6ed5d0d89 --- /dev/null +++ b/cypress/integration/test_customer.js @@ -0,0 +1,13 @@ + +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_organizational_chart_desktop.js b/cypress/integration/test_organizational_chart_desktop.js new file mode 100644 index 00000000000..fb46bbb4331 --- /dev/null +++ b/cypress/integration/test_organizational_chart_desktop.js @@ -0,0 +1,111 @@ +context('Organizational Chart', () => { + before(() => { + cy.login(); + cy.visit('/app/website'); + cy.awesomebar('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{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 new file mode 100644 index 00000000000..df90dbfa22f --- /dev/null +++ b/cypress/integration/test_organizational_chart_mobile.js @@ -0,0 +1,190 @@ +context('Organizational Chart Mobile', () => { + before(() => { + cy.login(); + cy.viewport(375, 667); + cy.visit('/app/website'); + cy.awesomebar('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{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 new file mode 100644 index 00000000000..07d9804a733 --- /dev/null +++ b/cypress/plugins/index.js @@ -0,0 +1,17 @@ +// *********************************************************** +// 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 new file mode 100644 index 00000000000..7ddc80ab8dd --- /dev/null +++ b/cypress/support/commands.js @@ -0,0 +1,31 @@ +// *********************************************** +// 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 new file mode 100644 index 00000000000..72070cc81c4 --- /dev/null +++ b/cypress/support/index.js @@ -0,0 +1,26 @@ +// *********************************************************** +// 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 new file mode 100644 index 00000000000..d90ebf6856d --- /dev/null +++ b/cypress/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "allowJs": true, + "baseUrl": "../node_modules", + "types": [ + "cypress" + ] + }, + "include": [ + "**/*.*" + ] +} \ No newline at end of file diff --git a/erpnext/.stylelintrc b/erpnext/.stylelintrc index 1e05d1fb41d..30075f13d04 100644 --- a/erpnext/.stylelintrc +++ b/erpnext/.stylelintrc @@ -6,4 +6,4 @@ "scss/at-rule-no-unknown": true, "no-descending-specificity": null } -} \ No newline at end of file +} diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.js b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.js index e12eae9c1c1..d8a83e53dc0 100644 --- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.js +++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.js @@ -19,4 +19,4 @@ frappe.dashboards.chart_sources["Account Balance Timeline"] = { reqd: 1 }, ] -}; \ No newline at end of file +}; diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 335e8a15ab0..0c81d83ed8e 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -450,5 +450,3 @@ def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr): return debit_account else: return credit_account - - diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 1be2fbf5c81..f763df0852b 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -230,7 +230,7 @@ class Account(NestedSet): if self.check_gle_exists(): throw(_("Account with existing transaction can not be converted to group.")) elif self.account_type and not self.flags.exclude_account_type_check: - throw(_("Cannot covert to Group because Account Type is selected.")) + throw(_("Cannot convert to Group because Account Type is selected.")) else: self.is_group = 1 self.save() diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js index 65c5ff1ceaf..2fa1d53c60c 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js @@ -60,4 +60,4 @@ frappe.ui.form.on('Accounting Dimension Detail', { let row = locals[cdt][cdn]; row.reference_document = frm.doc.document_type; } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py index e657a9ae34b..4f3ee7643ab 100644 --- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py @@ -113,5 +113,3 @@ def disable_dimension(): dimension2 = frappe.get_doc("Accounting Dimension", "Location") dimension2.disabled = 1 dimension2.save() - - diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.js b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.js index 74b7b516763..9dd882a3119 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.js +++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.js @@ -79,4 +79,4 @@ frappe.ui.form.on('Allowed Dimension', { row.accounting_dimension = frm.doc.accounting_dimension; frm.refresh_field("dimensions"); } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py index 63b5dbbd3e6..739d8f6bc63 100644 --- a/erpnext/accounts/doctype/accounting_period/accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py @@ -56,4 +56,4 @@ class AccountingPeriod(Document): self.append('closed_documents', { "document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed - }) \ No newline at end of file + }) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.js b/erpnext/accounts/doctype/accounts_settings/accounts_settings.js index 541901c9abf..e44af3a9167 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.js +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.js @@ -48,4 +48,4 @@ frappe.tour['Accounts Settings'] = [ title: "Unlink Advance Payment on Cancellation of Order", description: __("Similar to the previous option, this unlinks any advance payments made against Purchase/Sales Orders.") } -]; \ No newline at end of file +]; diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 703e93c0757..49a2afee85f 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -19,6 +19,7 @@ "book_asset_depreciation_entry_automatically", "unlink_advance_payment_on_cancelation_of_order", "post_change_gl_entries", + "enable_discount_accounting", "tax_settings_section", "determine_address_tax_category_from", "column_break_19", @@ -261,6 +262,13 @@ "fieldname": "post_change_gl_entries", "fieldtype": "Check", "label": "Create Ledger Entries for Change Amount" + }, + { + "default": "0", + "description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account", + "fieldname": "enable_discount_accounting", + "fieldtype": "Check", + "label": "Enable Discount Accounting" } ], "icon": "icon-cog", @@ -268,7 +276,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-06-17 20:26:03.721202", + "modified": "2021-07-12 18:54:29.084958", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index ac4a2d6f16d..62c97f24d5f 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -21,6 +21,7 @@ class AccountsSettings(Document): self.validate_stale_days() self.enable_payment_schedule_in_print() + self.toggle_discount_accounting_fields() def validate_stale_days(self): if not self.allow_stale and cint(self.stale_days) <= 0: @@ -33,3 +34,22 @@ class AccountsSettings(Document): for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"): make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False) make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check", validate_fields_for_doctype=False) + + def toggle_discount_accounting_fields(self): + enable_discount_accounting = cint(self.enable_discount_accounting) + + for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]: + make_property_setter(doctype, "discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False) + if enable_discount_accounting: + make_property_setter(doctype, "discount_account", "mandatory_depends_on", "eval: doc.discount_amount", "Code", validate_fields_for_doctype=False) + else: + make_property_setter(doctype, "discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False) + + for doctype in ["Sales Invoice", "Purchase Invoice"]: + make_property_setter(doctype, "additional_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False) + if enable_discount_accounting: + make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "eval: doc.discount_amount", "Code", validate_fields_for_doctype=False) + else: + make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False) + + make_property_setter("Item", "default_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False) diff --git a/erpnext/accounts/doctype/accounts_settings/regional/united_states.js b/erpnext/accounts/doctype/accounts_settings/regional/united_states.js index d47d6e58039..3e38386481c 100644 --- a/erpnext/accounts/doctype/accounts_settings/regional/united_states.js +++ b/erpnext/accounts/doctype/accounts_settings/regional/united_states.js @@ -5,4 +5,4 @@ frappe.ui.form.on('Accounts Settings', { frm.set_df_property("frozen_accounts_modifier", "label", "Role Allowed to Close Books & Make Changes to Closed Periods"); frm.set_df_property("credit_controller", "label", "Credit Manager"); } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js index 19041a3f73d..059e1d31588 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -120,4 +120,4 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink { plaid_success(token, response) { frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' }); } -}; \ No newline at end of file +}; diff --git a/erpnext/accounts/doctype/bank/bank.py b/erpnext/accounts/doctype/bank/bank.py index 41aae14362f..99fa21c8f9a 100644 --- a/erpnext/accounts/doctype/bank/bank.py +++ b/erpnext/accounts/doctype/bank/bank.py @@ -13,4 +13,4 @@ class Bank(Document): load_address_and_contact(self) def on_trash(self): - delete_contact_and_address('Bank', self.name) \ No newline at end of file + delete_contact_and_address('Bank', self.name) diff --git a/erpnext/accounts/doctype/bank_account/bank_account_dashboard.py b/erpnext/accounts/doctype/bank_account/bank_account_dashboard.py index a959cea98f2..c7ea1522993 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account_dashboard.py +++ b/erpnext/accounts/doctype/bank_account/bank_account_dashboard.py @@ -26,4 +26,4 @@ def get_data(): 'items': ['Journal Entry'] } ] - } \ No newline at end of file + } diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.js b/erpnext/accounts/doctype/bank_clearance/bank_clearance.js index ba3f2face63..63cc46518ff 100644 --- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.js +++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.js @@ -8,7 +8,7 @@ frappe.ui.form.on("Bank Clearance", { onload: function(frm) { - let default_bank_account = frappe.defaults.get_user_default("Company")? + let default_bank_account = frappe.defaults.get_user_default("Company")? locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: ""; frm.set_value("account", default_bank_account); diff --git a/erpnext/accounts/doctype/bank_clearance_detail/bank_clearance_detail.py b/erpnext/accounts/doctype/bank_clearance_detail/bank_clearance_detail.py index ecc536733f2..59299f81e50 100644 --- a/erpnext/accounts/doctype/bank_clearance_detail/bank_clearance_detail.py +++ b/erpnext/accounts/doctype/bank_clearance_detail/bank_clearance_detail.py @@ -6,4 +6,4 @@ import frappe from frappe.model.document import Document class BankClearanceDetail(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py index 88e1055beb4..a0aac6ab170 100644 --- a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py +++ b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py @@ -25,6 +25,6 @@ class BankGuarantee(Document): def get_vouchar_detials(column_list, doctype, docname): column_list = json.loads(column_list) for col in column_list: - sanitize_searchfield(col) + sanitize_searchfield(col) return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s''' .format(columns=", ".join(column_list), doctype=doctype), docname, as_dict=1)[0] diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index 5246baa02b3..31cfb2da1da 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -105,4 +105,3 @@ def unclear_reference_payment(doctype, docname): frappe.db.set_value(doc.payment_document, doc.payment_entry, "clearance_date", None) return doc.payment_entry - diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js index 2ecc2b0cda3..bff41d5539b 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js @@ -10,4 +10,4 @@ frappe.listview_settings['Bank Transaction'] = { return [__("Reconciled"), "green", "unallocated_amount,=,0"]; } } -}; \ No newline at end of file +}; diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py index 33ae45439e7..dc3b8674700 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py @@ -77,4 +77,4 @@ def get_bank_mapping(bank_account): mapping = {row.file_field:row.bank_transaction_field for row in bank.bank_transaction_mapping} - return mapping \ No newline at end of file + return mapping diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py index 603e21ea248..6c25f0024d5 100644 --- a/erpnext/accounts/doctype/budget/test_budget.py +++ b/erpnext/accounts/doctype/budget/test_budget.py @@ -249,7 +249,7 @@ class TestBudget(unittest.TestCase): def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None): if budget_against_field == "project": - budget_against = "_Test Project" + budget_against = frappe.db.get_value("Project", {"project_name": "_Test Project"}) else: budget_against = budget_against_CC or "_Test Cost Center - _TC" @@ -275,7 +275,7 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True) elif budget_against_field == "project": make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date=nowdate()) + "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project=budget_against, posting_date=nowdate()) def make_budget(**args): args = frappe._dict(args) diff --git a/erpnext/accounts/doctype/c_form_invoice_detail/c_form_invoice_detail.py b/erpnext/accounts/doctype/c_form_invoice_detail/c_form_invoice_detail.py index ee5098bea12..20e423a610e 100644 --- a/erpnext/accounts/doctype/c_form_invoice_detail/c_form_invoice_detail.py +++ b/erpnext/accounts/doctype/c_form_invoice_detail/c_form_invoice_detail.py @@ -6,4 +6,4 @@ import frappe from frappe.model.document import Document class CFormInvoiceDetail(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/accounts/doctype/campaign_item/__init__.py b/erpnext/accounts/doctype/campaign_item/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/campaign_item/campaign_item.json b/erpnext/accounts/doctype/campaign_item/campaign_item.json new file mode 100644 index 00000000000..69383a482b4 --- /dev/null +++ b/erpnext/accounts/doctype/campaign_item/campaign_item.json @@ -0,0 +1,31 @@ +{ + "actions": [], + "creation": "2021-05-06 16:18:25.410476", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "campaign" + ], + "fields": [ + { + "fieldname": "campaign", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Campaign", + "options": "Campaign" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-05-07 10:43:49.717633", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Campaign Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/campaign_item/campaign_item.py b/erpnext/accounts/doctype/campaign_item/campaign_item.py new file mode 100644 index 00000000000..4f5fd7f7d78 --- /dev/null +++ b/erpnext/accounts/doctype/campaign_item/campaign_item.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class CampaignItem(Document): + pass diff --git a/erpnext/accounts/doctype/cash_flow_mapping/cash_flow_mapping.py b/erpnext/accounts/doctype/cash_flow_mapping/cash_flow_mapping.py index 28d84b4442f..b1ad2972beb 100644 --- a/erpnext/accounts/doctype/cash_flow_mapping/cash_flow_mapping.py +++ b/erpnext/accounts/doctype/cash_flow_mapping/cash_flow_mapping.py @@ -18,5 +18,3 @@ class CashFlowMapping(Document): frappe._('You can only select a maximum of one option from the list of check boxes.'), title='Error' ) - - diff --git a/erpnext/accounts/doctype/cashier_closing/cashier_closing.py b/erpnext/accounts/doctype/cashier_closing/cashier_closing.py index 7ad1d3ab831..081c6fa4718 100644 --- a/erpnext/accounts/doctype/cashier_closing/cashier_closing.py +++ b/erpnext/accounts/doctype/cashier_closing/cashier_closing.py @@ -33,4 +33,4 @@ class CashierClosing(Document): def validate_time(self): if self.from_time >= self.time: - frappe.throw(_("From Time Should Be Less Than To Time")) \ No newline at end of file + frappe.throw(_("From Time Should Be Less Than To Time")) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 4fd8413d838..8456b49c8ee 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -391,5 +391,5 @@ def set_default_accounts(company): }) company.save() - install_country_fixtures(company.name) + install_country_fixtures(company.name, company.country) company.create_default_tax_template() diff --git a/erpnext/accounts/doctype/cheque_print_template/cheque_print_template.js b/erpnext/accounts/doctype/cheque_print_template/cheque_print_template.js index 6a430eb02bf..d10c61858f1 100644 --- a/erpnext/accounts/doctype/cheque_print_template/cheque_print_template.js +++ b/erpnext/accounts/doctype/cheque_print_template/cheque_print_template.js @@ -10,10 +10,10 @@ frappe.ui.form.on('Cheque Print Template', { function() { erpnext.cheque_print.view_cheque_print(frm); }).addClass("btn-primary"); - + $(frm.fields_dict.cheque_print_preview.wrapper).empty() - - + + var template = '
\
Signatory Name \
\
'; - + $(frappe.render(template, frm.doc)).appendTo(frm.fields_dict.cheque_print_preview.wrapper) - + if (frm.doc.scanned_cheque) { $(frm.fields_dict.cheque_print_preview.wrapper).find("#cheque_preview").css('background-image', 'url(' + frm.doc.scanned_cheque + ')'); } diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py index 8a5473f3a16..981fec308cc 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.py +++ b/erpnext/accounts/doctype/cost_center/cost_center.py @@ -129,4 +129,4 @@ def get_name_with_number(new_account, account_number): def check_if_distributed_cost_center_enabled(cost_center_list): value_list = frappe.get_list("Cost Center", {"name": ["in", cost_center_list]}, "enable_distributed_cost_center", as_list=1) - return next((True for x in value_list if x[0]), False) \ No newline at end of file + return next((True for x in value_list if x[0]), False) diff --git a/erpnext/accounts/doctype/cost_center/cost_center_dashboard.py b/erpnext/accounts/doctype/cost_center/cost_center_dashboard.py index 788ac8be83f..24cf3ea0689 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center_dashboard.py +++ b/erpnext/accounts/doctype/cost_center/cost_center_dashboard.py @@ -12,4 +12,4 @@ def get_data(): 'items': ['Budget Variance Report', 'General Ledger'] } ] - } \ No newline at end of file + } diff --git a/erpnext/accounts/doctype/cost_center/cost_center_tree.js b/erpnext/accounts/doctype/cost_center/cost_center_tree.js index fde41233c4d..1d482c58f1a 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center_tree.js +++ b/erpnext/accounts/doctype/cost_center/cost_center_tree.js @@ -51,4 +51,4 @@ frappe.treeview_settings["Cost Center"] = { } -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/cost_center/test_cost_center.py b/erpnext/accounts/doctype/cost_center/test_cost_center.py index b5fc7e3b497..7779ccefc20 100644 --- a/erpnext/accounts/doctype/cost_center/test_cost_center.py +++ b/erpnext/accounts/doctype/cost_center/test_cost_center.py @@ -62,6 +62,3 @@ def create_cost_center(**args): cc.is_group = args.is_group or 0 cc.parent_cost_center = args.parent_cost_center or "_Test Company - _TC" cc.insert() - - - diff --git a/erpnext/accounts/doctype/coupon_code/coupon_code.py b/erpnext/accounts/doctype/coupon_code/coupon_code.py index 55c119315e0..92a816d25e9 100644 --- a/erpnext/accounts/doctype/coupon_code/coupon_code.py +++ b/erpnext/accounts/doctype/coupon_code/coupon_code.py @@ -17,7 +17,7 @@ class CouponCode(Document): self.coupon_code =''.join(i for i in self.coupon_name if not i.isdigit())[0:8].upper() elif self.coupon_type == "Gift Card": self.coupon_code = frappe.generate_hash()[:10].upper() - + def validate(self): if self.coupon_type == "Gift Card": self.maximum_use = 1 diff --git a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py index 622bd33e20a..06987a8a4a5 100644 --- a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py +++ b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py @@ -57,7 +57,7 @@ def test_create_test_data(): }) item_price.insert() # create test item pricing rule - if not frappe.db.exists("Pricing Rule","_Test Pricing Rule for _Test Item"): + if not frappe.db.exists("Pricing Rule", {"title": "_Test Pricing Rule for _Test Item"}): item_pricing_rule = frappe.get_doc({ "doctype": "Pricing Rule", "title": "_Test Pricing Rule for _Test Item", @@ -86,14 +86,15 @@ def test_create_test_data(): sales_partner.insert() # create test item coupon code if not frappe.db.exists("Coupon Code", "SAVE30"): + pricing_rule = frappe.db.get_value("Pricing Rule", {"title": "_Test Pricing Rule for _Test Item"}, ['name']) coupon_code = frappe.get_doc({ - "doctype": "Coupon Code", - "coupon_name":"SAVE30", - "coupon_code":"SAVE30", - "pricing_rule": "_Test Pricing Rule for _Test Item", - "valid_from": "2014-01-01", - "maximum_use":1, - "used":0 + "doctype": "Coupon Code", + "coupon_name":"SAVE30", + "coupon_code":"SAVE30", + "pricing_rule": pricing_rule, + "valid_from": "2014-01-01", + "maximum_use":1, + "used":0 }) coupon_code.insert() @@ -102,7 +103,7 @@ class TestCouponCode(unittest.TestCase): test_create_test_data() def tearDown(self): - frappe.set_user("Administrator") + frappe.set_user("Administrator") def test_sales_order_with_coupon_code(self): frappe.db.set_value("Coupon Code", "SAVE30", "used", 0) @@ -123,6 +124,3 @@ class TestCouponCode(unittest.TestCase): so.submit() self.assertEqual(frappe.db.get_value("Coupon Code", "SAVE30", "used"), 1) - - - diff --git a/erpnext/accounts/doctype/customer_group_item/__init__.py b/erpnext/accounts/doctype/customer_group_item/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/customer_group_item/customer_group_item.json b/erpnext/accounts/doctype/customer_group_item/customer_group_item.json new file mode 100644 index 00000000000..bd1229d4e09 --- /dev/null +++ b/erpnext/accounts/doctype/customer_group_item/customer_group_item.json @@ -0,0 +1,31 @@ +{ + "actions": [], + "creation": "2021-05-06 16:12:42.558878", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "customer_group" + ], + "fields": [ + { + "fieldname": "customer_group", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Customer Group", + "options": "Customer Group" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-05-07 10:39:21.563506", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Customer Group Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/customer_group_item/customer_group_item.py b/erpnext/accounts/doctype/customer_group_item/customer_group_item.py new file mode 100644 index 00000000000..df782ac9e0c --- /dev/null +++ b/erpnext/accounts/doctype/customer_group_item/customer_group_item.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class CustomerGroupItem(Document): + pass diff --git a/erpnext/accounts/doctype/customer_item/__init__.py b/erpnext/accounts/doctype/customer_item/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/customer_item/customer_item.json b/erpnext/accounts/doctype/customer_item/customer_item.json new file mode 100644 index 00000000000..f3dac02f942 --- /dev/null +++ b/erpnext/accounts/doctype/customer_item/customer_item.json @@ -0,0 +1,31 @@ +{ + "actions": [], + "creation": "2021-05-05 14:04:54.266353", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "customer" + ], + "fields": [ + { + "fieldname": "customer", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Customer ", + "options": "Customer" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-05-06 10:02:32.967841", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Customer Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/customer_item/customer_item.py b/erpnext/accounts/doctype/customer_item/customer_item.py new file mode 100644 index 00000000000..a577145e4ee --- /dev/null +++ b/erpnext/accounts/doctype/customer_item/customer_item.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class CustomerItem(Document): + pass diff --git a/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.py b/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.py index 109737f7276..93dfcc14bda 100644 --- a/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.py +++ b/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.py @@ -7,4 +7,4 @@ from __future__ import unicode_literals from frappe.model.document import Document class DiscountedInvoice(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/accounts/doctype/dunning/dunning_dashboard.py b/erpnext/accounts/doctype/dunning/dunning_dashboard.py index 19a73ddfa48..33c6ab080c9 100644 --- a/erpnext/accounts/doctype/dunning/dunning_dashboard.py +++ b/erpnext/accounts/doctype/dunning/dunning_dashboard.py @@ -14,4 +14,4 @@ def get_data(): 'items': ['Payment Entry', 'Journal Entry'] } ] - } \ No newline at end of file + } diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index ed50f784b20..39ee74658ce 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -144,4 +144,4 @@ def create_dunning_type_with_zero_interest_rate(): 'closing_text': 'We kindly request that you pay the outstanding amount immediately, and late fees.' } ) - dunning_type.save() \ No newline at end of file + dunning_type.save() diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js index b7b6020caa9..926a442f808 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js @@ -31,7 +31,7 @@ frappe.ui.form.on('Exchange Rate Revaluation', { }, __('Create')); } } - }); + }); } }, @@ -128,4 +128,4 @@ var get_account_details = function(frm, cdt, cdn) { frm.events.get_total_gain_loss(frm); } }); -}; \ No newline at end of file +}; diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index 56193216a22..dbbcedcadfa 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -27,6 +27,9 @@ class ExchangeRateRevaluation(Document): if not (self.company and self.posting_date): frappe.throw(_("Please select Company and Posting Date to getting entries")) + def on_cancel(self): + self.ignore_linked_doctypes = ('GL Entry') + @frappe.whitelist() def check_journal_entry_condition(self): total_debit = frappe.db.get_value("Journal Entry Account", { @@ -41,7 +44,7 @@ class ExchangeRateRevaluation(Document): if total_amt != total_debit: return True - + return False @frappe.whitelist() @@ -99,10 +102,12 @@ class ExchangeRateRevaluation(Document): sum(debit) - sum(credit) as balance from `tabGL Entry` where account in (%s) - group by account, party_type, party + and posting_date <= %s + and is_cancelled = 0 + group by account, NULLIF(party_type,''), NULLIF(party,'') having sum(debit) != sum(credit) order by account - """ % ', '.join(['%s']*len(accounts)), tuple(accounts), as_dict=1) + """ % (', '.join(['%s']*len(accounts)), '%s'), tuple(accounts + [self.posting_date]), as_dict=1) return account_details @@ -143,9 +148,9 @@ class ExchangeRateRevaluation(Document): "party_type": d.get("party_type"), "party": d.get("party"), "account_currency": d.get("account_currency"), - "balance": d.get("balance_in_account_currency"), - dr_or_cr: abs(d.get("balance_in_account_currency")), - "exchange_rate":d.get("new_exchange_rate"), + "balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")), + dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")), + "exchange_rate": flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")), "reference_type": "Exchange Rate Revaluation", "reference_name": self.name, }) @@ -154,9 +159,9 @@ class ExchangeRateRevaluation(Document): "party_type": d.get("party_type"), "party": d.get("party"), "account_currency": d.get("account_currency"), - "balance": d.get("balance_in_account_currency"), - reverse_dr_or_cr: abs(d.get("balance_in_account_currency")), - "exchange_rate": d.get("current_exchange_rate"), + "balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")), + reverse_dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")), + "exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")), "reference_type": "Exchange Rate Revaluation", "reference_name": self.name }) @@ -185,9 +190,9 @@ def get_account_details(account, company, posting_date, party_type=None, party=N account_details = {} company_currency = erpnext.get_company_currency(company) - balance = get_balance_on(account, party_type=party_type, party=party, in_account_currency=False) + balance = get_balance_on(account, date=posting_date, party_type=party_type, party=party, in_account_currency=False) if balance: - balance_in_account_currency = get_balance_on(account, party_type=party_type, party=party) + balance_in_account_currency = get_balance_on(account, date=posting_date, party_type=party_type, party=party) current_exchange_rate = balance / balance_in_account_currency if balance_in_account_currency else 0 new_exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date) new_balance_in_base_currency = balance_in_account_currency * new_exchange_rate @@ -200,4 +205,4 @@ def get_account_details(account, company, posting_date, party_type=None, party=N "new_balance_in_base_currency": new_balance_in_base_currency } - return account_details \ No newline at end of file + return account_details diff --git a/erpnext/accounts/doctype/finance_book/test_finance_book.py b/erpnext/accounts/doctype/finance_book/test_finance_book.py index 502765812ae..cd8e204f4c8 100644 --- a/erpnext/accounts/doctype/finance_book/test_finance_book.py +++ b/erpnext/accounts/doctype/finance_book/test_finance_book.py @@ -19,7 +19,7 @@ class TestFinanceBook(unittest.TestCase): finance_book = frappe.get_doc("Finance Book", "_Test Finance Book") return finance_book - + def test_finance_book(self): finance_book = self.create_finance_book() diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 11465b711e3..0844995f296 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -58,8 +58,8 @@ class GLEntry(Document): if not self.get(k): frappe.throw(_("{0} is required").format(_(self.meta.get_label(k)))) - account_type = frappe.get_cached_value("Account", self.account, "account_type") if not (self.party_type and self.party): + account_type = frappe.get_cached_value("Account", self.account, "account_type") if account_type == "Receivable": frappe.throw(_("{0} {1}: Customer is required against Receivable account {2}") .format(self.voucher_type, self.voucher_no, self.account)) @@ -73,15 +73,19 @@ class GLEntry(Document): .format(self.voucher_type, self.voucher_no, self.account)) def pl_must_have_cost_center(self): - if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss": - if not self.cost_center and self.voucher_type != 'Period Closing Voucher': - msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format( - self.voucher_type, self.voucher_no, self.account) - msg += " " - msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format( - self.voucher_type) + """Validate that profit and loss type account GL entries have a cost center.""" - frappe.throw(msg, title=_("Missing Cost Center")) + if self.cost_center or self.voucher_type == 'Period Closing Voucher': + return + + if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss": + msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format( + self.voucher_type, self.voucher_no, self.account) + msg += " " + msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format( + self.voucher_type) + + frappe.throw(msg, title=_("Missing Cost Center")) def validate_dimensions_for_pl_and_bs(self): account_type = frappe.db.get_value("Account", self.account, "report_type") diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting_dashboard.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting_dashboard.py index 6523cd3cdb8..6d35ca24397 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting_dashboard.py +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting_dashboard.py @@ -17,4 +17,4 @@ def get_data(): 'items': ['Payment Entry', 'Journal Entry'] } ] - } \ No newline at end of file + } diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting_list.js b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting_list.js index a72023d8e12..4895efcd4cc 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting_list.js +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting_list.js @@ -18,4 +18,4 @@ frappe.listview_settings['Invoice Discounting'] = { return [__("Canceled"), "red", "status,=,Canceled"]; } } -}; \ No newline at end of file +}; diff --git a/erpnext/accounts/doctype/journal_entry/regional/india.js b/erpnext/accounts/doctype/journal_entry/regional/india.js index 75a69ac0cf3..c5f5520479f 100644 --- a/erpnext/accounts/doctype/journal_entry/regional/india.js +++ b/erpnext/accounts/doctype/journal_entry/regional/india.js @@ -14,4 +14,4 @@ frappe.ui.form.on("Journal Entry", { }; }); } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 5f003e022a0..5835d462ae9 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -100,7 +100,7 @@ class TestJournalEntry(unittest.TestCase): "debit_in_account_currency": 0 if diff > 0 else abs(diff), "credit_in_account_currency": diff if diff > 0 else 0 }) - + jv.append("accounts", { "account": "Stock Adjustment - TCP1", "cost_center": "Main - TCP1", diff --git a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js index cbb9fc4b0f9..1c19c1d2255 100644 --- a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js +++ b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js @@ -88,4 +88,4 @@ frappe.ui.form.on("Journal Entry Template", { frappe.model.clear_table(frm.doc, "accounts"); frm.refresh_field("accounts"); } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.js b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.js index 7a06d3572a6..103fa96d02d 100644 --- a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.js +++ b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.js @@ -14,4 +14,4 @@ frappe.ui.form.on('Mode of Payment', { }; }); }, -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py index 32473694c80..cea921e999e 100644 --- a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py +++ b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py @@ -39,4 +39,3 @@ class ModeofPayment(Document): message = "POS Profile " + frappe.bold(", ".join(pos_profiles)) + " contains \ Mode of Payment " + frappe.bold(str(self.name)) + ". Please remove them to disable this mode." frappe.throw(_(message), title="Not Allowed") - diff --git a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py index bff64227325..ad8623fb4ee 100644 --- a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py +++ b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py @@ -55,4 +55,4 @@ def get_percentage(doc, start_date, period): if d.month in months: percentage += d.percentage_allocation - return percentage \ No newline at end of file + return percentage diff --git a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution_dashboard.py b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution_dashboard.py index a6794998159..912bd9e331a 100644 --- a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution_dashboard.py +++ b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution_dashboard.py @@ -20,4 +20,4 @@ def get_data(): 'items': ['Budget'] } ] - } \ No newline at end of file + } diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js index b2e86267c8f..9f22ab0d76c 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js @@ -154,4 +154,4 @@ frappe.ui.form.on('Opening Invoice Creation Tool Item', { invoices_add: (frm) => { frm.trigger('update_invoice_table'); } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py index 29dc96e8c6f..3586e683d4d 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py @@ -240,5 +240,3 @@ def get_temporary_opening_account(company=None): frappe.throw(_("Please add a Temporary Opening account in Chart of Accounts")) return accounts[0].name - - diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index d3ac3a66760..cc8ab453fd9 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -7,6 +7,8 @@ cur_frm.cscript.tax_table = "Advance Taxes and Charges"; frappe.ui.form.on('Payment Entry', { onload: function(frm) { + frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice']; + if(frm.doc.__islocal) { if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null); if (!frm.doc.paid_to) frm.set_value("paid_to_account_currency", null); @@ -531,8 +533,8 @@ frappe.ui.form.on('Payment Entry', { source_exchange_rate: function(frm) { if (frm.doc.paid_amount) { frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate)); - if(!frm.set_paid_amount_based_on_received_amount && - (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency)) { + // target exchange rate should always be same as source if both account currencies are same + if(frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) { frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate); frm.set_value("base_received_amount", frm.doc.base_paid_amount); } diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 51f18a5a4e3..6f362c1fbb9 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -667,6 +667,7 @@ { "fieldname": "base_paid_amount_after_tax", "fieldtype": "Currency", + "hidden": 1, "label": "Paid Amount After Tax (Company Currency)", "options": "Company:company:default_currency", "read_only": 1 @@ -693,21 +694,25 @@ "depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'", "fieldname": "received_amount_after_tax", "fieldtype": "Currency", + "hidden": 1, "label": "Received Amount After Tax", - "options": "paid_to_account_currency" + "options": "paid_to_account_currency", + "read_only": 1 }, { "depends_on": "doc.received_amount", "fieldname": "base_received_amount_after_tax", "fieldtype": "Currency", + "hidden": 1, "label": "Received Amount After Tax (Company Currency)", - "options": "Company:company:default_currency" + "options": "Company:company:default_currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-06-22 20:37:06.154206", + "modified": "2021-07-09 08:58:15.008761", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index ff00fde523f..91e89745f5d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -55,14 +55,17 @@ class PaymentEntry(AccountsController): self.validate_mandatory() self.validate_reference_documents() self.set_tax_withholding() - self.apply_taxes() self.set_amounts() + self.validate_amounts() + self.apply_taxes() + self.set_amounts_after_tax() self.clear_unallocated_reference_document_rows() self.validate_payment_against_negative_invoice() self.validate_transaction_reference() self.set_title() self.set_remarks() self.validate_duplicate_entry() + self.validate_payment_type_with_outstanding() self.validate_allocated_amount() self.validate_paid_invoices() self.ensure_supplier_is_not_blocked() @@ -118,6 +121,11 @@ class PaymentEntry(AccountsController): if not self.get(field): self.set(field, bank_data.account) + def validate_payment_type_with_outstanding(self): + total_outstanding = sum(d.allocated_amount for d in self.get('references')) + if total_outstanding < 0 and self.party_type == 'Customer' and self.payment_type == 'Receive': + frappe.throw(_("Cannot receive from customer against negative outstanding"), title=_("Incorrect Payment Type")) + def validate_allocated_amount(self): for d in self.get("references"): if (flt(d.allocated_amount))> 0: @@ -185,7 +193,7 @@ class PaymentEntry(AccountsController): for field, value in iteritems(ref_details): if d.exchange_gain_loss: # for cases where gain/loss is booked into invoice - # exchange_gain_loss is calculated from invoice & populated + # exchange_gain_loss is calculated from invoice & populated # and row.exchange_rate is already set to payment entry's exchange rate # refer -> `update_reference_in_payment_entry()` in utils.py continue @@ -236,7 +244,9 @@ class PaymentEntry(AccountsController): self.company_currency, self.posting_date) def set_target_exchange_rate(self, ref_doc=None): - if self.paid_to and not self.target_exchange_rate: + if self.paid_from_account_currency == self.paid_to_account_currency: + self.target_exchange_rate = self.source_exchange_rate + elif self.paid_to and not self.target_exchange_rate: if ref_doc: if self.paid_to_account_currency == ref_doc.currency: self.target_exchange_rate = ref_doc.get("exchange_rate") @@ -411,9 +421,15 @@ class PaymentEntry(AccountsController): if not self.advance_tax_account: frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction")) - reference_doclist = [] net_total = self.paid_amount - included_in_paid_amount = 0 + + for reference in self.get("references"): + net_total_for_tds = 0 + if reference.reference_doctype == 'Purchase Order': + net_total_for_tds += flt(frappe.db.get_value('Purchase Order', reference.reference_name, 'net_total')) + + if net_total_for_tds: + net_total = net_total_for_tds # Adding args as purchase invoice to get TDS amount args = frappe._dict({ @@ -430,7 +446,7 @@ class PaymentEntry(AccountsController): return tax_withholding_details.update({ - 'included_in_paid_amount': included_in_paid_amount, + 'add_deduct_tax': 'Add', 'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company) }) @@ -462,13 +478,22 @@ class PaymentEntry(AccountsController): def set_amounts(self): self.set_received_amount() self.set_amounts_in_company_currency() - self.set_amounts_after_tax() self.set_total_allocated_amount() self.set_unallocated_amount() self.set_difference_amount() + def validate_amounts(self): + self.validate_received_amount() + + def validate_received_amount(self): + if self.paid_from_account_currency == self.paid_to_account_currency: + if self.paid_amount != self.received_amount: + frappe.throw(_("Received Amount should be same as Paid Amount")) + def set_received_amount(self): self.base_received_amount = self.base_paid_amount + if self.paid_from_account_currency == self.paid_to_account_currency: + self.received_amount = self.paid_amount def set_amounts_after_tax(self): applicable_tax = 0 @@ -519,16 +544,19 @@ class PaymentEntry(AccountsController): self.unallocated_amount = 0 if self.party: total_deductions = sum(flt(d.amount) for d in self.get("deductions")) + included_taxes = self.get_included_taxes() if self.payment_type == "Receive" \ - and self.base_total_allocated_amount < self.base_received_amount_after_tax + total_deductions \ - and self.total_allocated_amount < self.paid_amount_after_tax + (total_deductions / self.source_exchange_rate): - self.unallocated_amount = (self.received_amount_after_tax + total_deductions - + and self.base_total_allocated_amount < self.base_received_amount + total_deductions \ + and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate): + self.unallocated_amount = (self.base_received_amount + total_deductions - self.base_total_allocated_amount) / self.source_exchange_rate + self.unallocated_amount -= included_taxes elif self.payment_type == "Pay" \ - and self.base_total_allocated_amount < (self.base_paid_amount_after_tax - total_deductions) \ - and self.total_allocated_amount < self.received_amount_after_tax + (total_deductions / self.target_exchange_rate): - self.unallocated_amount = (self.base_paid_amount_after_tax - (total_deductions + + and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \ + and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate): + self.unallocated_amount = (self.base_paid_amount - (total_deductions + self.base_total_allocated_amount)) / self.target_exchange_rate + self.unallocated_amount -= included_taxes def set_difference_amount(self): base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate) @@ -537,17 +565,29 @@ class PaymentEntry(AccountsController): base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount) if self.payment_type == "Receive": - self.difference_amount = base_party_amount - self.base_received_amount_after_tax + self.difference_amount = base_party_amount - self.base_received_amount elif self.payment_type == "Pay": - self.difference_amount = self.base_paid_amount_after_tax - base_party_amount + self.difference_amount = self.base_paid_amount - base_party_amount else: - self.difference_amount = self.base_paid_amount_after_tax - flt(self.base_received_amount_after_tax) + self.difference_amount = self.base_paid_amount - flt(self.base_received_amount) total_deductions = sum(flt(d.amount) for d in self.get("deductions")) + included_taxes = self.get_included_taxes() - self.difference_amount = flt(self.difference_amount - total_deductions, + self.difference_amount = flt(self.difference_amount - total_deductions - included_taxes, self.precision("difference_amount")) + def get_included_taxes(self): + included_taxes = 0 + for tax in self.get('taxes'): + if tax.included_in_paid_amount: + if tax.add_deduct_tax == 'Add': + included_taxes += tax.base_tax_amount + else: + included_taxes -= tax.base_tax_amount + + return included_taxes + # Paid amount is auto allocated in the reference document by default. # Clear the reference document which doesn't have allocated amount on validate so that form can be loaded fast def clear_unallocated_reference_document_rows(self): @@ -690,8 +730,8 @@ class PaymentEntry(AccountsController): "account": self.paid_from, "account_currency": self.paid_from_account_currency, "against": self.party if self.payment_type=="Pay" else self.paid_to, - "credit_in_account_currency": self.paid_amount_after_tax, - "credit": self.base_paid_amount_after_tax, + "credit_in_account_currency": self.paid_amount, + "credit": self.base_paid_amount, "cost_center": self.cost_center }, item=self) ) @@ -701,8 +741,8 @@ class PaymentEntry(AccountsController): "account": self.paid_to, "account_currency": self.paid_to_account_currency, "against": self.party if self.payment_type=="Receive" else self.paid_from, - "debit_in_account_currency": self.received_amount_after_tax, - "debit": self.base_received_amount_after_tax, + "debit_in_account_currency": self.received_amount, + "debit": self.base_received_amount, "cost_center": self.cost_center }, item=self) ) @@ -715,35 +755,42 @@ class PaymentEntry(AccountsController): if self.payment_type in ('Pay', 'Internal Transfer'): dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit" + against = self.party or self.paid_from elif self.payment_type == 'Receive': dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit" + against = self.party or self.paid_to payment_or_advance_account = self.get_party_account_for_taxes() + tax_amount = d.tax_amount + base_tax_amount = d.base_tax_amount + + if self.advance_tax_account: + tax_amount = -1 * tax_amount + base_tax_amount = -1 * base_tax_amount gl_entries.append( self.get_gl_dict({ "account": d.account_head, - "against": self.party if self.payment_type=="Receive" else self.paid_from, - dr_or_cr: d.base_tax_amount, - dr_or_cr + "_in_account_currency": d.base_tax_amount + "against": against, + dr_or_cr: tax_amount, + dr_or_cr + "_in_account_currency": base_tax_amount if account_currency==self.company_currency else d.tax_amount, "cost_center": d.cost_center }, account_currency, item=d)) #Intentionally use -1 to get net values in party account - gl_entries.append( - self.get_gl_dict({ - "account": payment_or_advance_account, - "against": self.party if self.payment_type=="Receive" else self.paid_from, - dr_or_cr: -1 * d.base_tax_amount, - dr_or_cr + "_in_account_currency": -1*d.base_tax_amount - if account_currency==self.company_currency - else d.tax_amount, - "cost_center": self.cost_center, - "party_type": self.party_type, - "party": self.party - }, account_currency, item=d)) + if not d.included_in_paid_amount or self.advance_tax_account: + gl_entries.append( + self.get_gl_dict({ + "account": payment_or_advance_account, + "against": against, + dr_or_cr: -1 * tax_amount, + dr_or_cr + "_in_account_currency": -1 * base_tax_amount + if account_currency==self.company_currency + else d.tax_amount, + "cost_center": self.cost_center, + }, account_currency, item=d)) def add_deductions_gl_entries(self, gl_entries): for d in self.get("deductions"): @@ -767,9 +814,9 @@ class PaymentEntry(AccountsController): if self.advance_tax_account: return self.advance_tax_account elif self.payment_type == 'Receive': - return self.paid_from - elif self.payment_type in ('Pay', 'Internal Transfer'): return self.paid_to + elif self.payment_type in ('Pay', 'Internal Transfer'): + return self.paid_from def update_advance_paid(self): if self.payment_type in ("Receive", "Pay") and self.party: @@ -813,7 +860,7 @@ class PaymentEntry(AccountsController): if account_details: row.update(account_details) - + if not row.get('amount'): # if no difference amount return @@ -1648,12 +1695,6 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta if dt == "Employee Advance": paid_amount = received_amount * doc.get('exchange_rate', 1) - if dt == "Purchase Order" and doc.apply_tds: - if party_account_currency == bank.account_currency: - paid_amount = received_amount = doc.base_net_total - else: - paid_amount = received_amount = doc.base_net_total * doc.get('exchange_rate', 1) - return paid_amount, received_amount def apply_early_payment_discount(paid_amount, received_amount, doc): diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry_list.js b/erpnext/accounts/doctype/payment_entry/payment_entry_list.js index e6d83b9f683..2d76fe69ef9 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry_list.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry_list.js @@ -11,4 +11,4 @@ frappe.listview_settings['Payment Entry'] = { }; } } -}; \ No newline at end of file +}; diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index d1302f5ae78..dac927b2cef 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -107,7 +107,7 @@ class TestPaymentEntry(unittest.TestCase): pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC") pe.reference_no = "1" pe.reference_date = "2016-01-01" - pe.target_exchange_rate = 50 + pe.source_exchange_rate = 50 pe.insert() pe.submit() @@ -154,7 +154,7 @@ class TestPaymentEntry(unittest.TestCase): pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC") pe.reference_no = "1" pe.reference_date = "2016-01-01" - pe.target_exchange_rate = 50 + pe.source_exchange_rate = 50 pe.insert() pe.submit() @@ -295,6 +295,34 @@ class TestPaymentEntry(unittest.TestCase): outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount")) self.assertEqual(outstanding_amount, 80) + def test_payment_entry_against_si_usd_to_usd_with_deduction_in_base_currency (self): + si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC", + currency="USD", conversion_rate=50, do_not_save=1) + + si.plc_conversion_rate = 50 + si.save() + si.submit() + + pe = get_payment_entry("Sales Invoice", si.name, party_amount=20, + bank_account="_Test Bank USD - _TC", bank_amount=900) + + pe.source_exchange_rate = 45.263 + pe.target_exchange_rate = 45.263 + pe.reference_no = "1" + pe.reference_date = "2016-01-01" + + + pe.append("deductions", { + "account": "_Test Exchange Gain/Loss - _TC", + "cost_center": "_Test Cost Center - _TC", + "amount": 94.80 + }) + + pe.save() + + self.assertEqual(flt(pe.difference_amount, 2), 0.0) + self.assertEqual(flt(pe.unallocated_amount, 2), 0.0) + def test_payment_entry_retrieves_last_exchange_rate(self): from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records, save_new_records @@ -463,7 +491,7 @@ class TestPaymentEntry(unittest.TestCase): pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC") pe.reference_no = "1" pe.reference_date = "2016-01-01" - pe.target_exchange_rate = 55 + pe.source_exchange_rate = 55 pe.append("deductions", { "account": "_Test Exchange Gain/Loss - _TC", diff --git a/erpnext/accounts/doctype/payment_entry/tests/test_payment_against_purchase_invoice.js b/erpnext/accounts/doctype/payment_entry/tests/test_payment_against_purchase_invoice.js index 14aa0736d40..e8db2c3159d 100644 --- a/erpnext/accounts/doctype/payment_entry/tests/test_payment_against_purchase_invoice.js +++ b/erpnext/accounts/doctype/payment_entry/tests/test_payment_against_purchase_invoice.js @@ -57,4 +57,4 @@ QUnit.test("test payment entry", function(assert) { () => frappe.timeout(3), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js b/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js index 0c76343fa90..34af79fcd10 100644 --- a/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js @@ -25,4 +25,4 @@ QUnit.test("test payment entry", function(assert) { () => frappe.timeout(0.3), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry_write_off.js b/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry_write_off.js index 9849d767271..8c7f6f47dd3 100644 --- a/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry_write_off.js +++ b/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry_write_off.js @@ -64,4 +64,4 @@ QUnit.test("test payment entry", function(assert) { }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.py b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.py index fd213a47a1e..3529c16a1c2 100644 --- a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.py +++ b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.py @@ -9,19 +9,19 @@ from frappe.model.document import Document class PaymentGatewayAccount(Document): def autoname(self): self.name = self.payment_gateway + " - " + self.currency - + def validate(self): self.currency = frappe.db.get_value("Account", self.payment_account, "account_currency") - + self.update_default_payment_gateway() self.set_as_default_if_not_set() - + def update_default_payment_gateway(self): if self.is_default: frappe.db.sql("""update `tabPayment Gateway Account` set is_default = 0 where is_default = 1 """) - + def set_as_default_if_not_set(self): - if not frappe.db.get_value("Payment Gateway Account", + if not frappe.db.get_value("Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name)}, "name"): self.is_default = 1 diff --git a/erpnext/accounts/doctype/payment_order/payment_order.js b/erpnext/accounts/doctype/payment_order/payment_order.js index d12e474c5b1..aa373bc2fcc 100644 --- a/erpnext/accounts/doctype/payment_order/payment_order.js +++ b/erpnext/accounts/doctype/payment_order/payment_order.js @@ -136,4 +136,4 @@ frappe.ui.form.on('Payment Order', { dialog.show(); }, -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/payment_order/payment_order_dashboard.py b/erpnext/accounts/doctype/payment_order/payment_order_dashboard.py index 6b93f926cdf..a4f335833ee 100644 --- a/erpnext/accounts/doctype/payment_order/payment_order_dashboard.py +++ b/erpnext/accounts/doctype/payment_order/payment_order_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Payment Entry', 'Journal Entry'] } ] - } \ No newline at end of file + } diff --git a/erpnext/accounts/doctype/payment_order/test_payment_order.py b/erpnext/accounts/doctype/payment_order/test_payment_order.py index 5fdde07faa4..9ba57aef300 100644 --- a/erpnext/accounts/doctype/payment_order/test_payment_order.py +++ b/erpnext/accounts/doctype/payment_order/test_payment_order.py @@ -46,4 +46,4 @@ def create_payment_order_against_payment_entry(ref_doc, order_type): doc = make_payment_order(ref_doc.name, payment_order) doc.save() doc.submit() - return doc \ No newline at end of file + return doc diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 6635128f9ef..acfe1fef2ee 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -306,5 +306,5 @@ def reconcile_dr_cr_note(dr_cr_notes, company): } ] }) - - jv.submit() \ No newline at end of file + jv.flags.ignore_mandatory = True + jv.submit() diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 438951db627..f83cb375fcf 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -541,4 +541,4 @@ def make_payment_order(source_name, target_doc=None): } }, target_doc, set_missing_values) - return doclist \ No newline at end of file + return doclist diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index 5eba62c0b31..ad6ff6f5553 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -138,4 +138,4 @@ class TestPaymentRequest(unittest.TestCase): # Try to make Payment Request more than SO amount, should give validation pr2.grand_total = 900 - self.assertRaises(frappe.ValidationError, pr2.save) \ No newline at end of file + self.assertRaises(frappe.ValidationError, pr2.save) diff --git a/erpnext/accounts/doctype/payment_term/payment_term.js b/erpnext/accounts/doctype/payment_term/payment_term.js index acd0144c2ea..feecf93484c 100644 --- a/erpnext/accounts/doctype/payment_term/payment_term.js +++ b/erpnext/accounts/doctype/payment_term/payment_term.js @@ -19,4 +19,4 @@ frappe.ui.form.on('Payment Term', { frm.set_df_property("discount", "description", description); } } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js index 84c8d09b164..ea18adefa35 100644 --- a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js +++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js @@ -3,6 +3,6 @@ frappe.ui.form.on('Payment Terms Template', { setup: function(frm) { - + } }); diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template_dashboard.py b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template_dashboard.py index c705097ac66..5c8cb4fbdc1 100644 --- a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template_dashboard.py +++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template_dashboard.py @@ -30,4 +30,4 @@ def get_data(): 'items': ['Customer Group', 'Supplier Group'] } ] - } \ No newline at end of file + } diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index b0a5b04de64..2f9bf8b07eb 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -51,7 +51,7 @@ class PeriodClosingVoucher(AccountsController): def make_gl_entries(self): gl_entries = [] - net_pl_balance = 0 + net_pl_balance = 0 pl_accounts = self.get_pl_balances() @@ -79,7 +79,7 @@ class PeriodClosingVoucher(AccountsController): from erpnext.accounts.general_ledger import make_gl_entries make_gl_entries(gl_entries) - + def get_pnl_gl_entry(self, net_pl_balance): cost_center = frappe.db.get_value("Company", self.company, "cost_center") gl_entry = self.get_gl_dict({ diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 2f29372b01c..f17a5c51a08 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -139,7 +139,7 @@ def create_company(): 'company_name': "Test PCV Company", 'country': 'United States', 'default_currency': 'USD' - }) + }) company.insert(ignore_if_duplicate = True) return company.name diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js index 6418d730903..264d4a68b00 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -20,9 +20,9 @@ frappe.ui.form.on('POS Closing Entry', { frm.set_query("pos_opening_entry", function(doc) { return { filters: { 'status': 'Open', 'docstatus': 1 } }; }); - + if (frm.doc.docstatus === 0 && !frm.doc.amended_from) frm.set_value("period_end_date", frappe.datetime.now_datetime()); - + frappe.realtime.on('closing_process_complete', async function(data) { await frm.reload_doc(); if (frm.doc.status == 'Failed' && frm.doc.error_message && data.user == frappe.session.user) { @@ -43,7 +43,7 @@ frappe.ui.form.on('POS Closing Entry', { const issue = 'issue'; frm.dashboard.set_headline( __('POS Closing failed while running in a background process. You can resolve the {0} and retry the process again.', [issue])); - + $('#jump_to_error').on('click', (e) => { e.preventDefault(); frappe.utils.scroll_to( diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js index 493bd448024..04e392a0f27 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js @@ -235,4 +235,4 @@ frappe.ui.form.on('POS Invoice', { }); }); } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index 7459c11d4d9..fcccb39b70c 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -595,7 +595,8 @@ { "fieldname": "scan_barcode", "fieldtype": "Data", - "label": "Scan Barcode" + "label": "Scan Barcode", + "options": "Barcode" }, { "allow_bulk_edit": 1, @@ -1545,6 +1546,7 @@ "fieldname": "consolidated_invoice", "fieldtype": "Link", "label": "Consolidated Sales Invoice", + "no_copy": 1, "options": "Sales Invoice", "read_only": 1 } @@ -1552,7 +1554,7 @@ "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2021-02-01 15:03:33.800707", + "modified": "2021-08-17 20:13:44.255437", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js index cd08efc55fb..2f8081b95ce 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js @@ -5,10 +5,10 @@ frappe.ui.form.on('POS Invoice Merge Log', { setup: function(frm) { frm.set_query("pos_invoice", "pos_invoices", doc => { return{ - filters: { + filters: { 'docstatus': 1, - 'customer': doc.customer, - 'consolidated_invoice': '' + 'customer': doc.customer, + 'consolidated_invoice': '' } } }); diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 08e072e2049..e50d437ba6a 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -354,4 +354,4 @@ def safe_load_json(message): except Exception: json_message = message - return json_message \ No newline at end of file + return json_message diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py index 040a815fab3..1b9659409c0 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py @@ -147,4 +147,3 @@ class TestPOSInvoiceMergeLog(unittest.TestCase): frappe.set_user("Administrator") frappe.db.sql("delete from `tabPOS Profile`") frappe.db.sql("delete from `tabPOS Invoice`") - diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.js b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.js index 372e75649b3..d23f348f04e 100644 --- a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.js +++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.js @@ -53,4 +53,4 @@ frappe.ui.form.on('POS Opening Entry', { }); } } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py index 0023a84a46e..3318fefab14 100644 --- a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py +++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py @@ -38,4 +38,4 @@ class POSOpeningEntry(StatusUpdater): frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account")) def on_submit(self): - self.set_status(update=True) \ No newline at end of file + self.set_status(update=True) diff --git a/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py b/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py index 2e36391714b..c115be5ae94 100644 --- a/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py +++ b/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py @@ -21,8 +21,8 @@ def create_opening_entry(pos_profile, user): balance_details.append(frappe._dict({ 'mode_of_payment': d.mode_of_payment })) - + entry.set("balance_details", balance_details) entry.submit() - - return entry.as_dict() + + return entry.as_dict() diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.py b/erpnext/accounts/doctype/pos_settings/pos_settings.py index 913f49829c1..d925dd9d86e 100644 --- a/erpnext/accounts/doctype/pos_settings/pos_settings.py +++ b/erpnext/accounts/doctype/pos_settings/pos_settings.py @@ -8,4 +8,4 @@ from frappe.model.document import Document class POSSettings(Document): def validate(self): - pass \ No newline at end of file + pass diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index 428989aa965..99c5b34fa35 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -2,12 +2,13 @@ "actions": [], "allow_import": 1, "allow_rename": 1, - "autoname": "field:title", + "autoname": "naming_series:", "creation": "2014-02-21 15:02:51", "doctype": "DocType", "engine": "InnoDB", "field_order": [ "applicability_section", + "naming_series", "title", "disable", "apply_on", @@ -95,8 +96,7 @@ "fieldtype": "Data", "label": "Title", "no_copy": 1, - "reqd": 1, - "unique": 1 + "reqd": 1 }, { "default": "0", @@ -558,7 +558,8 @@ "description": "Simple Python Expression, Example: territory != 'All Territories'", "fieldname": "condition", "fieldtype": "Code", - "label": "Condition" + "label": "Condition", + "options": "PythonExpression" }, { "fieldname": "column_break_42", @@ -570,12 +571,19 @@ "fieldname": "is_recursive", "fieldtype": "Check", "label": "Is Recursive" + }, + { + "default": "PRLE-.####", + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "PRLE-.####" } ], "icon": "fa fa-gift", "idx": 1, "links": [], - "modified": "2021-03-06 22:01:24.840422", + "modified": "2021-08-06 15:10:04.219321", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", @@ -633,5 +641,6 @@ ], "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "DESC" -} \ No newline at end of file + "sort_order": "DESC", + "title_field": "title" +} diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index ffe8be1162f..662d7704a55 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -605,4 +605,4 @@ def delete_existing_pricing_rules(): for doctype in ["Pricing Rule", "Pricing Rule Item Code", "Pricing Rule Item Group", "Pricing Rule Brand"]: - frappe.db.sql("delete from `tab{0}`".format(doctype)) \ No newline at end of file + frappe.db.sql("delete from `tab{0}`".format(doctype)) diff --git a/erpnext/accounts/doctype/pricing_rule/tests/test_pricing_rule.js b/erpnext/accounts/doctype/pricing_rule/tests/test_pricing_rule.js index 8155e7d799a..8279b59cb41 100644 --- a/erpnext/accounts/doctype/pricing_rule/tests/test_pricing_rule.js +++ b/erpnext/accounts/doctype/pricing_rule/tests/test_pricing_rule.js @@ -26,4 +26,3 @@ QUnit.test("test pricing rule", function(assert) { () => done() ]); }); - diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index b54d0e73a8c..94abf3b3c06 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -168,7 +168,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True): frappe.throw(_("Invalid {0}").format(args.get(field))) parent_groups = frappe.db.sql_list("""select name from `tab%s` - where lft>=%s and rgt<=%s""" % (parenttype, '%s', '%s'), (lft, rgt)) + where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt)) if parenttype in ["Customer Group", "Item Group", "Territory"]: parent_field = "parent_{0}".format(frappe.scrub(parenttype)) diff --git a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py index 0eac73236e9..5e7583a9745 100644 --- a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py +++ b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py @@ -31,4 +31,4 @@ class ProcessDeferredAccounting(Document): 'against_voucher': self.name }) - make_reverse_gl_entries(gl_entries=gl_entries) \ No newline at end of file + make_reverse_gl_entries(gl_entries=gl_entries) diff --git a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py index e08a0e5cc2b..03c269ac766 100644 --- a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py +++ b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py @@ -45,4 +45,4 @@ class TestProcessDeferredAccounting(unittest.TestCase): ["Sales - _TC", 0.0, 33.85, "2019-01-31"] ] - check_gl_entries(self, si.name, expected_gle, "2019-01-10") \ No newline at end of file + check_gl_entries(self, si.name, expected_gle, "2019-01-10") diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index 7328f168e3d..f8d191cc3f8 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -106,4 +106,4 @@ {{ terms_and_conditions }} {% endif %} - \ No newline at end of file + diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 500952e38ad..a12ea4033df 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -284,4 +284,4 @@ def send_auto_email(): selected = frappe.get_list('Process Statement Of Accounts', filters={'to_date': format_date(today()), 'enable_auto_email': 1}) for entry in selected: send_emails(entry.name, from_scheduler=True) - return True \ No newline at end of file + return True diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.js b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.js index 890a1871bdd..e840c79cd75 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.js +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.js @@ -48,4 +48,4 @@ frappe.ui.form.on('Promotional Scheme', { frm.doc.apply_on === key ? 1 : 0); } } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.json b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.json index cc71324dbc3..1d68b23d6c7 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.json +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.json @@ -1,1381 +1,339 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, + "actions": [], "allow_import": 1, "allow_rename": 1, "autoname": "Prompt", - "beta": 0, "creation": "2019-02-08 17:10:36.077402", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "section_break_1", + "apply_on", + "disable", + "column_break_3", + "items", + "item_groups", + "brands", + "mixed_conditions", + "is_cumulative", + "section_break_10", + "apply_rule_on_other", + "column_break_11", + "other_item_code", + "other_item_group", + "other_brand", + "section_break_8", + "selling", + "buying", + "column_break_12", + "applicable_for", + "customer", + "customer_group", + "territory", + "sales_partner", + "campaign", + "supplier", + "supplier_group", + "period_settings_section", + "valid_from", + "valid_upto", + "column_break_26", + "company", + "currency", + "section_break_14", + "price_discount_slabs", + "section_break_15", + "product_discount_slabs" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "section_break_1", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Item Code", - "fetch_if_empty": 0, "fieldname": "apply_on", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Apply On", - "length": 0, - "no_copy": 0, "options": "\nItem Code\nItem Group\nBrand\nTransaction", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "disable", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Disable", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Disable" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.apply_on == 'Item Code'", - "fetch_if_empty": 0, "fieldname": "items", "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Pricing Rule Item Code", - "length": 0, - "no_copy": 0, - "options": "Pricing Rule Item Code", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Pricing Rule Item Code" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.apply_on == 'Item Group'", - "fetch_if_empty": 0, "fieldname": "item_groups", "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Pricing Rule Item Group", - "length": 0, - "no_copy": 0, - "options": "Pricing Rule Item Group", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Pricing Rule Item Group" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.apply_on == 'Brand'", - "fetch_if_empty": 0, "fieldname": "brands", "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Pricing Rule Brand", - "length": 0, - "no_copy": 0, - "options": "Pricing Rule Brand", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Pricing Rule Brand" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "mixed_conditions", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Mixed Conditions", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Mixed Conditions" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "is_cumulative", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Cumulative", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Is Cumulative" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "section_break_10", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Discount on Other Item", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Discount on Other Item" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "apply_rule_on_other", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Apply Rule On Other", - "length": 0, - "no_copy": 0, - "options": "\nItem Code\nItem Group\nBrand", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "\nItem Code\nItem Group\nBrand" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "column_break_11", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.apply_rule_on_other == 'Item Code'", - "fetch_if_empty": 0, "fieldname": "other_item_code", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Item Code", - "length": 0, - "no_copy": 0, - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Item" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.apply_rule_on_other == 'Item Group'", - "fetch_if_empty": 0, "fieldname": "other_item_group", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Item Group", - "length": 0, - "no_copy": 0, - "options": "Item Group", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Item Group" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.apply_rule_on_other == 'Brand'", - "fetch_if_empty": 0, "fieldname": "other_brand", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Brand", - "length": 0, - "no_copy": 0, - "options": "Brand", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Brand" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "section_break_8", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Party Information", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Party Information" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "selling", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Selling", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Selling" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "buying", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Buying", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Buying" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "column_break_12", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval: doc.buying || doc.selling", - "fetch_if_empty": 0, "fieldname": "applicable_for", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Applicable For", - "length": 0, - "no_copy": 0, - "options": "\nCustomer\nCustomer Group\nTerritory\nSales Partner\nCampaign\nSupplier\nSupplier Group", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "\nCustomer\nCustomer Group\nTerritory\nSales Partner\nCampaign\nSupplier\nSupplier Group" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.applicable_for=='Customer'", - "fetch_if_empty": 0, "fieldname": "customer", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "fieldtype": "Table MultiSelect", "label": "Customer", - "length": 0, - "no_copy": 0, - "options": "Customer", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Customer Item" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.applicable_for==\"Customer Group\"", - "fetch_if_empty": 0, "fieldname": "customer_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "fieldtype": "Table MultiSelect", "label": "Customer Group", - "length": 0, - "no_copy": 0, - "options": "Customer Group", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Customer Group Item" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.applicable_for==\"Territory\"", - "fetch_if_empty": 0, "fieldname": "territory", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "fieldtype": "Table MultiSelect", "label": "Territory", - "length": 0, - "no_copy": 0, - "options": "Territory", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Territory Item" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.applicable_for==\"Sales Partner\"", - "fetch_if_empty": 0, "fieldname": "sales_partner", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "fieldtype": "Table MultiSelect", "label": "Sales Partner", - "length": 0, - "no_copy": 0, - "options": "Sales Partner", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Sales Partner Item" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.applicable_for==\"Campaign\"", - "fetch_if_empty": 0, "fieldname": "campaign", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "fieldtype": "Table MultiSelect", "label": "Campaign", - "length": 0, - "no_copy": 0, - "options": "Campaign", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Campaign Item" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.applicable_for=='Supplier'", - "fetch_if_empty": 0, "fieldname": "supplier", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "fieldtype": "Table MultiSelect", "label": "Supplier", - "length": 0, - "no_copy": 0, - "options": "Supplier", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Supplier Item" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.applicable_for==\"Supplier Group\"", - "fetch_if_empty": 0, "fieldname": "supplier_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "fieldtype": "Table MultiSelect", "label": "Supplier Group", - "length": 0, - "no_copy": 0, - "options": "Supplier Group", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Supplier Group Item" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "period_settings_section", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Period Settings", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Period Settings" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Today", - "fetch_if_empty": 0, "fieldname": "valid_from", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Valid From", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Valid From" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "valid_upto", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Valid Upto", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Valid Upto" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_26", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "company", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Company", - "length": 0, - "no_copy": 0, "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "currency", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Currency", - "length": 0, - "no_copy": 0, - "options": "Currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Currency" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "section_break_14", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Price Discount Slabs", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Price Discount Slabs" }, { "allow_bulk_edit": 1, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "price_discount_slabs", "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Promotional Scheme Price Discount", - "length": 0, - "no_copy": 0, - "options": "Promotional Scheme Price Discount", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Promotional Scheme Price Discount" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "section_break_15", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Product Discount Slabs", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Product Discount Slabs" }, { "allow_bulk_edit": 1, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "product_discount_slabs", "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Promotional Scheme Product Discount", - "length": 0, - "no_copy": 0, - "options": "Promotional Scheme Product Discount", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Promotional Scheme Product Discount" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-03-25 12:14:27.486586", + "links": [], + "modified": "2021-05-06 16:20:22.039078", "modified_by": "Administrator", "module": "Accounts", "name": "Promotional Scheme", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Accounts Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Sales Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Accounts User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index 7d9302382f1..3d7a891f333 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -25,22 +25,31 @@ product_discount_fields = ['free_item', 'free_qty', 'free_item_uom', class PromotionalScheme(Document): def validate(self): + if not self.selling and not self.buying: + frappe.throw(_("Either 'Selling' or 'Buying' must be selected"), title=_("Mandatory")) if not (self.price_discount_slabs or self.product_discount_slabs): frappe.throw(_("Price or product discount slabs are required")) def on_update(self): - data = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name"], - filters = {'promotional_scheme': self.name}) or {} + pricing_rules = frappe.get_all( + 'Pricing Rule', + fields = ["promotional_scheme_id", "name", "creation"], + filters = { + 'promotional_scheme': self.name, + 'applicable_for': self.applicable_for + }, + order_by = 'creation asc', + ) or {} + self.update_pricing_rules(pricing_rules) - self.update_pricing_rules(data) - - def update_pricing_rules(self, data): + def update_pricing_rules(self, pricing_rules): rules = {} count = 0 - - for d in data: - rules[d.get('promotional_scheme_id')] = d.get('name') + names = [] + for rule in pricing_rules: + names.append(rule.name) + rules[rule.get('promotional_scheme_id')] = names docs = get_pricing_rules(self, rules) @@ -57,9 +66,9 @@ class PromotionalScheme(Document): frappe.msgprint(_("New {0} pricing rules are created").format(count)) def on_trash(self): - for d in frappe.get_all('Pricing Rule', + for rule in frappe.get_all('Pricing Rule', {'promotional_scheme': self.name}): - frappe.delete_doc('Pricing Rule', d.name) + frappe.delete_doc('Pricing Rule', rule.name) def get_pricing_rules(doc, rules = {}): new_doc = [] @@ -73,42 +82,80 @@ def get_pricing_rules(doc, rules = {}): def _get_pricing_rules(doc, child_doc, discount_fields, rules = {}): new_doc = [] args = get_args_for_pricing_rule(doc) - for d in doc.get(child_doc): + applicable_for = frappe.scrub(doc.get('applicable_for')) + for idx, d in enumerate(doc.get(child_doc)): if d.name in rules: - pr = frappe.get_doc('Pricing Rule', rules.get(d.name)) + for applicable_for_value in args.get(applicable_for): + temp_args = args.copy() + docname = frappe.get_all( + 'Pricing Rule', + fields = ["promotional_scheme_id", "name", applicable_for], + filters = { + 'promotional_scheme_id': d.name, + applicable_for: applicable_for_value + } + ) + + if docname: + pr = frappe.get_doc('Pricing Rule', docname[0].get('name')) + temp_args[applicable_for] = applicable_for_value + pr = set_args(temp_args, pr, doc, child_doc, discount_fields, d) + else: + pr = frappe.new_doc("Pricing Rule") + pr.title = doc.name + temp_args[applicable_for] = applicable_for_value + pr = set_args(temp_args, pr, doc, child_doc, discount_fields, d) + + new_doc.append(pr) + else: - pr = frappe.new_doc("Pricing Rule") - pr.title = make_autoname("{0}/.####".format(doc.name)) - - pr.update(args) - for field in (other_fields + discount_fields): - pr.set(field, d.get(field)) - - pr.promotional_scheme_id = d.name - pr.promotional_scheme = doc.name - pr.disable = d.disable if d.disable else doc.disable - pr.price_or_product_discount = ('Price' - if child_doc == 'price_discount_slabs' else 'Product') - - for field in ['items', 'item_groups', 'brands']: - if doc.get(field): - pr.set(field, []) - - apply_on = frappe.scrub(doc.get('apply_on')) - for d in doc.get(field): - pr.append(field, { - apply_on: d.get(apply_on), - 'uom': d.uom - }) - - new_doc.append(pr) + applicable_for_values = args.get(applicable_for) or [] + for applicable_for_value in applicable_for_values: + pr = frappe.new_doc("Pricing Rule") + pr.title = doc.name + temp_args = args.copy() + temp_args[applicable_for] = applicable_for_value + pr = set_args(temp_args, pr, doc, child_doc, discount_fields, d) + new_doc.append(pr) return new_doc + + + +def set_args(args, pr, doc, child_doc, discount_fields, child_doc_fields): + pr.update(args) + for field in (other_fields + discount_fields): + pr.set(field, child_doc_fields.get(field)) + + pr.promotional_scheme_id = child_doc_fields.name + pr.promotional_scheme = doc.name + pr.disable = child_doc_fields.disable if child_doc_fields.disable else doc.disable + pr.price_or_product_discount = ('Price' + if child_doc == 'price_discount_slabs' else 'Product') + + for field in ['items', 'item_groups', 'brands']: + if doc.get(field): + pr.set(field, []) + + apply_on = frappe.scrub(doc.get('apply_on')) + for d in doc.get(field): + pr.append(field, { + apply_on: d.get(apply_on), + 'uom': d.uom + }) + return pr + def get_args_for_pricing_rule(doc): args = { 'promotional_scheme': doc.name } + applicable_for = frappe.scrub(doc.get('applicable_for')) for d in pricing_rule_fields: - args[d] = doc.get(d) - + if d == applicable_for: + items = [] + for applicable_for_values in doc.get(applicable_for): + items.append(applicable_for_values.get(applicable_for)) + args[d] = items + else: + args[d] = doc.get(d) return args diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme_dashboard.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme_dashboard.py index 28c4c61b9fa..54fedb77387 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme_dashboard.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Pricing Rule'] } ] - } \ No newline at end of file + } diff --git a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py index 8dc04997796..286f7cf6edd 100644 --- a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py @@ -7,4 +7,54 @@ import frappe import unittest class TestPromotionalScheme(unittest.TestCase): - pass + def test_promotional_scheme(self): + ps = make_promotional_scheme() + price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name", "creation"], + filters = {'promotional_scheme': ps.name}) + self.assertTrue(len(price_rules),1) + price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[0].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1) + self.assertTrue(price_doc_details.customer, '_Test Customer') + self.assertTrue(price_doc_details.min_qty, 4) + self.assertTrue(price_doc_details.discount_percentage, 20) + + ps.price_discount_slabs[0].min_qty = 6 + ps.append('customer', { + 'customer': "_Test Customer 2"}) + ps.save() + price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name"], + filters = {'promotional_scheme': ps.name}) + self.assertTrue(len(price_rules), 2) + + price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[1].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1) + self.assertTrue(price_doc_details.customer, '_Test Customer 2') + self.assertTrue(price_doc_details.min_qty, 6) + self.assertTrue(price_doc_details.discount_percentage, 20) + + price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[0].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1) + self.assertTrue(price_doc_details.customer, '_Test Customer') + self.assertTrue(price_doc_details.min_qty, 6) + + frappe.delete_doc('Promotional Scheme', ps.name) + price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name"], + filters = {'promotional_scheme': ps.name}) + self.assertEqual(price_rules, []) + +def make_promotional_scheme(): + ps = frappe.new_doc('Promotional Scheme') + ps.name = '_Test Scheme' + ps.append('items',{ + 'item_code': '_Test Item' + }) + ps.selling = 1 + ps.append('price_discount_slabs',{ + 'min_qty': 4, + 'discount_percentage': 20, + 'rule_description': 'Test' + }) + ps.applicable_for = 'Customer' + ps.append('customer',{ + 'customer': "_Test Customer" + }) + ps.save() + + return ps diff --git a/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json b/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json index 795fb1c6f46..a70d5c9d430 100644 --- a/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json +++ b/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json @@ -106,7 +106,6 @@ "depends_on": "eval:doc.rate_or_discount==\"Rate\"", "fieldname": "rate", "fieldtype": "Currency", - "in_list_view": 1, "label": "Rate" }, { @@ -170,7 +169,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-03-07 11:56:23.424137", + "modified": "2021-08-19 15:49:29.598727", "modified_by": "Administrator", "module": "Accounts", "name": "Promotional Scheme Price Discount", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index dc9094c3e91..aef9243aad0 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -134,7 +134,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ }, get_query_filters: { docstatus: 1, - status: ["not in", ["Closed", "Completed"]], + status: ["not in", ["Closed", "Completed", "Return Issued"]], company: me.frm.doc.company, is_return: 0 } @@ -283,7 +283,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ party: this.frm.doc.supplier, party_type: "Supplier", account: this.frm.doc.credit_to, - price_list: this.frm.doc.buying_price_list + price_list: this.frm.doc.buying_price_list, + fetch_payment_terms_template: cint(!this.frm.doc.ignore_default_payment_terms_template) }, function() { me.apply_pricing_rule(); me.frm.doc.apply_tds = me.frm.supplier_tds ? 1 : 0; @@ -365,7 +366,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ items_add: function(doc, cdt, cdn) { var row = frappe.get_doc(cdt, cdn); this.frm.script_manager.copy_from_first_row("items", row, - ["expense_account", "cost_center", "project"]); + ["expense_account", "discount_account", "cost_center", "project"]); }, on_submit: function() { @@ -499,6 +500,16 @@ frappe.ui.form.on("Purchase Invoice", { 'Payment Entry': 'Payment' } + frm.set_query("additional_discount_account", function() { + return { + filters: { + company: frm.doc.company, + is_group: 0, + report_type: "Profit and Loss", + } + }; + }); + frm.fields_dict['items'].grid.get_field('deferred_expense_account').get_query = function(doc) { return { filters: { @@ -508,6 +519,16 @@ frappe.ui.form.on("Purchase Invoice", { } } } + + frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) { + return { + filters: { + 'report_type': 'Profit and Loss', + 'company': doc.company, + "is_group": 0 + } + } + } }, refresh: function(frm) { diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 00ef7d5c184..7822f747f64 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -96,6 +96,7 @@ "section_break_44", "apply_discount_on", "base_discount_amount", + "additional_discount_account", "column_break_46", "additional_discount_percentage", "discount_amount", @@ -131,6 +132,7 @@ "advances", "payment_schedule_section", "payment_terms_template", + "ignore_default_payment_terms_template", "payment_schedule", "terms_section_break", "tc_name", @@ -175,7 +177,9 @@ "hidden": 1, "label": "Title", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "naming_series", @@ -187,7 +191,9 @@ "options": "ACC-PINV-.YYYY.-\nACC-PINV-RET-.YYYY.-", "print_hide": 1, "reqd": 1, - "set_only_once": 1 + "set_only_once": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "supplier", @@ -199,7 +205,9 @@ "options": "Supplier", "print_hide": 1, "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -211,7 +219,9 @@ "label": "Supplier Name", "oldfieldname": "supplier_name", "oldfieldtype": "Data", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fetch_from": "supplier.tax_id", @@ -219,21 +229,27 @@ "fieldtype": "Read Only", "label": "Tax Id", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "due_date", "fieldtype": "Date", "label": "Due Date", "oldfieldname": "due_date", - "oldfieldtype": "Date" + "oldfieldtype": "Date", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "is_paid", "fieldtype": "Check", "label": "Is Paid", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -241,19 +257,25 @@ "fieldtype": "Check", "label": "Is Return (Debit Note)", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "apply_tds", "fieldtype": "Check", "label": "Apply Tax Withholding Amount", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break1", "fieldtype": "Column Break", "oldfieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -263,13 +285,17 @@ "label": "Company", "options": "Company", "print_hide": 1, - "remember_last_selected_value": 1 + "remember_last_selected_value": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", - "options": "Cost Center" + "options": "Cost Center", + "show_days": 1, + "show_seconds": 1 }, { "default": "Today", @@ -281,7 +307,9 @@ "oldfieldtype": "Date", "print_hide": 1, "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "posting_time", @@ -290,6 +318,8 @@ "no_copy": 1, "print_hide": 1, "print_width": "100px", + "show_days": 1, + "show_seconds": 1, "width": "100px" }, { @@ -298,7 +328,9 @@ "fieldname": "set_posting_time", "fieldtype": "Check", "label": "Edit Posting Date and Time", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "amended_from", @@ -310,44 +342,58 @@ "oldfieldtype": "Link", "options": "Purchase Invoice", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "collapsible_depends_on": "eval:doc.on_hold", "fieldname": "sb_14", "fieldtype": "Section Break", - "label": "Hold Invoice" + "label": "Hold Invoice", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "on_hold", "fieldtype": "Check", - "label": "Hold Invoice" + "label": "Hold Invoice", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.on_hold", "description": "Once set, this invoice will be on hold till the set date", "fieldname": "release_date", "fieldtype": "Date", - "label": "Release Date" + "label": "Release Date", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cb_17", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.on_hold", "fieldname": "hold_comment", "fieldtype": "Small Text", - "label": "Reason For Putting On Hold" + "label": "Reason For Putting On Hold", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "collapsible_depends_on": "bill_no", "fieldname": "supplier_invoice_details", "fieldtype": "Section Break", - "label": "Supplier Invoice Details" + "label": "Supplier Invoice Details", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "bill_no", @@ -355,11 +401,15 @@ "label": "Supplier Invoice No", "oldfieldname": "bill_no", "oldfieldtype": "Data", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_15", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "bill_date", @@ -368,13 +418,17 @@ "no_copy": 1, "oldfieldname": "bill_date", "oldfieldtype": "Date", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "return_against", "fieldname": "returns", "fieldtype": "Section Break", - "label": "Returns" + "label": "Returns", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "return_against", @@ -384,26 +438,34 @@ "no_copy": 1, "options": "Purchase Invoice", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "section_addresses", "fieldtype": "Section Break", - "label": "Address and Contact" + "label": "Address and Contact", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "supplier_address", "fieldtype": "Link", "label": "Select Supplier Address", "options": "Address", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "address_display", "fieldtype": "Small Text", "label": "Address", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_person", @@ -411,51 +473,67 @@ "in_global_search": 1, "label": "Contact Person", "options": "Contact", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_display", "fieldtype": "Small Text", "label": "Contact", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_mobile", "fieldtype": "Small Text", "label": "Mobile No", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_email", "fieldtype": "Small Text", "label": "Contact Email", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_break_address", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "shipping_address", "fieldtype": "Link", "label": "Select Shipping Address", "options": "Address", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "shipping_address_display", "fieldtype": "Small Text", "label": "Shipping Address", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "currency_and_price_list", "fieldtype": "Section Break", "label": "Currency and Price List", - "options": "fa fa-tag" + "options": "fa fa-tag", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "currency", @@ -464,7 +542,9 @@ "oldfieldname": "currency", "oldfieldtype": "Select", "options": "Currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "conversion_rate", @@ -473,18 +553,24 @@ "oldfieldname": "conversion_rate", "oldfieldtype": "Currency", "precision": "9", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break2", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "buying_price_list", "fieldtype": "Link", "label": "Price List", "options": "Price List", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "price_list_currency", @@ -492,14 +578,18 @@ "label": "Price List Currency", "options": "Currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "plc_conversion_rate", "fieldtype": "Float", "label": "Price List Exchange Rate", "precision": "9", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -508,11 +598,15 @@ "label": "Ignore Pricing Rule", "no_copy": 1, "permlevel": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "sec_warehouse", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "update_stock", @@ -521,7 +615,9 @@ "fieldtype": "Link", "label": "Set Accepted Warehouse", "options": "Warehouse", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "update_stock", @@ -531,11 +627,15 @@ "label": "Rejected Warehouse", "no_copy": 1, "options": "Warehouse", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_break_warehouse", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "No", @@ -543,25 +643,32 @@ "fieldtype": "Select", "label": "Raw Materials Supplied", "options": "No\nYes", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "items_section", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-shopping-cart" + "options": "fa fa-shopping-cart", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "update_stock", "fieldtype": "Check", "label": "Update Stock", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "scan_barcode", "fieldtype": "Data", - "label": "Scan Barcode" + "label": "Scan Barcode", + "options": "Barcode" }, { "allow_bulk_edit": 1, @@ -571,25 +678,33 @@ "oldfieldname": "entries", "oldfieldtype": "Table", "options": "Purchase Invoice Item", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "pricing_rule_details", "fieldtype": "Section Break", - "label": "Pricing Rules" + "label": "Pricing Rules", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "pricing_rules", "fieldtype": "Table", "label": "Pricing Rule Detail", "options": "Pricing Rule Detail", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible_depends_on": "supplied_items", "fieldname": "raw_materials_supplied", "fieldtype": "Section Break", - "label": "Raw Materials Supplied" + "label": "Raw Materials Supplied", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "update_stock", @@ -597,17 +712,23 @@ "fieldtype": "Table", "label": "Supplied Items", "no_copy": 1, - "options": "Purchase Receipt Item Supplied" + "options": "Purchase Receipt Item Supplied", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_26", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_qty", "fieldtype": "Float", "label": "Total Quantity", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_total", @@ -615,7 +736,9 @@ "label": "Total (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_net_total", @@ -625,18 +748,24 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_28", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total", "fieldtype": "Currency", "label": "Total", "options": "currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "net_total", @@ -646,42 +775,56 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_net_weight", "fieldtype": "Float", "label": "Total Net Weight", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes_section", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-money" + "options": "fa fa-money", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "tax_category", "fieldtype": "Link", "label": "Tax Category", "options": "Tax Category", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_49", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "shipping_rule", "fieldtype": "Link", "label": "Shipping Rule", "options": "Shipping Rule", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_51", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes_and_charges", @@ -690,7 +833,9 @@ "oldfieldname": "purchase_other_charges", "oldfieldtype": "Link", "options": "Purchase Taxes and Charges Template", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes", @@ -698,13 +843,17 @@ "label": "Purchase Taxes and Charges", "oldfieldname": "purchase_tax_details", "oldfieldtype": "Table", - "options": "Purchase Taxes and Charges" + "options": "Purchase Taxes and Charges", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "sec_tax_breakup", "fieldtype": "Section Break", - "label": "Tax Breakup" + "label": "Tax Breakup", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "other_charges_calculation", @@ -713,13 +862,17 @@ "no_copy": 1, "oldfieldtype": "HTML", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "totals", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-money" + "options": "fa fa-money", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_taxes_and_charges_added", @@ -729,7 +882,9 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_taxes_and_charges_deducted", @@ -739,7 +894,9 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_total_taxes_and_charges", @@ -749,11 +906,15 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_40", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes_and_charges_added", @@ -763,7 +924,9 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes_and_charges_deducted", @@ -773,7 +936,9 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_taxes_and_charges", @@ -781,14 +946,18 @@ "label": "Total Taxes and Charges", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "collapsible_depends_on": "discount_amount", "fieldname": "section_break_44", "fieldtype": "Section Break", - "label": "Additional Discount" + "label": "Additional Discount", + "show_days": 1, + "show_seconds": 1 }, { "default": "Grand Total", @@ -796,7 +965,9 @@ "fieldtype": "Select", "label": "Apply Additional Discount On", "options": "\nGrand Total\nNet Total", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_discount_amount", @@ -804,28 +975,38 @@ "label": "Additional Discount Amount (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_46", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "additional_discount_percentage", "fieldtype": "Float", "label": "Additional Discount Percentage", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "discount_amount", "fieldtype": "Currency", "label": "Additional Discount Amount", "options": "currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_49", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_grand_total", @@ -835,7 +1016,9 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -845,7 +1028,9 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -855,7 +1040,9 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_in_words", @@ -865,13 +1052,17 @@ "oldfieldname": "in_words", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break8", "fieldtype": "Column Break", "oldfieldtype": "Column Break", "print_hide": 1, + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -882,7 +1073,9 @@ "oldfieldname": "grand_total_import", "oldfieldtype": "Currency", "options": "currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -892,7 +1085,9 @@ "no_copy": 1, "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -902,7 +1097,9 @@ "no_copy": 1, "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "in_words", @@ -912,7 +1109,9 @@ "oldfieldname": "in_words_import", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_advance", @@ -923,7 +1122,9 @@ "oldfieldtype": "Currency", "options": "party_account_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "outstanding_amount", @@ -934,14 +1135,18 @@ "oldfieldtype": "Currency", "options": "party_account_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "depends_on": "grand_total", "fieldname": "disable_rounded_total", "fieldtype": "Check", - "label": "Disable Rounded Total" + "label": "Disable Rounded Total", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -949,20 +1154,26 @@ "depends_on": "eval:doc.is_paid===1||(doc.advances && doc.advances.length>0)", "fieldname": "payments_section", "fieldtype": "Section Break", - "label": "Payments" + "label": "Payments", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "mode_of_payment", "fieldtype": "Link", "label": "Mode of Payment", "options": "Mode of Payment", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cash_bank_account", "fieldtype": "Link", "label": "Cash/Bank Account", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "clearance_date", @@ -970,11 +1181,15 @@ "label": "Clearance Date", "no_copy": 1, "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_br_payments", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "is_paid", @@ -983,7 +1198,9 @@ "label": "Paid Amount", "no_copy": 1, "options": "currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_paid_amount", @@ -992,7 +1209,9 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1000,7 +1219,9 @@ "depends_on": "grand_total", "fieldname": "write_off", "fieldtype": "Section Break", - "label": "Write Off" + "label": "Write Off", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "write_off_amount", @@ -1008,7 +1229,9 @@ "label": "Write Off Amount", "no_copy": 1, "options": "currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_write_off_amount", @@ -1017,11 +1240,15 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_61", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:flt(doc.write_off_amount)!=0", @@ -1029,7 +1256,9 @@ "fieldtype": "Link", "label": "Write Off Account", "options": "Account", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:flt(doc.write_off_amount)!=0", @@ -1037,7 +1266,9 @@ "fieldtype": "Link", "label": "Write Off Cost Center", "options": "Cost Center", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1047,13 +1278,17 @@ "label": "Advance Payments", "oldfieldtype": "Section Break", "options": "fa fa-money", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "allocate_advances_automatically", "fieldtype": "Check", - "label": "Set Advances and Allocate (FIFO)" + "label": "Set Advances and Allocate (FIFO)", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.allocate_advances_automatically", @@ -1061,7 +1296,9 @@ "fieldtype": "Button", "label": "Get Advances Paid", "oldfieldtype": "Button", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "advances", @@ -1071,20 +1308,26 @@ "oldfieldname": "advance_allocation_details", "oldfieldtype": "Table", "options": "Purchase Invoice Advance", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "collapsible_depends_on": "eval:(!doc.is_return)", "fieldname": "payment_schedule_section", "fieldtype": "Section Break", - "label": "Payment Terms" + "label": "Payment Terms", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "payment_terms_template", "fieldtype": "Link", "label": "Payment Terms Template", - "options": "Payment Terms Template" + "options": "Payment Terms Template", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "payment_schedule", @@ -1092,7 +1335,9 @@ "label": "Payment Schedule", "no_copy": 1, "options": "Payment Schedule", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1100,25 +1345,33 @@ "fieldname": "terms_section_break", "fieldtype": "Section Break", "label": "Terms and Conditions", - "options": "fa fa-legal" + "options": "fa fa-legal", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "tc_name", "fieldtype": "Link", "label": "Terms", "options": "Terms and Conditions", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "terms", "fieldtype": "Text Editor", - "label": "Terms and Conditions1" + "label": "Terms and Conditions1", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "printing_settings", "fieldtype": "Section Break", - "label": "Printing Settings" + "label": "Printing Settings", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1126,7 +1379,9 @@ "fieldtype": "Link", "label": "Letter Head", "options": "Letter Head", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1134,11 +1389,15 @@ "fieldname": "group_same_items", "fieldtype": "Check", "label": "Group same items", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_112", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1150,14 +1409,18 @@ "oldfieldtype": "Link", "options": "Print Heading", "print_hide": 1, - "report_hide": 1 + "report_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "language", "fieldtype": "Data", "label": "Print Language", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1166,7 +1429,9 @@ "label": "More Information", "oldfieldtype": "Section Break", "options": "fa fa-file-text", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "credit_to", @@ -1177,7 +1442,9 @@ "options": "Account", "print_hide": 1, "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "party_account_currency", @@ -1187,7 +1454,9 @@ "no_copy": 1, "options": "Currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "No", @@ -1197,7 +1466,9 @@ "oldfieldname": "is_opening", "oldfieldtype": "Select", "options": "No\nYes", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "against_expense_account", @@ -1207,11 +1478,15 @@ "no_copy": 1, "oldfieldname": "against_expense_account", "oldfieldtype": "Small Text", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_63", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "Draft", @@ -1220,7 +1495,9 @@ "in_standard_filter": 1, "label": "Status", "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "inter_company_invoice_reference", @@ -1229,7 +1506,9 @@ "no_copy": 1, "options": "Sales Invoice", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "remarks", @@ -1238,14 +1517,18 @@ "no_copy": 1, "oldfieldname": "remarks", "oldfieldtype": "Text", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "subscription_section", "fieldtype": "Section Break", "label": "Subscription Section", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1254,7 +1537,9 @@ "fieldtype": "Date", "label": "From Date", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1263,11 +1548,15 @@ "fieldtype": "Date", "label": "To Date", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_114", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "auto_repeat", @@ -1276,24 +1565,32 @@ "no_copy": 1, "options": "Auto Repeat", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, "depends_on": "eval: doc.auto_repeat", "fieldname": "update_auto_repeat_reference", "fieldtype": "Button", - "label": "Update Auto Repeat Reference" + "label": "Update Auto Repeat Reference", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", - "label": "Accounting Dimensions " + "label": "Accounting Dimensions ", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "dimension_col_break", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -1301,7 +1598,9 @@ "fieldname": "is_internal_supplier", "fieldtype": "Check", "label": "Is Internal Supplier", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "tax_withholding_category", @@ -1309,25 +1608,33 @@ "hidden": 1, "label": "Tax Withholding Category", "options": "Tax Withholding Category", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "billing_address", "fieldtype": "Link", "label": "Select Billing Address", - "options": "Address" + "options": "Address", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "billing_address_display", "fieldtype": "Small Text", "label": "Billing Address", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "project", "fieldtype": "Link", "label": "Project", - "options": "Project" + "options": "Project", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.is_internal_supplier", @@ -1335,7 +1642,9 @@ "fieldname": "unrealized_profit_loss_account", "fieldtype": "Link", "label": "Unrealized Profit / Loss Account", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.is_internal_supplier", @@ -1344,7 +1653,9 @@ "fieldname": "represents_company", "fieldtype": "Link", "label": "Represents Company", - "options": "Company" + "options": "Company", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.update_stock && doc.is_internal_supplier", @@ -1356,6 +1667,8 @@ "options": "Warehouse", "print_hide": 1, "print_width": "50px", + "show_days": 1, + "show_seconds": 1, "width": "50px" }, { @@ -1367,6 +1680,8 @@ "options": "Warehouse", "print_hide": 1, "print_width": "50px", + "show_days": 1, + "show_seconds": 1, "width": "50px" }, { @@ -1377,13 +1692,29 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "additional_discount_account", + "fieldtype": "Link", + "label": "Additional Discount Account", + "options": "Account" + }, + { + "default": "0", + "fieldname": "ignore_default_payment_terms_template", + "fieldtype": "Check", + "hidden": 1, + "label": "Ignore Default Payment Terms Template", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-06-15 18:20:56.806195", + "modified": "2021-08-17 20:16:12.737743", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index f7992797ed4..5094b1752b4 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -22,7 +22,7 @@ from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accoun from frappe.model.mapper import get_mapped_doc from six import iteritems from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\ - unlink_inter_company_doc + unlink_inter_company_doc, check_if_return_invoice_linked_with_payment_entry from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_item_account_wise_additional_cost @@ -446,6 +446,7 @@ class PurchaseInvoice(BuyingController): self.make_supplier_gl_entry(gl_entries) self.make_item_gl_entries(gl_entries) + self.make_discount_gl_entries(gl_entries) if self.check_asset_cwip_enabled(): self.get_asset_gl_entry(gl_entries) @@ -608,7 +609,7 @@ class PurchaseInvoice(BuyingController): if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account) if not item.is_fixed_asset: - amount = flt(item.base_net_amount, item.precision("base_net_amount")) + dummy, amount = self.get_amount_and_base_amount(item, self.enable_discount_accounting) else: amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount")) @@ -822,8 +823,10 @@ class PurchaseInvoice(BuyingController): def make_tax_gl_entries(self, gl_entries): # tax table gl entries valuation_tax = {} + for tax in self.get("taxes"): - if tax.category in ("Total", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount): + amount, base_amount = self.get_tax_amounts(tax, self.enable_discount_accounting) + if tax.category in ("Total", "Valuation and Total") and flt(base_amount): account_currency = get_account_currency(tax.account_head) dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" @@ -832,21 +835,21 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict({ "account": tax.account_head, "against": self.supplier, - dr_or_cr: tax.base_tax_amount_after_discount_amount, - dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \ - if account_currency==self.company_currency \ - else tax.tax_amount_after_discount_amount, + dr_or_cr: base_amount, + dr_or_cr + "_in_account_currency": base_amount + if account_currency==self.company_currency + else amount, "cost_center": tax.cost_center }, account_currency, item=tax) ) # accumulate valuation tax - if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount) \ + if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(base_amount) \ and not self.is_internal_transfer(): if self.auto_accounting_for_stock and not tax.cost_center: frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category))) valuation_tax.setdefault(tax.name, 0) valuation_tax[tax.name] += \ - (tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount) + (tax.add_deduct_tax == "Add" and 1 or -1) * flt(base_amount) if self.is_opening == "No" and self.negative_expense_to_be_booked and valuation_tax: # credit valuation tax amount in "Expenses Included In Valuation" @@ -887,6 +890,13 @@ class PurchaseInvoice(BuyingController): "remarks": self.remarks or "Accounting Entry for Stock" }, item=tax)) + @property + def enable_discount_accounting(self): + if not hasattr(self, "_enable_discount_accounting"): + self._enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting')) + + return self._enable_discount_accounting + def make_internal_transfer_gl_entries(self, gl_entries): if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges): account_currency = get_account_currency(self.unrealized_profit_loss_account) @@ -982,6 +992,8 @@ class PurchaseInvoice(BuyingController): }, item=self)) def on_cancel(self): + check_if_return_invoice_linked_with_payment_entry(self) + super(PurchaseInvoice, self).on_cancel() self.check_on_hold_or_closed_status() diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_dashboard.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_dashboard.py index 173939df008..b6467a3d5ca 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_dashboard.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_dashboard.py @@ -34,4 +34,4 @@ def get_data(): 'items': ['Auto Repeat'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js index 914a2457d45..771b49ac629 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js @@ -26,4 +26,4 @@ frappe.listview_settings['Purchase Invoice'] = { return [__("Paid"), "green", "outstanding_amount,=,0"]; } } -}; \ No newline at end of file +}; diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.js index b470051b51d..94b3b9ed338 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.js @@ -72,4 +72,3 @@ QUnit.test("test purchase invoice", function(assert) { () => done() ]); }); - diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index c9384be6eb3..bfe34bb4a73 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -230,6 +230,50 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(expected_values[gle.account][1], gle.debit) self.assertEqual(expected_values[gle.account][2], gle.credit) + def test_purchase_invoice_with_discount_accounting_enabled(self): + enable_discount_accounting() + + discount_account = create_account(account_name="Discount Account", + parent_account="Indirect Expenses - _TC", company="_Test Company") + pi = make_purchase_invoice(discount_account=discount_account, rate=45) + + expected_gle = [ + ["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()], + ["Creditors - _TC", 0.0, 225.0, nowdate()], + ["Discount Account - _TC", 0.0, 25.0, nowdate()] + ] + + check_gl_entries(self, pi.name, expected_gle, nowdate()) + enable_discount_accounting(enable=0) + + def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self): + enable_discount_accounting() + additional_discount_account = create_account(account_name="Discount Account", + parent_account="Indirect Expenses - _TC", company="_Test Company") + + pi = make_purchase_invoice(do_not_save=1, parent_cost_center="Main - _TC") + pi.apply_discount_on = "Grand Total" + pi.additional_discount_account = additional_discount_account + pi.additional_discount_percentage = 10 + pi.disable_rounded_total = 1 + pi.append("taxes", { + "charge_type": "On Net Total", + "account_head": "_Test Account VAT - _TC", + "cost_center": "Main - _TC", + "description": "Test", + "rate": 10 + }) + pi.submit() + + expected_gle = [ + ["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()], + ["_Test Account VAT - _TC", 25.0, 0.0, nowdate()], + ["Creditors - _TC", 0.0, 247.5, nowdate()], + ["Discount Account - _TC", 0.0, 27.5, nowdate()] + ] + + check_gl_entries(self, pi.name, expected_gle, nowdate()) + def test_purchase_invoice_change_naming_series(self): pi = frappe.copy_doc(test_records[1]) pi.insert() @@ -954,8 +998,17 @@ class TestPurchaseInvoice(unittest.TestCase): acc_settings.save() def test_gain_loss_with_advance_entry(self): - unlink_enabled = frappe.db.get_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice") - frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1) + unlink_enabled = frappe.db.get_value( + "Accounts Settings", "Accounts Settings", + "unlink_payment_on_cancel_of_invoice") + + frappe.db.set_value( + "Accounts Settings", "Accounts Settings", + "unlink_payment_on_cancel_of_invoice", 1) + + original_account = frappe.db.get_value("Company", "_Test Company", "exchange_gain_loss_account") + frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", "Exchange Gain/Loss - _TC") + pay = frappe.get_doc({ 'doctype': 'Payment Entry', 'company': '_Test Company', @@ -988,15 +1041,16 @@ class TestPurchaseInvoice(unittest.TestCase): expected_gle = [ ["_Test Account Cost for Goods Sold - _TC", 37500.0], - ["_Test Payable USD - _TC", -40000.0], - ["Exchange Gain/Loss - _TC", 2500.0] + ["_Test Payable USD - _TC", -35000.0], + ["Exchange Gain/Loss - _TC", -2500.0] ] gl_entries = frappe.db.sql(""" select account, sum(debit - credit) as balance from `tabGL Entry` where voucher_no=%s - group by account order by account asc""", (pi.name), as_dict=1) - + group by account + order by account asc""", (pi.name), as_dict=1) + for i, gle in enumerate(gl_entries): self.assertEqual(expected_gle[i][0], gle.account) self.assertEqual(expected_gle[i][1], gle.balance) @@ -1018,8 +1072,8 @@ class TestPurchaseInvoice(unittest.TestCase): expected_gle = [ ["_Test Account Cost for Goods Sold - _TC", 36500.0], - ["_Test Payable USD - _TC", -38000.0], - ["Exchange Gain/Loss - _TC", 1500.0] + ["_Test Payable USD - _TC", -35000.0], + ["Exchange Gain/Loss - _TC", -1500.0] ] gl_entries = frappe.db.sql(""" @@ -1055,6 +1109,7 @@ class TestPurchaseInvoice(unittest.TestCase): pay.cancel() frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled) + frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account) def test_purchase_invoice_advance_taxes(self): from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order @@ -1129,6 +1184,18 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(expected_gle[i][0], gle.account) self.assertEqual(expected_gle[i][1], gle.amount) +def check_gl_entries(doc, voucher_no, expected_gle, posting_date): + gl_entries = frappe.db.sql("""select account, debit, credit, posting_date + from `tabGL Entry` + where voucher_type='Purchase Invoice' and voucher_no=%s and posting_date >= %s + order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1) + + for i, gle in enumerate(gl_entries): + doc.assertEqual(expected_gle[i][0], gle.account) + doc.assertEqual(expected_gle[i][1], gle.debit) + doc.assertEqual(expected_gle[i][2], gle.credit) + doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) + def update_tax_witholding_category(company, account, date): from erpnext.accounts.utils import get_fiscal_year @@ -1159,6 +1226,11 @@ def unlink_payment_on_cancel_of_invoice(enable=1): accounts_settings.unlink_payment_on_cancellation_of_invoice = enable accounts_settings.save() +def enable_discount_accounting(enable=1): + accounts_settings = frappe.get_doc("Accounts Settings") + accounts_settings.enable_discount_accounting = enable + accounts_settings.save() + def make_purchase_invoice(**args): pi = frappe.new_doc("Purchase Invoice") args = frappe._dict(args) @@ -1181,6 +1253,7 @@ def make_purchase_invoice(**args): pi.return_against = args.return_against pi.is_subcontracted = args.is_subcontracted or "No" pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC" + pi.cost_center = args.parent_cost_center pi.append("items", { "item_code": args.item or args.item_code or "_Test Item", @@ -1189,7 +1262,10 @@ def make_purchase_invoice(**args): "received_qty": args.received_qty or 0, "rejected_qty": args.rejected_qty or 0, "rate": args.rate or 50, - 'expense_account': args.expense_account or '_Test Account Cost for Goods Sold - _TC', + "price_list_rate": args.price_list_rate or 50, + "expense_account": args.expense_account or '_Test Account Cost for Goods Sold - _TC', + "discount_account": args.discount_account or None, + "discount_amount": args.discount_amount or 0, "conversion_factor": 1.0, "serial_no": args.serial_no, "stock_uom": args.uom or "_Test UOM", diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py index bfaa849200e..d157837a7a5 100644 --- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py +++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class PurchaseInvoiceAdvance(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 8a55ff87e39..51e5c1a6a9a 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -73,6 +73,7 @@ "manufacturer_part_no", "accounting", "expense_account", + "discount_account", "col_break5", "is_fixed_asset", "asset_location", @@ -501,6 +502,7 @@ }, { "collapsible": 1, + "collapsible_depends_on": "enable_deferred_expense", "fieldname": "deferred_expense_section", "fieldtype": "Section Break", "label": "Deferred Expense" @@ -849,12 +851,18 @@ "options": "Company:company:default_currency", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "discount_account", + "fieldtype": "Link", + "label": "Discount Account", + "options": "Account" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-06-16 19:57:03.101571", + "modified": "2021-08-12 20:14:45.506639", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json index 1fa68e0a8a8..d86abade924 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json @@ -22,7 +22,7 @@ "cost_center", "dimension_col_break", "section_break_9", - "currency", + "account_currency", "tax_amount", "tax_amount_after_discount_amount", "total", @@ -208,14 +208,6 @@ "fieldname": "dimension_col_break", "fieldtype": "Column Break" }, - { - "fetch_from": "account_head.account_currency", - "fieldname": "currency", - "fieldtype": "Link", - "label": "Account Currency", - "options": "Currency", - "read_only": 1 - }, { "default": "0", "depends_on": "eval:['Purchase Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)", @@ -223,12 +215,20 @@ "fieldname": "included_in_paid_amount", "fieldtype": "Check", "label": "Considered In Paid Amount" + }, + { + "fetch_from": "account_head.account_currency", + "fieldname": "account_currency", + "fieldtype": "Link", + "label": "Account Currency", + "options": "Currency", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-06-14 01:43:50.750455", + "modified": "2021-08-05 20:04:36.618240", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Taxes and Charges", diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py index a7489da316f..5854ddee940 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class PurchaseTaxesandCharges(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template_dashboard.py b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template_dashboard.py index 11c220bf2db..db9793d77a6 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template_dashboard.py +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template_dashboard.py @@ -19,4 +19,4 @@ def get_data(): 'items': ['Supplier Quotation', 'Tax Rule'] } ] - } \ No newline at end of file + } diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.js b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.js index c73f03b57bb..10b05d0594f 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.js +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.js @@ -26,4 +26,3 @@ QUnit.test("test sales taxes and charges template", function(assert) { () => done() ]); }); - diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js index ada665a0ca9..f01325d80bd 100644 --- a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js +++ b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js @@ -67,7 +67,7 @@ frappe.listview_settings['Sales Invoice'].onload = function (list_view) { "default": "1-Duplicate", "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] }, - { + { "label": "Remark", "fieldname": "remark", "fieldtype": "Data", @@ -82,7 +82,7 @@ frappe.listview_settings['Sales Invoice'].onload = function (list_view) { const data = d.get_values(); frappe.call({ method: 'erpnext.regional.india.e_invoice.utils.cancel_irns', - args: { + args: { doctype: list_view.doctype, docnames, reason: data.reason.split('-')[0], @@ -122,7 +122,7 @@ frappe.listview_settings['Sales Invoice'].onload = function (list_view) { frappe.realtime.on("bulk_einvoice_generation_complete", (data) => { const { failures, user, invoices } = data; - + if (invoices.length != failures.length) { frappe.msgprint({ message: __('{0} e-invoices generated successfully', [invoices.length]), @@ -171,4 +171,4 @@ frappe.listview_settings['Sales Invoice'].onload = function (list_view) { }); } }); -}; \ No newline at end of file +}; diff --git a/erpnext/accounts/doctype/sales_invoice/regional/italy.js b/erpnext/accounts/doctype/sales_invoice/regional/italy.js index 1c47d3ab9fd..21eb8ce6619 100644 --- a/erpnext/accounts/doctype/sales_invoice/regional/italy.js +++ b/erpnext/accounts/doctype/sales_invoice/regional/italy.js @@ -1,3 +1,3 @@ {% include "erpnext/regional/italy/sales_invoice.js" %} -erpnext.setup_e_invoice_button('Sales Invoice') \ No newline at end of file +erpnext.setup_e_invoice_button('Sales Invoice') diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index f813425e6b5..568e7721a39 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -347,7 +347,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte items_add: function(doc, cdt, cdn) { var row = frappe.get_doc(cdt, cdn); - this.frm.script_manager.copy_from_first_row("items", row, ["income_account", "cost_center"]); + this.frm.script_manager.copy_from_first_row("items", row, ["income_account", "discount_account", "cost_center"]); }, set_dynamic_labels: function() { @@ -447,6 +447,15 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte this.frm.refresh_field("outstanding_amount"); this.frm.refresh_field("paid_amount"); this.frm.refresh_field("base_paid_amount"); + }, + + currency() { + this._super(); + $.each(cur_frm.doc.timesheets, function(i, d) { + let row = frappe.get_doc(d.doctype, d.name) + set_timesheet_detail_rate(row.doctype, row.name, cur_frm.doc.currency, row.timesheet_detail) + }); + calculate_total_billing_amount(cur_frm) } }); @@ -510,7 +519,6 @@ cur_frm.set_query("income_account", "items", function(doc) { } }); - // Cost Center in Details Table // ----------------------------- cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function(doc) { @@ -592,6 +600,16 @@ frappe.ui.form.on('Sales Invoice', { }; }); + frm.set_query("additional_discount_account", function() { + return { + filters: { + company: frm.doc.company, + is_group: 0, + report_type: "Profit and Loss", + } + }; + }); + frm.custom_make_buttons = { 'Delivery Note': 'Delivery', 'Sales Invoice': 'Return / Credit Note', @@ -618,6 +636,17 @@ frappe.ui.form.on('Sales Invoice', { } } + // discount account + frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) { + return { + filters: { + 'report_type': 'Profit and Loss', + 'company': doc.company, + "is_group": 0 + } + } + } + frm.fields_dict['items'].grid.get_field('deferred_revenue_account').get_query = function(doc) { return { filters: { @@ -826,7 +855,8 @@ frappe.ui.form.on('Sales Invoice', { 'time_sheet': row.parent, 'billing_hours': row.billing_hours, 'billing_amount': flt(row.billing_amount) * flt(exchange_rate), - 'timesheet_detail': row.name + 'timesheet_detail': row.name, + 'project_name': row.project_name }); frm.refresh_field('timesheets'); calculate_total_billing_amount(frm); @@ -945,43 +975,34 @@ frappe.ui.form.on('Sales Invoice', { } }) -frappe.ui.form.on('Sales Invoice Timesheet', { - time_sheet: function(frm, cdt, cdn){ - var d = locals[cdt][cdn]; - if(d.time_sheet) { - frappe.call({ - method: "erpnext.projects.doctype.timesheet.timesheet.get_timesheet_data", - args: { - 'name': d.time_sheet, - 'project': frm.doc.project || null - }, - callback: function(r, rt) { - if(r.message){ - let data = r.message; - frappe.model.set_value(cdt, cdn, "billing_hours", data.billing_hours); - frappe.model.set_value(cdt, cdn, "billing_amount", data.billing_amount); - frappe.model.set_value(cdt, cdn, "timesheet_detail", data.timesheet_detail); - calculate_total_billing_amount(frm) - } - } - }) - } - } -}) - var calculate_total_billing_amount = function(frm) { var doc = frm.doc; doc.total_billing_amount = 0.0 - if(doc.timesheets) { + if (doc.timesheets) { $.each(doc.timesheets, function(index, data){ - doc.total_billing_amount += data.billing_amount + doc.total_billing_amount += flt(data.billing_amount) }) } refresh_field('total_billing_amount') } +var set_timesheet_detail_rate = function(cdt, cdn, currency, timelog) { + frappe.call({ + method: "erpnext.projects.doctype.timesheet.timesheet.get_timesheet_detail_rate", + args: { + timelog: timelog, + currency: currency + }, + callback: function(r) { + if (!r.exc && r.message) { + frappe.model.set_value(cdt, cdn, 'billing_amount', r.message); + } + } + }); +} + var select_loyalty_program = function(frm, loyalty_programs) { var dialog = new frappe.ui.Dialog({ title: __("Select Loyalty Program"), diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index e7dd6b8a606..4c7a6b51ac7 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -48,6 +48,8 @@ "shipping_address", "company_address", "company_address_display", + "dispatch_address_name", + "dispatch_address", "currency_and_price_list", "currency", "conversion_rate", @@ -104,6 +106,7 @@ "section_break_49", "apply_discount_on", "base_discount_amount", + "additional_discount_account", "column_break_51", "additional_discount_percentage", "discount_amount", @@ -125,6 +128,7 @@ "get_advances", "advances", "payment_schedule_section", + "ignore_default_payment_terms_template", "payment_terms_template", "payment_schedule", "payments_section", @@ -688,6 +692,7 @@ { "fieldname": "scan_barcode", "fieldtype": "Data", + "options": "Barcode", "hide_days": 1, "hide_seconds": 1, "label": "Scan Barcode" @@ -1930,6 +1935,7 @@ "description": "Unrealized Profit / Loss account for intra-company transfers", "fieldname": "unrealized_profit_loss_account", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Unrealized Profit / Loss Account", "options": "Account" }, @@ -1951,6 +1957,7 @@ "depends_on": "eval: doc.is_internal_customer && doc.update_stock", "fieldname": "set_target_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Set Target Warehouse", "options": "Warehouse" }, @@ -1966,6 +1973,37 @@ "fieldname": "disable_rounded_total", "fieldtype": "Check", "label": "Disable Rounded Total" + }, + { + "fieldname": "additional_discount_account", + "fieldtype": "Link", + "label": "Additional Discount Account", + "options": "Account" + }, + { + "default": "0", + "fieldname": "ignore_default_payment_terms_template", + "fieldtype": "Check", + "hidden": 1, + "label": "Ignore Default Payment Terms Template", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "dispatch_address_name", + "fieldtype": "Link", + "label": "Dispatch Address Name", + "options": "Address", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "dispatch_address", + "fieldtype": "Small Text", + "label": "Dispatch Address", + "read_only": 1 } ], "icon": "fa fa-file-text", @@ -1978,7 +2016,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-05-20 22:48:33.988881", + "modified": "2021-08-17 20:16:12.737743", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 6d1f6249c13..6e643db8d72 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -290,6 +290,8 @@ class SalesInvoice(SellingController): self.update_time_sheet(None) def on_cancel(self): + check_if_return_invoice_linked_with_payment_entry(self) + super(SalesInvoice, self).on_cancel() self.check_sales_order_on_hold_or_close("sales_order") @@ -476,11 +478,14 @@ class SalesInvoice(SellingController): if cint(self.is_pos) != 1: return + if not self.account_for_change_amount: + self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account') + from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile if not self.pos_profile: pos_profile = get_pos_profile(self.company) or {} if not pos_profile: - frappe.throw(_("No POS Profile found. Please create a New POS Profile first")) + return self.pos_profile = pos_profile.get('name') pos = {} @@ -490,9 +495,6 @@ class SalesInvoice(SellingController): if not self.get('payments') and not for_validate: update_multi_mode_option(self, pos) - if not self.account_for_change_amount: - self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account') - if pos: if not for_validate: self.tax_category = pos.get("tax_category") @@ -846,6 +848,7 @@ class SalesInvoice(SellingController): self.allocate_advance_taxes(gl_entries) self.make_item_gl_entries(gl_entries) + self.make_discount_gl_entries(gl_entries) # merge gl entries before adding pos entries gl_entries = merge_similar_entries(gl_entries) @@ -886,17 +889,19 @@ class SalesInvoice(SellingController): def make_tax_gl_entries(self, gl_entries): for tax in self.get("taxes"): + amount, base_amount = self.get_tax_amounts(tax, self.enable_discount_accounting) + if flt(tax.base_tax_amount_after_discount_amount): account_currency = get_account_currency(tax.account_head) gl_entries.append( self.get_gl_dict({ "account": tax.account_head, "against": self.customer, - "credit": flt(tax.base_tax_amount_after_discount_amount, + "credit": flt(base_amount, tax.precision("tax_amount_after_discount_amount")), - "credit_in_account_currency": (flt(tax.base_tax_amount_after_discount_amount, + "credit_in_account_currency": (flt(base_amount, tax.precision("base_tax_amount_after_discount_amount")) if account_currency==self.company_currency else - flt(tax.tax_amount_after_discount_amount, tax.precision("tax_amount_after_discount_amount"))), + flt(amount, tax.precision("tax_amount_after_discount_amount"))), "cost_center": tax.cost_center }, account_currency, item=tax) ) @@ -940,15 +945,17 @@ class SalesInvoice(SellingController): income_account = (item.income_account if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account) + amount, base_amount = self.get_amount_and_base_amount(item, self.enable_discount_accounting) + account_currency = get_account_currency(income_account) gl_entries.append( self.get_gl_dict({ "account": income_account, "against": self.customer, - "credit": flt(item.base_net_amount, item.precision("base_net_amount")), - "credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount")) + "credit": flt(base_amount, item.precision("base_net_amount")), + "credit_in_account_currency": (flt(base_amount, item.precision("base_net_amount")) if account_currency==self.company_currency - else flt(item.net_amount, item.precision("net_amount"))), + else flt(amount, item.precision("net_amount"))), "cost_center": item.cost_center, "project": item.project or self.project }, account_currency, item=item) @@ -959,6 +966,19 @@ class SalesInvoice(SellingController): erpnext.is_perpetual_inventory_enabled(self.company): gl_entries += super(SalesInvoice, self).get_gl_entries() + @property + def enable_discount_accounting(self): + if not hasattr(self, "_enable_discount_accounting"): + self._enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting')) + + return self._enable_discount_accounting + + def set_asset_status(self, asset): + if self.is_return: + asset.set_status() + else: + asset.set_status("Sold" if self.docstatus==1 else None) + def make_loyalty_point_redemption_gle(self, gl_entries): if cint(self.redeem_loyalty_points): gl_entries.append( @@ -1348,7 +1368,7 @@ class SalesInvoice(SellingController): discounting_status = None if self.is_discounted: - discountng_status = get_discounting_status(self.name) + discounting_status = get_discounting_status(self.name) if not status: if self.docstatus == 2: @@ -1356,11 +1376,11 @@ class SalesInvoice(SellingController): elif self.docstatus == 1: if self.is_internal_transfer(): self.status = 'Internal Transfer' - elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed': + elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discounting_status=='Disbursed': self.status = "Overdue and Discounted" elif outstanding_amount > 0 and due_date < nowdate: self.status = "Overdue" - elif outstanding_amount > 0 and due_date >= nowdate and self.is_discounted and discountng_status=='Disbursed': + elif outstanding_amount > 0 and due_date >= nowdate and self.is_discounted and discounting_status=='Disbursed': self.status = "Unpaid and Discounted" elif outstanding_amount > 0 and due_date >= nowdate: self.status = "Unpaid" @@ -1924,3 +1944,41 @@ def create_dunning(source_name, target_doc=None): } }, target_doc, set_missing_values) return doclist + +def check_if_return_invoice_linked_with_payment_entry(self): + # If a Return invoice is linked with payment entry along with other invoices, + # the cancellation of the Return causes allocated amount to be greater than paid + + if not frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'): + return + + payment_entries = [] + if self.is_return and self.return_against: + invoice = self.return_against + else: + invoice = self.name + + payment_entries = frappe.db.sql_list(""" + SELECT + t1.name + FROM + `tabPayment Entry` t1, `tabPayment Entry Reference` t2 + WHERE + t1.name = t2.parent + and t1.docstatus = 1 + and t2.reference_name = %s + and t2.allocated_amount < 0 + """, invoice) + + links_to_pe = [] + if payment_entries: + for payment in payment_entries: + payment_entry = frappe.get_doc("Payment Entry", payment) + if len(payment_entry.references) > 1: + links_to_pe.append(payment_entry.name) + if links_to_pe: + payment_entries_link = [get_link_to_form('Payment Entry', name, label=name) for name in links_to_pe] + message = _("Please cancel and amend the Payment Entry") + message += " " + ", ".join(payment_entries_link) + " " + message += _("to unallocate the amount of this Return Invoice before cancelling it.") + frappe.throw(message) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py index f1069282edc..3238ead4316 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py @@ -33,4 +33,4 @@ def get_data(): 'items': ['Auto Repeat'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index dbc7f8632fc..5bbde09c1f7 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -23,6 +23,7 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice from erpnext.stock.utils import get_incoming_rate +from erpnext.accounts.utils import PaymentEntryUnlinkError class TestSalesInvoice(unittest.TestCase): def make(self): @@ -133,7 +134,7 @@ class TestSalesInvoice(unittest.TestCase): pe.paid_to_account_currency = si.currency pe.source_exchange_rate = 1 pe.target_exchange_rate = 1 - pe.paid_amount = si.grand_total + pe.paid_amount = si.outstanding_amount pe.insert() pe.submit() @@ -142,6 +143,42 @@ class TestSalesInvoice(unittest.TestCase): self.assertRaises(frappe.LinkExistsError, si.cancel) unlink_payment_on_cancel_of_invoice() + def test_payment_entry_unlink_against_standalone_credit_note(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + si1 = create_sales_invoice(rate=1000) + si2 = create_sales_invoice(rate=300) + si3 = create_sales_invoice(qty=-1, rate=300, is_return=1) + + + pe = get_payment_entry("Sales Invoice", si1.name, bank_account="_Test Bank - _TC") + pe.append('references', { + 'reference_doctype': 'Sales Invoice', + 'reference_name': si2.name, + 'total_amount': si2.grand_total, + 'outstanding_amount': si2.outstanding_amount, + 'allocated_amount': si2.outstanding_amount + }) + + pe.append('references', { + 'reference_doctype': 'Sales Invoice', + 'reference_name': si3.name, + 'total_amount': si3.grand_total, + 'outstanding_amount': si3.outstanding_amount, + 'allocated_amount': si3.outstanding_amount + }) + + pe.reference_no = 'Test001' + pe.reference_date = nowdate() + pe.save() + pe.submit() + + si2.load_from_db() + si2.cancel() + + si1.load_from_db() + self.assertRaises(PaymentEntryUnlinkError, si1.cancel) + + def test_sales_invoice_calculation_export_currency(self): si = frappe.copy_doc(test_records[2]) si.currency = "USD" @@ -1898,7 +1935,7 @@ class TestSalesInvoice(unittest.TestCase): data = get_ewb_data("Sales Invoice", [si.name]) - self.assertEqual(data['version'], '1.0.1118') + self.assertEqual(data['version'], '1.0.0421') self.assertEqual(data['billLists'][0]['fromGstin'], '27AAECE4835E1ZR') self.assertEqual(data['billLists'][0]['fromTrdName'], '_Test Company') self.assertEqual(data['billLists'][0]['toTrdName'], '_Test Customer') @@ -1908,6 +1945,8 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(data['billLists'][0]['sgstValue'], 5400) self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234') self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000) + self.assertEqual(data['billLists'][0]['actualFromStateCode'],7) + self.assertEqual(data['billLists'][0]['fromStateCode'],27) def test_einvoice_submission_without_irn(self): # init @@ -1984,6 +2023,54 @@ class TestSalesInvoice(unittest.TestCase): sales_invoice.save() self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC") + def test_sales_invoice_with_discount_accounting_enabled(self): + from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import enable_discount_accounting + + enable_discount_accounting() + + discount_account = create_account(account_name="Discount Account", + parent_account="Indirect Expenses - _TC", company="_Test Company") + si = create_sales_invoice(discount_account=discount_account, discount_percentage=10, rate=90) + + expected_gle = [ + ["Debtors - _TC", 90.0, 0.0, nowdate()], + ["Discount Account - _TC", 10.0, 0.0, nowdate()], + ["Sales - _TC", 0.0, 100.0, nowdate()] + ] + + check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1)) + enable_discount_accounting(enable=0) + + def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self): + from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import enable_discount_accounting + + enable_discount_accounting() + additional_discount_account = create_account(account_name="Discount Account", + parent_account="Indirect Expenses - _TC", company="_Test Company") + + si = create_sales_invoice(parent_cost_center='Main - _TC', do_not_save=1) + si.apply_discount_on = "Grand Total" + si.additional_discount_account = additional_discount_account + si.additional_discount_percentage = 20 + si.append("taxes", { + "charge_type": "On Net Total", + "account_head": "_Test Account VAT - _TC", + "cost_center": "Main - _TC", + "description": "Test", + "rate": 10 + }) + si.submit() + + expected_gle = [ + ["_Test Account VAT - _TC", 0.0, 10.0, nowdate()], + ["Debtors - _TC", 88, 0.0, nowdate()], + ["Discount Account - _TC", 22.0, 0.0, nowdate()], + ["Sales - _TC", 0.0, 100.0, nowdate()] + ] + + check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1)) + enable_discount_accounting(enable=0) + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() si.naming_series = 'INV-2020-.#####' @@ -2062,6 +2149,30 @@ def make_test_address_for_ewaybill(): address.save() + if not frappe.db.exists('Address', '_Test Dispatch-Address for Eway bill-Shipping'): + address = frappe.get_doc({ + "address_line1": "_Test Dispatch Address Line 1", + "address_title": "_Test Dispatch-Address for Eway bill", + "address_type": "Shipping", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 0, + "phone": "+910000000000", + "gstin": "07AAACC1206D1ZI", + "gst_state": "Delhi", + "gst_state_number": "07", + "pincode": "1100101" + }).insert() + + address.append("links", { + "link_doctype": "Company", + "link_name": "_Test Company" + }) + + address.save() + def make_test_transporter_for_ewaybill(): if not frappe.db.exists('Supplier', '_Test Transporter'): frappe.get_doc({ @@ -2100,6 +2211,7 @@ def make_sales_invoice_for_ewaybill(): si.distance = 2000 si.company_address = "_Test Address for Eway bill-Billing" si.customer_address = "_Test Customer-Address for Eway bill-Shipping" + si.dispatch_address_name = "_Test Dispatch-Address for Eway bill-Shipping" si.vehicle_no = "KA12KA1234" si.gst_category = "Registered Regular" si.mode_of_transport = 'Road' @@ -2152,6 +2264,7 @@ def create_sales_invoice(**args): si.currency=args.currency or "INR" si.conversion_rate = args.conversion_rate or 1 si.naming_series = args.naming_series or "T-SINV-" + si.cost_center = args.parent_cost_center si.append("items", { "item_code": args.item or args.item_code or "_Test Item", @@ -2163,8 +2276,11 @@ def create_sales_invoice(**args): "uom": args.uom or "Nos", "stock_uom": args.uom or "Nos", "rate": args.rate if args.get("rate") is not None else 100, + "price_list_rate": args.price_list_rate if args.get("price_list_rate") is not None else 100, "income_account": args.income_account or "Sales - _TC", "expense_account": args.expense_account or "Cost of Goods Sold - _TC", + "discount_account": args.discount_account or None, + "discount_amount": args.discount_amount or 0, "cost_center": args.cost_center or "_Test Cost Center - _TC", "serial_no": args.serial_no, "conversion_factor": 1 diff --git a/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice.js index e12ac038500..61d78e1fe4b 100644 --- a/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice.js @@ -40,4 +40,3 @@ QUnit.test("test sales Invoice", function(assert) { () => done() ]); }); - diff --git a/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_margin.js b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_margin.js index f1cb22a4976..cf2d0fbedba 100644 --- a/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_margin.js +++ b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_margin.js @@ -33,4 +33,3 @@ QUnit.test("test sales invoice with margin", function(assert) { () => done() ]); }); - diff --git a/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment.js b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment.js index 651bf0aa4ce..45d9a14bffb 100644 --- a/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment.js +++ b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment.js @@ -54,4 +54,3 @@ QUnit.test("test sales Invoice with payment", function(assert) { () => done() ]); }); - diff --git a/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment_request.js b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment_request.js index b959cf961b8..0464e4509f6 100644 --- a/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment_request.js +++ b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment_request.js @@ -49,4 +49,3 @@ QUnit.test("test sales Invoice with payment request", function(assert) { () => done() ]); }); - diff --git a/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_serialize_item.js b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_serialize_item.js index 2697758d7a3..af484d7899c 100644 --- a/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_serialize_item.js +++ b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_serialize_item.js @@ -42,4 +42,3 @@ QUnit.test("test sales Invoice with serialize item", function(assert) { () => done() ]); }); - diff --git a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.py b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.py index 1ec517929ef..28aeef4d5e1 100644 --- a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.py +++ b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class SalesInvoiceAdvance(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index 8e6952a93c4..eede3268365 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -63,6 +63,7 @@ "finance_book", "col_break4", "expense_account", + "discount_account", "deferred_revenue", "deferred_revenue_account", "service_stop_date", @@ -473,6 +474,7 @@ }, { "collapsible": 1, + "collapsible_depends_on": "enable_deferred_revenue", "fieldname": "deferred_revenue", "fieldtype": "Section Break", "label": "Deferred Revenue" @@ -821,12 +823,18 @@ "no_copy": 1, "options": "currency", "read_only": 1 + }, + { + "fieldname": "discount_account", + "fieldtype": "Link", + "label": "Discount Account", + "options": "Account" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-02-23 01:05:22.123527", + "modified": "2021-08-12 20:15:42.668399", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json index f069e8dd0b8..c90297328ee 100644 --- a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json +++ b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json @@ -9,7 +9,9 @@ "description", "billing_hours", "billing_amount", + "column_break_5", "time_sheet", + "project_name", "timesheet_detail" ], "fields": [ @@ -61,11 +63,21 @@ "in_list_view": 1, "label": "Description", "read_only": 1 + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "fieldname": "project_name", + "fieldtype": "Data", + "label": "Project Name", + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2021-05-20 22:33:57.234846", + "modified": "2021-06-08 14:43:02.748981", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Timesheet", diff --git a/erpnext/accounts/doctype/sales_partner_item/__init__.py b/erpnext/accounts/doctype/sales_partner_item/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/sales_partner_item/sales_partner_item.json b/erpnext/accounts/doctype/sales_partner_item/sales_partner_item.json new file mode 100644 index 00000000000..c176e4d173d --- /dev/null +++ b/erpnext/accounts/doctype/sales_partner_item/sales_partner_item.json @@ -0,0 +1,31 @@ +{ + "actions": [], + "creation": "2021-05-06 16:17:44.329943", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "sales_partner" + ], + "fields": [ + { + "fieldname": "sales_partner", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Sales Partner ", + "options": "Sales Partner" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-05-07 10:43:37.532095", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Sales Partner Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_partner_item/sales_partner_item.py b/erpnext/accounts/doctype/sales_partner_item/sales_partner_item.py new file mode 100644 index 00000000000..9796c7b0ccf --- /dev/null +++ b/erpnext/accounts/doctype/sales_partner_item/sales_partner_item.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class SalesPartnerItem(Document): + pass diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json index 1b7a0fe562e..3a871bfcede 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json @@ -19,7 +19,7 @@ "section_break_8", "rate", "section_break_9", - "currency", + "account_currency", "tax_amount", "total", "tax_amount_after_discount_amount", @@ -27,7 +27,8 @@ "base_tax_amount", "base_total", "base_tax_amount_after_discount_amount", - "item_wise_tax_detail" + "item_wise_tax_detail", + "dont_recompute_tax" ], "fields": [ { @@ -185,14 +186,6 @@ "fieldname": "dimension_col_break", "fieldtype": "Column Break" }, - { - "fetch_from": "account_head.account_currency", - "fieldname": "currency", - "fieldtype": "Link", - "label": "Account Currency", - "options": "Currency", - "read_only": 1 - }, { "default": "0", "depends_on": "eval:['Sales Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)", @@ -200,13 +193,30 @@ "fieldname": "included_in_paid_amount", "fieldtype": "Check", "label": "Considered In Paid Amount" + }, + { + "default": "0", + "fieldname": "dont_recompute_tax", + "fieldtype": "Check", + "hidden": 1, + "label": "Dont Recompute tax", + "print_hide": 1, + "read_only": 1 + }, + { + "fetch_from": "account_head.account_currency", + "fieldname": "account_currency", + "fieldtype": "Link", + "label": "Account Currency", + "options": "Currency", + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-06-14 01:44:36.899147", + "modified": "2021-08-05 20:04:01.726867", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Taxes and Charges", diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.py b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.py index 8d1df5c19a3..b1de9d85fdb 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.py +++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class SalesTaxesandCharges(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py index 52d19d54a8b..8f9eb6577b8 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py @@ -6,7 +6,7 @@ import frappe from frappe import _ from frappe.utils import flt from frappe.model.document import Document -from erpnext.controllers.accounts_controller import validate_taxes_and_charges, validate_inclusive_tax +from erpnext.controllers.accounts_controller import validate_taxes_and_charges, validate_inclusive_tax, validate_cost_center, validate_account_head class SalesTaxesandChargesTemplate(Document): def validate(self): @@ -39,6 +39,8 @@ def valdiate_taxes_and_charges_template(doc): for tax in doc.get("taxes"): validate_taxes_and_charges(tax) + validate_account_head(tax, doc) + validate_cost_center(tax, doc) validate_inclusive_tax(tax, doc) def validate_disabled(doc): diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template_dashboard.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template_dashboard.py index d825c6fd325..522e282a170 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template_dashboard.py +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template_dashboard.py @@ -21,4 +21,4 @@ def get_data(): 'items': ['POS Profile', 'Subscription', 'Restaurant', 'Tax Rule'] } ] - } \ No newline at end of file + } diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_records.json b/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_records.json index 2b737b98048..74db08d5b86 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_records.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_records.json @@ -8,6 +8,7 @@ "charge_type": "On Net Total", "description": "VAT", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 6 }, @@ -16,6 +17,7 @@ "charge_type": "On Net Total", "description": "Service Tax", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 6.36 } @@ -114,6 +116,7 @@ "charge_type": "On Net Total", "description": "VAT", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 12 }, @@ -122,6 +125,7 @@ "charge_type": "On Net Total", "description": "Service Tax", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 4 } @@ -137,6 +141,7 @@ "charge_type": "On Net Total", "description": "VAT", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 12 }, @@ -145,6 +150,7 @@ "charge_type": "On Net Total", "description": "Service Tax", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 4 } @@ -160,6 +166,7 @@ "charge_type": "On Net Total", "description": "VAT", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 12 }, @@ -168,6 +175,7 @@ "charge_type": "On Net Total", "description": "Service Tax", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 4 } diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_sales_taxes_and_charges_template.js b/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_sales_taxes_and_charges_template.js index d02e70b5419..8cd42f63a41 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_sales_taxes_and_charges_template.js +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_sales_taxes_and_charges_template.js @@ -26,4 +26,3 @@ QUnit.test("test sales taxes and charges template", function(assert) { () => done() ]); }); - diff --git a/erpnext/accounts/doctype/share_transfer/share_transfer.js b/erpnext/accounts/doctype/share_transfer/share_transfer.js index 1cad4dfae3d..6317c9c8c0d 100644 --- a/erpnext/accounts/doctype/share_transfer/share_transfer.js +++ b/erpnext/accounts/doctype/share_transfer/share_transfer.js @@ -115,4 +115,4 @@ erpnext.share_transfer.make_jv = function (frm) { frappe.set_route("Form", doc.doctype, doc.name); } }); -}; \ No newline at end of file +}; diff --git a/erpnext/accounts/doctype/share_transfer/share_transfer.py b/erpnext/accounts/doctype/share_transfer/share_transfer.py index 4024b8155a4..3d4543fb051 100644 --- a/erpnext/accounts/doctype/share_transfer/share_transfer.py +++ b/erpnext/accounts/doctype/share_transfer/share_transfer.py @@ -299,4 +299,4 @@ def make_jv_entry( company, account, amount, payment_account,\ "party": credit_applicant, }) journal_entry.set("accounts", account_amt_list) - return journal_entry.as_dict() \ No newline at end of file + return journal_entry.as_dict() diff --git a/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.js b/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.js index 0201f762b37..63ea1bf35f4 100644 --- a/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.js +++ b/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.js @@ -34,4 +34,3 @@ QUnit.test("test Shipping Rule", function(assert) { () => done() ]); }); - diff --git a/erpnext/accounts/doctype/shipping_rule/tests/test_shipping_rule_for_buying.js b/erpnext/accounts/doctype/shipping_rule/tests/test_shipping_rule_for_buying.js index ab1b77cd5f5..f3668b8b406 100644 --- a/erpnext/accounts/doctype/shipping_rule/tests/test_shipping_rule_for_buying.js +++ b/erpnext/accounts/doctype/shipping_rule/tests/test_shipping_rule_for_buying.js @@ -34,4 +34,3 @@ QUnit.test("test Shipping Rule", function(assert) { () => done() ]); }); - diff --git a/erpnext/accounts/doctype/shipping_rule_condition/shipping_rule_condition.py b/erpnext/accounts/doctype/shipping_rule_condition/shipping_rule_condition.py index dab59db70c3..db6ef117c22 100644 --- a/erpnext/accounts/doctype/shipping_rule_condition/shipping_rule_condition.py +++ b/erpnext/accounts/doctype/shipping_rule_condition/shipping_rule_condition.py @@ -9,4 +9,4 @@ import frappe from frappe.model.document import Document class ShippingRuleCondition(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/accounts/doctype/subscription/subscription_list.js b/erpnext/accounts/doctype/subscription/subscription_list.js index c7325fb9f74..6490ff3776e 100644 --- a/erpnext/accounts/doctype/subscription/subscription_list.js +++ b/erpnext/accounts/doctype/subscription/subscription_list.js @@ -14,4 +14,4 @@ frappe.listview_settings['Subscription'] = { return [__("Cancelled"), "gray"]; } } -}; \ No newline at end of file +}; diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index 7c58e9865fd..4f2cf487a4f 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -630,5 +630,3 @@ class TestSubscription(unittest.TestCase): subscription.process() self.assertEqual(len(subscription.invoices), 1) - - diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.js b/erpnext/accounts/doctype/subscription_plan/subscription_plan.js index aaa32cfe7ef..7d6f2aed100 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.js +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.js @@ -6,4 +6,4 @@ frappe.ui.form.on('Subscription Plan', { frm.toggle_reqd("cost", frm.doc.price_determination === 'Fixed rate'); frm.toggle_reqd("price_list", frm.doc.price_determination === 'Based on price list'); } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json index 46ce0939e4f..771611a7860 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json @@ -78,7 +78,7 @@ "label": "Cost" }, { - "depends_on": "eval:doc.price_determination==\"Based on price list\"", + "depends_on": "eval:doc.price_determination==\"Based On Price List\"", "fieldname": "price_list", "fieldtype": "Link", "label": "Price List", @@ -147,7 +147,7 @@ } ], "links": [], - "modified": "2020-06-25 10:53:44.205774", + "modified": "2021-08-09 10:53:44.205774", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription Plan", diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py index 1ca442a4531..a341c2af6ac 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py @@ -54,4 +54,4 @@ def get_plan_rate(plan, quantity=1, customer=None, start_date=None, end_date=Non cost -= (plan.cost * prorate_factor) - return cost \ No newline at end of file + return cost diff --git a/erpnext/accounts/doctype/supplier_group_item/__init__.py b/erpnext/accounts/doctype/supplier_group_item/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/supplier_group_item/supplier_group_item.json b/erpnext/accounts/doctype/supplier_group_item/supplier_group_item.json new file mode 100644 index 00000000000..67fac458456 --- /dev/null +++ b/erpnext/accounts/doctype/supplier_group_item/supplier_group_item.json @@ -0,0 +1,31 @@ +{ + "actions": [], + "creation": "2021-05-06 16:19:22.040795", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "supplier_group" + ], + "fields": [ + { + "fieldname": "supplier_group", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Supplier Group", + "options": "Supplier Group" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-05-07 10:43:59.877938", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Supplier Group Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/supplier_group_item/supplier_group_item.py b/erpnext/accounts/doctype/supplier_group_item/supplier_group_item.py new file mode 100644 index 00000000000..de0444ee193 --- /dev/null +++ b/erpnext/accounts/doctype/supplier_group_item/supplier_group_item.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class SupplierGroupItem(Document): + pass diff --git a/erpnext/accounts/doctype/supplier_item/__init__.py b/erpnext/accounts/doctype/supplier_item/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/supplier_item/supplier_item.json b/erpnext/accounts/doctype/supplier_item/supplier_item.json new file mode 100644 index 00000000000..95c4dc6db36 --- /dev/null +++ b/erpnext/accounts/doctype/supplier_item/supplier_item.json @@ -0,0 +1,31 @@ +{ + "actions": [], + "creation": "2021-05-06 16:18:54.758468", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "supplier" + ], + "fields": [ + { + "fieldname": "supplier", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Supplier", + "options": "Supplier" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-05-07 10:44:09.707778", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Supplier Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/supplier_item/supplier_item.py b/erpnext/accounts/doctype/supplier_item/supplier_item.py new file mode 100644 index 00000000000..ad66e230c80 --- /dev/null +++ b/erpnext/accounts/doctype/supplier_item/supplier_item.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class SupplierItem(Document): + pass diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.py b/erpnext/accounts/doctype/tax_rule/tax_rule.py index e4ebc6d12f9..58142318177 100644 --- a/erpnext/accounts/doctype/tax_rule/tax_rule.py +++ b/erpnext/accounts/doctype/tax_rule/tax_rule.py @@ -188,4 +188,4 @@ def get_customer_group_condition(customer_group): customer_groups = ["%s"%(frappe.db.escape(d.name)) for d in get_parent_customer_groups(customer_group)] if customer_groups: condition = ",".join(['%s'] * len(customer_groups))%(tuple(customer_groups)) - return condition \ No newline at end of file + return condition diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json index f9160e281da..153906ffe97 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json @@ -1,263 +1,151 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "Prompt", - "beta": 0, - "creation": "2018-04-13 18:42:06.431683", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "Prompt", + "creation": "2018-04-13 18:42:06.431683", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "category_details_section", + "category_name", + "round_off_tax_amount", + "column_break_2", + "consider_party_ledger_amount", + "tax_on_excess_amount", + "section_break_8", + "rates", + "section_break_7", + "accounts" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "category_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Category Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_8", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Tax Withholding Rates", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "show_days": 1, + "show_seconds": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "rates", "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Rates", - "length": 0, - "no_copy": 0, "options": "Tax Withholding Rate", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "show_days": 1, + "show_seconds": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "fieldname": "section_break_7", + "fieldtype": "Section Break", "label": "Account Details", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "accounts", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Accounts", - "length": 0, - "no_copy": 0, - "options": "Tax Withholding Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "accounts", + "fieldtype": "Table", + "label": "Accounts", + "options": "Tax Withholding Account", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "category_details_section", + "fieldtype": "Section Break", + "label": "Category Details", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "0", + "description": "Even invoices with apply tax withholding unchecked will be considered for checking cumulative threshold breach", + "fieldname": "consider_party_ledger_amount", + "fieldtype": "Check", + "label": "Consider Entire Party Ledger Amount", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "0", + "description": "Tax will be withheld only for amount exceeding the cumulative threshold", + "fieldname": "tax_on_excess_amount", + "fieldtype": "Check", + "label": "Only Deduct Tax On Excess Amount ", + "show_days": 1, + "show_seconds": 1 + }, + { + "description": "Checking this will round off the tax amount to the nearest integer", + "fieldname": "round_off_tax_amount", + "fieldtype": "Check", + "label": "Round Off Tax Amount", + "show_days": 1, + "show_seconds": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-07-17 22:53:26.193179", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Tax Withholding Category", - "name_case": "", - "owner": "Administrator", + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-07-27 21:47:34.396071", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Tax Withholding Category", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index b9ee4a0963f..1536a237dec 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import flt, getdate +from frappe.utils import flt, getdate, cint from erpnext.accounts.utils import get_fiscal_year class TaxWithholdingCategory(Document): @@ -86,7 +86,10 @@ def get_tax_withholding_details(tax_withholding_category, fiscal_year, company): "rate": tax_rate_detail.tax_withholding_rate, "threshold": tax_rate_detail.single_threshold, "cumulative_threshold": tax_rate_detail.cumulative_threshold, - "description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category + "description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category, + "consider_party_ledger_amount": tax_withholding.consider_party_ledger_amount, + "tax_on_excess_amount": tax_withholding.tax_on_excess_amount, + "round_off_tax_amount": tax_withholding.round_off_tax_amount }) def get_tax_withholding_rates(tax_withholding, fiscal_year): @@ -145,6 +148,7 @@ def get_lower_deduction_certificate(fiscal_year, pan_no): def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None): fiscal_year = fiscal_year_details[0] + vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type) advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type) taxable_vouchers = vouchers + advance_vouchers @@ -235,10 +239,18 @@ def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details): def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers): tds_amount = 0 + invoice_filters = { + 'name': ('in', vouchers), + 'docstatus': 1 + } - supp_credit_amt = frappe.db.get_value('Purchase Invoice', { - 'name': ('in', vouchers), 'docstatus': 1, 'apply_tds': 1 - }, 'sum(net_total)') or 0.0 + field = 'sum(net_total)' + + if not cint(tax_details.consider_party_ledger_amount): + invoice_filters.update({'apply_tds': 1}) + field = 'sum(grand_total)' + + supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, field) or 0.0 supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', { 'parent': ('in', vouchers), 'docstatus': 1, @@ -255,6 +267,13 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu cumulative_threshold = tax_details.get('cumulative_threshold', 0) if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)): + if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(tax_details.tax_on_excess_amount): + # Get net total again as TDS is calculated on net total + # Grand is used to just check for threshold breach + net_total = frappe.db.get_value('Purchase Invoice', invoice_filters, 'sum(net_total)') or 0.0 + net_total += inv.net_total + supp_credit_amt = net_total - cumulative_threshold + if ldc and is_valid_certificate( ldc.valid_from, ldc.valid_upto, inv.get('posting_date') or inv.get('transaction_date'), tax_deducted, @@ -264,6 +283,9 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu else: tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0 + if cint(tax_details.round_off_tax_amount): + tds_amount = round(tds_amount) + return tds_amount def get_tcs_amount(parties, inv, tax_details, fiscal_year_details, vouchers, adv_vouchers): diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index dd26be7c992..1c687e5cb15 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -87,6 +87,31 @@ class TestTaxWithholdingCategory(unittest.TestCase): for d in invoices: d.cancel() + def test_tax_withholding_category_checks(self): + invoices = [] + frappe.db.set_value("Supplier", "Test TDS Supplier3", "tax_withholding_category", "New TDS Category") + + # First Invoice with no tds check + pi = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000, do_not_save=True) + pi.apply_tds = 0 + pi.save() + pi.submit() + invoices.append(pi) + + # Second Invoice will apply TDS checked + pi1 = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000) + pi1.submit() + invoices.append(pi1) + + # Cumulative threshold is 30000 + # Threshold calculation should be on both the invoices + # TDS should be applied only on 1000 + self.assertEqual(pi1.taxes[0].tax_amount, 1000) + + for d in invoices: + d.cancel() + + def test_cumulative_threshold_tcs(self): frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS") invoices = [] @@ -195,7 +220,7 @@ def create_sales_invoice(**args): def create_records(): # create a new suppliers - for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']: + for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3']: if frappe.db.exists('Supplier', name): continue @@ -311,3 +336,23 @@ def create_tax_with_holding_category(): 'account': 'TDS - _TC' }] }).insert() + + if not frappe.db.exists("Tax Withholding Category", "New TDS Category"): + frappe.get_doc({ + "doctype": "Tax Withholding Category", + "name": "New TDS Category", + "category_name": "New TDS Category", + "round_off_tax_amount": 1, + "consider_party_ledger_amount": 1, + "tax_on_excess_amount": 1, + "rates": [{ + 'fiscal_year': fiscal_year, + 'tax_withholding_rate': 10, + 'single_threshold': 0, + 'cumulative_threshold': 30000 + }], + "accounts": [{ + 'company': '_Test Company', + 'account': 'TDS - _TC' + }] + }).insert() diff --git a/erpnext/accounts/doctype/territory_item/__init__.py b/erpnext/accounts/doctype/territory_item/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/territory_item/territory_item.json b/erpnext/accounts/doctype/territory_item/territory_item.json new file mode 100644 index 00000000000..0f0fdea8c01 --- /dev/null +++ b/erpnext/accounts/doctype/territory_item/territory_item.json @@ -0,0 +1,31 @@ +{ + "actions": [], + "creation": "2021-05-06 16:16:51.885441", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "territory" + ], + "fields": [ + { + "fieldname": "territory", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Territory", + "options": "Territory" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-05-07 10:43:26.641030", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Territory Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/territory_item/territory_item.py b/erpnext/accounts/doctype/territory_item/territory_item.py new file mode 100644 index 00000000000..d46edc9dca8 --- /dev/null +++ b/erpnext/accounts/doctype/territory_item/territory_item.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class TerritoryItem(Document): + pass diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 25d2cf10bd4..4c7c567b42a 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -100,8 +100,8 @@ def merge_similar_entries(gl_map, precision=None): return merged_gl_map def check_if_in_list(gle, gl_map, dimensions=None): - account_head_fieldnames = ['party_type', 'party', 'against_voucher', 'against_voucher_type', - 'cost_center', 'project', 'voucher_detail_no'] + account_head_fieldnames = ['voucher_detail_no', 'party', 'against_voucher', + 'cost_center', 'against_voucher_type', 'party_type', 'project'] if dimensions: account_head_fieldnames = account_head_fieldnames + dimensions @@ -110,10 +110,12 @@ def check_if_in_list(gle, gl_map, dimensions=None): same_head = True if e.account != gle.account: same_head = False + continue for fieldname in account_head_fieldnames: if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)): same_head = False + break if same_head: return e @@ -143,16 +145,19 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False): validate_expense_against_budget(args) def validate_cwip_accounts(gl_map): - cwip_enabled = any(cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")) + """Validate that CWIP account are not used in Journal Entry""" + if gl_map and gl_map[0].voucher_type != "Journal Entry": + return - if cwip_enabled and gl_map[0].voucher_type == "Journal Entry": - cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount - where account_type = 'Capital Work in Progress' and is_group=0""")] + cwip_enabled = any(cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting")) + if cwip_enabled: + cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount + where account_type = 'Capital Work in Progress' and is_group=0""")] - for entry in gl_map: - if entry.account in cwip_accounts: - frappe.throw( - _("Account: {0} is capital Work in progress and can not be updated by Journal Entry").format(entry.account)) + for entry in gl_map: + if entry.account in cwip_accounts: + frappe.throw( + _("Account: {0} is capital Work in progress and can not be updated by Journal Entry").format(entry.account)) def round_off_debit_credit(gl_map): precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index b97dc401e6a..de7dde9dd1f 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -8,7 +8,7 @@ from frappe import _, msgprint, scrub from frappe.core.doctype.user_permission.user_permission import get_permitted_documents from frappe.model.utils import get_fetch_values from frappe.utils import (add_days, getdate, formatdate, date_diff, - add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day) + add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day, cint) from frappe.contacts.doctype.address.address import (get_address_display, get_default_address, get_company_address) from frappe.contacts.doctype.contact.contact import get_contact_details @@ -58,7 +58,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company= customer_group=party_details.customer_group, supplier_group=party_details.supplier_group, tax_category=party_details.tax_category, billing_address=party_address, shipping_address=shipping_address) - if fetch_payment_terms_template: + if cint(fetch_payment_terms_template): party_details["payment_terms_template"] = get_payment_terms_template(party.name, party_type, company) if not party_details.get("currency"): @@ -286,6 +286,7 @@ def validate_party_gle_currency(party_type, party, company, party_account_curren .format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)), InvalidAccountCurrency) def validate_party_accounts(doc): + companies = [] for account in doc.get("accounts"): @@ -446,6 +447,10 @@ def get_payment_terms_template(party_name, party_type, company=None): return template def validate_party_frozen_disabled(party_type, party_name): + + if frappe.flags.ignore_party_validation: + return + if party_type and party_name: if party_type in ("Customer", "Supplier"): party = frappe.get_cached_value(party_type, party_name, ["is_frozen", "disabled"], as_dict=True) diff --git a/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.html b/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.html index e588ed6609e..4ac657d1ae6 100644 --- a/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.html +++ b/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.html @@ -73,4 +73,4 @@
-
\ No newline at end of file + diff --git a/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.json b/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.json index e3afaec2ad0..9e126bade7b 100644 --- a/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.json +++ b/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.json @@ -16,7 +16,7 @@ "name": "Bank and Cash Payment Voucher", "owner": "Administrator", "print_format_builder": 0, - "print_format_type": "Server", + "print_format_type": "Jinja", "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/accounts/print_format/cheque_printing_format/cheque_printing_format.json b/erpnext/accounts/print_format/cheque_printing_format/cheque_printing_format.json index 8c9c26691e5..08b8ef88900 100755 --- a/erpnext/accounts/print_format/cheque_printing_format/cheque_printing_format.json +++ b/erpnext/accounts/print_format/cheque_printing_format/cheque_printing_format.json @@ -10,6 +10,6 @@ "modified_by": "Administrator", "name": "Cheque Printing Format", "owner": "Administrator", - "print_format_type": "Server", + "print_format_type": "Jinja", "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/accounts/print_format/credit_note/credit_note.json b/erpnext/accounts/print_format/credit_note/credit_note.json index c5a11cf3958..d12b872dd79 100644 --- a/erpnext/accounts/print_format/credit_note/credit_note.json +++ b/erpnext/accounts/print_format/credit_note/credit_note.json @@ -17,7 +17,7 @@ "owner": "Administrator", "parentfield": "__print_formats", "print_format_builder": 0, - "print_format_type": "Server", + "print_format_type": "Jinja", "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html index 71c26e8c55a..7643eca7635 100644 --- a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html @@ -159,4 +159,4 @@ - \ No newline at end of file + diff --git a/erpnext/accounts/print_format/gst_purchase_invoice/gst_purchase_invoice.json b/erpnext/accounts/print_format/gst_purchase_invoice/gst_purchase_invoice.json index 6d7c3d31ae4..1467f27e66a 100644 --- a/erpnext/accounts/print_format/gst_purchase_invoice/gst_purchase_invoice.json +++ b/erpnext/accounts/print_format/gst_purchase_invoice/gst_purchase_invoice.json @@ -16,7 +16,7 @@ "name": "GST Purchase Invoice", "owner": "Administrator", "print_format_builder": 1, - "print_format_type": "Server", + "print_format_type": "Jinja", "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html b/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html index 0ca940f8bd5..c1c611ee3a3 100644 --- a/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html +++ b/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html @@ -68,4 +68,4 @@
-
\ No newline at end of file + diff --git a/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.json b/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.json index 927e818e017..e45295d9fea 100644 --- a/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.json +++ b/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.json @@ -16,7 +16,7 @@ "name": "Journal Auditing Voucher", "owner": "Administrator", "print_format_builder": 0, - "print_format_type": "Server", + "print_format_type": "Jinja", "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.html b/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.html index 283d505e3be..ae07582704c 100644 --- a/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.html +++ b/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.html @@ -27,4 +27,3 @@ {{ _("Authorized Signatory") }}

- diff --git a/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.json b/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.json index f1de448bb98..c52eeb2dfb9 100755 --- a/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.json +++ b/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.json @@ -12,6 +12,6 @@ "name": "Payment Receipt Voucher", "owner": "Administrator", "print_format_builder": 0, - "print_format_type": "Server", + "print_format_type": "Jinja", "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.html b/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.html index 043ac254ed3..8696bffbfcb 100644 --- a/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.html +++ b/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.html @@ -103,4 +103,4 @@ - \ No newline at end of file + diff --git a/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.json b/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.json index 73779d49aab..33981178220 100644 --- a/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.json +++ b/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.json @@ -16,7 +16,7 @@ "name": "Purchase Auditing Voucher", "owner": "Administrator", "print_format_builder": 0, - "print_format_type": "Server", + "print_format_type": "Jinja", "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.html b/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.html index a53b593a72a..efb2d00f0bf 100644 --- a/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.html +++ b/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.html @@ -93,4 +93,4 @@ - \ No newline at end of file + diff --git a/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.json b/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.json index 0544e0bc9e8..289e1848488 100644 --- a/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.json +++ b/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.json @@ -16,7 +16,7 @@ "name": "Sales Auditing Voucher", "owner": "Administrator", "print_format_builder": 0, - "print_format_type": "Server", + "print_format_type": "Jinja", "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/accounts/report/account_balance/test_account_balance.py b/erpnext/accounts/report/account_balance/test_account_balance.py index 14ddf4a30fc..f5c9449e85d 100644 --- a/erpnext/accounts/report/account_balance/test_account_balance.py +++ b/erpnext/accounts/report/account_balance/test_account_balance.py @@ -62,8 +62,3 @@ def make_sales_invoice(): income_account = 'Sales - _TC2', expense_account = 'Cost of Goods Sold - _TC2', cost_center = 'Main - _TC2') - - - - - diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 6abd6e5cf77..b6c6689be0b 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -136,4 +136,3 @@ frappe.query_reports["Accounts Payable"] = { } erpnext.utils.add_dimensions('Accounts Payable', 9); - diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js index 9c6b0639c0a..ea200720dff 100644 --- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js +++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js @@ -105,4 +105,3 @@ frappe.query_reports["Accounts Payable Summary"] = { } erpnext.utils.add_dimensions('Accounts Payable Summary', 9); - diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.py b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.py index 729eda9492f..c08582b564c 100644 --- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.py +++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.py @@ -12,4 +12,3 @@ def execute(filters=None): "naming_by": ["Buying Settings", "supp_master_name"], } return AccountsReceivableSummary(filters).run(args) - diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 29c4f7d3941..1a32e2a8e06 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -200,4 +200,3 @@ frappe.query_reports["Accounts Receivable"] = { } erpnext.utils.add_dimensions('Accounts Receivable', 9); - diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index a11b77a6f64..cedfc0f58b8 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -99,7 +99,6 @@ class ReceivablePayableReport(object): voucher_no = gle.voucher_no, party = gle.party, posting_date = gle.posting_date, - remarks = gle.remarks, account_currency = gle.account_currency, invoiced = 0.0, paid = 0.0, @@ -536,6 +535,8 @@ class ReceivablePayableReport(object): if getdate(entry_date) > getdate(self.filters.report_date): row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0 + row.total_due = row.range1 + row.range2 + row.range3 + row.range4 + row.range5 + def get_ageing_data(self, entry_date, row): # [0-30, 30-60, 60-90, 90-120, 120-above] row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0 @@ -579,7 +580,7 @@ class ReceivablePayableReport(object): self.gl_entries = frappe.db.sql(""" select name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center, - against_voucher_type, against_voucher, account_currency, remarks, {0} + against_voucher_type, against_voucher, account_currency, {0} from `tabGL Entry` where @@ -792,8 +793,6 @@ class ReceivablePayableReport(object): self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link', options='Supplier Group') - self.add_column(label=_('Remarks'), fieldname='remarks', fieldtype='Text', width=200) - def add_column(self, label, fieldname=None, fieldtype='Currency', options=None, width=120): if not fieldname: fieldname = scrub(label) if fieldtype=='Currency': options='currency' diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 2ff5b531c51..cca67608238 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -93,4 +93,3 @@ def make_credit_note(docname): cost_center = 'Main - _TC2', is_return = 1, return_against = docname) - diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index 657b3e8f204..4bfb022c4ee 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -82,6 +82,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): "range3": 0.0, "range4": 0.0, "range5": 0.0, + "total_due": 0.0, "sales_person": [] })) @@ -134,4 +135,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): "{range2}-{range3}".format(range2=cint(self.filters["range2"])+ 1, range3=self.filters["range3"]), "{range3}-{range4}".format(range3=cint(self.filters["range3"])+ 1, range4=self.filters["range4"]), "{range4}-{above}".format(range4=cint(self.filters["range4"])+ 1, above=_("Above"))]): - self.add_column(label=label, fieldname='range' + str(i+1)) \ No newline at end of file + self.add_column(label=label, fieldname='range' + str(i+1)) + + # Add column for total due amount + self.add_column(label="Total Amount Due", fieldname='total_due') diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index 26bb44f4f7b..7838385dc56 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -209,4 +209,4 @@ def get_chart_data(filters, columns, asset, liability, equity): else: chart["type"] = "line" - return chart \ No newline at end of file + return chart diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.js b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.js index dbee0229737..f0b6c6b20ac 100644 --- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.js +++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.js @@ -22,7 +22,7 @@ frappe.query_reports["Bank Clearance Summary"] = { "fieldtype": "Link", "options": "Account", "reqd": 1, - "default": frappe.defaults.get_user_default("Company")? + "default": frappe.defaults.get_user_default("Company")? locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "", "get_query": function() { return { diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py index 79b0a6f30ec..95f724cc580 100644 --- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py +++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py @@ -74,19 +74,19 @@ def get_entries(filters): journal_entries = frappe.db.sql("""SELECT "Journal Entry", jv.name, jv.posting_date, jv.cheque_no, jv.clearance_date, jvd.against_account, jvd.debit - jvd.credit - FROM + FROM `tabJournal Entry Account` jvd, `tabJournal Entry` jv - WHERE + WHERE jvd.parent = jv.name and jv.docstatus=1 and jvd.account = %(account)s {0} order by posting_date DESC, jv.name DESC""".format(conditions), filters, as_list=1) payment_entries = frappe.db.sql("""SELECT - "Payment Entry", name, posting_date, reference_no, clearance_date, party, + "Payment Entry", name, posting_date, reference_no, clearance_date, party, if(paid_from=%(account)s, paid_amount * -1, received_amount) - FROM + FROM `tabPayment Entry` - WHERE + WHERE docstatus=1 and (paid_from = %(account)s or paid_to = %(account)s) {0} order by posting_date DESC, name DESC""".format(conditions), filters, as_list=1) - return sorted(journal_entries + payment_entries, key=lambda k: k[2] or getdate(nowdate())) \ No newline at end of file + return sorted(journal_entries + payment_entries, key=lambda k: k[2] or getdate(nowdate())) diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js index 8f028496cd5..9bb6a14c677 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js @@ -16,7 +16,7 @@ frappe.query_reports["Bank Reconciliation Statement"] = { "label": __("Bank Account"), "fieldtype": "Link", "options": "Account", - "default": frappe.defaults.get_user_default("Company")? + "default": frappe.defaults.get_user_default("Company")? locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "", "reqd": 1, "get_query": function() { diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py index 2ce5d50edf4..2dcea22f7e6 100644 --- a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py +++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py @@ -104,4 +104,4 @@ def get_columns(): 'fieldtype': 'Currency', 'width': 100 } - ] \ No newline at end of file + ] diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js index f547ca619bd..718b6e2fcb6 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js @@ -92,4 +92,3 @@ frappe.query_reports["Budget Variance Report"] = { erpnext.dimension_filters.forEach((dimension) => { frappe.query_reports["Budget Variance Report"].filters[4].options.push(dimension["document_type"]); }); - diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index f1b231b6901..443126e4655 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -38,8 +38,8 @@ def execute(filters=None): GROUP BY parent''',{'dimension':[dimension]}) if DCC_allocation: filters['budget_against_filter'] = [DCC_allocation[0][0]] - cam_map = get_dimension_account_month_map(filters) - dimension_items = cam_map.get(DCC_allocation[0][0]) + ddc_cam_map = get_dimension_account_month_map(filters) + dimension_items = ddc_cam_map.get(DCC_allocation[0][0]) if dimension_items: data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation[0][1]) @@ -48,7 +48,6 @@ def execute(filters=None): return columns, data, None, chart def get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation): - for account, monthwise_data in iteritems(dimension_items): row = [dimension, account] totals = [0, 0, 0] @@ -79,7 +78,7 @@ def get_final_data(dimension, dimension_items, filters, period_month_ranges, dat if filters["period"] != "Yearly" : row += totals data.append(row) - + return data @@ -389,7 +388,7 @@ def get_chart_data(filters, columns, data): budget_values[i] += values[index] actual_values[i] += values[index+1] index += 3 - + return { 'data': { 'labels': labels, @@ -400,4 +399,3 @@ def get_chart_data(filters, columns, data): }, 'type' : 'bar' } - diff --git a/erpnext/accounts/report/cash_flow/cash_flow.html b/erpnext/accounts/report/cash_flow/cash_flow.html index 40ba20c4ac6..d4ae54d4f38 100644 --- a/erpnext/accounts/report/cash_flow/cash_flow.html +++ b/erpnext/accounts/report/cash_flow/cash_flow.html @@ -1 +1 @@ -{% include "accounts/report/financial_statements.html" %} \ No newline at end of file +{% include "accounts/report/financial_statements.html" %} diff --git a/erpnext/accounts/report/cash_flow/cash_flow.js b/erpnext/accounts/report/cash_flow/cash_flow.js index a984bf46b50..a2c34c6ee26 100644 --- a/erpnext/accounts/report/cash_flow/cash_flow.js +++ b/erpnext/accounts/report/cash_flow/cash_flow.js @@ -21,4 +21,4 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "default": 1 } ); -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js index 1363b53746a..6a8301a6f91 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js @@ -94,10 +94,10 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "default": 1 } ], - "formatter": function(value, row, column, data, default_formatter) { + "formatter": function(value, row, column, data, default_formatter) { if (data && column.fieldname=="account") { value = data.account_name || value; - + column.link_onclick = "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")"; column.is_tree = true; @@ -126,4 +126,4 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { }); } } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index 56a67bb0989..fc4212733a3 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -210,10 +210,10 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i company_currency = get_company_currency(filters) if filters.filter_based_on == 'Fiscal Year': - start_date = fiscal_year.year_start_date + start_date = fiscal_year.year_start_date if filters.report != 'Balance Sheet' else None end_date = fiscal_year.year_end_date else: - start_date = filters.period_start_date + start_date = filters.period_start_date if filters.report != 'Balance Sheet' else None end_date = filters.period_end_date gl_entries_by_account = {} diff --git a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py index 515fd995e66..9953d8fcaf5 100644 --- a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py +++ b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py @@ -105,4 +105,4 @@ def get_column(): def get_args(): return {'doctype': 'Delivery Note', 'party': 'customer', - 'date': 'posting_date', 'order': 'name', 'order_by': 'desc'} \ No newline at end of file + 'date': 'posting_date', 'order': 'name', 'order_by': 'desc'} diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 4a551b80124..095f5eda66a 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -176,4 +176,3 @@ frappe.query_reports["General Ledger"] = { } erpnext.utils.add_dimensions('General Ledger', 15) - diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index e724e9b51b6..5d8d49d6a65 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -48,16 +48,18 @@ def validate_filters(filters, account_details): if not filters.get("from_date") and not filters.get("to_date"): frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date")))) - + if filters.get('account'): filters.account = frappe.parse_json(filters.get('account')) for account in filters.account: if not account_details.get(account): frappe.throw(_("Account {0} does not exists").format(account)) - if (filters.get("account") and filters.get("group_by") == _('Group by Account') - and account_details[filters.account].is_group == 0): - frappe.throw(_("Can not filter based on Account, if grouped by Account")) + if (filters.get("account") and filters.get("group_by") == _('Group by Account')): + filters.account = frappe.parse_json(filters.get('account')) + for account in filters.account: + if account_details[account].is_group == 0: + frappe.throw(_("Can not filter based on Child Account, if grouped by Account")) if (filters.get("voucher_no") and filters.get("group_by") in [_('Group by Voucher')]): @@ -90,7 +92,7 @@ def set_account_currency(filters): account_currency = None if filters.get("account"): - if len(filters.get("account")) == 1: + if len(filters.get("account")) == 1: account_currency = get_account_currency(filters.account[0]) else: currency = get_account_currency(filters.account[0]) diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.html b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.html index 40ba20c4ac6..d4ae54d4f38 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.html +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.html @@ -1 +1 @@ -{% include "accounts/report/financial_statements.html" %} \ No newline at end of file +{% include "accounts/report/financial_statements.html" %} diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py index 714e48d279b..8e33af7ee8e 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py @@ -165,4 +165,4 @@ def get_net_profit(non_gross_income, gross_income, gross_expense, non_gross_expe has_value=True if has_value: - return profit_loss \ No newline at end of file + return profit_loss diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 84c74543dae..6d8623c189d 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -241,6 +241,7 @@ class GrossProfitGenerator(object): sle.voucher_detail_no == row.item_row: previous_stock_value = len(my_sle) > i+1 and \ flt(my_sle[i+1].stock_value) or 0.0 + if previous_stock_value: return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty)) else: @@ -335,7 +336,7 @@ class GrossProfitGenerator(object): res = frappe.db.sql("""select item_code, voucher_type, voucher_no, voucher_detail_no, stock_value, warehouse, actual_qty as qty from `tabStock Ledger Entry` - where company=%(company)s + where company=%(company)s and is_cancelled = 0 order by item_code desc, warehouse desc, posting_date desc, posting_time desc, creation desc""", self.filters, as_dict=True) 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 2e794da8425..c9c22c246ed 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 @@ -76,7 +76,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum 'company': d.company, 'sales_order': d.sales_order, 'delivery_note': d.delivery_note, - 'income_account': d.unrealized_profit_loss_account or d.income_account, + 'income_account': d.unrealized_profit_loss_account if d.is_internal_customer == 1 else d.income_account, 'cost_center': d.cost_center, 'stock_qty': d.stock_qty, 'stock_uom': d.stock_uom @@ -380,6 +380,7 @@ def get_items(filters, additional_query_columns): `tabSales Invoice Item`.name, `tabSales Invoice Item`.parent, `tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to, `tabSales Invoice`.unrealized_profit_loss_account, + `tabSales Invoice`.is_internal_customer, `tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks, `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total, `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description, @@ -625,7 +626,3 @@ def add_sub_total_row(item, total_row_map, group_by_value, tax_columns): for tax in tax_columns: total_row.setdefault(frappe.scrub(tax + ' Amount'), 0.0) total_row[frappe.scrub(tax + ' Amount')] += flt(item[frappe.scrub(tax + ' Amount')]) - - - - diff --git a/erpnext/accounts/report/non_billed_report.py b/erpnext/accounts/report/non_billed_report.py index 2e18ce11ddc..51735056896 100644 --- a/erpnext/accounts/report/non_billed_report.py +++ b/erpnext/accounts/report/non_billed_report.py @@ -44,4 +44,4 @@ def get_ordered_to_be_billed_data(args): def get_project_field(doctype, party): if party == "supplier": doctype = doctype + ' Item' - return "`tab%s`.project"%(doctype) \ No newline at end of file + return "`tab%s`.project"%(doctype) diff --git a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py index 7195c7e0b8b..556f5ad4f79 100644 --- a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py +++ b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py @@ -40,7 +40,7 @@ def execute(filters=None): row = [ d.voucher_type, d.voucher_no, d.party_type, d.party, d.posting_date, d.against_voucher, - invoice.posting_date, invoice.due_date, d.debit, d.credit, d.remarks, + invoice.posting_date, invoice.due_date, d.debit, d.credit, d.remarks, d.age, d.range1, d.range2, d.range3, d.range4 ] diff --git a/erpnext/accounts/report/pos_register/pos_register.py b/erpnext/accounts/report/pos_register/pos_register.py index 6a42bb4fb65..b7e112c0c9a 100644 --- a/erpnext/accounts/report/pos_register/pos_register.py +++ b/erpnext/accounts/report/pos_register/pos_register.py @@ -10,7 +10,7 @@ from erpnext.accounts.report.sales_register.sales_register import get_mode_of_pa def execute(filters=None): if not filters: return [], [] - + validate_filters(filters) columns = get_columns(filters) @@ -29,7 +29,7 @@ def execute(filters=None): invoice_map, grouped_data = {}, [] for d in pos_entries: invoice_map.setdefault(d[group_by_field], []).append(d) - + for key in invoice_map: invoices = invoice_map[key] grouped_data += invoices @@ -56,7 +56,7 @@ def get_pos_entries(filters, group_by_field): return frappe.db.sql( """ - SELECT + SELECT p.posting_date, p.name as pos_invoice, p.pos_profile, p.owner, p.base_grand_total as grand_total, p.base_paid_amount as paid_amount, p.customer, p.is_return {select_mop_field} @@ -96,22 +96,22 @@ def add_subtotal_row(data, group_invoices, group_by_field, group_by_value): def validate_filters(filters): if not filters.get("company"): frappe.throw(_("{0} is mandatory").format(_("Company"))) - + if not filters.get("from_date") and not filters.get("to_date"): frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date")))) - + if filters.from_date > filters.to_date: frappe.throw(_("From Date must be before To Date")) if (filters.get("pos_profile") and filters.get("group_by") == _('POS Profile')): frappe.throw(_("Can not filter based on POS Profile, if grouped by POS Profile")) - + if (filters.get("customer") and filters.get("group_by") == _('Customer')): frappe.throw(_("Can not filter based on Customer, if grouped by Customer")) - + if (filters.get("owner") and filters.get("group_by") == _('Cashier')): frappe.throw(_("Can not filter based on Cashier, if grouped by Cashier")) - + if (filters.get("mode_of_payment") and filters.get("group_by") == _('Payment Method')): frappe.throw(_("Can not filter based on Payment Method, if grouped by Payment Method")) @@ -120,23 +120,23 @@ def get_conditions(filters): if filters.get("pos_profile"): conditions += " AND pos_profile = %(pos_profile)s" - + if filters.get("owner"): conditions += " AND owner = %(owner)s" - + if filters.get("customer"): conditions += " AND customer = %(customer)s" - + if filters.get("is_return"): conditions += " AND is_return = %(is_return)s" - + if filters.get("mode_of_payment"): conditions += """ AND EXISTS( SELECT name FROM `tabSales Invoice Payment` sip WHERE parent=p.name AND ifnull(sip.mode_of_payment, '') = %(mode_of_payment)s )""" - + return conditions def get_group_by_field(group_by): @@ -150,7 +150,7 @@ def get_group_by_field(group_by): group_by_field = "customer" elif group_by == "Payment Method": group_by_field = "mode_of_payment" - + return group_by_field def get_columns(filters): @@ -217,4 +217,4 @@ def get_columns(filters): }, ] - return columns \ No newline at end of file + return columns diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.html b/erpnext/accounts/report/profitability_analysis/profitability_analysis.html index 40ba20c4ac6..d4ae54d4f38 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.html +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.html @@ -1 +1 @@ -{% include "accounts/report/financial_statements.html" %} \ No newline at end of file +{% include "accounts/report/financial_statements.html" %} diff --git a/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.js b/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.js index a95cfacaeef..feab96f2652 100644 --- a/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.js +++ b/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.js @@ -5,4 +5,4 @@ frappe.require("assets/erpnext/js/purchase_trends_filters.js", function() { frappe.query_reports["Purchase Invoice Trends"] = { filters: erpnext.get_purchase_trends_filters() } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.py b/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.py index ad3783f0de1..ba236b9969d 100644 --- a/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.py +++ b/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.py @@ -11,4 +11,4 @@ def execute(filters=None): conditions = get_columns(filters, "Purchase Invoice") data = get_data(filters, conditions) - return conditions["columns"], data \ No newline at end of file + return conditions["columns"], data diff --git a/erpnext/accounts/report/purchase_register/purchase_register.js b/erpnext/accounts/report/purchase_register/purchase_register.js index f34ea571639..aaf76c42997 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.js +++ b/erpnext/accounts/report/purchase_register/purchase_register.js @@ -56,4 +56,4 @@ frappe.query_reports["Purchase Register"] = { ] } -erpnext.utils.add_dimensions('Purchase Register', 7); \ No newline at end of file +erpnext.utils.add_dimensions('Purchase Register', 7); diff --git a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py index e9e9c9c4e69..a5eced5f80b 100644 --- a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py +++ b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py @@ -105,4 +105,4 @@ def get_column(): def get_args(): return {'doctype': 'Purchase Receipt', 'party': 'supplier', - 'date': 'posting_date', 'order': 'name', 'order_by': 'desc'} \ No newline at end of file + 'date': 'posting_date', 'order': 'name', 'order_by': 'desc'} diff --git a/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.js b/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.js index 2d320f52cfa..e3d43a7de1e 100644 --- a/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.js +++ b/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.js @@ -5,4 +5,4 @@ frappe.require("assets/erpnext/js/sales_trends_filters.js", function() { frappe.query_reports["Sales Invoice Trends"] = { filters: erpnext.get_sales_trends_filters() } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.js b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.js index 068926b063d..44e20e83c50 100644 --- a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.js +++ b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.js @@ -42,4 +42,4 @@ frappe.query_reports["Sales Payment Summary"] = { "fieldtype": "Check" }, ] -}; \ No newline at end of file +}; diff --git a/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py b/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py index a51c4276301..e4a3d3527fd 100644 --- a/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py +++ b/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py @@ -162,4 +162,4 @@ def create_records(): "price_list": "Standard Selling", "item_code": item.item_code, "price_list_rate": 10000 - }).insert() \ No newline at end of file + }).insert() diff --git a/erpnext/accounts/report/sales_register/sales_register.js b/erpnext/accounts/report/sales_register/sales_register.js index 85bbceab827..2c9b01bbaa3 100644 --- a/erpnext/accounts/report/sales_register/sales_register.js +++ b/erpnext/accounts/report/sales_register/sales_register.js @@ -69,4 +69,3 @@ frappe.query_reports["Sales Register"] = { } erpnext.utils.add_dimensions('Sales Register', 7); - diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index 909959323f7..f38bd78c0d2 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -84,7 +84,7 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No # Add amount in unrealized account for account in unrealized_profit_loss_accounts: row.update({ - frappe.scrub(account): flt(internal_invoice_map.get((inv.name, account))) + frappe.scrub(account+"_unrealized"): flt(internal_invoice_map.get((inv.name, account))) }) # net total @@ -258,6 +258,7 @@ def get_columns(invoice_list, additional_table_columns): unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account from `tabSales Invoice` where docstatus = 1 and name in (%s) + and is_internal_customer = 1 and ifnull(unrealized_profit_loss_account, '') != '' order by unrealized_profit_loss_account""" % ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list)) @@ -284,7 +285,7 @@ def get_columns(invoice_list, additional_table_columns): for account in unrealized_profit_loss_accounts: unrealized_profit_loss_account_columns.append({ "label": account, - "fieldname": frappe.scrub(account), + "fieldname": frappe.scrub(account+"_unrealized"), "fieldtype": "Currency", "options": "currency", "width": 120 diff --git a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.py b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.py index d2c23ee4e78..fbd25b13bb5 100644 --- a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.py +++ b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.py @@ -10,4 +10,4 @@ def execute(filters=None): "party_type": "Supplier", "naming_by": ["Buying Settings", "supp_master_name"], } - return PartyLedgerSummaryReport(filters).run(args) \ No newline at end of file + return PartyLedgerSummaryReport(filters).run(args) diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py index e15715dccd8..6b9df41f54e 100644 --- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py +++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py @@ -75,7 +75,8 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date, f select voucher_no, credit from `tabGL Entry` where party in (%s) and credit > 0 - and company=%s and posting_date between %s and %s + and company=%s and is_cancelled = 0 + and posting_date between %s and %s """, (supplier, company, from_date, to_date), as_dict=1) supplier_credit_amount = flt(sum(d.credit for d in entries)) diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js index 8645d55d0fe..078b06519f1 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.js +++ b/erpnext/accounts/report/trial_balance/trial_balance.js @@ -110,6 +110,3 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { erpnext.utils.add_dimensions('Trial Balance', 6); }); - - - diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 33360e2b01e..1fc0faab3a7 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -321,4 +321,4 @@ def prepare_opening_closing(row): row[reverse_col] = abs(row[valid_col]) row[valid_col] = 0.0 else: - row[reverse_col] = 0.0 \ No newline at end of file + row[reverse_col] = 0.0 diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py index 78c7e439d38..f034e7450ee 100644 --- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py +++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py @@ -242,4 +242,4 @@ def is_party_name_visible(filters): else: show_party_name = True - return show_party_name \ No newline at end of file + return show_party_name diff --git a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py index eee620b7cc8..1250d676a06 100644 --- a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py +++ b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py @@ -20,11 +20,11 @@ def get_unclaimed_expese_claims(filters): if filters.get("employee"): cond = "ec.employee = %(employee)s" - return frappe.db.sql(""" + return frappe.db.sql(""" select ec.employee, ec.employee_name, ec.name, ec.total_sanctioned_amount, ec.total_amount_reimbursed, sum(gle.credit_in_account_currency - gle.debit_in_account_currency) as outstanding_amt - from + from `tabExpense Claim` ec, `tabGL Entry` gle where gle.against_voucher_type = "Expense Claim" and gle.against_voucher = ec.name diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 1cdbd8d38a6..68355535c7e 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -19,6 +19,7 @@ from erpnext.stock import get_warehouse_account_map class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass class FiscalYearError(frappe.ValidationError): pass +class PaymentEntryUnlinkError(frappe.ValidationError): pass @frappe.whitelist() def get_fiscal_year(date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False): @@ -350,6 +351,7 @@ def reconcile_against_document(args): # cancel advance entry doc = frappe.get_doc(d.voucher_type, d.voucher_no) + frappe.flags.ignore_party_validation = True doc.make_gl_entries(cancel=1, adv_adj=1) # update ref in advance entry @@ -361,6 +363,7 @@ def reconcile_against_document(args): # re-submit advance entry doc = frappe.get_doc(d.voucher_type, d.voucher_no) doc.make_gl_entries(cancel = 0, adv_adj =1) + frappe.flags.ignore_party_validation = False if d.voucher_type in ('Payment Entry', 'Journal Entry'): doc.update_expense_claim() @@ -553,10 +556,16 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no): and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no)) for pe in linked_pe: - pe_doc = frappe.get_doc("Payment Entry", pe) - pe_doc.set_total_allocated_amount() - pe_doc.set_unallocated_amount() - pe_doc.clear_unallocated_reference_document_rows() + try: + pe_doc = frappe.get_doc("Payment Entry", pe) + pe_doc.set_amounts() + pe_doc.clear_unallocated_reference_document_rows() + pe_doc.validate_payment_type_with_outstanding() + except Exception as e: + msg = _("There were issues unlinking payment entry {0}.").format(pe_doc.name) + msg += '
' + msg += _("Please cancel payment entry manually first") + frappe.throw(msg, exc=PaymentEntryUnlinkError, title=_("Payment Unlink Error")) frappe.db.sql("""update `tabPayment Entry` set total_allocated_amount=%s, base_total_allocated_amount=%s, unallocated_amount=%s, modified=%s, modified_by=%s @@ -920,7 +929,6 @@ def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, wa _delete_gl_entries(voucher_type, voucher_no) def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None, company=None): - future_stock_vouchers = [] values = [] condition = "" @@ -936,37 +944,53 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f condition += " and company = %s" values.append(company) - for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no + future_stock_vouchers = frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no from `tabStock Ledger Entry` sle where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) and is_cancelled = 0 {condition} order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition), - tuple([posting_date, posting_time] + values), as_dict=True): - future_stock_vouchers.append([d.voucher_type, d.voucher_no]) + tuple([posting_date, posting_time] + values), as_dict=True) - return future_stock_vouchers + return [(d.voucher_type, d.voucher_no) for d in future_stock_vouchers] def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): + """ Get voucherwise list of GL entries. + + Only fetches GLE fields required for comparing with new GLE. + Check compare_existing_and_expected_gle function below. + """ gl_entries = {} - if future_stock_vouchers: - for d in frappe.db.sql("""select * from `tabGL Entry` - where posting_date >= %s and voucher_no in (%s)""" % - ('%s', ', '.join(['%s']*len(future_stock_vouchers))), - tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1): - gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) + if not future_stock_vouchers: + return gl_entries + + voucher_nos = [d[1] for d in future_stock_vouchers] + + gles = frappe.db.sql(""" + select name, account, credit, debit, cost_center, project + from `tabGL Entry` + where + posting_date >= %s and voucher_no in (%s)""" % + ('%s', ', '.join(['%s'] * len(voucher_nos))), + tuple([posting_date] + voucher_nos), as_dict=1) + + for d in gles: + gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) return gl_entries def compare_existing_and_expected_gle(existing_gle, expected_gle, precision): + if len(existing_gle) != len(expected_gle): + return False + matched = True for entry in expected_gle: account_existed = False for e in existing_gle: if entry.account == e.account: account_existed = True - if (entry.account == e.account and entry.against_account == e.against_account + if (entry.account == e.account and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center) and ( flt(entry.debit, precision) != flt(e.debit, precision) or flt(entry.credit, precision) != flt(e.credit, precision))): diff --git a/erpnext/agriculture/doctype/crop/crop.js b/erpnext/agriculture/doctype/crop/crop.js index afd84fd9f66..550824636b6 100644 --- a/erpnext/agriculture/doctype/crop/crop.js +++ b/erpnext/agriculture/doctype/crop/crop.js @@ -52,4 +52,4 @@ erpnext.crop.update_item_qty_amount = function(frm, cdt, cdn) { } }); }); -}; \ No newline at end of file +}; diff --git a/erpnext/agriculture/doctype/crop/crop_dashboard.py b/erpnext/agriculture/doctype/crop/crop_dashboard.py index 9a8f26fe90c..8f37735c812 100644 --- a/erpnext/agriculture/doctype/crop/crop_dashboard.py +++ b/erpnext/agriculture/doctype/crop/crop_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Crop Cycle'] } ] - } \ No newline at end of file + } diff --git a/erpnext/agriculture/doctype/crop/test_crop.js b/erpnext/agriculture/doctype/crop/test_crop.js index 138acbf85a3..40555634a2c 100644 --- a/erpnext/agriculture/doctype/crop/test_crop.js +++ b/erpnext/agriculture/doctype/crop/test_crop.js @@ -105,7 +105,7 @@ QUnit.test("test: Crop", function (assert) { ] ]} ]), - // agriculture task list + // agriculture task list () => { assert.equal(cur_frm.doc.name, 'Basil from seed'); assert.equal(cur_frm.doc.period, 15); diff --git a/erpnext/agriculture/doctype/crop/test_crop.py b/erpnext/agriculture/doctype/crop/test_crop.py index c2e49174047..b3079837c35 100644 --- a/erpnext/agriculture/doctype/crop/test_crop.py +++ b/erpnext/agriculture/doctype/crop/test_crop.py @@ -11,4 +11,4 @@ test_dependencies = ["Fertilizer"] class TestCrop(unittest.TestCase): def test_crop_period(self): basil = frappe.get_doc('Crop', 'Basil from seed') - self.assertEqual(basil.period, 15) \ No newline at end of file + self.assertEqual(basil.period, 15) diff --git a/erpnext/agriculture/doctype/crop_cycle/test_crop_cycle.js b/erpnext/agriculture/doctype/crop_cycle/test_crop_cycle.js index 464a3680baa..87184daedc9 100644 --- a/erpnext/agriculture/doctype/crop_cycle/test_crop_cycle.js +++ b/erpnext/agriculture/doctype/crop_cycle/test_crop_cycle.js @@ -19,7 +19,7 @@ QUnit.test("test: Crop Cycle", function (assert) { {disease: 'Aphids'} ] ]}, - {linked_land_unit: [ + {linked_land_unit: [ [ {land_unit: 'Basil Farm'} ] diff --git a/erpnext/agriculture/doctype/disease/disease.py b/erpnext/agriculture/doctype/disease/disease.py index c7707a54652..affa57046e5 100644 --- a/erpnext/agriculture/doctype/disease/disease.py +++ b/erpnext/agriculture/doctype/disease/disease.py @@ -17,4 +17,4 @@ class Disease(Document): frappe.throw(_("Start day is greater than end day in task '{0}'").format(task.task_name)) # to calculate the period of the Crop Cycle if task.end_day > max_period: max_period = task.end_day - self.treatment_period = max_period \ No newline at end of file + self.treatment_period = max_period diff --git a/erpnext/agriculture/doctype/disease/test_disease.js b/erpnext/agriculture/doctype/disease/test_disease.js index 57d62c16c25..33f60c4e152 100644 --- a/erpnext/agriculture/doctype/disease/test_disease.js +++ b/erpnext/agriculture/doctype/disease/test_disease.js @@ -36,4 +36,3 @@ QUnit.test("test: Disease", function (assert) { ]); }); - diff --git a/erpnext/agriculture/doctype/disease/test_disease.py b/erpnext/agriculture/doctype/disease/test_disease.py index 54788a2c817..80861770b0d 100644 --- a/erpnext/agriculture/doctype/disease/test_disease.py +++ b/erpnext/agriculture/doctype/disease/test_disease.py @@ -9,4 +9,4 @@ import unittest class TestDisease(unittest.TestCase): def test_treatment_period(self): disease = frappe.get_doc('Disease', 'Aphids') - self.assertEqual(disease.treatment_period, 3) \ No newline at end of file + self.assertEqual(disease.treatment_period, 3) diff --git a/erpnext/agriculture/doctype/fertilizer/fertilizer.py b/erpnext/agriculture/doctype/fertilizer/fertilizer.py index 9cb492aff1e..c475f002981 100644 --- a/erpnext/agriculture/doctype/fertilizer/fertilizer.py +++ b/erpnext/agriculture/doctype/fertilizer/fertilizer.py @@ -11,4 +11,4 @@ class Fertilizer(Document): def load_contents(self): docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Fertilizer'}) for doc in docs: - self.append('fertilizer_contents', {'title': str(doc.name)}) \ No newline at end of file + self.append('fertilizer_contents', {'title': str(doc.name)}) diff --git a/erpnext/agriculture/doctype/fertilizer/test_fertilizer.py b/erpnext/agriculture/doctype/fertilizer/test_fertilizer.py index 3a25b3f0a7a..4c71d33fe80 100644 --- a/erpnext/agriculture/doctype/fertilizer/test_fertilizer.py +++ b/erpnext/agriculture/doctype/fertilizer/test_fertilizer.py @@ -8,4 +8,4 @@ import unittest class TestFertilizer(unittest.TestCase): def test_fertilizer_creation(self): - self.assertEqual(frappe.db.exists('Fertilizer', 'Urea'), 'Urea') \ No newline at end of file + self.assertEqual(frappe.db.exists('Fertilizer', 'Urea'), 'Urea') diff --git a/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py b/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py index 2806cc6523e..b65f93de0a0 100644 --- a/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py +++ b/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py @@ -12,4 +12,4 @@ class PlantAnalysis(Document): def load_contents(self): docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Plant Analysis'}) for doc in docs: - self.append('plant_analysis_criteria', {'title': str(doc.name)}) \ No newline at end of file + self.append('plant_analysis_criteria', {'title': str(doc.name)}) diff --git a/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py b/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py index 37835f8c7b1..234d0d4b011 100644 --- a/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py +++ b/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py @@ -11,4 +11,4 @@ class SoilAnalysis(Document): def load_contents(self): docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Soil Analysis'}) for doc in docs: - self.append('soil_analysis_criteria', {'title': str(doc.name)}) \ No newline at end of file + self.append('soil_analysis_criteria', {'title': str(doc.name)}) diff --git a/erpnext/agriculture/doctype/soil_texture/test_soil_texture.py b/erpnext/agriculture/doctype/soil_texture/test_soil_texture.py index 937c06ccadf..16d105c9c58 100644 --- a/erpnext/agriculture/doctype/soil_texture/test_soil_texture.py +++ b/erpnext/agriculture/doctype/soil_texture/test_soil_texture.py @@ -11,4 +11,4 @@ class TestSoilTexture(unittest.TestCase): soil_tex = frappe.get_all('Soil Texture', fields=['name'], filters={'collection_datetime': '2017-11-08'}) doc = frappe.get_doc('Soil Texture', soil_tex[0].name) self.assertEqual(doc.silt_composition, 50) - self.assertEqual(doc.soil_type, 'Silt Loam') \ No newline at end of file + self.assertEqual(doc.soil_type, 'Silt Loam') diff --git a/erpnext/agriculture/doctype/water_analysis/water_analysis.py b/erpnext/agriculture/doctype/water_analysis/water_analysis.py index d9f007cea13..cb2691d4555 100644 --- a/erpnext/agriculture/doctype/water_analysis/water_analysis.py +++ b/erpnext/agriculture/doctype/water_analysis/water_analysis.py @@ -24,4 +24,4 @@ class WaterAnalysis(Document): if self.collection_datetime > self.laboratory_testing_datetime: frappe.throw(_('Lab testing datetime cannot be before collection datetime')) if self.laboratory_testing_datetime > self.result_datetime: - frappe.throw(_('Lab result datetime cannot be before testing datetime')) \ No newline at end of file + frappe.throw(_('Lab result datetime cannot be before testing datetime')) diff --git a/erpnext/agriculture/setup.py b/erpnext/agriculture/setup.py index ab91343d5d1..75f07be5de2 100644 --- a/erpnext/agriculture/setup.py +++ b/erpnext/agriculture/setup.py @@ -426,5 +426,5 @@ def create_agriculture_data(): title='Degree Days', standard=1, linked_doctype='Weather') - ] + ] insert_record(records) diff --git a/erpnext/assets/dashboard_fixtures.py b/erpnext/assets/dashboard_fixtures.py index 7f3c1de406a..2c701796072 100644 --- a/erpnext/assets/dashboard_fixtures.py +++ b/erpnext/assets/dashboard_fixtures.py @@ -176,4 +176,4 @@ def get_number_cards(fiscal_year, year_start_date, year_end_date): "filters_json": "[]", "doctype": "Number Card" } - ] \ No newline at end of file + ] diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 6f1bb28f370..da5778ea3d5 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -82,24 +82,46 @@ frappe.ui.form.on('Asset', { if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) { frm.add_custom_button("Transfer Asset", function() { erpnext.asset.transfer_asset(frm); - }); + }, __("Manage")); frm.add_custom_button("Scrap Asset", function() { erpnext.asset.scrap_asset(frm); - }); + }, __("Manage")); frm.add_custom_button("Sell Asset", function() { frm.trigger("make_sales_invoice"); - }); + }, __("Manage")); } else if (frm.doc.status=='Scrapped') { frm.add_custom_button("Restore Asset", function() { erpnext.asset.restore_asset(frm); - }); + }, __("Manage")); + } + + if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) { + frm.add_custom_button(__("Maintain Asset"), function() { + frm.trigger("create_asset_maintenance"); + }, __("Manage")); + } + + frm.add_custom_button(__("Repair Asset"), function() { + frm.trigger("create_asset_repair"); + }, __("Manage")); + + if (frm.doc.status != 'Fully Depreciated') { + frm.add_custom_button(__("Adjust Asset Value"), function() { + frm.trigger("create_asset_adjustment"); + }, __("Manage")); + } + + if (!frm.doc.calculate_depreciation) { + frm.add_custom_button(__("Create Depreciation Entry"), function() { + frm.trigger("make_journal_entry"); + }, __("Manage")); } if (frm.doc.purchase_receipt || !frm.doc.is_existing_asset) { - frm.add_custom_button("General Ledger", function() { + frm.add_custom_button("View General Ledger", function() { frappe.route_options = { "voucher_no": frm.doc.name, "from_date": frm.doc.available_for_use_date, @@ -107,27 +129,9 @@ frappe.ui.form.on('Asset', { "company": frm.doc.company }; frappe.set_route("query-report", "General Ledger"); - }); + }, __("Manage")); } - if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) { - frm.add_custom_button(__("Asset Maintenance"), function() { - frm.trigger("create_asset_maintenance"); - }, __('Create')); - } - if (frm.doc.status != 'Fully Depreciated') { - frm.add_custom_button(__("Asset Value Adjustment"), function() { - frm.trigger("create_asset_adjustment"); - }, __('Create')); - } - - if (!frm.doc.calculate_depreciation) { - frm.add_custom_button(__("Depreciation Entry"), function() { - frm.trigger("make_journal_entry"); - }, __('Create')); - } - - frm.page.set_inner_btn_group_as_primary(__('Create')); frm.trigger("setup_chart"); } @@ -304,6 +308,20 @@ frappe.ui.form.on('Asset', { }) }, + create_asset_repair: function(frm) { + frappe.call({ + args: { + "asset": frm.doc.name, + "asset_name": frm.doc.asset_name + }, + method: "erpnext.assets.doctype.asset.asset.create_asset_repair", + callback: function(r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); + }, + create_asset_adjustment: function(frm) { frappe.call({ args: { diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 421b9a6c378..de060757e2e 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -502,7 +502,7 @@ "link_fieldname": "asset" } ], - "modified": "2021-01-22 12:38:59.091510", + "modified": "2021-06-24 14:58:51.097908", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 8799275fc4e..d955430f23c 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -168,17 +168,24 @@ class Asset(AccountsController): d.precision("rate_of_depreciation")) def make_depreciation_schedule(self): - if 'Manual' not in [d.depreciation_method for d in self.finance_books]: + if 'Manual' not in [d.depreciation_method for d in self.finance_books] and not self.schedules: self.schedules = [] - if self.get("schedules") or not self.available_for_use_date: + if not self.available_for_use_date: return for d in self.get('finance_books'): self.validate_asset_finance_books(d) - value_after_depreciation = (flt(self.gross_purchase_amount) - - flt(self.opening_accumulated_depreciation)) + start = self.clear_depreciation_schedule() + + # value_after_depreciation - current Asset value + if d.value_after_depreciation: + value_after_depreciation = (flt(d.value_after_depreciation) - + flt(self.opening_accumulated_depreciation)) + else: + value_after_depreciation = (flt(self.gross_purchase_amount) - + flt(self.opening_accumulated_depreciation)) d.value_after_depreciation = value_after_depreciation @@ -191,7 +198,7 @@ class Asset(AccountsController): number_of_pending_depreciations += 1 skip_row = False - for n in range(number_of_pending_depreciations): + for n in range(start, number_of_pending_depreciations): # If depreciation is already completed (for double declining balance) if skip_row: continue @@ -216,11 +223,13 @@ class Asset(AccountsController): # For last row elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1: - to_date = add_months(self.available_for_use_date, - n * cint(d.frequency_of_depreciation)) + if not self.flags.increase_in_asset_life: + # In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission + self.to_date = add_months(self.available_for_use_date, + n * cint(d.frequency_of_depreciation)) depreciation_amount, days, months = self.get_pro_rata_amt(d, - depreciation_amount, schedule_date, to_date) + depreciation_amount, schedule_date, self.to_date) monthly_schedule_date = add_months(schedule_date, 1) @@ -284,10 +293,23 @@ class Asset(AccountsController): "finance_book_id": d.idx }) + # used when depreciation schedule needs to be modified due to increase in asset life + def clear_depreciation_schedule(self): + start = 0 + for n in range(len(self.schedules)): + if not self.schedules[n].journal_entry: + del self.schedules[n:] + start = n + break + return start + + + # if it returns True, depreciation_amount will not be equal for the first and last rows def check_is_pro_rata(self, row): has_pro_rata = False - days = date_diff(row.depreciation_start_date, self.available_for_use_date) + 1 + + # if frequency_of_depreciation is 12 months, total_days = 365 total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation) if days < total_days: @@ -346,11 +368,12 @@ class Asset(AccountsController): if d.finance_book_id not in finance_books: accumulated_depreciation = flt(self.opening_accumulated_depreciation) value_after_depreciation = flt(self.get_value_after_depreciation(d.finance_book_id)) - finance_books.append(d.finance_book_id) + finance_books.append(int(d.finance_book_id)) depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount")) value_after_depreciation -= flt(depreciation_amount) + # for the last row, if depreciation method = Straight Line if straight_line_idx and i == max(straight_line_idx) - 1: book = self.get('finance_books')[cint(d.finance_book_id) - 1] depreciation_amount += flt(value_after_depreciation - @@ -625,9 +648,18 @@ def create_asset_maintenance(asset, item_code, item_name, asset_category, compan }) return asset_maintenance +@frappe.whitelist() +def create_asset_repair(asset, asset_name): + asset_repair = frappe.new_doc("Asset Repair") + asset_repair.update({ + "asset": asset, + "asset_name": asset_name + }) + return asset_repair + @frappe.whitelist() def create_asset_adjustment(asset, asset_category, company): - asset_maintenance = frappe.new_doc("Asset Value Adjustment") + asset_maintenance = frappe.get_doc("Asset Value Adjustment") asset_maintenance.update({ "asset": asset, "company": company, @@ -757,9 +789,16 @@ def get_depreciation_amount(asset, depreciable_value, row): depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) if row.depreciation_method in ("Straight Line", "Manual"): - depreciation_amount = (flt(row.value_after_depreciation) - - flt(row.expected_value_after_useful_life)) / depreciation_left + # if the Depreciation Schedule is being prepared for the first time + if not asset.flags.increase_in_asset_life: + depreciation_amount = (flt(row.value_after_depreciation) - + flt(row.expected_value_after_useful_life)) / depreciation_left + + # if the Depreciation Schedule is being modified after Asset Repair + else: + depreciation_amount = (flt(row.value_after_depreciation) - + flt(row.expected_value_after_useful_life)) / (date_diff(asset.to_date, asset.available_for_use_date) / 365) else: depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100)) - return depreciation_amount \ No newline at end of file + return depreciation_amount diff --git a/erpnext/assets/doctype/asset/asset_dashboard.py b/erpnext/assets/doctype/asset/asset_dashboard.py index a5cf23803d2..62bb4be53aa 100644 --- a/erpnext/assets/doctype/asset/asset_dashboard.py +++ b/erpnext/assets/doctype/asset/asset_dashboard.py @@ -11,4 +11,4 @@ def get_data(): 'items': ['Asset Movement'] } ] - } \ No newline at end of file + } diff --git a/erpnext/assets/doctype/asset/asset_list.js b/erpnext/assets/doctype/asset/asset_list.js index 02f39e0e7f4..4302cb2c518 100644 --- a/erpnext/assets/doctype/asset/asset_list.js +++ b/erpnext/assets/doctype/asset/asset_list.js @@ -50,4 +50,4 @@ frappe.listview_settings['Asset'] = { }); }); }, -} \ No newline at end of file +} diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 8845f24d104..605ce2e2503 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -125,7 +125,6 @@ class TestAsset(unittest.TestCase): "frequency_of_depreciation": 12, "depreciation_start_date": "2030-12-31" }) - asset.insert() self.assertEqual(asset.status, "Draft") asset.save() expected_schedules = [ @@ -154,9 +153,8 @@ class TestAsset(unittest.TestCase): "frequency_of_depreciation": 12, "depreciation_start_date": '2030-12-31' }) - asset.insert() - self.assertEqual(asset.status, "Draft") asset.save() + self.assertEqual(asset.status, "Draft") expected_schedules = [ ['2030-12-31', 66667.00, 66667.00], @@ -185,7 +183,7 @@ class TestAsset(unittest.TestCase): "frequency_of_depreciation": 12, "depreciation_start_date": "2030-12-31" }) - asset.insert() + asset.save() self.assertEqual(asset.status, "Draft") expected_schedules = [ @@ -216,7 +214,6 @@ class TestAsset(unittest.TestCase): "depreciation_start_date": "2030-12-31" }) - asset.insert() asset.save() expected_schedules = [ @@ -247,7 +244,6 @@ class TestAsset(unittest.TestCase): "frequency_of_depreciation": 10, "depreciation_start_date": "2020-12-31" }) - asset.insert() asset.submit() asset.load_from_db() self.assertEqual(asset.status, "Submitted") @@ -350,7 +346,6 @@ class TestAsset(unittest.TestCase): "frequency_of_depreciation": 10, "depreciation_start_date": "2020-12-31" }) - asset.insert() asset.submit() post_depreciation_entries(date="2021-01-01") @@ -380,7 +375,6 @@ class TestAsset(unittest.TestCase): "total_number_of_depreciations": 10, "frequency_of_depreciation": 1 }) - asset.insert() asset.submit() post_depreciation_entries(date=add_months('2020-01-01', 4)) @@ -424,7 +418,6 @@ class TestAsset(unittest.TestCase): "frequency_of_depreciation": 10, "depreciation_start_date": "2020-12-31" }) - asset.insert() asset.submit() post_depreciation_entries(date="2021-01-01") @@ -468,7 +461,7 @@ class TestAsset(unittest.TestCase): "total_number_of_depreciations": 3, "frequency_of_depreciation": 10 }) - asset.insert() + asset.save() accumulated_depreciation_after_full_schedule = \ max(d.accumulated_depreciation_amount for d in asset.get("schedules")) @@ -646,7 +639,7 @@ class TestAsset(unittest.TestCase): asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-06-12' + asset.available_for_use_date = '2030-07-12' asset.purchase_date = '2030-01-01' asset.append("finance_books", { "expected_value_after_useful_life": 1000, @@ -660,10 +653,10 @@ class TestAsset(unittest.TestCase): self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) expected_schedules = [ - ["2030-12-31", 1106.85, 1106.85], - ["2031-12-31", 3446.58, 4553.43], - ["2032-12-31", 1723.29, 6276.72], - ["2033-06-12", 723.28, 7000.00] + ["2030-12-31", 942.47, 942.47], + ["2031-12-31", 3528.77, 4471.24], + ["2032-12-31", 1764.38, 6235.62], + ["2033-07-12", 764.38, 7000.00] ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] @@ -699,7 +692,7 @@ def create_asset(**args): "item_code": args.item_code or "Macbook Pro", "company": args.company or"_Test Company", "purchase_date": "2015-01-01", - "calculate_depreciation": 0, + "calculate_depreciation": args.calculate_depreciation or 0, "gross_purchase_amount": 100000, "purchase_receipt_amount": 100000, "expected_value_after_useful_life": 10000, @@ -707,9 +700,16 @@ def create_asset(**args): "available_for_use_date": "2020-06-06", "location": "Test Location", "asset_owner": "Company", - "is_existing_asset": args.is_existing_asset or 0 + "is_existing_asset": 1 }) + if asset.calculate_depreciation: + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 5 + }) + try: asset.save() except frappe.DuplicateEntryError: @@ -763,4 +763,4 @@ def set_depreciation_settings_in_company(): company.save() # Enable booking asset depreciation entry automatically - frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) \ No newline at end of file + frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index 46620d56e98..39032d637b5 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -20,7 +20,7 @@ class AssetCategory(Document): for field in ("Total Number of Depreciations", "Frequency of Depreciation"): if cint(d.get(frappe.scrub(field)))<1: frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError) - + def validate_account_currency(self): account_types = [ 'fixed_asset_account', 'accumulated_depreciation_account', 'depreciation_expense_account', 'capital_work_in_progress_account' @@ -33,13 +33,13 @@ class AssetCategory(Document): account_currency = frappe.get_value("Account", d.get(type_of_account), "account_currency") if account_currency != company_currency: invalid_accounts.append(frappe._dict({ 'type': type_of_account, 'idx': d.idx, 'account': d.get(type_of_account) })) - + for d in invalid_accounts: frappe.throw(_("Row #{}: Currency of {} - {} doesn't matches company currency.") .format(d.idx, frappe.bold(frappe.unscrub(d.type)), frappe.bold(d.account)), title=_("Invalid Account")) - + def validate_account_types(self): account_type_map = { 'fixed_asset_account': { 'account_type': 'Fixed Asset' }, @@ -59,12 +59,12 @@ class AssetCategory(Document): frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.") .format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)), title=_("Invalid Account")) - + def valide_cwip_account(self): if self.enable_cwip_accounting: missing_cwip_accounts_for_company = [] for d in self.accounts: - if (not d.capital_work_in_progress_account and + if (not d.capital_work_in_progress_account and not frappe.db.get_value("Company", d.company_name, "capital_work_in_progress_account")): missing_cwip_accounts_for_company.append(get_link_to_form("Company", d.company_name)) @@ -93,4 +93,4 @@ def get_asset_category_account(fieldname, item=None, asset=None, account=None, a account = frappe.db.get_value("Asset Category Account", filters={"parent": asset_category, "company_name": company}, fieldname=fieldname) - return account \ No newline at end of file + return account diff --git a/erpnext/assets/doctype/asset_category/test_asset_category.py b/erpnext/assets/doctype/asset_category/test_asset_category.py index 39b79d6c507..9f7ada65d82 100644 --- a/erpnext/assets/doctype/asset_category/test_asset_category.py +++ b/erpnext/assets/doctype/asset_category/test_asset_category.py @@ -10,9 +10,9 @@ class TestAssetCategory(unittest.TestCase): def test_mandatory_fields(self): asset_category = frappe.new_doc("Asset Category") asset_category.asset_category_name = "Computers" - + self.assertRaises(frappe.MandatoryError, asset_category.insert) - + asset_category.total_number_of_depreciations = 3 asset_category.frequency_of_depreciation = 3 asset_category.append("accounts", { @@ -21,7 +21,7 @@ class TestAssetCategory(unittest.TestCase): "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC", "depreciation_expense_account": "_Test Depreciations - _TC" }) - + try: asset_category.insert() except frappe.DuplicateEntryError: @@ -44,4 +44,4 @@ class TestAssetCategory(unittest.TestCase): "depreciation_expense_account": "_Test Depreciations - _TC" }) - self.assertRaises(frappe.ValidationError, asset_category.insert) \ No newline at end of file + self.assertRaises(frappe.ValidationError, asset_category.insert) diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json index d9b7b695f7f..e5a5f194c1b 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -67,7 +67,6 @@ { "fieldname": "value_after_depreciation", "fieldtype": "Currency", - "hidden": 1, "label": "Value After Depreciation", "no_copy": 1, "options": "Company:company:default_currency", @@ -85,7 +84,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-11-05 16:30:09.213479", + "modified": "2021-06-17 12:59:05.743683", "modified_by": "Administrator", "module": "Assets", "name": "Asset Finance Book", diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js index 70b8654509f..52996e93475 100644 --- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js @@ -97,4 +97,4 @@ var get_next_due_date = function (frm, cdt, cdn) { } }); } -}; \ No newline at end of file +}; diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py index a506deec93e..e14f1d88dcb 100644 --- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py @@ -116,4 +116,4 @@ def get_maintenance_log(asset_name): select maintenance_status, count(asset_name) as count, asset_name from `tabAsset Maintenance Log` where asset_name=%s group by maintenance_status""", - (asset_name), as_dict=1) \ No newline at end of file + (asset_name), as_dict=1) diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py index 392fbdd2af7..7610152039d 100644 --- a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py @@ -73,7 +73,7 @@ def create_asset_data(): 'doctype': 'Location', 'location_name': 'Test Location' }).insert() - + if not frappe.db.exists("Item", "Photocopier"): meta = frappe.get_meta('Asset') naming_series = meta.get_field("naming_series").options @@ -157,6 +157,6 @@ def set_depreciation_settings_in_company(): company.disposal_account = "_Test Gain/Loss on Asset Disposal - _TC" company.depreciation_cost_center = "_Test Cost Center - _TC" company.save() - + # Enable booking asset depreciation entry automatically - frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) \ No newline at end of file + frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.js b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.js index c5db90ad370..bcdc3acf0ac 100644 --- a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.js +++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.js @@ -12,4 +12,4 @@ frappe.ui.form.on('Asset Maintenance Log', { }; }); } -}); \ No newline at end of file +}); diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.js b/erpnext/assets/doctype/asset_movement/asset_movement.js index 06d8879091c..2df7db97446 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.js +++ b/erpnext/assets/doctype/asset_movement/asset_movement.js @@ -99,4 +99,4 @@ frappe.ui.form.on('Asset Movement Item', { }); } } -}); \ No newline at end of file +}); diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py index b2de250b168..1771e27ddfe 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -40,14 +40,14 @@ class AssetMovement(Document): if current_location != d.source_location: frappe.throw(_("Asset {0} does not belongs to the location {1}"). format(d.asset, d.source_location)) - + if self.purpose == 'Issue': if d.target_location: frappe.throw(_("Issuing cannot be done to a location. \ Please enter employee who has issued Asset {0}").format(d.asset), title="Incorrect Movement Purpose") if not d.to_employee: frappe.throw(_("Employee is required while issuing Asset {0}").format(d.asset)) - + if self.purpose == 'Transfer': if d.to_employee: frappe.throw(_("Transferring cannot be done to an Employee. \ @@ -57,7 +57,7 @@ class AssetMovement(Document): frappe.throw(_("Target Location is required while transferring Asset {0}").format(d.asset)) if d.source_location == d.target_location: frappe.throw(_("Source and Target Location cannot be same")) - + if self.purpose == 'Receipt': # only when asset is bought and first entry is made if not d.source_location and not (d.target_location or d.to_employee): @@ -80,14 +80,14 @@ class AssetMovement(Document): if current_custodian != d.from_employee: frappe.throw(_("Asset {0} does not belongs to the custodian {1}"). format(d.asset, d.from_employee)) - + if d.to_employee and frappe.db.get_value("Employee", d.to_employee, "company") != self.company: frappe.throw(_("Employee {0} does not belongs to the company {1}"). format(d.to_employee, self.company)) def on_submit(self): self.set_latest_location_in_asset() - + def on_cancel(self): self.set_latest_location_in_asset() @@ -105,12 +105,12 @@ class AssetMovement(Document): # In case of cancellation it corresponds to previous latest document's location, employee latest_movement_entry = frappe.db.sql( """ - SELECT asm_item.target_location, asm_item.to_employee + SELECT asm_item.target_location, asm_item.to_employee FROM `tabAsset Movement Item` asm_item, `tabAsset Movement` asm - WHERE + WHERE asm_item.parent=asm.name and asm_item.asset=%(asset)s and - asm.company=%(company)s and + asm.company=%(company)s and asm.docstatus=1 and {0} ORDER BY asm.transaction_date desc limit 1 diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py index cddee5fa0f1..2b2d2b44004 100644 --- a/erpnext/assets/doctype/asset_movement/test_asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py @@ -15,6 +15,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu class TestAssetMovement(unittest.TestCase): def setUp(self): + frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC") create_asset_data() make_location() @@ -45,12 +46,12 @@ class TestAssetMovement(unittest.TestCase): 'location_name': 'Test Location 2' }).insert() - movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, + movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}], reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") - movement2 = create_asset_movement(purpose = 'Transfer', company = asset.company, + create_asset_movement(purpose = 'Transfer', company = asset.company, assets = [{ 'asset': asset.name , 'source_location': 'Test Location 2', 'target_location': 'Test Location'}], reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") @@ -59,18 +60,18 @@ class TestAssetMovement(unittest.TestCase): self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") employee = make_employee("testassetmovemp@example.com", company="_Test Company") - movement3 = create_asset_movement(purpose = 'Issue', company = asset.company, + create_asset_movement(purpose = 'Issue', company = asset.company, assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'to_employee': employee}], reference_doctype = 'Purchase Receipt', reference_name = pr.name) - + # after issuing asset should belong to an employee not at a location self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None) self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee) - + def test_last_movement_cancellation(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") - + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 @@ -85,17 +86,17 @@ class TestAssetMovement(unittest.TestCase): }) if asset.docstatus == 0: asset.submit() - + if not frappe.db.exists("Location", "Test Location 2"): frappe.get_doc({ 'doctype': 'Location', 'location_name': 'Test Location 2' }).insert() - + movement = frappe.get_doc({'doctype': 'Asset Movement', 'reference_name': pr.name }) self.assertRaises(frappe.ValidationError, movement.cancel) - movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, + movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}], reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js index 4ba2b4474a9..18a56d33e6d 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.js +++ b/erpnext/assets/doctype/asset_repair/asset_repair.js @@ -2,6 +2,45 @@ // For license information, please see license.txt frappe.ui.form.on('Asset Repair', { + setup: function(frm) { + frm.fields_dict.cost_center.get_query = function(doc) { + return { + filters: { + 'is_group': 0, + 'company': doc.company + } + }; + }; + + frm.fields_dict.project.get_query = function(doc) { + return { + filters: { + 'company': doc.company + } + }; + }; + + frm.fields_dict.warehouse.get_query = function(doc) { + return { + filters: { + 'is_group': 0, + 'company': doc.company + } + }; + }; + }, + + refresh: function(frm) { + if (frm.doc.docstatus) { + frm.add_custom_button("View General Ledger", function() { + frappe.route_options = { + "voucher_no": frm.doc.name + }; + frappe.set_route("query-report", "General Ledger"); + }); + } + }, + repair_status: (frm) => { if (frm.doc.completion_date && frm.doc.repair_status == "Completed") { frappe.call ({ @@ -17,5 +56,16 @@ frappe.ui.form.on('Asset Repair', { } }); } + + if (frm.doc.repair_status == "Completed") { + frm.set_value('completion_date', frappe.datetime.now_datetime()); + } } }); + +frappe.ui.form.on('Asset Repair Consumed Item', { + consumed_quantity: function(frm, cdt, cdn) { + var row = locals[cdt][cdn]; + frappe.model.set_value(cdt, cdn, 'total_value', row.consumed_quantity * row.valuation_rate); + }, +}); diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.json b/erpnext/assets/doctype/asset_repair/asset_repair.json index d338fc0fb79..19528a26ccd 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.json +++ b/erpnext/assets/doctype/asset_repair/asset_repair.json @@ -7,38 +7,43 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "naming_series", - "asset_name", + "asset", + "company", "column_break_2", - "item_code", - "item_name", + "asset_name", + "naming_series", "section_break_5", "failure_date", - "assign_to", - "assign_to_name", + "repair_status", "column_break_6", "completion_date", - "repair_status", + "accounting_dimensions_section", + "cost_center", + "column_break_14", + "project", + "accounting_details", "repair_cost", + "capitalize_repair_cost", + "stock_consumption", + "column_break_8", + "purchase_invoice", + "stock_consumption_details_section", + "warehouse", + "stock_items", + "total_repair_cost", + "stock_entry", + "asset_depreciation_details_section", + "increase_in_asset_life", "section_break_9", "description", "column_break_9", "actions_performed", - "section_break_17", + "section_break_23", "downtime", "column_break_19", "amended_from" ], "fields": [ - { - "columns": 1, - "fieldname": "asset_name", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Asset", - "options": "Asset", - "reqd": 1 - }, { "fieldname": "naming_series", "fieldtype": "Select", @@ -50,18 +55,6 @@ "fieldname": "column_break_2", "fieldtype": "Column Break" }, - { - "fetch_from": "asset_name.item_code", - "fieldname": "item_code", - "fieldtype": "Read Only", - "label": "Item Code" - }, - { - "fetch_from": "asset_name.item_name", - "fieldname": "item_name", - "fieldtype": "Read Only", - "label": "Item Name" - }, { "fieldname": "section_break_5", "fieldtype": "Section Break", @@ -74,33 +67,20 @@ "label": "Failure Date", "reqd": 1 }, - { - "allow_on_submit": 1, - "fieldname": "assign_to", - "fieldtype": "Link", - "label": "Assign To", - "options": "User" - }, - { - "allow_on_submit": 1, - "fetch_from": "assign_to.full_name", - "fieldname": "assign_to_name", - "fieldtype": "Read Only", - "label": "Assign To Name" - }, { "fieldname": "column_break_6", "fieldtype": "Column Break" }, { - "allow_on_submit": 1, + "depends_on": "eval:!doc.__islocal", "fieldname": "completion_date", "fieldtype": "Datetime", - "label": "Completion Date" + "label": "Completion Date", + "no_copy": 1 }, { - "allow_on_submit": 1, "default": "Pending", + "depends_on": "eval:!doc.__islocal", "fieldname": "repair_status", "fieldtype": "Select", "label": "Repair Status", @@ -116,25 +96,18 @@ { "fieldname": "description", "fieldtype": "Long Text", - "label": "Error Description", - "reqd": 1 + "label": "Error Description" }, { "fieldname": "column_break_9", "fieldtype": "Column Break" }, { - "allow_on_submit": 1, "fieldname": "actions_performed", "fieldtype": "Long Text", "label": "Actions performed" }, { - "fieldname": "section_break_17", - "fieldtype": "Section Break" - }, - { - "allow_on_submit": 1, "fieldname": "downtime", "fieldtype": "Data", "in_list_view": 1, @@ -146,7 +119,7 @@ "fieldtype": "Column Break" }, { - "allow_on_submit": 1, + "default": "0", "fieldname": "repair_cost", "fieldtype": "Currency", "label": "Repair Cost" @@ -159,12 +132,138 @@ "options": "Asset Repair", "print_hide": 1, "read_only": 1 + }, + { + "columns": 1, + "fieldname": "asset", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Asset", + "options": "Asset", + "reqd": 1 + }, + { + "fetch_from": "asset.asset_name", + "fieldname": "asset_name", + "fieldtype": "Read Only", + "label": "Asset Name" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval:!doc.__islocal", + "fieldname": "capitalize_repair_cost", + "fieldtype": "Check", + "label": "Capitalize Repair Cost" + }, + { + "fieldname": "accounting_details", + "fieldtype": "Section Break", + "label": "Accounting Details" + }, + { + "fieldname": "stock_items", + "fieldtype": "Table", + "label": "Stock Items", + "mandatory_depends_on": "stock_consumption", + "options": "Asset Repair Consumed Item" + }, + { + "fieldname": "section_break_23", + "fieldtype": "Section Break" + }, + { + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval:!doc.__islocal", + "fieldname": "stock_consumption", + "fieldtype": "Check", + "label": "Stock Consumed During Repair" + }, + { + "depends_on": "stock_consumption", + "fieldname": "stock_consumption_details_section", + "fieldtype": "Section Break", + "label": "Stock Consumption Details" + }, + { + "depends_on": "eval: doc.stock_consumption && doc.total_repair_cost > 0", + "description": "Sum of Repair Cost and Value of Consumed Stock Items.", + "fieldname": "total_repair_cost", + "fieldtype": "Currency", + "label": "Total Repair Cost", + "read_only": 1 + }, + { + "depends_on": "stock_consumption", + "fieldname": "warehouse", + "fieldtype": "Link", + "label": "Warehouse", + "options": "Warehouse" + }, + { + "depends_on": "capitalize_repair_cost", + "fieldname": "asset_depreciation_details_section", + "fieldtype": "Section Break", + "label": "Asset Depreciation Details" + }, + { + "fieldname": "increase_in_asset_life", + "fieldtype": "Int", + "label": "Increase In Asset Life(Months)", + "no_copy": 1 + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "purchase_invoice", + "fieldtype": "Link", + "label": "Purchase Invoice", + "mandatory_depends_on": "eval: doc.repair_status == 'Completed' && doc.repair_cost > 0", + "no_copy": 1, + "options": "Purchase Invoice" + }, + { + "fetch_from": "asset.company", + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + }, + { + "fieldname": "stock_entry", + "fieldtype": "Link", + "label": "Stock Entry", + "options": "Stock Entry", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-01-22 15:08:12.495850", + "modified": "2021-06-25 13:14:38.307723", "modified_by": "Administrator", "module": "Assets", "name": "Asset Repair", @@ -203,6 +302,7 @@ ], "sort_field": "modified", "sort_order": "DESC", + "title_field": "asset_name", "track_changes": 1, "track_seen": 1 } \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 049b931b5e8..746f582fdcd 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -5,16 +5,252 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import time_diff_in_hours -from frappe.model.document import Document +from frappe.utils import time_diff_in_hours, getdate, add_months, flt, cint +from erpnext.accounts.general_ledger import make_gl_entries +from erpnext.assets.doctype.asset.asset import get_asset_account +from erpnext.controllers.accounts_controller import AccountsController -class AssetRepair(Document): +class AssetRepair(AccountsController): def validate(self): - if self.repair_status == "Completed" and not self.completion_date: - frappe.throw(_("Please select Completion Date for Completed Repair")) + self.asset_doc = frappe.get_doc('Asset', self.asset) + self.update_status() + if self.get('stock_items'): + self.set_total_value() + self.calculate_total_repair_cost() + + def update_status(self): + if self.repair_status == 'Pending': + frappe.db.set_value('Asset', self.asset, 'status', 'Out of Order') + else: + self.asset_doc.set_status() + + def set_total_value(self): + for item in self.get('stock_items'): + item.total_value = flt(item.valuation_rate) * flt(item.consumed_quantity) + + def calculate_total_repair_cost(self): + self.total_repair_cost = flt(self.repair_cost) + + total_value_of_stock_consumed = self.get_total_value_of_stock_consumed() + self.total_repair_cost += total_value_of_stock_consumed + + def before_submit(self): + self.check_repair_status() + + if self.get('stock_consumption') or self.get('capitalize_repair_cost'): + self.increase_asset_value() + if self.get('stock_consumption'): + self.check_for_stock_items_and_warehouse() + self.decrease_stock_quantity() + if self.get('capitalize_repair_cost'): + self.make_gl_entries() + if frappe.db.get_value('Asset', self.asset, 'calculate_depreciation') and self.increase_in_asset_life: + self.modify_depreciation_schedule() + + self.asset_doc.flags.ignore_validate_update_after_submit = True + self.asset_doc.prepare_depreciation_data() + self.asset_doc.save() + + def before_cancel(self): + self.asset_doc = frappe.get_doc('Asset', self.asset) + + if self.get('stock_consumption') or self.get('capitalize_repair_cost'): + self.decrease_asset_value() + if self.get('stock_consumption'): + self.increase_stock_quantity() + if self.get('capitalize_repair_cost'): + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') + self.make_gl_entries(cancel=True) + if frappe.db.get_value('Asset', self.asset, 'calculate_depreciation') and self.increase_in_asset_life: + self.revert_depreciation_schedule_on_cancellation() + + self.asset_doc.flags.ignore_validate_update_after_submit = True + self.asset_doc.prepare_depreciation_data() + self.asset_doc.save() + + def check_repair_status(self): + if self.repair_status == "Pending": + frappe.throw(_("Please update Repair Status.")) + + def check_for_stock_items_and_warehouse(self): + if not self.get('stock_items'): + frappe.throw(_("Please enter Stock Items consumed during the Repair."), title=_("Missing Items")) + if not self.warehouse: + frappe.throw(_("Please enter Warehouse from which Stock Items consumed during the Repair were taken."), title=_("Missing Warehouse")) + + def increase_asset_value(self): + total_value_of_stock_consumed = self.get_total_value_of_stock_consumed() + + if self.asset_doc.calculate_depreciation: + for row in self.asset_doc.finance_books: + row.value_after_depreciation += total_value_of_stock_consumed + + if self.capitalize_repair_cost: + row.value_after_depreciation += self.repair_cost + + def decrease_asset_value(self): + total_value_of_stock_consumed = self.get_total_value_of_stock_consumed() + + if self.asset_doc.calculate_depreciation: + for row in self.asset_doc.finance_books: + row.value_after_depreciation -= total_value_of_stock_consumed + + if self.capitalize_repair_cost: + row.value_after_depreciation -= self.repair_cost + + def get_total_value_of_stock_consumed(self): + total_value_of_stock_consumed = 0 + if self.get('stock_consumption'): + for item in self.get('stock_items'): + total_value_of_stock_consumed += item.total_value + + return total_value_of_stock_consumed + + def decrease_stock_quantity(self): + stock_entry = frappe.get_doc({ + "doctype": "Stock Entry", + "stock_entry_type": "Material Issue", + "company": self.company + }) + + for stock_item in self.get('stock_items'): + stock_entry.append('items', { + "s_warehouse": self.warehouse, + "item_code": stock_item.item, + "qty": stock_item.consumed_quantity, + "basic_rate": stock_item.valuation_rate + }) + + stock_entry.insert() + stock_entry.submit() + + self.db_set('stock_entry', stock_entry.name) + + def increase_stock_quantity(self): + stock_entry = frappe.get_doc('Stock Entry', self.stock_entry) + stock_entry.flags.ignore_links = True + stock_entry.cancel() + + def make_gl_entries(self, cancel=False): + if flt(self.repair_cost) > 0: + gl_entries = self.get_gl_entries() + make_gl_entries(gl_entries, cancel) + + def get_gl_entries(self): + gl_entries = [] + repair_and_maintenance_account = frappe.db.get_value('Company', self.company, 'repair_and_maintenance_account') + fixed_asset_account = get_asset_account("fixed_asset_account", asset=self.asset, company=self.company) + expense_account = frappe.get_doc('Purchase Invoice', self.purchase_invoice).items[0].expense_account + + gl_entries.append( + self.get_gl_dict({ + "account": expense_account, + "credit": self.repair_cost, + "credit_in_account_currency": self.repair_cost, + "against": repair_and_maintenance_account, + "voucher_type": self.doctype, + "voucher_no": self.name, + "cost_center": self.cost_center, + "posting_date": getdate(), + "company": self.company + }, item=self) + ) + + if self.get('stock_consumption'): + # creating GL Entries for each row in Stock Items based on the Stock Entry created for it + stock_entry = frappe.get_doc('Stock Entry', self.stock_entry) + for item in stock_entry.items: + gl_entries.append( + self.get_gl_dict({ + "account": item.expense_account, + "credit": item.amount, + "credit_in_account_currency": item.amount, + "against": repair_and_maintenance_account, + "voucher_type": self.doctype, + "voucher_no": self.name, + "cost_center": self.cost_center, + "posting_date": getdate(), + "company": self.company + }, item=self) + ) + + gl_entries.append( + self.get_gl_dict({ + "account": fixed_asset_account, + "debit": self.total_repair_cost, + "debit_in_account_currency": self.total_repair_cost, + "against": expense_account, + "voucher_type": self.doctype, + "voucher_no": self.name, + "cost_center": self.cost_center, + "posting_date": getdate(), + "against_voucher_type": "Purchase Invoice", + "against_voucher": self.purchase_invoice, + "company": self.company + }, item=self) + ) + + return gl_entries + + def modify_depreciation_schedule(self): + for row in self.asset_doc.finance_books: + row.total_number_of_depreciations += self.increase_in_asset_life/row.frequency_of_depreciation + + self.asset_doc.flags.increase_in_asset_life = False + extra_months = self.increase_in_asset_life % row.frequency_of_depreciation + if extra_months != 0: + self.calculate_last_schedule_date(self.asset_doc, row, extra_months) + + # to help modify depreciation schedule when increase_in_asset_life is not a multiple of frequency_of_depreciation + def calculate_last_schedule_date(self, asset, row, extra_months): + asset.flags.increase_in_asset_life = True + number_of_pending_depreciations = cint(row.total_number_of_depreciations) - \ + cint(asset.number_of_depreciations_booked) + + # the Schedule Date in the final row of the old Depreciation Schedule + last_schedule_date = asset.schedules[len(asset.schedules)-1].schedule_date + + # the Schedule Date in the final row of the new Depreciation Schedule + asset.to_date = add_months(last_schedule_date, extra_months) + + # the latest possible date at which the depreciation can occur, without increasing the Total Number of Depreciations + # if depreciations happen yearly and the Depreciation Posting Date is 01-01-2020, this could be 01-01-2021, 01-01-2022... + schedule_date = add_months(row.depreciation_start_date, + number_of_pending_depreciations * cint(row.frequency_of_depreciation)) + + if asset.to_date > schedule_date: + row.total_number_of_depreciations += 1 + + def revert_depreciation_schedule_on_cancellation(self): + for row in self.asset_doc.finance_books: + row.total_number_of_depreciations -= self.increase_in_asset_life/row.frequency_of_depreciation + + self.asset_doc.flags.increase_in_asset_life = False + extra_months = self.increase_in_asset_life % row.frequency_of_depreciation + if extra_months != 0: + self.calculate_last_schedule_date_before_modification(self.asset_doc, row, extra_months) + + def calculate_last_schedule_date_before_modification(self, asset, row, extra_months): + asset.flags.increase_in_asset_life = True + number_of_pending_depreciations = cint(row.total_number_of_depreciations) - \ + cint(asset.number_of_depreciations_booked) + + # the Schedule Date in the final row of the modified Depreciation Schedule + last_schedule_date = asset.schedules[len(asset.schedules)-1].schedule_date + + # the Schedule Date in the final row of the original Depreciation Schedule + asset.to_date = add_months(last_schedule_date, -extra_months) + + # the latest possible date at which the depreciation can occur, without decreasing the Total Number of Depreciations + # if depreciations happen yearly and the Depreciation Posting Date is 01-01-2020, this could be 01-01-2021, 01-01-2022... + schedule_date = add_months(row.depreciation_start_date, + (number_of_pending_depreciations - 1) * cint(row.frequency_of_depreciation)) + + if asset.to_date < schedule_date: + row.total_number_of_depreciations -= 1 @frappe.whitelist() def get_downtime(failure_date, completion_date): downtime = time_diff_in_hours(completion_date, failure_date) - return round(downtime, 2) \ No newline at end of file + return round(downtime, 2) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair_list.js b/erpnext/assets/doctype/asset_repair/asset_repair_list.js index f36fd2f8dcb..86376f40046 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair_list.js +++ b/erpnext/assets/doctype/asset_repair/asset_repair_list.js @@ -10,4 +10,3 @@ frappe.listview_settings['Asset Repair'] = { } } }; - diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 3d325a9683c..5e727d007a9 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -2,8 +2,167 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt from __future__ import unicode_literals - +import frappe +from frappe.utils import nowdate, flt import unittest +from erpnext.assets.doctype.asset.test_asset import create_asset_data, create_asset, set_depreciation_settings_in_company class TestAssetRepair(unittest.TestCase): - pass + def setUp(self): + set_depreciation_settings_in_company() + create_asset_data() + frappe.db.sql("delete from `tabTax Rule`") + + def test_update_status(self): + asset = create_asset() + initial_status = asset.status + asset_repair = create_asset_repair(asset = asset) + + if asset_repair.repair_status == "Pending": + asset.reload() + self.assertEqual(asset.status, "Out of Order") + + asset_repair.repair_status = "Completed" + asset_repair.save() + asset_status = frappe.db.get_value("Asset", asset_repair.asset, "status") + self.assertEqual(asset_status, initial_status) + + def test_stock_item_total_value(self): + asset_repair = create_asset_repair(stock_consumption = 1) + + for item in asset_repair.stock_items: + total_value = flt(item.valuation_rate) * flt(item.consumed_quantity) + self.assertEqual(item.total_value, total_value) + + def test_total_repair_cost(self): + asset_repair = create_asset_repair(stock_consumption = 1) + + total_repair_cost = asset_repair.repair_cost + self.assertEqual(total_repair_cost, asset_repair.repair_cost) + for item in asset_repair.stock_items: + total_repair_cost += item.total_value + + self.assertEqual(total_repair_cost, asset_repair.total_repair_cost) + + def test_repair_status_after_submit(self): + asset_repair = create_asset_repair(submit = 1) + self.assertNotEqual(asset_repair.repair_status, "Pending") + + def test_stock_items(self): + asset_repair = create_asset_repair(stock_consumption = 1) + self.assertTrue(asset_repair.stock_consumption) + self.assertTrue(asset_repair.stock_items) + + def test_warehouse(self): + asset_repair = create_asset_repair(stock_consumption = 1) + self.assertTrue(asset_repair.stock_consumption) + self.assertTrue(asset_repair.warehouse) + + def test_decrease_stock_quantity(self): + asset_repair = create_asset_repair(stock_consumption = 1, submit = 1) + stock_entry = frappe.get_last_doc('Stock Entry') + + self.assertEqual(stock_entry.stock_entry_type, "Material Issue") + self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.warehouse) + self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item) + self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity) + + def test_increase_in_asset_value_due_to_stock_consumption(self): + asset = create_asset(calculate_depreciation = 1) + initial_asset_value = get_asset_value(asset) + asset_repair = create_asset_repair(asset= asset, stock_consumption = 1, submit = 1) + asset.reload() + + increase_in_asset_value = get_asset_value(asset) - initial_asset_value + self.assertEqual(asset_repair.stock_items[0].total_value, increase_in_asset_value) + + def test_increase_in_asset_value_due_to_repair_cost_capitalisation(self): + asset = create_asset(calculate_depreciation = 1) + initial_asset_value = get_asset_value(asset) + asset_repair = create_asset_repair(asset= asset, capitalize_repair_cost = 1, submit = 1) + asset.reload() + + increase_in_asset_value = get_asset_value(asset) - initial_asset_value + self.assertEqual(asset_repair.repair_cost, increase_in_asset_value) + + def test_purchase_invoice(self): + asset_repair = create_asset_repair(capitalize_repair_cost = 1, submit = 1) + self.assertTrue(asset_repair.purchase_invoice) + + def test_gl_entries(self): + asset_repair = create_asset_repair(capitalize_repair_cost = 1, submit = 1) + gl_entry = frappe.get_last_doc('GL Entry') + self.assertEqual(asset_repair.name, gl_entry.voucher_no) + + def test_increase_in_asset_life(self): + asset = create_asset(calculate_depreciation = 1) + initial_num_of_depreciations = num_of_depreciations(asset) + create_asset_repair(asset= asset, capitalize_repair_cost = 1, submit = 1) + asset.reload() + + self.assertEqual((initial_num_of_depreciations + 1), num_of_depreciations(asset)) + self.assertEqual(asset.schedules[-1].accumulated_depreciation_amount, asset.finance_books[0].value_after_depreciation) + +def get_asset_value(asset): + return asset.finance_books[0].value_after_depreciation + +def num_of_depreciations(asset): + return asset.finance_books[0].total_number_of_depreciations + +def create_asset_repair(**args): + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice + + args = frappe._dict(args) + + if args.asset: + asset = args.asset + else: + asset = create_asset(is_existing_asset = 1) + asset_repair = frappe.new_doc("Asset Repair") + asset_repair.update({ + "asset": asset.name, + "asset_name": asset.asset_name, + "failure_date": nowdate(), + "description": "Test Description", + "repair_cost": 0, + "company": asset.company + }) + + if args.stock_consumption: + asset_repair.stock_consumption = 1 + asset_repair.warehouse = create_warehouse("Test Warehouse", company = asset.company) + asset_repair.append("stock_items", { + "item": args.item or args.item_code or "_Test Item", + "valuation_rate": args.rate if args.get("rate") is not None else 100, + "consumed_quantity": args.qty or 1 + }) + + asset_repair.insert(ignore_if_duplicate=True) + + if args.submit: + asset_repair.repair_status = "Completed" + asset_repair.cost_center = "_Test Cost Center - _TC" + + if args.stock_consumption: + stock_entry = frappe.get_doc({ + "doctype": "Stock Entry", + "stock_entry_type": "Material Receipt", + "company": asset.company + }) + stock_entry.append('items', { + "t_warehouse": asset_repair.warehouse, + "item_code": asset_repair.stock_items[0].item, + "qty": asset_repair.stock_items[0].consumed_quantity + }) + stock_entry.submit() + + if args.capitalize_repair_cost: + asset_repair.capitalize_repair_cost = 1 + asset_repair.repair_cost = 1000 + if asset.calculate_depreciation: + asset_repair.increase_in_asset_life = 12 + asset_repair.purchase_invoice = make_purchase_invoice().name + + asset_repair.submit() + return asset_repair diff --git a/erpnext/assets/doctype/asset_repair_consumed_item/__init__.py b/erpnext/assets/doctype/asset_repair_consumed_item/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json new file mode 100644 index 00000000000..528f0ec986a --- /dev/null +++ b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json @@ -0,0 +1,55 @@ +{ + "actions": [], + "creation": "2021-05-12 02:41:54.161024", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item", + "valuation_rate", + "consumed_quantity", + "total_value" + ], + "fields": [ + { + "fieldname": "item", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item", + "options": "Item" + }, + { + "fetch_from": "item.valuation_rate", + "fieldname": "valuation_rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Valuation Rate", + "read_only": 1 + }, + { + "fieldname": "consumed_quantity", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Consumed Quantity" + }, + { + "fieldname": "total_value", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Total Value", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-05-12 03:19:55.006300", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Repair Consumed Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.py b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.py new file mode 100644 index 00000000000..fa22a5712f4 --- /dev/null +++ b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class AssetRepairConsumedItem(Document): + pass diff --git a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py index 03dc47b0bba..a9dc9795ee3 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py @@ -91,4 +91,4 @@ def make_asset_value_adjustment(**args): "cost_center": args.cost_center or "Main - _TC" }).insert() - return doc \ No newline at end of file + return doc diff --git a/erpnext/assets/doctype/location/location_tree.js b/erpnext/assets/doctype/location/location_tree.js index b405afd1ddd..3e105f6ca49 100644 --- a/erpnext/assets/doctype/location/location_tree.js +++ b/erpnext/assets/doctype/location/location_tree.js @@ -30,4 +30,4 @@ frappe.treeview_settings["Location"] = { onload: function (treeview) { treeview.make_tree(); } -}; \ No newline at end of file +}; diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js index 1a6ef54a830..75f42a9f783 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js @@ -76,7 +76,7 @@ frappe.query_reports["Fixed Asset Register"] = { fieldtype: "Link", options: "Asset Category" }, - { + { fieldname:"finance_book", label: __("Finance Book"), fieldtype: "Link", diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index d1457b9b85a..7d07397944b 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -99,7 +99,7 @@ def prepare_chart_data(data, filters): labels_values_map = {} date_field = frappe.scrub(filters.date_based_on) - period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, + period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, filters.from_date, filters.to_date, filters.filter_based_on, "Monthly", company=filters.company) for d in period_list: @@ -293,4 +293,4 @@ def get_columns(filters): "options": "Location", "width": 100 }, - ] \ No newline at end of file + ] diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.js b/erpnext/buying/doctype/buying_settings/buying_settings.js index e496e9628d1..944bb61cfeb 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.js +++ b/erpnext/buying/doctype/buying_settings/buying_settings.js @@ -28,4 +28,4 @@ frappe.tour['Buying Settings'] = [ title: "Purchase Receipt Required for Purchase Invoice Creation", description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Purchase Invoice without creating a Purchase Receipt first. This configuration can be overridden for a particular supplier by enabling the 'Allow Purchase Invoice Creation Without Purchase Receipt' checkbox in the Supplier master.") } -]; \ No newline at end of file +]; diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index bb0ad60cabc..a55a0b7f9fc 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -565,6 +565,7 @@ "fieldname": "scan_barcode", "fieldtype": "Data", "label": "Scan Barcode", + "options": "Barcode", "show_days": 1, "show_seconds": 1 }, @@ -1378,7 +1379,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-05-30 15:17:53.663648", + "modified": "2021-08-17 20:16:12.737743", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index eaa502ff7f0..ca3bd90960c 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -447,10 +447,11 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions target.flags.ignore_permissions = ignore_permissions set_missing_values(source, target) #Get the advance paid Journal Entries in Purchase Invoice Advance - if target.get("allocate_advances_automatically"): target.set_advances() + target.set_payment_schedule() + def update_item(obj, target, source_parent): target.amount = flt(obj.amount) - flt(obj.billed_amt) target.base_amount = target.amount * flt(source_parent.conversion_rate) @@ -470,6 +471,7 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions "party_account_currency": "party_account_currency", "supplier_warehouse":"supplier_warehouse" }, + "field_no_map" : ["payment_terms_template"], "validation": { "docstatus": ["=", 1], } @@ -489,12 +491,6 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions }, } - if frappe.get_single("Accounts Settings").automatically_fetch_payment_terms == 1: - fields["Payment Schedule"] = { - "doctype": "Payment Schedule", - "add_if_empty": True - } - doc = get_mapped_doc("Purchase Order", source_name, fields, target_doc, postprocess, ignore_permissions=ignore_permissions) @@ -639,4 +635,4 @@ def add_items_in_ste(ste_doc, row, qty, po_details, batch_no=None): 'item_code': row.item_details['rm_item_code'], 'subcontracted_item': row.item_details['main_item_code'], 'serial_no': '\n'.join(row.serial_no) if row.serial_no else '' - }) \ No newline at end of file + }) diff --git a/erpnext/buying/doctype/purchase_order/regional/india.js b/erpnext/buying/doctype/purchase_order/regional/india.js index 42d3995907f..ef83f203e73 100644 --- a/erpnext/buying/doctype/purchase_order/regional/india.js +++ b/erpnext/buying/doctype/purchase_order/regional/india.js @@ -1,3 +1,3 @@ {% include "erpnext/regional/india/taxes.js" %} -erpnext.setup_auto_gst_taxation('Purchase Order'); \ No newline at end of file +erpnext.setup_auto_gst_taxation('Purchase Order'); diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 8563b97ab74..fa174ba8fa8 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -484,6 +484,9 @@ class TestPurchaseOrder(unittest.TestCase): def test_make_purchase_invoice_with_terms(self): + from erpnext.selling.doctype.sales_order.test_sales_order import automatically_fetch_payment_terms, compare_payment_schedules + + automatically_fetch_payment_terms() po = create_purchase_order(do_not_save=True) self.assertRaises(frappe.ValidationError, make_pi_from_po, po.name) @@ -509,6 +512,7 @@ class TestPurchaseOrder(unittest.TestCase): self.assertEqual(getdate(pi.payment_schedule[0].due_date), getdate(po.transaction_date)) self.assertEqual(pi.payment_schedule[1].payment_amount, 2500.0) self.assertEqual(getdate(pi.payment_schedule[1].due_date), add_days(getdate(po.transaction_date), 30)) + automatically_fetch_payment_terms(enable=0) def test_subcontracting(self): po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes") @@ -632,14 +636,18 @@ class TestPurchaseOrder(unittest.TestCase): else: raise Exception - def test_terms_does_not_copy(self): - po = create_purchase_order() - - self.assertTrue(po.get('payment_schedule')) + def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self): + po = create_purchase_order(do_not_save=1) + po.payment_terms_template = '_Test Payment Term Template' + po.save() + po.submit() + frappe.db.set_value('Company', '_Test Company', 'payment_terms', '_Test Payment Term Template 1') pi = make_pi_from_po(po.name) + pi.save() - self.assertFalse(pi.get('payment_schedule')) + self.assertEqual(pi.get('payment_terms_template'), '_Test Payment Term Template 1') + frappe.db.set_value('Company', '_Test Company', 'payment_terms', '') def test_terms_copied(self): po = create_purchase_order(do_not_save=1) @@ -968,8 +976,27 @@ class TestPurchaseOrder(unittest.TestCase): # To test if the PO does NOT have a Blanket Order self.assertEqual(po_doc.items[0].blanket_order, None) + def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template + from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice + from erpnext.selling.doctype.sales_order.test_sales_order import automatically_fetch_payment_terms, compare_payment_schedules + automatically_fetch_payment_terms() + po = create_purchase_order(qty=10, rate=100, do_not_save=1) + create_payment_terms_template() + po.payment_terms_template = 'Test Receivable Template' + po.submit() + + pi = make_purchase_invoice(qty=10, rate=100, do_not_save=1) + pi.items[0].purchase_order = po.name + pi.items[0].po_detail = po.items[0].name + pi.insert() + + # self.assertEqual(po.payment_terms_template, pi.payment_terms_template) + compare_payment_schedules(self, po, pi) + + automatically_fetch_payment_terms(enable=0) def make_pr_against_po(po, received_qty=0): pr = make_purchase_receipt(po) diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js index 5d196874c98..012b0619cc9 100644 --- a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js @@ -77,4 +77,4 @@ QUnit.test("test: purchase order", function(assert) { () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_get_items.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_get_items.js index 8c0c1443144..bc3d767f95d 100644 --- a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_get_items.js +++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_get_items.js @@ -58,4 +58,4 @@ QUnit.test("test: purchase order with get items", function(assert) { () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_discount_on_grand_total.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_discount_on_grand_total.js index 4e73ab8ef4f..83eb295010a 100644 --- a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_discount_on_grand_total.js +++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_discount_on_grand_total.js @@ -44,4 +44,4 @@ QUnit.test("test: purchase order with discount on grand total", function(assert) () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_item_wise_discount.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_item_wise_discount.js index 1e54e50dda9..a729dd9839f 100644 --- a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_item_wise_discount.js +++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_item_wise_discount.js @@ -41,4 +41,4 @@ QUnit.test("test: purchase order with item wise discount", function(assert) { () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_multi_uom.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_multi_uom.js index bf2dfeb37b7..b605e76ddf4 100644 --- a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_multi_uom.js +++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_multi_uom.js @@ -36,4 +36,4 @@ QUnit.test("test: purchase order with multi UOM", function(assert) { () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_shipping_rule.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_shipping_rule.js index 96775eb0075..c258756b2a1 100644 --- a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_shipping_rule.js +++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_shipping_rule.js @@ -40,4 +40,4 @@ QUnit.test("test: purchase order with shipping rule", function(assert) { () => frappe.timeout(0.3), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_taxes_and_charges.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_taxes_and_charges.js index 39716ed560c..ccc383fd74e 100644 --- a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_taxes_and_charges.js +++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_taxes_and_charges.js @@ -41,4 +41,4 @@ QUnit.test("test: purchase order with taxes and charges", function(assert) { () => frappe.timeout(0.3), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py index 8bdcd47e028..b6e28b6c674 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py @@ -10,4 +10,4 @@ class PurchaseOrderItem(Document): pass def on_doctype_update(): - frappe.db.add_index("Purchase Order Item", ["item_code", "warehouse"]) \ No newline at end of file + frappe.db.add_index("Purchase Order Item", ["item_code", "warehouse"]) diff --git a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.py b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.py index 6caffbda1f3..c85ca2fbafc 100644 --- a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.py +++ b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class PurchaseOrderItemSupplied(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.py b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.py index 1a76f0ee7db..00c93ed1ea3 100644 --- a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.py +++ b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class PurchaseReceiptItemSupplied(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index a4ce84e1cf9..8ed6c9e2a6d 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -425,4 +425,4 @@ def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filt .format(filters.get("supplier"), filters.get("company"), conditions), {"page_len": page_len, "start": start}, as_dict=1) - return rfq_data \ No newline at end of file + return rfq_data diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation_dashboard.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation_dashboard.py index 6efbc782252..751336dc4c6 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation_dashboard.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation_dashboard.py @@ -10,4 +10,4 @@ def get_data(): 'items': ['Supplier Quotation'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js index 1fcfe75bb03..75f85f86d1d 100644 --- a/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js @@ -73,4 +73,4 @@ QUnit.test("test: request_for_quotation", function(assert) { () => frappe.click_button('Close'), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js index 2e1652de733..f06c3f34c44 100644 --- a/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js +++ b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js @@ -125,4 +125,4 @@ QUnit.test("Test: Request for Quotation", function (assert) { }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/buying/doctype/supplier/regional/india.js b/erpnext/buying/doctype/supplier/regional/india.js index bd710e0df71..5f49a47e75e 100644 --- a/erpnext/buying/doctype/supplier/regional/india.js +++ b/erpnext/buying/doctype/supplier/regional/india.js @@ -1,3 +1,3 @@ {% include "erpnext/regional/india/party.js" %} -erpnext.setup_gst_reminder_button('Supplier'); \ No newline at end of file +erpnext.setup_gst_reminder_button('Supplier'); diff --git a/erpnext/buying/doctype/supplier/test_supplier.js b/erpnext/buying/doctype/supplier/test_supplier.js index bf7c192c91c..eaa4d0989d2 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.js +++ b/erpnext/buying/doctype/supplier/test_supplier.js @@ -74,4 +74,4 @@ QUnit.test("test: supplier", function(assert) { }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py index 3a2e5d6dcef..4473ddea28e 100644 --- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py +++ b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py @@ -15,4 +15,4 @@ class SupplierItemGroup(Document): 'item_group': self.item_group }) if exists: - frappe.throw(_("Item Group has already been linked to this supplier.")) \ No newline at end of file + frappe.throw(_("Item Group has already been linked to this supplier.")) diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py index 6a4c02c075c..25e4e2a4dcf 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py @@ -166,4 +166,4 @@ def set_expired_status(): `tabSupplier Quotation` SET `status` = 'Expired' WHERE `status` not in ('Cancelled', 'Stopped') AND `valid_till` < %s - """, (nowdate())) \ No newline at end of file + """, (nowdate())) diff --git a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js index 2d2b29cb916..20fb43026ab 100644 --- a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js +++ b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js @@ -71,4 +71,4 @@ QUnit.test("test: supplier quotation", function(assert) { () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js index b151824ba68..0a51565b08e 100644 --- a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js +++ b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js @@ -31,4 +31,4 @@ QUnit.test("test: supplier quotation with item wise discount", function(assert){ () => frappe.timeout(0.3), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js index e37731eb579..7ea3e6079cd 100644 --- a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js +++ b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js @@ -34,4 +34,4 @@ QUnit.test("test: supplier quotation with taxes and charges", function(assert) { () => frappe.timeout(0.3), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js index 5f5f54b79f5..b4cd852c32f 100644 --- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js +++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js @@ -93,5 +93,3 @@ var loadAllStandings = function(frm) { } }); }; - - diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_dashboard.py b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_dashboard.py index 3d2305e2853..8e5cce5696b 100644 --- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_dashboard.py +++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_dashboard.py @@ -13,4 +13,4 @@ def get_data(): 'items': ['Supplier Scorecard Period'] } ] - } \ No newline at end of file + } diff --git a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py index 25282405492..a5f05ea5258 100644 --- a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py +++ b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py @@ -128,4 +128,3 @@ valid_scorecard = [ "weighting_function":"{total_score} * max( 0, min ( 1 , (12 - {period_number}) / 12) )" } ] - diff --git a/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py b/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py index 4eef4b4e03e..3babfc8cab3 100644 --- a/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py +++ b/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py @@ -72,4 +72,4 @@ test_bad_criteria = [ "criteria_name":"Fake Criteria 3", "max_score":100.0 }, -] \ No newline at end of file +] diff --git a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py index 9938710e6e6..cc345e96bb8 100644 --- a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py +++ b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py @@ -109,4 +109,3 @@ def make_supplier_scorecard(source_name, target_doc=None): }, target_doc, post_process, ignore_permissions=True) return doc - diff --git a/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.py b/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.py index 1ba5d06c536..678855a457b 100644 --- a/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.py +++ b/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.py @@ -26,4 +26,4 @@ def get_standings_list(): `tabSupplier Scorecard Standing` scs""", {}, as_dict=1) - return standings \ No newline at end of file + return standings diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py index 37fdc5724f5..89a6459bbab 100644 --- a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py +++ b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py @@ -493,4 +493,4 @@ def get_rfq_response_days(scorecard): total_sq_days = 0 - return total_sq_days \ No newline at end of file + return total_sq_days diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py b/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py index fe6dde50489..14b87105e66 100644 --- a/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py +++ b/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py @@ -54,4 +54,4 @@ test_bad_variables = [ "variable_label":"Fake Variable 1", "path":"get_fake_variable1" }, -] \ No newline at end of file +] diff --git a/erpnext/buying/print_format/drop_shipping_format/drop_shipping_format.json b/erpnext/buying/print_format/drop_shipping_format/drop_shipping_format.json index 0c7cad6e942..2e2a04826a3 100644 --- a/erpnext/buying/print_format/drop_shipping_format/drop_shipping_format.json +++ b/erpnext/buying/print_format/drop_shipping_format/drop_shipping_format.json @@ -13,6 +13,6 @@ "name": "Drop Shipping Format", "owner": "Administrator", "print_format_builder": 0, - "print_format_type": "Server", + "print_format_type": "Jinja", "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py index beeca091c8a..99bcbe633cc 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py @@ -296,4 +296,4 @@ def get_po_entries(conditions): {conditions} GROUP BY parent.name, child.item_code - """.format(conditions=conditions), as_dict=1) #nosec \ No newline at end of file + """.format(conditions=conditions), as_dict=1) #nosec diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index 44ab767c0a9..c36083f2aff 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -68,4 +68,4 @@ class TestProcurementTracker(unittest.TestCase): "actual_delivery_date": date_obj } - return expected_data \ No newline at end of file + return expected_data diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py index 89be62231b9..bda172769a9 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -268,4 +268,3 @@ def get_columns(filters): ]) return columns - diff --git a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.js b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.js index 83d25d80ba2..90919dcc6a3 100644 --- a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.js +++ b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.js @@ -5,4 +5,4 @@ frappe.require("assets/erpnext/js/purchase_trends_filters.js", function() { frappe.query_reports["Purchase Order Trends"] = { filters: erpnext.get_purchase_trends_filters() } -}); \ No newline at end of file +}); diff --git a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py index 1ed6cad6b46..095a44319d6 100644 --- a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py +++ b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py @@ -55,4 +55,4 @@ def get_chart_data(data, conditions, filters): "lineOptions": { "regionFill": 1 } - } \ No newline at end of file + } diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py index 0c0d4f0531d..9a45972837b 100644 --- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py +++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py @@ -149,4 +149,4 @@ def get_columns(): "fieldtype": "Float", "width": 110 } - ] \ No newline at end of file + ] diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py index d8de701bf6e..cb304a1fdab 100644 --- a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py +++ b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py @@ -33,4 +33,4 @@ def make_purchase_receipt_against_po(po, quantity=5): pr.items[0].qty = quantity pr.supplier_warehouse = '_Test Warehouse 1 - _TC' pr.insert() - pr.submit() \ No newline at end of file + pr.submit() diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py index 68426abbb04..96cacb6f1b5 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py @@ -94,4 +94,4 @@ def get_po_items_to_supply(filters): ["Purchase Order", "transaction_date", ">=", filters.from_date], ["Purchase Order", "docstatus", "=", 1] ] - ) \ No newline at end of file + ) diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html index 098214d741c..015b31c2064 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html @@ -129,4 +129,4 @@ -

Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}

\ No newline at end of file +

Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}

diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js index 80e521a8bfa..7a8d08dd22d 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js @@ -174,4 +174,4 @@ frappe.query_reports["Supplier Quotation Comparison"] = { }); dialog.show(); } -} \ No newline at end of file +} diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py index 2b371915f32..a5a3105a847 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py @@ -263,4 +263,4 @@ def get_message():    Expires today / Already Expired - """ \ No newline at end of file + """ diff --git a/erpnext/buying/utils.py b/erpnext/buying/utils.py index a73cb0d62ec..17928634e78 100644 --- a/erpnext/buying/utils.py +++ b/erpnext/buying/utils.py @@ -102,4 +102,3 @@ def get_linked_material_requests(items): mr_list.append(material_request) return mr_list - diff --git a/erpnext/commands/__init__.py b/erpnext/commands/__init__.py index a991cf9881e..2276c738fbe 100644 --- a/erpnext/commands/__init__.py +++ b/erpnext/commands/__init__.py @@ -46,4 +46,4 @@ def make_demo(context, site, domain='Manufacturing', days=100, commands = [ make_demo -] \ No newline at end of file +] diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a9860ed2f05..219da37a687 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -674,19 +674,24 @@ class AccountsController(TransactionBase): if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']: for d in self.get("advances"): if d.exchange_gain_loss: - party = self.supplier if self.get('doctype') == 'Purchase Invoice' else self.customer - party_account = self.credit_to if self.get('doctype') == 'Purchase Invoice' else self.debit_to - party_type = "Supplier" if self.get('doctype') == 'Purchase Invoice' else "Customer" + is_purchase_invoice = self.get('doctype') == 'Purchase Invoice' + party = self.supplier if is_purchase_invoice else self.customer + party_account = self.credit_to if is_purchase_invoice else self.debit_to + party_type = "Supplier" if is_purchase_invoice else "Customer" gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account') + if not gain_loss_account: + frappe.throw(_("Please set Default Exchange Gain/Loss Account in Company {}") + .format(self.get('company'))) account_currency = get_account_currency(gain_loss_account) if account_currency != self.company_currency: - frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency)) + frappe.throw(_("Currency for {0} must be {1}").format(gain_loss_account, self.company_currency)) # for purchase dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit' - # just reverse for sales? - dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit' + if not is_purchase_invoice: + # just reverse for sales? + dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit' gl_entries.append( self.get_gl_dict({ @@ -808,6 +813,89 @@ class AccountsController(TransactionBase): tax_map[tax.account_head] -= allocated_amount allocated_tax_map[tax.account_head] -= allocated_amount + def get_amount_and_base_amount(self, item, enable_discount_accounting): + amount = item.net_amount + base_amount = item.base_net_amount + + if enable_discount_accounting and self.get('discount_amount') and self.get('additional_discount_account'): + amount = item.amount + base_amount = item.base_amount + + return amount, base_amount + + def get_tax_amounts(self, tax, enable_discount_accounting): + amount = tax.tax_amount_after_discount_amount + base_amount = tax.base_tax_amount_after_discount_amount + + if enable_discount_accounting and self.get('discount_amount') and self.get('additional_discount_account') \ + and self.get('apply_discount_on') == 'Grand Total': + amount = tax.tax_amount + base_amount = tax.base_tax_amount + + return amount, base_amount + + def make_discount_gl_entries(self, gl_entries): + enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting')) + + if enable_discount_accounting: + if self.doctype == "Purchase Invoice": + dr_or_cr = "credit" + rev_dr_cr = "debit" + supplier_or_customer = self.supplier + + else: + dr_or_cr = "debit" + rev_dr_cr = "credit" + supplier_or_customer = self.customer + + for item in self.get("items"): + if item.get('discount_amount') and item.get('discount_account'): + discount_amount = item.discount_amount * item.qty + if self.doctype == "Purchase Invoice": + income_or_expense_account = (item.expense_account + if (not item.enable_deferred_expense or self.is_return) + else item.deferred_expense_account) + else: + income_or_expense_account = (item.income_account + if (not item.enable_deferred_revenue or self.is_return) + else item.deferred_revenue_account) + + account_currency = get_account_currency(item.discount_account) + gl_entries.append( + self.get_gl_dict({ + "account": item.discount_account, + "against": supplier_or_customer, + dr_or_cr: flt(discount_amount, item.precision('discount_amount')), + dr_or_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'), + item.precision('discount_amount')), + "cost_center": item.cost_center, + "project": item.project + }, account_currency, item=item) + ) + + account_currency = get_account_currency(income_or_expense_account) + gl_entries.append( + self.get_gl_dict({ + "account": income_or_expense_account, + "against": supplier_or_customer, + rev_dr_cr: flt(discount_amount, item.precision('discount_amount')), + rev_dr_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'), + item.precision('discount_amount')), + "cost_center": item.cost_center, + "project": item.project or self.project + }, account_currency, item=item) + ) + + if self.get('discount_amount') and self.get('additional_discount_account'): + gl_entries.append( + self.get_gl_dict({ + "account": self.additional_discount_account, + "against": supplier_or_customer, + dr_or_cr: self.discount_amount, + "cost_center": self.cost_center + }, item=self) + ) + def allocate_advance_taxes(self, gl_entries): tax_map = self.get_tax_map() for pe in self.get("advances"): @@ -818,11 +906,11 @@ class AccountsController(TransactionBase): account_currency = get_account_currency(tax.account_head) if self.doctype == "Purchase Invoice": - dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" - rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" - else: dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" + else: + dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" + rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" party = self.supplier if self.doctype == "Purchase Invoice" else self.customer unallocated_amount = tax.tax_amount - tax.allocated_amount @@ -1091,6 +1179,8 @@ class AccountsController(TransactionBase): if self.doctype in ("Sales Invoice", "Purchase Invoice"): base_grand_total = base_grand_total - flt(self.base_write_off_amount) grand_total = grand_total - flt(self.write_off_amount) + po_or_so, doctype, fieldname = self.get_order_details() + automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms')) if self.get("total_advance"): if party_account_currency == self.company_currency: @@ -1101,19 +1191,86 @@ class AccountsController(TransactionBase): base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) if not self.get("payment_schedule"): - if self.get("payment_terms_template"): + if self.doctype in ["Sales Invoice", "Purchase Invoice"] and automatically_fetch_payment_terms \ + and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype): + self.fetch_payment_terms_from_order(po_or_so, doctype) + if self.get('payment_terms_template'): + self.ignore_default_payment_terms_template = 1 + elif self.get("payment_terms_template"): data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total) for item in data: self.append("payment_schedule", item) - else: + elif self.doctype not in ["Purchase Receipt"]: data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total) self.append("payment_schedule", data) + + for d in self.get("payment_schedule"): + if d.invoice_portion: + d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) + d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount')) + d.outstanding = d.payment_amount + elif not d.invoice_portion: + d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount')) + + + def get_order_details(self): + if self.doctype == "Sales Invoice": + po_or_so = self.get('items')[0].get('sales_order') + po_or_so_doctype = "Sales Order" + po_or_so_doctype_name = "sales_order" + else: - for d in self.get("payment_schedule"): - if d.invoice_portion: - d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) - d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) - d.outstanding = d.payment_amount + po_or_so = self.get('items')[0].get('purchase_order') + po_or_so_doctype = "Purchase Order" + po_or_so_doctype_name = "purchase_order" + + return po_or_so, po_or_so_doctype, po_or_so_doctype_name + + def linked_order_has_payment_terms(self, po_or_so, fieldname, doctype): + if po_or_so and self.all_items_have_same_po_or_so(po_or_so, fieldname): + if self.linked_order_has_payment_terms_template(po_or_so, doctype): + return True + elif self.linked_order_has_payment_schedule(po_or_so): + return True + + return False + + def all_items_have_same_po_or_so(self, po_or_so, fieldname): + for item in self.get('items'): + if item.get(fieldname) != po_or_so: + return False + + return True + + def linked_order_has_payment_terms_template(self, po_or_so, doctype): + return frappe.get_value(doctype, po_or_so, 'payment_terms_template') + + def linked_order_has_payment_schedule(self, po_or_so): + return frappe.get_all('Payment Schedule', filters={'parent': po_or_so}) + + def fetch_payment_terms_from_order(self, po_or_so, po_or_so_doctype): + """ + Fetch Payment Terms from Purchase/Sales Order on creating a new Purchase/Sales Invoice. + """ + po_or_so = frappe.get_cached_doc(po_or_so_doctype, po_or_so) + + self.payment_schedule = [] + self.payment_terms_template = po_or_so.payment_terms_template + + for schedule in po_or_so.payment_schedule: + payment_schedule = { + 'payment_term': schedule.payment_term, + 'due_date': schedule.due_date, + 'invoice_portion': schedule.invoice_portion, + 'mode_of_payment': schedule.mode_of_payment, + 'description': schedule.description + } + + if schedule.discount_type == 'Percentage': + payment_schedule['discount_type'] = schedule.discount_type + payment_schedule['discount'] = schedule.discount + + self.append("payment_schedule", payment_schedule) def set_due_date(self): due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] @@ -1280,6 +1437,27 @@ def validate_taxes_and_charges(tax): tax.rate = None +def validate_account_head(tax, doc): + company = frappe.get_cached_value('Account', + tax.account_head, 'company') + + if company != doc.company: + frappe.throw(_('Row {0}: Account {1} does not belong to Company {2}') + .format(tax.idx, frappe.bold(tax.account_head), frappe.bold(doc.company)), title=_('Invalid Account')) + + +def validate_cost_center(tax, doc): + if not tax.cost_center: + return + + company = frappe.get_cached_value('Cost Center', + tax.cost_center, 'company') + + if company != doc.company: + frappe.throw(_('Row {0}: Cost Center {1} does not belong to Company {2}') + .format(tax.idx, frappe.bold(tax.cost_center), frappe.bold(doc.company)), title=_('Invalid Cost Center')) + + def validate_inclusive_tax(tax, doc): def _on_previous_row_error(row_range): throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, row_range)) @@ -1499,7 +1677,7 @@ def set_child_tax_template_and_map(item, child_item, parent_doc): if child_item.get("item_tax_template"): child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True) -def add_taxes_from_tax_template(child_item, parent_doc): +def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True): add_taxes_from_item_tax_template = frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template") if child_item.get("item_tax_rate") and add_taxes_from_item_tax_template: @@ -1522,7 +1700,8 @@ def add_taxes_from_tax_template(child_item, parent_doc): "category" : "Total", "add_deduct_tax" : "Add" }) - tax_row.db_insert() + if db_insert: + tax_row.db_insert() def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, trans_item): """ @@ -1662,6 +1841,11 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil for d in data: new_child_flag = False + + if not d.get("item_code"): + # ignore empty rows + continue + if not d.get("docname"): new_child_flag = True check_doc_permissions(parent, 'create') @@ -1684,7 +1868,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil qty_unchanged = prev_qty == new_qty uom_unchanged = prev_uom == new_uom conversion_factor_unchanged = prev_con_fac == new_con_fac - date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc + date_unchanged = prev_date == getdate(new_date) if prev_date and new_date else False # in case of delivery note etc if rate_unchanged and qty_unchanged and conversion_factor_unchanged and uom_unchanged and date_unchanged: continue diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 6a550e0e975..974ade35849 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -72,7 +72,8 @@ class BuyingController(StockController, Subcontracting): # set contact and address details for supplier, if they are not mentioned if getattr(self, "supplier", None): self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions, - doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.get('shipping_address'))) + doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.get('shipping_address'), + fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template'))) self.set_missing_item_details(for_validate) diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index 051481ff603..8c361a2e561 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -344,4 +344,3 @@ def create_variant_doc_for_quick_entry(template, args): variant.name = variant.item_code validate_item_variant_attributes(variant, args) return variant.as_dict() - diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 280319321f2..4b4c8befa53 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -407,6 +407,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): INNER JOIN `tabBatch` batch on sle.batch_no = batch.name where batch.disabled = 0 + and sle.is_cancelled = 0 and sle.item_code = %(item_code)s and sle.warehouse = %(warehouse)s and (sle.batch_no like %(txt)s @@ -525,6 +526,9 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters) if meta.is_tree: query_filters.append(['is_group', '=', 0]) + if meta.has_field('disabled'): + query_filters.append(['disabled', '!=', 1]) + if meta.has_field('company'): query_filters.append(['company', '=', filters.get('company')]) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 943f7aaeb12..b1f89b08d79 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import flt, comma_or, nowdate, getdate +from frappe.utils import flt, comma_or, nowdate, getdate, now from frappe import _ from frappe.model.document import Document @@ -336,10 +336,14 @@ class StatusUpdater(Document): target.notify_update() def _update_modified(self, args, update_modified): - args['update_modified'] = '' - if update_modified: - args['update_modified'] = ', modified = now(), modified_by = {0}'\ - .format(frappe.db.escape(frappe.session.user)) + if not update_modified: + args['update_modified'] = '' + return + + args['update_modified'] = ', modified = {0}, modified_by = {1}'.format( + frappe.db.escape(now()), + frappe.db.escape(frappe.session.user) + ) def update_billing_status_for_zero_amount_refdoc(self, ref_dt): ref_fieldname = frappe.scrub(ref_dt) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 2526e6df0ef..17707ecae7f 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -27,6 +27,7 @@ class StockController(AccountsController): if not self.get('is_return'): self.validate_inspection() self.validate_serialized_batch() + self.clean_serial_nos() self.validate_customer_provided_item() self.set_rate_of_stock_uom() self.validate_internal_transfer() @@ -53,12 +54,17 @@ class StockController(AccountsController): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos for d in self.get("items"): if hasattr(d, 'serial_no') and hasattr(d, 'batch_no') and d.serial_no and d.batch_no: - serial_nos = get_serial_nos(d.serial_no) - for serial_no_data in frappe.get_all("Serial No", - filters={"name": ("in", serial_nos)}, fields=["batch_no", "name"]): - if serial_no_data.batch_no != d.batch_no: + serial_nos = frappe.get_all("Serial No", + fields=["batch_no", "name", "warehouse"], + filters={ + "name": ("in", get_serial_nos(d.serial_no)) + } + ) + + for row in serial_nos: + if row.warehouse and row.batch_no != d.batch_no: frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}") - .format(d.idx, serial_no_data.name, d.batch_no)) + .format(d.idx, row.name, d.batch_no)) if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2: expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date") @@ -67,6 +73,12 @@ class StockController(AccountsController): frappe.throw(_("Row #{0}: The batch {1} has already expired.") .format(d.idx, get_link_to_form("Batch", d.get("batch_no")))) + def clean_serial_nos(self): + for row in self.get("items"): + if hasattr(row, "serial_no") and row.serial_no: + # replace commas by linefeed and remove all spaces in string + row.serial_no = row.serial_no.replace(",", "\n").replace(" ", "") + def get_gl_entries(self, warehouse_account=None, default_expense_account=None, default_cost_center=None): diff --git a/erpnext/controllers/subcontracting.py b/erpnext/controllers/subcontracting.py index 36ae1102164..969829f9651 100644 --- a/erpnext/controllers/subcontracting.py +++ b/erpnext/controllers/subcontracting.py @@ -390,4 +390,4 @@ class Subcontracting(): incorrect_sn = "\n".join(incorrect_sn) link = get_link_to_form('Purchase Order', row.purchase_order) msg = f'The Serial Nos {incorrect_sn} has not supplied against the Purchase Order {link}' - frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed")) \ No newline at end of file + frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed")) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 56da5b71da0..05edb2530c2 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -152,7 +152,7 @@ class calculate_taxes_and_totals(object): validate_taxes_and_charges(tax) validate_inclusive_tax(tax, self.doc) - if not self.doc.get('is_consolidated'): + if not (self.doc.get('is_consolidated') or tax.get("dont_recompute_tax")): tax.item_wise_tax_detail = {} tax_fields = ["total", "tax_amount_after_discount_amount", @@ -347,7 +347,7 @@ class calculate_taxes_and_totals(object): elif tax.charge_type == "On Item Quantity": current_tax_amount = tax_rate * item.qty - if not self.doc.get("is_consolidated"): + if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")): self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) return current_tax_amount @@ -455,7 +455,8 @@ class calculate_taxes_and_totals(object): def _cleanup(self): if not self.doc.get('is_consolidated'): for tax in self.doc.get("taxes"): - tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':')) + if not tax.get("dont_recompute_tax"): + tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':')) def set_discount_amount(self): if self.doc.additional_discount_percentage: @@ -678,17 +679,13 @@ class calculate_taxes_and_totals(object): default_mode_of_payment = frappe.db.get_value('POS Payment Method', {'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1) - self.doc.payments = [] - if default_mode_of_payment: + self.doc.payments = [] self.doc.append('payments', { 'mode_of_payment': default_mode_of_payment.mode_of_payment, 'amount': total_amount_to_pay, 'default': 1 }) - else: - self.doc.is_pos = 0 - self.doc.pos_profile = '' self.calculate_paid_amount() diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index df73f09c493..f7c6b6c7993 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -235,4 +235,3 @@ def _get_employee_from_user(user): # frappe.db.exists returns a tuple of a tuple return frappe.get_doc('Employee', employee_docname[0][0]) return None - diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js index dc3ae8bf41a..0c64eb8e822 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js @@ -7,4 +7,4 @@ function check_times(frm) { frappe.throw(__('In row {0} of Appointment Booking Slots: "To Time" must be later than "From Time".', [i + 1])); } }); -} \ No newline at end of file +} diff --git a/erpnext/crm/doctype/contract/contract.js b/erpnext/crm/doctype/contract/contract.js index 99688551630..7848de7a727 100644 --- a/erpnext/crm/doctype/contract/contract.js +++ b/erpnext/crm/doctype/contract/contract.js @@ -15,7 +15,7 @@ frappe.ui.form.on("Contract", { let contract_template = r.message.contract_template; frm.set_value("contract_terms", r.message.contract_terms); frm.set_value("requires_fulfilment", contract_template.requires_fulfilment); - + if (frm.doc.requires_fulfilment) { // Populate the fulfilment terms table from a contract template, if any r.message.contract_template.fulfilment_terms.forEach(element => { @@ -23,7 +23,7 @@ frappe.ui.form.on("Contract", { d.requirement = element.requirement; }); frm.refresh_field("fulfilment_terms"); - } + } } } }); diff --git a/erpnext/crm/doctype/contract/contract_list.js b/erpnext/crm/doctype/contract/contract_list.js index 26a2907c7cc..7d5609651a1 100644 --- a/erpnext/crm/doctype/contract/contract_list.js +++ b/erpnext/crm/doctype/contract/contract_list.js @@ -9,4 +9,4 @@ frappe.listview_settings['Contract'] = { return [__(doc.status), "gray", "status,=," + doc.status]; } }, -}; \ No newline at end of file +}; diff --git a/erpnext/crm/doctype/contract_template/contract_template.py b/erpnext/crm/doctype/contract_template/contract_template.py index 69fd86f7fb5..9281220eef4 100644 --- a/erpnext/crm/doctype/contract_template/contract_template.py +++ b/erpnext/crm/doctype/contract_template/contract_template.py @@ -24,8 +24,8 @@ def get_contract_template(template_name, doc): if contract_template.contract_terms: contract_terms = frappe.render_template(contract_template.contract_terms, doc) - + return { - 'contract_template': contract_template, + 'contract_template': contract_template, 'contract_terms': contract_terms - } \ No newline at end of file + } diff --git a/erpnext/crm/doctype/lead/lead_dashboard.py b/erpnext/crm/doctype/lead/lead_dashboard.py index 69d8ca70926..3950d063f22 100644 --- a/erpnext/crm/doctype/lead/lead_dashboard.py +++ b/erpnext/crm/doctype/lead/lead_dashboard.py @@ -16,4 +16,4 @@ def get_data(): 'items': ['Opportunity', 'Quotation'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index ac374a95f4e..875d221efeb 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -53,6 +53,13 @@ frappe.ui.form.on("Opportunity", { frm.get_field("items").grid.set_multiple_add("item_code", "qty"); }, + status:function(frm){ + if (frm.doc.status == "Lost"){ + frm.trigger('set_as_lost_dialog'); + } + + }, + customer_address: function(frm, cdt, cdn) { erpnext.utils.get_address_display(frm, 'customer_address', 'address_display', false); }, @@ -91,11 +98,6 @@ frappe.ui.form.on("Opportunity", { frm.add_custom_button(__('Quotation'), cur_frm.cscript.create_quotation, __('Create')); - if(doc.status!=="Quotation") { - frm.add_custom_button(__('Lost'), () => { - frm.trigger('set_as_lost_dialog'); - }); - } } if(!frm.doc.__islocal && frm.perm[0].write && frm.doc.docstatus==0) { @@ -213,4 +215,4 @@ cur_frm.cscript.item_code = function(doc, cdt, cdn) { } }) } -} \ No newline at end of file +} diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 23ad98a2828..8ce482a3f9f 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -372,4 +372,4 @@ def get_events(start, end, filters=None): "start": start, "end": end }, as_dict=True, update={"allDay": 0}) - return data \ No newline at end of file + return data diff --git a/erpnext/crm/doctype/opportunity/opportunity_dashboard.py b/erpnext/crm/doctype/opportunity/opportunity_dashboard.py index 68f0104fd6c..b8c53f077ae 100644 --- a/erpnext/crm/doctype/opportunity/opportunity_dashboard.py +++ b/erpnext/crm/doctype/opportunity/opportunity_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Quotation', 'Supplier Quotation'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py index 04cd8a26cad..52aa0b036ae 100644 --- a/erpnext/crm/doctype/opportunity/test_opportunity.py +++ b/erpnext/crm/doctype/opportunity/test_opportunity.py @@ -87,4 +87,4 @@ def make_opportunity(**args): }) opp_doc.insert() - return opp_doc \ No newline at end of file + return opp_doc diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.js b/erpnext/crm/doctype/social_media_post/social_media_post.js index 0ce8b44e19b..6fb0f975f46 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.js +++ b/erpnext/crm/doctype/social_media_post/social_media_post.js @@ -19,7 +19,7 @@ frappe.ui.form.on('Social Media Post', { refresh: function(frm){ if (frm.doc.docstatus === 1){ if (frm.doc.post_status != "Posted"){ - add_post_btn(frm); + add_post_btn(frm); } else if (frm.doc.post_status == "Posted"){ frm.set_df_property('sheduled_time', 'read_only', 1); @@ -63,5 +63,5 @@ var post = function(frm){ frappe.dom.unfreeze(); } }) - -} \ No newline at end of file + +} diff --git a/erpnext/crm/report/campaign_efficiency/campaign_efficiency.js b/erpnext/crm/report/campaign_efficiency/campaign_efficiency.js index 0bc77a3f2a8..f29c2c64e14 100644 --- a/erpnext/crm/report/campaign_efficiency/campaign_efficiency.js +++ b/erpnext/crm/report/campaign_efficiency/campaign_efficiency.js @@ -16,4 +16,3 @@ frappe.query_reports["Campaign Efficiency"] = { } ] }; - diff --git a/erpnext/crm/report/campaign_efficiency/campaign_efficiency.py b/erpnext/crm/report/campaign_efficiency/campaign_efficiency.py index ec498837f5e..238884b5190 100644 --- a/erpnext/crm/report/campaign_efficiency/campaign_efficiency.py +++ b/erpnext/crm/report/campaign_efficiency/campaign_efficiency.py @@ -132,4 +132,4 @@ def get_order_amount(leads): where prevdoc_docname in ( select name from `tabQuotation` where status = 'Ordered' and quotation_to = 'Lead' and party_name in (%s) - )""" % ', '.join(["%s"]*len(leads)), tuple(leads))[0][0] \ No newline at end of file + )""" % ', '.join(["%s"]*len(leads)), tuple(leads))[0][0] diff --git a/erpnext/crm/report/lead_conversion_time/lead_conversion_time.js b/erpnext/crm/report/lead_conversion_time/lead_conversion_time.js index 0325de9b8d9..eeb8984513e 100644 --- a/erpnext/crm/report/lead_conversion_time/lead_conversion_time.js +++ b/erpnext/crm/report/lead_conversion_time/lead_conversion_time.js @@ -20,5 +20,3 @@ frappe.query_reports["Lead Conversion Time"] = { }, ] }; - - diff --git a/erpnext/crm/report/lead_details/lead_details.js b/erpnext/crm/report/lead_details/lead_details.js index f92070daf3f..2f6d24224fb 100644 --- a/erpnext/crm/report/lead_details/lead_details.js +++ b/erpnext/crm/report/lead_details/lead_details.js @@ -49,4 +49,4 @@ frappe.query_reports["Lead Details"] = { "options": "Territory", } ] -}; \ No newline at end of file +}; diff --git a/erpnext/crm/report/lead_details/lead_details.py b/erpnext/crm/report/lead_details/lead_details.py index eeaaec2bce2..072a47611b7 100644 --- a/erpnext/crm/report/lead_details/lead_details.py +++ b/erpnext/crm/report/lead_details/lead_details.py @@ -107,7 +107,7 @@ def get_columns(): "options": "Country", "width": 100 }, - + ] return columns @@ -142,7 +142,7 @@ def get_data(filters): company = %(company)s AND `tabLead`.creation BETWEEN %(from_date)s AND %(to_date)s {conditions} - ORDER BY + ORDER BY `tabLead`.creation asc """.format(conditions=get_conditions(filters)), filters, as_dict=1) def get_conditions(filters) : @@ -153,6 +153,5 @@ def get_conditions(filters) : if filters.get("status"): conditions.append(" and `tabLead`.status=%(status)s") - - return " ".join(conditions) if conditions else "" + return " ".join(conditions) if conditions else "" diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.js b/erpnext/crm/report/lost_opportunity/lost_opportunity.js index d79f8c8480f..97c56f8c434 100644 --- a/erpnext/crm/report/lost_opportunity/lost_opportunity.js +++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.js @@ -64,4 +64,4 @@ frappe.query_reports["Lost Opportunity"] = { "options": "User" }, ] -}; \ No newline at end of file +}; diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.py b/erpnext/crm/report/lost_opportunity/lost_opportunity.py index 1aa4afe1865..858dcc4da81 100644 --- a/erpnext/crm/report/lost_opportunity/lost_opportunity.py +++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.py @@ -87,17 +87,17 @@ def get_data(filters): `tabOpportunity`.sales_stage, `tabOpportunity`.territory FROM - `tabOpportunity` + `tabOpportunity` {join} WHERE `tabOpportunity`.status = 'Lost' and `tabOpportunity`.company = %(company)s - AND `tabOpportunity`.modified BETWEEN %(from_date)s AND %(to_date)s - {conditions} - GROUP BY - `tabOpportunity`.name - ORDER BY + AND `tabOpportunity`.modified BETWEEN %(from_date)s AND %(to_date)s + {conditions} + GROUP BY + `tabOpportunity`.name + ORDER BY `tabOpportunity`.creation asc """.format(conditions=get_conditions(filters), join=get_join(filters)), filters, as_dict=1) - + def get_conditions(filters): conditions = [] @@ -117,15 +117,15 @@ def get_conditions(filters): return " ".join(conditions) if conditions else "" def get_join(filters): - join = """LEFT JOIN `tabOpportunity Lost Reason Detail` - ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and + join = """LEFT JOIN `tabOpportunity Lost Reason Detail` + ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and `tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name""" if filters.get("lost_reason"): - join = """JOIN `tabOpportunity Lost Reason Detail` - ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and + join = """JOIN `tabOpportunity Lost Reason Detail` + ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and `tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name and `tabOpportunity Lost Reason Detail`.lost_reason = '{0}' """.format(filters.get("lost_reason")) - - return join \ No newline at end of file + + return join diff --git a/erpnext/crm/report/prospects_engaged_but_not_converted/prospects_engaged_but_not_converted.py b/erpnext/crm/report/prospects_engaged_but_not_converted/prospects_engaged_but_not_converted.py index 3a9d57d6075..425b7a8fdd7 100644 --- a/erpnext/crm/report/prospects_engaged_but_not_converted/prospects_engaged_but_not_converted.py +++ b/erpnext/crm/report/prospects_engaged_but_not_converted/prospects_engaged_but_not_converted.py @@ -106,4 +106,4 @@ def get_lead_filters(filters): return lead_filters def get_creation_date_based_on_lead_age(filters): - return add_days(now(), (filters.get('lead_age') * -1)) \ No newline at end of file + return add_days(now(), (filters.get('lead_age') * -1)) diff --git a/erpnext/demo/domains.py b/erpnext/demo/domains.py index d5c2bfd2f02..b1db7b57b17 100644 --- a/erpnext/demo/domains.py +++ b/erpnext/demo/domains.py @@ -25,4 +25,4 @@ data = { 'Non Profit': { 'company_name': 'Erpnext Foundation' } -} \ No newline at end of file +} diff --git a/erpnext/demo/user/education.py b/erpnext/demo/user/education.py index fc31176e1e5..883a6d88cf2 100644 --- a/erpnext/demo/user/education.py +++ b/erpnext/demo/user/education.py @@ -19,7 +19,7 @@ def work(): approve_random_student_applicant() enroll_random_student(frappe.flags.current_date) # if frappe.flags.current_date.weekday()== 0: - # make_course_schedule(frappe.flags.current_date, frappe.utils.add_days(frappe.flags.current_date, 5)) + # make_course_schedule(frappe.flags.current_date, frappe.utils.add_days(frappe.flags.current_date, 5)) mark_student_attendance(frappe.flags.current_date) # make_assessment_plan() make_fees() @@ -48,7 +48,7 @@ def enroll_random_student(current_date): frappe.db.commit() assign_student_group(enrollment.student, enrollment.student_name, enrollment.program, enrolled_courses, enrollment.student_batch_name) - + def assign_student_group(student, student_name, program, courses, batch): course_list = [d["course"] for d in courses] for d in frappe.get_list("Student Group", fields=("name"), filters={"program": program, "course":("in", course_list), "disabled": 0}): @@ -69,11 +69,11 @@ def mark_student_attendance(current_date): students = get_student_group_students(d.name) for stud in students: make_attendance_records(stud.student, stud.student_name, status[weighted_choice([9,4])], None, d.name, current_date) - + def make_fees(): for d in range(1,10): random_fee = get_random("Fees", {"paid_amount": 0}) - collect_fees(random_fee, frappe.db.get_value("Fees", random_fee, "outstanding_amount")) + collect_fees(random_fee, frappe.db.get_value("Fees", random_fee, "outstanding_amount")) def make_assessment_plan(date): for d in range(1,4): @@ -84,7 +84,7 @@ def make_assessment_plan(date): doc.assessment_group = get_random("Assessment Group", {"is_group": 0, "parent": "2017-18 (Semester 2)"}) doc.grading_scale = get_random("Grading Scale") doc.maximum_assessment_score = 100 - + def make_course_schedule(start_date, end_date): for d in frappe.db.get_list("Student Group"): cs = frappe.new_doc("Scheduling Tool") @@ -114,4 +114,4 @@ def weighted_choice(weights): rnd = random.random() * running_total for i, total in enumerate(totals): if rnd < total: - return i \ No newline at end of file + return i diff --git a/erpnext/domains/agriculture.py b/erpnext/domains/agriculture.py index 8c7427ab2d1..9212d2ea719 100644 --- a/erpnext/domains/agriculture.py +++ b/erpnext/domains/agriculture.py @@ -25,4 +25,4 @@ data = { ], 'default_portal_role': 'System Manager', 'on_setup': 'erpnext.agriculture.setup.setup_agriculture' -} \ No newline at end of file +} diff --git a/erpnext/domains/education.py b/erpnext/domains/education.py index bbaa6e55d99..870624ab3b2 100644 --- a/erpnext/domains/education.py +++ b/erpnext/domains/education.py @@ -26,4 +26,4 @@ data = { ], 'on_setup': 'erpnext.education.setup.setup_education' -} \ No newline at end of file +} diff --git a/erpnext/domains/manufacturing.py b/erpnext/domains/manufacturing.py index 259ee9238e5..b9ad49e772b 100644 --- a/erpnext/domains/manufacturing.py +++ b/erpnext/domains/manufacturing.py @@ -21,4 +21,4 @@ data = { ['Stock Settings', None, 'show_barcode_field', 1] ], 'default_portal_role': 'Customer' -} \ No newline at end of file +} diff --git a/erpnext/domains/non_profit.py b/erpnext/domains/non_profit.py index b6772c53153..7c4f6b1f9de 100644 --- a/erpnext/domains/non_profit.py +++ b/erpnext/domains/non_profit.py @@ -21,4 +21,4 @@ data = { 'Non Profit' ], 'default_portal_role': 'Non Profit Manager' -} \ No newline at end of file +} diff --git a/erpnext/domains/services.py b/erpnext/domains/services.py index 7a4ffc4993f..89213720767 100644 --- a/erpnext/domains/services.py +++ b/erpnext/domains/services.py @@ -18,4 +18,4 @@ data = { ['Stock Settings', None, 'show_barcode_field', 0] ], 'default_portal_role': 'Customer' -} \ No newline at end of file +} diff --git a/erpnext/education/api.py b/erpnext/education/api.py index afa0be9b9f3..4493a3fef17 100644 --- a/erpnext/education/api.py +++ b/erpnext/education/api.py @@ -34,11 +34,14 @@ def enroll_student(source_name): } }}, ignore_permissions=True) student.save() + + student_applicant = frappe.db.get_value("Student Applicant", source_name, + ["student_category", "program"], as_dict=True) program_enrollment = frappe.new_doc("Program Enrollment") program_enrollment.student = student.name - program_enrollment.student_category = student.student_category + program_enrollment.student_category = student_applicant.student_category program_enrollment.student_name = student.title - program_enrollment.program = frappe.db.get_value("Student Applicant", source_name, "program") + program_enrollment.program = student_applicant.program frappe.publish_realtime('enroll_student_progress', {"progress": [2, 4]}, user=frappe.session.user) return program_enrollment diff --git a/erpnext/education/doctype/academic_term/academic_term.py b/erpnext/education/doctype/academic_term/academic_term.py index 3aa0be157bc..fa7f2899dcb 100644 --- a/erpnext/education/doctype/academic_term/academic_term.py +++ b/erpnext/education/doctype/academic_term/academic_term.py @@ -22,9 +22,9 @@ class AcademicTerm(Document): and getdate(self.term_start_date) > getdate(self.term_end_date): frappe.throw(_("The Term End Date cannot be earlier than the Term Start Date. Please correct the dates and try again.")) - # Check that the start of the term is not before the start of the academic year + # Check that the start of the term is not before the start of the academic year # and end of term is not after the end of the academic year""" - + year = frappe.get_doc("Academic Year",self.academic_year) if self.term_start_date and getdate(year.year_start_date) and (getdate(self.term_start_date) < getdate(year.year_start_date)): frappe.throw(_("The Term Start Date cannot be earlier than the Year Start Date of the Academic Year to which the term is linked (Academic Year {}). Please correct the dates and try again.").format(self.academic_year)) diff --git a/erpnext/education/doctype/academic_term/academic_term_dashboard.py b/erpnext/education/doctype/academic_term/academic_term_dashboard.py index 871e0f32845..eb2f90742ce 100644 --- a/erpnext/education/doctype/academic_term/academic_term_dashboard.py +++ b/erpnext/education/doctype/academic_term/academic_term_dashboard.py @@ -22,4 +22,4 @@ def get_data(): 'items': ['Assessment Plan', 'Assessment Result'] } ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/academic_term/test_academic_term.js b/erpnext/education/doctype/academic_term/test_academic_term.js index 6d91e977c63..383b65a7032 100644 --- a/erpnext/education/doctype/academic_term/test_academic_term.js +++ b/erpnext/education/doctype/academic_term/test_academic_term.js @@ -21,4 +21,4 @@ QUnit.test('Test: Academic Term', function(assert){ }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/academic_year/academic_year.js b/erpnext/education/doctype/academic_year/academic_year.js index 0e8619849cd..20e25281ffc 100644 --- a/erpnext/education/doctype/academic_year/academic_year.js +++ b/erpnext/education/doctype/academic_year/academic_year.js @@ -1,2 +1,2 @@ frappe.ui.form.on("Academic Year", { -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/academic_year/academic_year_dashboard.py b/erpnext/education/doctype/academic_year/academic_year_dashboard.py index f27f7d14cf6..d3734df8036 100644 --- a/erpnext/education/doctype/academic_year/academic_year_dashboard.py +++ b/erpnext/education/doctype/academic_year/academic_year_dashboard.py @@ -22,4 +22,4 @@ def get_data(): 'items': ['Assessment Plan', 'Assessment Result'] } ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/academic_year/test_academic_year.js b/erpnext/education/doctype/academic_year/test_academic_year.js index ec2f49c5a1b..51e9cf307d8 100644 --- a/erpnext/education/doctype/academic_year/test_academic_year.js +++ b/erpnext/education/doctype/academic_year/test_academic_year.js @@ -20,4 +20,4 @@ QUnit.test('Test: Academic Year', function(assert){ }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/article/article.js b/erpnext/education/doctype/article/article.js index edfec26273b..85b387f6217 100644 --- a/erpnext/education/doctype/article/article.js +++ b/erpnext/education/doctype/article/article.js @@ -53,4 +53,4 @@ let get_topics_without_article = function(article) { method: 'erpnext.education.doctype.article.article.get_topics_without_article', args: {'article': article} }); -}; \ No newline at end of file +}; diff --git a/erpnext/education/doctype/article/article.py b/erpnext/education/doctype/article/article.py index 8ba367da76e..b5cc5cbc7a6 100644 --- a/erpnext/education/doctype/article/article.py +++ b/erpnext/education/doctype/article/article.py @@ -18,4 +18,4 @@ def get_topics_without_article(article): topic_contents = [tc.content for tc in topic.topic_content] if not topic_contents or article not in topic_contents: data.append(topic.name) - return data \ No newline at end of file + return data diff --git a/erpnext/education/doctype/assessment_criteria/assessment_criteria.py b/erpnext/education/doctype/assessment_criteria/assessment_criteria.py index 1ea37023b24..bfbf26cf6c1 100644 --- a/erpnext/education/doctype/assessment_criteria/assessment_criteria.py +++ b/erpnext/education/doctype/assessment_criteria/assessment_criteria.py @@ -12,4 +12,4 @@ STD_CRITERIA = ["total", "total score", "total grade", "maximum score", "score", class AssessmentCriteria(Document): def validate(self): if self.assessment_criteria.lower() in STD_CRITERIA: - frappe.throw(_("Can't create standard criteria. Please rename the criteria")) \ No newline at end of file + frappe.throw(_("Can't create standard criteria. Please rename the criteria")) diff --git a/erpnext/education/doctype/assessment_criteria/test_assessment_criteria.js b/erpnext/education/doctype/assessment_criteria/test_assessment_criteria.js index db4a4cf5a8d..724c4dac499 100644 --- a/erpnext/education/doctype/assessment_criteria/test_assessment_criteria.js +++ b/erpnext/education/doctype/assessment_criteria/test_assessment_criteria.js @@ -13,4 +13,4 @@ QUnit.test('Test: Assessment Criteria', function(assert){ }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/assessment_criteria_group/test_assessment_criteria_group.js b/erpnext/education/doctype/assessment_criteria_group/test_assessment_criteria_group.js index bcfcaf82e63..ab27e637239 100644 --- a/erpnext/education/doctype/assessment_criteria_group/test_assessment_criteria_group.js +++ b/erpnext/education/doctype/assessment_criteria_group/test_assessment_criteria_group.js @@ -12,4 +12,4 @@ QUnit.test('Test: Assessment Criteria Group', function(assert){ }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/assessment_group/assessment_group_dashboard.py b/erpnext/education/doctype/assessment_group/assessment_group_dashboard.py index 2649d4b90c9..1a23606a61d 100644 --- a/erpnext/education/doctype/assessment_group/assessment_group_dashboard.py +++ b/erpnext/education/doctype/assessment_group/assessment_group_dashboard.py @@ -12,4 +12,4 @@ def get_data(): 'items': ['Assessment Plan', 'Assessment Result'] } ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/assessment_group/assessment_group_tree.js b/erpnext/education/doctype/assessment_group/assessment_group_tree.js index e4676831a3b..e0dfaa31fd7 100644 --- a/erpnext/education/doctype/assessment_group/assessment_group_tree.js +++ b/erpnext/education/doctype/assessment_group/assessment_group_tree.js @@ -1,3 +1,3 @@ frappe.treeview_settings["Assessment Group"] = { - -} \ No newline at end of file + +} diff --git a/erpnext/education/doctype/assessment_group/test_assessment_group.js b/erpnext/education/doctype/assessment_group/test_assessment_group.js index a127fd4adf5..00e6309837d 100644 --- a/erpnext/education/doctype/assessment_group/test_assessment_group.js +++ b/erpnext/education/doctype/assessment_group/test_assessment_group.js @@ -62,4 +62,4 @@ frappe.map_group = { () => frappe.click_button('Create New'), ]); } -}; \ No newline at end of file +}; diff --git a/erpnext/education/doctype/assessment_plan/assessment_plan.js b/erpnext/education/doctype/assessment_plan/assessment_plan.js index 726c0fcecd4..cf545c41afb 100644 --- a/erpnext/education/doctype/assessment_plan/assessment_plan.js +++ b/erpnext/education/doctype/assessment_plan/assessment_plan.js @@ -75,4 +75,4 @@ frappe.ui.form.on('Assessment Plan', { maximum_assessment_score: function(frm) { frm.trigger('course'); } -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py b/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py index 5e6c29dcdf3..8ac3faf6dde 100644 --- a/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py +++ b/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py @@ -18,4 +18,4 @@ def get_data(): 'items': ['Assessment Plan Status'] } ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/assessment_result/assessment_result.js b/erpnext/education/doctype/assessment_result/assessment_result.js index c35f607a99d..b6d28817b5d 100644 --- a/erpnext/education/doctype/assessment_result/assessment_result.js +++ b/erpnext/education/doctype/assessment_result/assessment_result.js @@ -122,4 +122,4 @@ frappe.ui.form.on('Assessment Result Detail', { }); } } -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/assessment_result/assessment_result.py b/erpnext/education/doctype/assessment_result/assessment_result.py index 6b873ecf97a..7dfe0cf6c27 100644 --- a/erpnext/education/doctype/assessment_result/assessment_result.py +++ b/erpnext/education/doctype/assessment_result/assessment_result.py @@ -42,7 +42,3 @@ class AssessmentResult(Document): "student":self.student, "assessment_plan":self.assessment_plan, "docstatus":("!=", 2)}) if assessment_result: frappe.throw(_("Assessment Result record {0} already exists.").format(getlink("Assessment Result",assessment_result[0].name))) - - - - diff --git a/erpnext/education/doctype/assessment_result/assessment_result_dashboard.py b/erpnext/education/doctype/assessment_result/assessment_result_dashboard.py index 438379d08e4..2526076d308 100644 --- a/erpnext/education/doctype/assessment_result/assessment_result_dashboard.py +++ b/erpnext/education/doctype/assessment_result/assessment_result_dashboard.py @@ -11,4 +11,4 @@ def get_data(): 'items': ['Final Assessment Grades', 'Course wise Assessment Report'] } ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/assessment_result/test_assessment_result.js b/erpnext/education/doctype/assessment_result/test_assessment_result.js index b7adfacb1a2..d4eb4b8ba66 100644 --- a/erpnext/education/doctype/assessment_result/test_assessment_result.js +++ b/erpnext/education/doctype/assessment_result/test_assessment_result.js @@ -70,4 +70,4 @@ QUnit.test('Test: Assessment Result', function(assert){ () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/assessment_result/test_assessment_result.py b/erpnext/education/doctype/assessment_result/test_assessment_result.py index e5535d6085a..adce57769dd 100644 --- a/erpnext/education/doctype/assessment_result/test_assessment_result.py +++ b/erpnext/education/doctype/assessment_result/test_assessment_result.py @@ -16,4 +16,3 @@ class TestAssessmentResult(unittest.TestCase): grade = get_grade("_Test Grading Scale", 70) self.assertEqual("B", grade) - \ No newline at end of file diff --git a/erpnext/education/doctype/assessment_result_tool/assessment_result_tool.py b/erpnext/education/doctype/assessment_result_tool/assessment_result_tool.py index 649f420d41f..a0d286ccbe9 100644 --- a/erpnext/education/doctype/assessment_result_tool/assessment_result_tool.py +++ b/erpnext/education/doctype/assessment_result_tool/assessment_result_tool.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class AssessmentResultTool(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/education/doctype/assessment_result_tool/test_assessment_result_tool.js b/erpnext/education/doctype/assessment_result_tool/test_assessment_result_tool.js index 0bbe33194a3..7ef5c688fb9 100644 --- a/erpnext/education/doctype/assessment_result_tool/test_assessment_result_tool.js +++ b/erpnext/education/doctype/assessment_result_tool/test_assessment_result_tool.js @@ -26,4 +26,4 @@ QUnit.test('Test: Assessment Result Tool', function(assert){ }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/course/course.js b/erpnext/education/doctype/course/course.js index 81e4a8c08d3..bd8d62c8d2a 100644 --- a/erpnext/education/doctype/course/course.js +++ b/erpnext/education/doctype/course/course.js @@ -76,4 +76,4 @@ let get_programs_without_course = function(course) { method: 'erpnext.education.doctype.course.course.get_programs_without_course', args: {'course': course} }); -} \ No newline at end of file +} diff --git a/erpnext/education/doctype/course/course.py b/erpnext/education/doctype/course/course.py index 06efa54e770..92f92ed9f3e 100644 --- a/erpnext/education/doctype/course/course.py +++ b/erpnext/education/doctype/course/course.py @@ -53,4 +53,4 @@ def get_programs_without_course(course): courses = [c.course for c in program.courses] if not courses or course not in courses: data.append(program.name) - return data \ No newline at end of file + return data diff --git a/erpnext/education/doctype/course/course_dashboard.py b/erpnext/education/doctype/course/course_dashboard.py index 8a570bdc579..8de91b1c092 100644 --- a/erpnext/education/doctype/course/course_dashboard.py +++ b/erpnext/education/doctype/course/course_dashboard.py @@ -20,4 +20,4 @@ def get_data(): 'items': ['Assessment Plan', 'Assessment Result'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/course/test_course.js b/erpnext/education/doctype/course/test_course.js index 88fddc2bb6d..2b6860cb7f4 100644 --- a/erpnext/education/doctype/course/test_course.js +++ b/erpnext/education/doctype/course/test_course.js @@ -33,4 +33,4 @@ QUnit.test('test course', function(assert) { }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/course_activity/course_activity.py b/erpnext/education/doctype/course_activity/course_activity.py index e7fc08a1d7f..3aa1ea0c5b3 100644 --- a/erpnext/education/doctype/course_activity/course_activity.py +++ b/erpnext/education/doctype/course_activity/course_activity.py @@ -16,4 +16,4 @@ class CourseActivity(Document): if frappe.db.exists("Course Enrollment", self.enrollment): return True else: - frappe.throw(_("Course Enrollment {0} does not exists").format(self.enrollment)) \ No newline at end of file + frappe.throw(_("Course Enrollment {0} does not exists").format(self.enrollment)) diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment.py b/erpnext/education/doctype/course_enrollment/course_enrollment.py index 2b3acf1b93b..ce88990a70d 100644 --- a/erpnext/education/doctype/course_enrollment/course_enrollment.py +++ b/erpnext/education/doctype/course_enrollment/course_enrollment.py @@ -91,4 +91,4 @@ def check_activity_exists(enrollment, content_type, content): if activity: return activity[0].name else: - return None \ No newline at end of file + return None diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment_dashboard.py b/erpnext/education/doctype/course_enrollment/course_enrollment_dashboard.py index b9dd457b61c..37972fe354c 100644 --- a/erpnext/education/doctype/course_enrollment/course_enrollment_dashboard.py +++ b/erpnext/education/doctype/course_enrollment/course_enrollment_dashboard.py @@ -12,4 +12,4 @@ def get_data(): 'items': ['Course Activity', 'Quiz Activity'] } ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/course_enrollment/test_course_enrollment.py b/erpnext/education/doctype/course_enrollment/test_course_enrollment.py index e22c7ce0bab..874bf121f47 100644 --- a/erpnext/education/doctype/course_enrollment/test_course_enrollment.py +++ b/erpnext/education/doctype/course_enrollment/test_course_enrollment.py @@ -39,6 +39,3 @@ class TestCourseEnrollment(unittest.TestCase): doc = frappe.get_doc("Program Enrollment", entry.name) doc.cancel() doc.delete() - - - diff --git a/erpnext/education/doctype/course_schedule/course_schedule.js b/erpnext/education/doctype/course_schedule/course_schedule.js index 4275f6ef2a6..366bbd8b0d4 100644 --- a/erpnext/education/doctype/course_schedule/course_schedule.js +++ b/erpnext/education/doctype/course_schedule/course_schedule.js @@ -13,4 +13,4 @@ frappe.ui.form.on("Course Schedule", { }).addClass("btn-primary"); } } -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/course_schedule/course_schedule.py b/erpnext/education/doctype/course_schedule/course_schedule.py index 5083ff6589d..748728d104e 100644 --- a/erpnext/education/doctype/course_schedule/course_schedule.py +++ b/erpnext/education/doctype/course_schedule/course_schedule.py @@ -14,11 +14,11 @@ class CourseSchedule(Document): self.validate_course() self.validate_date() self.validate_overlap() - + def set_title(self): """Set document Title""" self.title = self.course + " by " + (self.instructor_name if self.instructor_name else self.instructor) - + def validate_course(self): group_based_on, course = frappe.db.get_value("Student Group", self.student_group, ["group_based_on", "course"]) if group_based_on == "Course": @@ -28,23 +28,22 @@ class CourseSchedule(Document): """Validates if from_time is greater than to_time""" if self.from_time > self.to_time: frappe.throw(_("From Time cannot be greater than To Time.")) - + def validate_overlap(self): """Validates overlap for Student Group, Instructor, Room""" - + from erpnext.education.utils import validate_overlap_for #Validate overlapping course schedules. if self.student_group: validate_overlap_for(self, "Course Schedule", "student_group") - + validate_overlap_for(self, "Course Schedule", "instructor") validate_overlap_for(self, "Course Schedule", "room") #validate overlapping assessment schedules. if self.student_group: validate_overlap_for(self, "Assessment Plan", "student_group") - + validate_overlap_for(self, "Assessment Plan", "room") validate_overlap_for(self, "Assessment Plan", "supervisor", self.instructor) - diff --git a/erpnext/education/doctype/course_schedule/course_schedule_dashboard.py b/erpnext/education/doctype/course_schedule/course_schedule_dashboard.py index 0866cd65357..22ce7e1ec24 100644 --- a/erpnext/education/doctype/course_schedule/course_schedule_dashboard.py +++ b/erpnext/education/doctype/course_schedule/course_schedule_dashboard.py @@ -12,4 +12,4 @@ def get_data(): 'items': ['Student Attendance'] } ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/course_schedule/test_course_schedule.py b/erpnext/education/doctype/course_schedule/test_course_schedule.py index a901f1e467a..5bb4de85846 100644 --- a/erpnext/education/doctype/course_schedule/test_course_schedule.py +++ b/erpnext/education/doctype/course_schedule/test_course_schedule.py @@ -17,28 +17,28 @@ class TestCourseSchedule(unittest.TestCase): def test_student_group_conflict(self): cs1 = make_course_schedule_test_record(simulate= True) - cs2 = make_course_schedule_test_record(schedule_date=cs1.schedule_date, from_time= cs1.from_time, + cs2 = make_course_schedule_test_record(schedule_date=cs1.schedule_date, from_time= cs1.from_time, to_time= cs1.to_time, instructor="_Test Instructor 2", room=frappe.get_all("Room")[1].name, do_not_save= 1) self.assertRaises(OverlapError, cs2.save) def test_instructor_conflict(self): cs1 = make_course_schedule_test_record(simulate= True) - - cs2 = make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time, + + cs2 = make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time, student_group="Course-TC101-2014-2015 (_Test Academic Term)", room=frappe.get_all("Room")[1].name, do_not_save= 1) self.assertRaises(OverlapError, cs2.save) def test_room_conflict(self): cs1 = make_course_schedule_test_record(simulate= True) - - cs2 = make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time, + + cs2 = make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time, student_group="Course-TC101-2014-2015 (_Test Academic Term)", instructor="_Test Instructor 2", do_not_save= 1) self.assertRaises(OverlapError, cs2.save) - + def test_no_conflict(self): cs1 = make_course_schedule_test_record(simulate= True) - - make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time, + + make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time, student_group="Course-TC102-2014-2015 (_Test Academic Term)", instructor="_Test Instructor 2", room=frappe.get_all("Room")[1].name) def make_course_schedule_test_record(**args): @@ -49,12 +49,12 @@ def make_course_schedule_test_record(**args): course_schedule.course = args.course or "TC101" course_schedule.instructor = args.instructor or "_Test Instructor" course_schedule.room = args.room or frappe.get_all("Room")[0].name - + course_schedule.schedule_date = args.schedule_date or today() course_schedule.from_time = args.from_time or to_timedelta("01:00:00") course_schedule.to_time = args.to_time or course_schedule.from_time + datetime.timedelta(hours= 1) - + if not args.do_not_save: if args.simulate: while True: diff --git a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.js b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.js index d57f46ab98e..7b0e4ab47c8 100644 --- a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.js +++ b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.js @@ -41,4 +41,4 @@ frappe.ui.form.on('Course Scheduling Tool', { }); }); } -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/education_settings/education_settings.py b/erpnext/education/doctype/education_settings/education_settings.py index 658380ea429..6c7e95c80da 100644 --- a/erpnext/education/doctype/education_settings/education_settings.py +++ b/erpnext/education/doctype/education_settings/education_settings.py @@ -36,4 +36,4 @@ class EducationSettings(Document): make_property_setter('Instructor', "naming_series", "hidden", 1, "Check", validate_fields_for_doctype=False) def update_website_context(context): - context["lms_enabled"] = frappe.get_doc("Education Settings").enable_lms \ No newline at end of file + context["lms_enabled"] = frappe.get_doc("Education Settings").enable_lms diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule.js b/erpnext/education/doctype/fee_schedule/fee_schedule.js index 0089957df40..97691a5b62a 100644 --- a/erpnext/education/doctype/fee_schedule/fee_schedule.js +++ b/erpnext/education/doctype/fee_schedule/fee_schedule.js @@ -130,4 +130,4 @@ frappe.ui.form.on('Fee Schedule Student Group', { }); } } -}) \ No newline at end of file +}) diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule_dashboard.py b/erpnext/education/doctype/fee_schedule/fee_schedule_dashboard.py index acfe4002193..4d7da21ea17 100644 --- a/erpnext/education/doctype/fee_schedule/fee_schedule_dashboard.py +++ b/erpnext/education/doctype/fee_schedule/fee_schedule_dashboard.py @@ -10,4 +10,4 @@ def get_data(): 'items': ['Fees'] } ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/fee_structure/fee_structure.js b/erpnext/education/doctype/fee_structure/fee_structure.js index 310c4105f47..d9ab99f8180 100644 --- a/erpnext/education/doctype/fee_structure/fee_structure.js +++ b/erpnext/education/doctype/fee_structure/fee_structure.js @@ -69,4 +69,4 @@ frappe.ui.form.on('Fee Component', { } frm.set_value('total_amount', total_amount); } -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/fee_structure/fee_structure.py b/erpnext/education/doctype/fee_structure/fee_structure.py index 781382b51be..9755717ee94 100644 --- a/erpnext/education/doctype/fee_structure/fee_structure.py +++ b/erpnext/education/doctype/fee_structure/fee_structure.py @@ -11,13 +11,13 @@ from frappe.model.mapper import get_mapped_doc class FeeStructure(Document): def validate(self): self.calculate_total() - + def calculate_total(self): """Calculates total amount.""" self.total_amount = 0 for d in self.components: self.total_amount += d.amount - + @frappe.whitelist() def make_fee_schedule(source_name, target_doc=None): @@ -31,4 +31,4 @@ def make_fee_schedule(source_name, target_doc=None): "Fee Component": { "doctype": "Fee Component" } - }, target_doc) \ No newline at end of file + }, target_doc) diff --git a/erpnext/education/doctype/fee_structure/fee_structure_dashboard.py b/erpnext/education/doctype/fee_structure/fee_structure_dashboard.py index 73e314f3512..fdf7df7aa26 100644 --- a/erpnext/education/doctype/fee_structure/fee_structure_dashboard.py +++ b/erpnext/education/doctype/fee_structure/fee_structure_dashboard.py @@ -12,4 +12,4 @@ def get_data(): 'items': ['Fees', 'Fee Schedule'] } ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/fees/fees.py b/erpnext/education/doctype/fees/fees.py index 25d67d2d5f6..7e867049047 100644 --- a/erpnext/education/doctype/fees/fees.py +++ b/erpnext/education/doctype/fees/fees.py @@ -132,4 +132,4 @@ def get_list_context(context=None): "title": _("Fees"), "get_list": get_fee_list, "row_template": "templates/includes/fee/fee_row.html" - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/fees/fees_list.js b/erpnext/education/doctype/fees/fees_list.js index 52e1c4beb5a..ee8e1e382e9 100644 --- a/erpnext/education/doctype/fees/fees_list.js +++ b/erpnext/education/doctype/fees/fees_list.js @@ -9,4 +9,4 @@ frappe.listview_settings['Fees'] = { return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<=,Today"]; } } -}; \ No newline at end of file +}; diff --git a/erpnext/education/doctype/grading_scale/grading_scale.py b/erpnext/education/doctype/grading_scale/grading_scale.py index 6309d02c155..0e732971619 100644 --- a/erpnext/education/doctype/grading_scale/grading_scale.py +++ b/erpnext/education/doctype/grading_scale/grading_scale.py @@ -17,4 +17,4 @@ class GradingScale(Document): else: thresholds.append(cint(d.threshold)) if 0 not in thresholds: - frappe.throw(_("Please define grade for Threshold 0%")) \ No newline at end of file + frappe.throw(_("Please define grade for Threshold 0%")) diff --git a/erpnext/education/doctype/grading_scale/test_grading_scale.js b/erpnext/education/doctype/grading_scale/test_grading_scale.js index e363545ff8d..fb56918fdb8 100644 --- a/erpnext/education/doctype/grading_scale/test_grading_scale.js +++ b/erpnext/education/doctype/grading_scale/test_grading_scale.js @@ -99,4 +99,4 @@ QUnit.test('Test: Grading Scale', function(assert){ () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/guardian/test_guardian.js b/erpnext/education/doctype/guardian/test_guardian.js index 9bbfacd5802..1ea6dc290bd 100644 --- a/erpnext/education/doctype/guardian/test_guardian.js +++ b/erpnext/education/doctype/guardian/test_guardian.js @@ -31,4 +31,4 @@ QUnit.test('Test: Guardian', function(assert){ }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/instructor/instructor.js b/erpnext/education/doctype/instructor/instructor.js index 24e80fa9378..034b0aaf5dd 100644 --- a/erpnext/education/doctype/instructor/instructor.js +++ b/erpnext/education/doctype/instructor/instructor.js @@ -61,4 +61,4 @@ frappe.ui.form.on("Instructor", { }; }); } -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/instructor/instructor_dashboard.py b/erpnext/education/doctype/instructor/instructor_dashboard.py index a404fc56c54..c19c85947d6 100644 --- a/erpnext/education/doctype/instructor/instructor_dashboard.py +++ b/erpnext/education/doctype/instructor/instructor_dashboard.py @@ -21,4 +21,4 @@ def get_data(): 'items': ['Student Group'] } ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/program/program.js b/erpnext/education/doctype/program/program.js index 98263b55a1f..2d89351504b 100644 --- a/erpnext/education/doctype/program/program.js +++ b/erpnext/education/doctype/program/program.js @@ -4,5 +4,5 @@ cur_frm.add_fetch('fee_structure', 'total_amount', 'amount'); frappe.ui.form.on("Program", "refresh", function(frm) { - -}); \ No newline at end of file + +}); diff --git a/erpnext/education/doctype/program/program.py b/erpnext/education/doctype/program/program.py index d24df5d6142..9d886b7b9e6 100644 --- a/erpnext/education/doctype/program/program.py +++ b/erpnext/education/doctype/program/program.py @@ -11,4 +11,4 @@ class Program(Document): def get_course_list(self): program_course_list = self.courses course_list = [frappe.get_doc("Course", program_course.course) for program_course in program_course_list] - return course_list \ No newline at end of file + return course_list diff --git a/erpnext/education/doctype/program/program_dashboard.py b/erpnext/education/doctype/program/program_dashboard.py index c5d249451f2..6c503e1bf1f 100644 --- a/erpnext/education/doctype/program/program_dashboard.py +++ b/erpnext/education/doctype/program/program_dashboard.py @@ -21,4 +21,4 @@ def get_data(): 'items': ['Assessment Plan', 'Assessment Result'] } ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/program/test_program.js b/erpnext/education/doctype/program/test_program.js index dc347cf1b06..b9ca41ae3f9 100644 --- a/erpnext/education/doctype/program/test_program.js +++ b/erpnext/education/doctype/program/test_program.js @@ -31,4 +31,4 @@ QUnit.test('Test: Program', function(assert){ }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/program/test_program.py b/erpnext/education/doctype/program/test_program.py index d7530365117..204f2961e7f 100644 --- a/erpnext/education/doctype/program/test_program.py +++ b/erpnext/education/doctype/program/test_program.py @@ -88,4 +88,4 @@ def setup_program(): course_list = [course['course_name'] for course in test_data['course']] program = make_program_and_linked_courses(test_data['program_name'], course_list) - return program \ No newline at end of file + return program diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.js b/erpnext/education/doctype/program_enrollment/program_enrollment.js index f9c65fbbfb3..e92d063602d 100644 --- a/erpnext/education/doctype/program_enrollment/program_enrollment.js +++ b/erpnext/education/doctype/program_enrollment/program_enrollment.js @@ -101,4 +101,4 @@ frappe.ui.form.on('Program Enrollment Course', { return { filters: [['Course', 'name', 'not in', course_list]] }; }; } -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py index b282babd0fc..dd4aa576ac0 100644 --- a/erpnext/education/doctype/program_enrollment/program_enrollment.py +++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py @@ -174,4 +174,3 @@ def get_students(doctype, txt, searchfield, start, page_len, filters): tuple(students + ["%%%s%%" % txt, start, page_len] ) ) - diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment_dashboard.py b/erpnext/education/doctype/program_enrollment/program_enrollment_dashboard.py index 18d307cdf07..c47f8666898 100644 --- a/erpnext/education/doctype/program_enrollment/program_enrollment_dashboard.py +++ b/erpnext/education/doctype/program_enrollment/program_enrollment_dashboard.py @@ -16,4 +16,4 @@ def get_data(): 'items': ['Student and Guardian Contact Details'] } ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/program_enrollment/test_program_enrollment.py b/erpnext/education/doctype/program_enrollment/test_program_enrollment.py index fec6422e75f..497ee288aac 100644 --- a/erpnext/education/doctype/program_enrollment/test_program_enrollment.py +++ b/erpnext/education/doctype/program_enrollment/test_program_enrollment.py @@ -32,4 +32,4 @@ class TestProgramEnrollment(unittest.TestCase): for entry in frappe.db.get_all("Program Enrollment"): doc = frappe.get_doc("Program Enrollment", entry.name) doc.cancel() - doc.delete() \ No newline at end of file + doc.delete() diff --git a/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json b/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json index 9be292b65ea..1d7497387f9 100644 --- a/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json +++ b/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json @@ -1,195 +1,68 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-06-10 03:29:02.539914", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "actions": [], + "creation": "2016-06-10 03:29:02.539914", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "student_applicant", + "student", + "student_name", + "column_break_3", + "student_batch_name", + "student_category" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "student_applicant", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Student Applicant", - "length": 0, - "no_copy": 0, - "options": "Student Applicant", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "student_applicant", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Student Applicant", + "options": "Student Applicant" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "student", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Student", - "length": 0, - "no_copy": 0, - "options": "Student", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "student", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Student", + "options": "Student" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "student_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Student Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "student_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Student Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "student_batch_name", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Student Batch Name", - "length": 0, - "no_copy": 0, - "options": "Student Batch Name", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "student_batch_name", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Student Batch Name", + "options": "Student Batch Name" + }, + { + "fieldname": "student_category", + "fieldtype": "Link", + "label": "Student Category", + "options": "Student Category", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-01-02 12:03:53.890741", - "modified_by": "Administrator", - "module": "Education", - "name": "Program Enrollment Tool Student", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Education", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0 + ], + "istable": 1, + "links": [], + "modified": "2021-07-29 18:19:54.471594", + "modified_by": "Administrator", + "module": "Education", + "name": "Program Enrollment Tool Student", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/education/doctype/question/question.py b/erpnext/education/doctype/question/question.py index a7deeab6f65..fb3b50478c8 100644 --- a/erpnext/education/doctype/question/question.py +++ b/erpnext/education/doctype/question/question.py @@ -43,4 +43,4 @@ class Question(Document): elif len(answers) == 1: return answers[0] else: - return answers \ No newline at end of file + return answers diff --git a/erpnext/education/doctype/quiz/quiz.js b/erpnext/education/doctype/quiz/quiz.js index 01bcf732360..320869be31e 100644 --- a/erpnext/education/doctype/quiz/quiz.js +++ b/erpnext/education/doctype/quiz/quiz.js @@ -68,4 +68,4 @@ let get_topics_without_quiz = function(quiz) { method: 'erpnext.education.doctype.quiz.quiz.get_topics_without_quiz', args: {'quiz': quiz} }); -}; \ No newline at end of file +}; diff --git a/erpnext/education/doctype/quiz/quiz.py b/erpnext/education/doctype/quiz/quiz.py index a774b88579a..a128e1f3427 100644 --- a/erpnext/education/doctype/quiz/quiz.py +++ b/erpnext/education/doctype/quiz/quiz.py @@ -68,4 +68,4 @@ def get_topics_without_quiz(quiz): topic_contents = [tc.content for tc in topic.topic_content] if not topic_contents or quiz not in topic_contents: data.append(topic.name) - return data \ No newline at end of file + return data diff --git a/erpnext/education/doctype/room/room.js b/erpnext/education/doctype/room/room.js index 20cee6b2a61..1263b60ced2 100644 --- a/erpnext/education/doctype/room/room.js +++ b/erpnext/education/doctype/room/room.js @@ -1,2 +1,2 @@ frappe.ui.form.on("Room", { -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/room/room_dashboard.py b/erpnext/education/doctype/room/room_dashboard.py index 99aac3393e6..7bcb97f709f 100644 --- a/erpnext/education/doctype/room/room_dashboard.py +++ b/erpnext/education/doctype/room/room_dashboard.py @@ -16,4 +16,4 @@ def get_data(): 'items': ['Assessment Plan'] } ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/student/student.js b/erpnext/education/doctype/student/student.js index fd23ae41ef1..aead556dc9f 100644 --- a/erpnext/education/doctype/student/student.js +++ b/erpnext/education/doctype/student/student.js @@ -60,4 +60,4 @@ frappe.ui.form.on('Student Sibling', { return { filters: [['Student', 'name', 'not in', sibling_list]] }; }; } -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/student/student_list.js b/erpnext/education/doctype/student/student_list.js index 763f120f417..c1bce24d153 100644 --- a/erpnext/education/doctype/student/student_list.js +++ b/erpnext/education/doctype/student/student_list.js @@ -1,3 +1,3 @@ frappe.listview_settings['Student'] = { add_fields: [ "image"] -} \ No newline at end of file +} diff --git a/erpnext/education/doctype/student/test_student.py b/erpnext/education/doctype/student/test_student.py index 2e5263788f7..fcb2b5fb930 100644 --- a/erpnext/education/doctype/student/test_student.py +++ b/erpnext/education/doctype/student/test_student.py @@ -68,4 +68,4 @@ def get_student(email): student_id = frappe.get_all("Student", {"student_email_id": email}, ["name"])[0].name return frappe.get_doc("Student", student_id) except IndexError: - return None \ No newline at end of file + return None diff --git a/erpnext/education/doctype/student_admission/templates/student_admission_row.html b/erpnext/education/doctype/student_admission/templates/student_admission_row.html index 99868d5f020..529d65184a8 100644 --- a/erpnext/education/doctype/student_admission/templates/student_admission_row.html +++ b/erpnext/education/doctype/student_admission/templates/student_admission_row.html @@ -41,4 +41,4 @@ - \ No newline at end of file + diff --git a/erpnext/education/doctype/student_admission/test_student_admission.js b/erpnext/education/doctype/student_admission/test_student_admission.js index 3a0bb0b2f23..e01791a78ac 100644 --- a/erpnext/education/doctype/student_admission/test_student_admission.js +++ b/erpnext/education/doctype/student_admission/test_student_admission.js @@ -37,4 +37,4 @@ QUnit.test('Test: Student Admission', function(assert) { }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/student_applicant/student_applicant.js b/erpnext/education/doctype/student_applicant/student_applicant.js index b4cfdf16e0d..7b41a721748 100644 --- a/erpnext/education/doctype/student_applicant/student_applicant.js +++ b/erpnext/education/doctype/student_applicant/student_applicant.js @@ -59,4 +59,4 @@ frappe.ui.form.on('Student Sibling', { frm.add_fetch("student", "gender", "gender"); frm.add_fetch("student", "date_of_birth", "date_of_birth"); } -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/student_applicant/student_applicant.py b/erpnext/education/doctype/student_applicant/student_applicant.py index 211348201e3..193b6d32977 100644 --- a/erpnext/education/doctype/student_applicant/student_applicant.py +++ b/erpnext/education/doctype/student_applicant/student_applicant.py @@ -49,7 +49,7 @@ class StudentApplicant(Document): frappe.throw(_("Please select Student Admission which is mandatory for the paid student applicant")) def validation_from_student_admission(self): - + student_admission = get_student_admission_data(self.student_admission, self.program) if student_admission and student_admission.min_age and \ diff --git a/erpnext/education/doctype/student_applicant/student_applicant_list.js b/erpnext/education/doctype/student_applicant/student_applicant_list.js index 817a728f696..c39d46ec63a 100644 --- a/erpnext/education/doctype/student_applicant/student_applicant_list.js +++ b/erpnext/education/doctype/student_applicant/student_applicant_list.js @@ -18,4 +18,4 @@ frappe.listview_settings['Student Applicant'] = { return [__("Admitted"), "blue", "application_status,=,Admitted"]; } } -}; \ No newline at end of file +}; diff --git a/erpnext/education/doctype/student_applicant/tests/test_student_applicant.js b/erpnext/education/doctype/student_applicant/tests/test_student_applicant.js index a69ad8a5646..fa679779858 100644 --- a/erpnext/education/doctype/student_applicant/tests/test_student_applicant.js +++ b/erpnext/education/doctype/student_applicant/tests/test_student_applicant.js @@ -92,4 +92,4 @@ QUnit.test('Test: Student Applicant', function(assert){ }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/student_applicant/tests/test_student_applicant_dummy_data.js b/erpnext/education/doctype/student_applicant/tests/test_student_applicant_dummy_data.js index 26244ab1845..03101e41e00 100644 --- a/erpnext/education/doctype/student_applicant/tests/test_student_applicant_dummy_data.js +++ b/erpnext/education/doctype/student_applicant/tests/test_student_applicant_dummy_data.js @@ -84,4 +84,4 @@ QUnit.test('Make Students', function(assert){ }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/student_applicant/tests/test_student_applicant_options.js b/erpnext/education/doctype/student_applicant/tests/test_student_applicant_options.js index 114358f32a1..daa36e75ce4 100644 --- a/erpnext/education/doctype/student_applicant/tests/test_student_applicant_options.js +++ b/erpnext/education/doctype/student_applicant/tests/test_student_applicant_options.js @@ -107,4 +107,4 @@ QUnit.test('test student applicant', function(assert){ }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/student_attendance/student_attendance.js b/erpnext/education/doctype/student_attendance/student_attendance.js index f025a1a5397..2bbecb911f6 100644 --- a/erpnext/education/doctype/student_attendance/student_attendance.js +++ b/erpnext/education/doctype/student_attendance/student_attendance.js @@ -2,4 +2,4 @@ // For license information, please see license.txt cur_frm.add_fetch("course_schedule", "schedule_date", "date"); -cur_frm.add_fetch("course_schedule", "student_group", "student_group") \ No newline at end of file +cur_frm.add_fetch("course_schedule", "student_group", "student_group") diff --git a/erpnext/education/doctype/student_attendance/student_attendance_dashboard.py b/erpnext/education/doctype/student_attendance/student_attendance_dashboard.py index 9c41b8f3dc6..e405b8aed9d 100644 --- a/erpnext/education/doctype/student_attendance/student_attendance_dashboard.py +++ b/erpnext/education/doctype/student_attendance/student_attendance_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Student Monthly Attendance Sheet', 'Student Batch-Wise Attendance'] } ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/student_attendance/student_attendance_list.js b/erpnext/education/doctype/student_attendance/student_attendance_list.js index 0d3e7ade152..e89b76c8d55 100644 --- a/erpnext/education/doctype/student_attendance/student_attendance_list.js +++ b/erpnext/education/doctype/student_attendance/student_attendance_list.js @@ -8,4 +8,4 @@ frappe.listview_settings['Student Attendance'] = { return [__("Present"), "green", "status,=,Present"]; } } -}; \ No newline at end of file +}; diff --git a/erpnext/education/doctype/student_attendance/test_student_attendance.js b/erpnext/education/doctype/student_attendance/test_student_attendance.js index c7da6f6b246..3d30b090ba0 100644 --- a/erpnext/education/doctype/student_attendance/test_student_attendance.js +++ b/erpnext/education/doctype/student_attendance/test_student_attendance.js @@ -28,4 +28,4 @@ QUnit.test('Test: Student Attendance', function(assert){ () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py index 028db918812..972973fbadb 100644 --- a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py +++ b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py @@ -38,4 +38,4 @@ def get_student_attendance_records(based_on, date=None, student_group=None, cour if student.student == attendance.student: student.status = attendance.status - return student_list \ No newline at end of file + return student_list diff --git a/erpnext/education/doctype/student_attendance_tool/test_student_attendance_tool.js b/erpnext/education/doctype/student_attendance_tool/test_student_attendance_tool.js index cea0761ae8b..b66d8397ba2 100644 --- a/erpnext/education/doctype/student_attendance_tool/test_student_attendance_tool.js +++ b/erpnext/education/doctype/student_attendance_tool/test_student_attendance_tool.js @@ -82,4 +82,4 @@ QUnit.test('Test: Student Attendace Tool', function(assert){ () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/student_group/student_group.js b/erpnext/education/doctype/student_group/student_group.js index 51e3b74a5cf..39ee9cebd10 100644 --- a/erpnext/education/doctype/student_group/student_group.js +++ b/erpnext/education/doctype/student_group/student_group.js @@ -142,4 +142,4 @@ frappe.ui.form.on('Student Group Instructor', { return { filters: [['Instructor', 'name', 'not in', instructor_list]] }; }; } -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/student_group/student_group.py b/erpnext/education/doctype/student_group/student_group.py index 0260b808646..3d4572abf70 100644 --- a/erpnext/education/doctype/student_group/student_group.py +++ b/erpnext/education/doctype/student_group/student_group.py @@ -128,4 +128,3 @@ def fetch_students(doctype, txt, searchfield, start, page_len, filters): order by idx desc, name limit %s, %s""".format(searchfield), tuple(["%%%s%%" % txt, "%%%s%%" % txt, start, page_len])) - diff --git a/erpnext/education/doctype/student_group/student_group_dashboard.py b/erpnext/education/doctype/student_group/student_group_dashboard.py index ad7a6de7b3c..d37445f7b98 100644 --- a/erpnext/education/doctype/student_group/student_group_dashboard.py +++ b/erpnext/education/doctype/student_group/student_group_dashboard.py @@ -16,4 +16,4 @@ def get_data(): 'items': ['Course Schedule'] } ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/student_group/test_student_group.js b/erpnext/education/doctype/student_group/test_student_group.js index 6673343be7e..4c7e47bc38f 100644 --- a/erpnext/education/doctype/student_group/test_student_group.js +++ b/erpnext/education/doctype/student_group/test_student_group.js @@ -53,4 +53,4 @@ QUnit.test('Test: Student Group', function(assert){ () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.js b/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.js index d0d7afd701c..c189e2763c8 100644 --- a/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.js +++ b/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.js @@ -37,4 +37,4 @@ frappe.ui.form.on("Student Group Creation Tool", "onload", function(frm){ } }; }); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.py b/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.py index dc8667ec065..28ff7d618c1 100644 --- a/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.py +++ b/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.py @@ -76,4 +76,4 @@ class StudentGroupCreationTool(Document): student_group.append('students', student) student_group.save() - frappe.msgprint(_("{0} Student Groups created.").format(l)) \ No newline at end of file + frappe.msgprint(_("{0} Student Groups created.").format(l)) diff --git a/erpnext/education/doctype/student_group_creation_tool/test_student_group_creation_tool.js b/erpnext/education/doctype/student_group_creation_tool/test_student_group_creation_tool.js index 34c10930b57..fa612ba2727 100644 --- a/erpnext/education/doctype/student_group_creation_tool/test_student_group_creation_tool.js +++ b/erpnext/education/doctype/student_group_creation_tool/test_student_group_creation_tool.js @@ -81,4 +81,4 @@ QUnit.test('Test: Student Group Creation Tool', function(assert){ () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/student_group_student/student_group_student.py b/erpnext/education/doctype/student_group_student/student_group_student.py index 820e30118dc..1fe4ea1dc35 100644 --- a/erpnext/education/doctype/student_group_student/student_group_student.py +++ b/erpnext/education/doctype/student_group_student/student_group_student.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class StudentGroupStudent(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/education/doctype/student_leave_application/student_leave_application_dashboard.py b/erpnext/education/doctype/student_leave_application/student_leave_application_dashboard.py index fdcc1474797..0ff6d1a76ea 100644 --- a/erpnext/education/doctype/student_leave_application/student_leave_application_dashboard.py +++ b/erpnext/education/doctype/student_leave_application/student_leave_application_dashboard.py @@ -8,4 +8,4 @@ def get_data(): 'items': ['Student Attendance'] } ] - } \ No newline at end of file + } diff --git a/erpnext/education/doctype/student_leave_application/test_student_leave_application.js b/erpnext/education/doctype/student_leave_application/test_student_leave_application.js index 5af9f5d50f7..6bbf17babfa 100644 --- a/erpnext/education/doctype/student_leave_application/test_student_leave_application.js +++ b/erpnext/education/doctype/student_leave_application/test_student_leave_application.js @@ -66,4 +66,4 @@ QUnit.test('Test: Student Leave Application', function(assert){ () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/student_leave_application/test_student_leave_application.py b/erpnext/education/doctype/student_leave_application/test_student_leave_application.py index fcdd42825f3..9cae2577481 100644 --- a/erpnext/education/doctype/student_leave_application/test_student_leave_application.py +++ b/erpnext/education/doctype/student_leave_application/test_student_leave_application.py @@ -112,4 +112,4 @@ def create_holiday_list(): company = get_default_company() or frappe.get_all('Company')[0].name frappe.db.set_value('Company', company, 'default_holiday_list', holiday_list) - return holiday_list \ No newline at end of file + return holiday_list diff --git a/erpnext/education/doctype/student_log/test_student_log.js b/erpnext/education/doctype/student_log/test_student_log.js index 5775369e52e..4c90c5f6ef2 100644 --- a/erpnext/education/doctype/student_log/test_student_log.js +++ b/erpnext/education/doctype/student_log/test_student_log.js @@ -32,4 +32,4 @@ QUnit.test('Test: Student Log', function(assert){ }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/student_report_generation_tool/student_report_generation_tool.html b/erpnext/education/doctype/student_report_generation_tool/student_report_generation_tool.html index 72772b7b32c..a9e84e6e277 100644 --- a/erpnext/education/doctype/student_report_generation_tool/student_report_generation_tool.html +++ b/erpnext/education/doctype/student_report_generation_tool/student_report_generation_tool.html @@ -12,67 +12,67 @@ padding: 0.75in; margin: auto; } - + .print-format.landscape { max-width: 11.69in; padding: 0.2in; } - + .page-break { padding: 30px 0px; border-bottom: 1px dashed #888; } - + .page-break:first-child { padding-top: 0px; } - + .page-break:last-child { border-bottom: 0px; } - + /* mozilla hack for images in table */ body:last-child .print-format td img { width: 100% !important; } - + @media(max-width: 767px) { .print-format { padding: 0.2in; } } } - + @media print { .print-format p { margin-left: 1px; margin-right: 1px; } } - + .data-field { margin-top: 5px; margin-bottom: 5px; } - + .data-field .value { word-wrap: break-word; } - + .important .value { font-size: 120%; font-weight: bold; } - + .important label { line-height: 1.8; margin: 0px; } - + .table { margin: 20px 0px; } - + .square-image { width: 100%; height: 0; @@ -83,88 +83,88 @@ background-position: center center; border-radius: 4px; } - + .print-item-image { object-fit: contain; } - + .pdf-variables, .pdf-variable, .visible-pdf { display: none !important; } - + .print-format { font-size: 9pt; font-family: "Helvetica Neue", Helvetica, Arial, "Open Sans", sans-serif; -webkit-print-color-adjust:exact; } - + .page-break { page-break-after: always; } - + .print-heading { border-bottom: 1px solid #aaa; margin-bottom: 10px; } - + .print-heading h2 { margin: 0px; } .print-heading h4 { margin-top: 5px; } - + table.no-border, table.no-border td { border: 0px; } - + .print-format label { /* wkhtmltopdf breaks label into multiple lines when it is inline-block */ display: block; } - + .print-format img { max-width: 100%; } - + .print-format table td > .primary:first-child { font-weight: bold; } - + .print-format td, .print-format th { vertical-align: top !important; padding: 6px !important; } - + .print-format p { margin: 3px 0px 3px; } - + table td div { - + /* needed to avoid partial cutting of text between page break in wkhtmltopdf */ page-break-inside: avoid !important; - + } - + /* hack for webkit specific browser */ @media (-webkit-min-device-pixel-ratio:0) { thead, tfoot { display: table-row-group; } } - + [document-status] { margin-bottom: 5mm; } - + .signature-img { background: #fff; border-radius: 3px; margin-top: 5px; max-height: 150px; } - + .print-heading { text-align: right; text-transform: uppercase; @@ -173,16 +173,16 @@ margin-bottom: 20px; border-bottom: 1px solid #d1d8dd; } - + .print-heading h2 { font-size: 24px; } - + .print-format th { background-color: #eee !important; border-bottom: 0px !important; } - + /* modern format: for-test */ .pbi_avoid { @@ -344,7 +344,7 @@
-
+

{{ _("Student Attendance")}}


Present {{ doc.attendance.get("Present") if doc.attendance.get("Present") != None else '0' }} days @@ -352,7 +352,7 @@
-
+

{{ _("Parents Teacher Meeting Attendance")}}


Present {{ doc.parents_attendance if doc.parents_attendance != None else '0' }} diff --git a/erpnext/education/doctype/topic/topic.js b/erpnext/education/doctype/topic/topic.js index 2002b0c8e3b..0c903c5a56a 100644 --- a/erpnext/education/doctype/topic/topic.js +++ b/erpnext/education/doctype/topic/topic.js @@ -52,4 +52,4 @@ let get_courses_without_topic = function(topic) { method: 'erpnext.education.doctype.topic.topic.get_courses_without_topic', args: {'topic': topic} }); -}; \ No newline at end of file +}; diff --git a/erpnext/education/doctype/topic/topic.py b/erpnext/education/doctype/topic/topic.py index a5253e93294..fb680d725b0 100644 --- a/erpnext/education/doctype/topic/topic.py +++ b/erpnext/education/doctype/topic/topic.py @@ -56,4 +56,4 @@ def add_content_to_topics(content_type, content, topics): topic.save() frappe.db.commit() frappe.msgprint(_('{0} {1} has been added to all the selected topics successfully.').format(content_type, frappe.bold(content)), - title=_('Topics updated'), indicator='green') \ No newline at end of file + title=_('Topics updated'), indicator='green') diff --git a/erpnext/education/report/program_wise_fee_collection/program_wise_fee_collection.py b/erpnext/education/report/program_wise_fee_collection/program_wise_fee_collection.py index c145359129e..c0ec0357cc6 100644 --- a/erpnext/education/report/program_wise_fee_collection/program_wise_fee_collection.py +++ b/erpnext/education/report/program_wise_fee_collection/program_wise_fee_collection.py @@ -121,4 +121,3 @@ def get_chart_data(data): }, 'type': 'bar' } - diff --git a/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.js b/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.js index ad04356201e..9f1fcbc8162 100644 --- a/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.js +++ b/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.js @@ -9,4 +9,4 @@ frappe.query_reports["Student Batch-Wise Attendance"] = { "default": frappe.datetime.get_today(), "reqd": 1 }] -} \ No newline at end of file +} diff --git a/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.py b/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.py index 7793dcf3953..e2576a0c710 100644 --- a/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.py +++ b/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.py @@ -67,4 +67,4 @@ def get_student_attendance(student_group, date): student_group= %s and date= %s and docstatus = 1 and (course_schedule is Null or course_schedule='') group by status""", (student_group, date), as_dict=1) - return student_attendance \ No newline at end of file + return student_attendance diff --git a/erpnext/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.js b/erpnext/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.js index 104d3ec06f9..62c94557d7e 100644 --- a/erpnext/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.js +++ b/erpnext/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.js @@ -39,4 +39,4 @@ frappe.query_reports["Student Monthly Attendance Sheet"] = { } }); } -} \ No newline at end of file +} diff --git a/erpnext/education/web_form/student_applicant/student_applicant.js b/erpnext/education/web_form/student_applicant/student_applicant.js index 699703c5792..ffc5e984253 100644 --- a/erpnext/education/web_form/student_applicant/student_applicant.js +++ b/erpnext/education/web_form/student_applicant/student_applicant.js @@ -1,3 +1,3 @@ frappe.ready(function() { // bind events here -}) \ No newline at end of file +}) diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py index 5d5b2e19ce3..912674ff1a2 100644 --- a/erpnext/erpnext_integrations/connectors/shopify_connection.py +++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py @@ -350,4 +350,3 @@ def is_sync_complete(shopify_settings, order): return getdate(shopify_settings.to_date) < getdate(order.get('created_at')) else: return cstr(order.get('id')) == cstr(shopify_settings.to_order_id) - diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.js b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.js index a9925adee7a..f5ea8047c6a 100644 --- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.js +++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.js @@ -1,3 +1,2 @@ // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt - diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/xml_utils.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/xml_utils.py index a25a29f9e5f..99ede8f31de 100644 --- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/xml_utils.py +++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/xml_utils.py @@ -103,4 +103,4 @@ class xml2dict(object): """parse a string""" t = ET.fromstring(s) root_tag, root_tree = self._namespace_split(t.tag, self._parse_node(t)) - return object_dict({root_tag: root_tree}) \ No newline at end of file + return object_dict({root_tag: root_tree}) diff --git a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.py b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.py index 6a846efad70..bff928c1c96 100644 --- a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.py +++ b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.py @@ -17,4 +17,4 @@ class ExotelSettings(Document): response = requests.get('https://api.exotel.com/v1/Accounts/{sid}' .format(sid = self.account_sid), auth=(self.api_key, self.api_token)) if response.status_code != 200: - frappe.throw(_("Invalid credentials")) \ No newline at end of file + frappe.throw(_("Invalid credentials")) diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/account_balance.html b/erpnext/erpnext_integrations/doctype/mpesa_settings/account_balance.html index 2c4d4bbdecf..b74a7187f0c 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/account_balance.html +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/account_balance.html @@ -25,4 +25,4 @@ {% else %}

Account Balance Information Not Available.

-{% endif %} \ No newline at end of file +{% endif %} diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py index 554c6b0eb0f..d1adeeee072 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py @@ -115,4 +115,4 @@ class MpesaConnector(): saf_url = "{0}{1}".format(self.base_url, "/mpesa/stkpush/v1/processrequest") r = requests.post(saf_url, headers=headers, json=payload) - return r.json() \ No newline at end of file + return r.json() diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_custom_fields.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_custom_fields.py index 0499e88b5e7..139e2fb192b 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_custom_fields.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_custom_fields.py @@ -50,4 +50,4 @@ def create_pos_settings(record_dict): for record in record_dict: if frappe.db.exists("POS Field", {"fieldname": record.get("fieldname")}): continue - frappe.get_doc(record).insert() \ No newline at end of file + frappe.get_doc(record).insert() diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py index fdfaa1b0540..de933578613 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py @@ -276,4 +276,4 @@ def fetch_param_value(response, key, key_field): """Fetch the specified key from list of dictionary. Key is identified via the key field.""" for param in response: if param[key_field] == key: - return param["Value"] \ No newline at end of file + return param["Value"] diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py index b0e662d3f32..d4cb6b982bb 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -355,4 +355,4 @@ def get_account_balance_callback_payload(): } } } - } \ No newline at end of file + } diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py index 42d4b9b2b43..73f5927df40 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py @@ -50,7 +50,7 @@ class PlaidConnector(): "secret": self.settings.plaid_secret, "products": self.products, }) - + return args def get_link_token(self, update_mode=False): diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js index 37bf2824505..3740d049839 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js @@ -135,4 +135,4 @@ erpnext.integrations.plaidLink = class plaidLink { }); }, __("Select a company"), __("Continue")); } -}; \ No newline at end of file +}; diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index 3ef069b5e20..eddcb3401f6 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -110,7 +110,7 @@ def add_bank_accounts(response, bank, company): frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(account["name"])) except Exception: frappe.log_error(frappe.get_traceback(), title=_("Plaid Link Error")) - frappe.throw(_("There was an error creating Bank Account while linking with Plaid."), + frappe.throw(_("There was an error creating Bank Account while linking with Plaid."), title=_("Plaid Link Failed")) else: diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js index d3fe7d2b4d6..12faeecc87f 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js +++ b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js @@ -18,5 +18,8 @@ frappe.ui.form.on('Shopify Log', { }) }).addClass('btn-primary'); } + + let app_link = "Ecommerce Integrations" + frm.dashboard.add_comment(__("Shopify Integration will be removed from ERPNext in Version 14. Please install {0} app to continue using it.", [app_link]), "yellow", true); } }); diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js index 1574795dfad..a926a7e52a5 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js @@ -36,6 +36,10 @@ frappe.ui.form.on("Shopify Settings", "refresh", function(frm){ frm.toggle_reqd("delivery_note_series", frm.doc.sync_delivery_note); } + + let app_link = "Ecommerce Integrations" + frm.dashboard.add_comment(__("Shopify Integration will be removed from ERPNext in Version 14. Please install {0} app to continue using it.", [app_link]), "yellow", true); + }) $.extend(erpnext_integrations.shopify_settings, { diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js index 5482b9cc695..af06b3451e0 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js @@ -346,4 +346,4 @@ erpnext.tally_migration.get_html_rows = (logs, field) => { }).join(""); return rows -} \ No newline at end of file +} diff --git a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py index bd072f40a19..45f261007f8 100644 --- a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py +++ b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py @@ -27,7 +27,7 @@ class WoocommerceSettings(Document): for doctype in ["Customer", "Address"]: df = dict(fieldname='woocommerce_email', label='Woocommerce Email', fieldtype='Data', read_only=1, print_hide=1) create_custom_field(doctype, df) - + if not frappe.get_value("Item Group", {"name": _("WooCommerce Products")}): item_group = frappe.new_doc("Item Group") item_group.item_group_name = _("WooCommerce Products") @@ -74,4 +74,4 @@ def generate_secret(): def get_series(): return { "sales_order_series" : frappe.get_meta("Sales Order").get_options("naming_series") or "SO-WOO-", - } \ No newline at end of file + } diff --git a/erpnext/erpnext_integrations/stripe_integration.py b/erpnext/erpnext_integrations/stripe_integration.py index a35ca28e0a3..108b4c0dd81 100644 --- a/erpnext/erpnext_integrations/stripe_integration.py +++ b/erpnext/erpnext_integrations/stripe_integration.py @@ -50,4 +50,4 @@ def create_subscription_on_stripe(stripe_settings): stripe_settings.integration_request.db_set('status', 'Failed', update_modified=False) frappe.log_error(frappe.get_traceback()) - return stripe_settings.finalize_request() \ No newline at end of file + return stripe_settings.finalize_request() diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index a5e162f8b5d..caafc0821e1 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -52,7 +52,7 @@ def create_mode_of_payment(gateway, payment_type="General"): "payment_gateway": gateway }, ['payment_account']) - mode_of_payment = frappe.db.exists("Mode of Payment", gateway) + mode_of_payment = frappe.db.exists("Mode of Payment", gateway) if not mode_of_payment and payment_gateway_account: mode_of_payment = frappe.get_doc({ "doctype": "Mode of Payment", diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js index dd6dc666d23..e494489d21a 100644 --- a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js +++ b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js @@ -11,4 +11,4 @@ frappe.dashboards.chart_sources["Department wise Patient Appointments"] = { default: frappe.defaults.get_user_default("Company") } ] -}; \ No newline at end of file +}; diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py index 062da6e4654..eca7143e689 100644 --- a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py +++ b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py @@ -69,4 +69,4 @@ def get(chart_name = None, chart = None, no_cache = None, filters = None, from_d } ], 'type': 'bar' - } \ No newline at end of file + } diff --git a/erpnext/healthcare/doctype/appointment_type/appointment_type.js b/erpnext/healthcare/doctype/appointment_type/appointment_type.js index 861675acea3..99b7cb295a9 100644 --- a/erpnext/healthcare/doctype/appointment_type/appointment_type.js +++ b/erpnext/healthcare/doctype/appointment_type/appointment_type.js @@ -80,4 +80,4 @@ frappe.ui.form.on('Appointment Type Service Item', { }); } } -}); \ No newline at end of file +}); diff --git a/erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.json b/erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.json index 5ff68cd682c..ccae129ea0b 100644 --- a/erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.json +++ b/erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.json @@ -48,13 +48,13 @@ "fieldname": "inpatient_visit_charge", "fieldtype": "Currency", "in_list_view": 1, - "label": "Inpatient Visit Charge Item" + "label": "Inpatient Visit Charge" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-01-22 09:35:26.503443", + "modified": "2021-08-17 06:05:02.240812", "modified_by": "Administrator", "module": "Healthcare", "name": "Appointment Type Service Item", diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py index 03e96a4b3be..0326e5e9da7 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py @@ -11,7 +11,7 @@ test_dependencies = ['Item'] class TestClinicalProcedure(unittest.TestCase): def test_procedure_template_item(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() procedure_template = create_clinical_procedure_template() self.assertTrue(frappe.db.exists('Item', procedure_template.item)) @@ -20,7 +20,7 @@ class TestClinicalProcedure(unittest.TestCase): self.assertEqual(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1) def test_consumables(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() procedure_template = create_clinical_procedure_template() procedure_template.allow_stock_consumption = 1 consumable = create_consumable() @@ -63,4 +63,4 @@ def create_procedure(procedure_template, patient, practitioner): procedure.company = "_Test Company" procedure.warehouse = "_Test Warehouse - _TC" procedure.submit() - return procedure \ No newline at end of file + return procedure diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js index 1ef110dc6f4..ae6b39bb181 100644 --- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js +++ b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js @@ -188,4 +188,3 @@ frappe.tour['Clinical Procedure Template'] = [ description: __('You can also set the Medical Department for the template. After saving the document, an Item will automatically be created for billing this Clinical Procedure. You can then use this template while creating Clinical Procedures for Patients. Templates save you from filling up redundant data every single time. You can also create templates for other operations like Lab Tests, Therapy Sessions, etc.') } ]; - diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py index f32b7cf9d8d..58194f10a8c 100644 --- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py +++ b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py @@ -118,4 +118,3 @@ def change_item_code_from_template(item_code, doc): rename_doc('Item', doc.item_code, item_code, ignore_permissions=True) frappe.db.set_value('Clinical Procedure Template', doc.name, 'item_code', item_code) return - diff --git a/erpnext/healthcare/doctype/exercise_type/exercise_type.py b/erpnext/healthcare/doctype/exercise_type/exercise_type.py index fb635c85788..ae44a2b77b5 100644 --- a/erpnext/healthcare/doctype/exercise_type/exercise_type.py +++ b/erpnext/healthcare/doctype/exercise_type/exercise_type.py @@ -12,4 +12,3 @@ class ExerciseType(Document): self.name = ' - '.join(filter(None, [self.exercise_name, self.difficulty_level])) else: self.name = self.exercise_name - diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.py b/erpnext/healthcare/doctype/fee_validity/fee_validity.py index 058bc971929..5b9c17934fa 100644 --- a/erpnext/healthcare/doctype/fee_validity/fee_validity.py +++ b/erpnext/healthcare/doctype/fee_validity/fee_validity.py @@ -60,4 +60,4 @@ def check_is_new_patient(appointment): }) if len(appointment_exists) and appointment_exists[0]: return False - return True \ No newline at end of file + return True diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py index 7e7fd824119..54f388b370b 100644 --- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py +++ b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py @@ -27,7 +27,7 @@ class TestFeeValidity(unittest.TestCase): healthcare_settings.automate_appointment_invoicing = 1 healthcare_settings.op_consulting_charge_item = item healthcare_settings.save(ignore_permissions=True) - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() # appointment should not be invoiced. Check Fee Validity created for new patient appointment = create_appointment(patient, practitioner, nowdate()) @@ -47,4 +47,4 @@ class TestFeeValidity(unittest.TestCase): # appointment should be invoiced as it is not within fee validity and the max_visits are exceeded appointment = create_appointment(patient, practitioner, add_days(nowdate(), 10), invoice=1) invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced") - self.assertEqual(invoiced, 1) \ No newline at end of file + self.assertEqual(invoiced, 1) diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js index fc0b24122ae..44c399856c8 100644 --- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js +++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js @@ -142,4 +142,3 @@ frappe.tour['Healthcare Practitioner'] = [ description: __('If this Healthcare Practitioner also works for the In-Patient Department, set the inpatient visit charge for this Practitioner.') } ]; - diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js index 2cdd5506565..2d1caf7efc7 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js +++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js @@ -7,8 +7,8 @@ frappe.ui.form.on('Healthcare Service Unit', { // get query select healthcare service unit frm.fields_dict['parent_healthcare_service_unit'].get_query = function(doc) { - return{ - filters:[ + return { + filters: [ ['Healthcare Service Unit', 'is_group', '=', 1], ['Healthcare Service Unit', 'name', '!=', doc.healthcare_service_unit_name] ] @@ -21,6 +21,14 @@ frappe.ui.form.on('Healthcare Service Unit', { frm.add_custom_button(__('Healthcare Service Unit Tree'), function() { frappe.set_route('Tree', 'Healthcare Service Unit'); }); + + frm.set_query('warehouse', function() { + return { + filters: { + 'company': frm.doc.company + } + }; + }); }, set_root_readonly: function(frm) { // read-only for root healthcare service unit @@ -43,5 +51,10 @@ frappe.ui.form.on('Healthcare Service Unit', { else { frm.set_df_property('service_unit_type', 'reqd', 1); } + }, + overlap_appointments: function(frm) { + if (frm.doc.overlap_appointments == 0) { + frm.set_value('service_unit_capacity', ''); + } } }); diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json index 9ee865a62a4..8935ec7d3c9 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json +++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json @@ -16,6 +16,7 @@ "service_unit_type", "allow_appointments", "overlap_appointments", + "service_unit_capacity", "inpatient_occupancy", "occupancy_status", "column_break_9", @@ -31,6 +32,8 @@ { "fieldname": "healthcare_service_unit_name", "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, "in_global_search": 1, "in_list_view": 1, "label": "Service Unit", @@ -41,6 +44,8 @@ "bold": 1, "fieldname": "parent_healthcare_service_unit", "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, "ignore_user_permissions": 1, "in_list_view": 1, "label": "Parent Service Unit", @@ -52,6 +57,8 @@ "depends_on": "eval:doc.inpatient_occupancy != 1 && doc.allow_appointments != 1", "fieldname": "is_group", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Is Group" }, { @@ -59,6 +66,8 @@ "depends_on": "eval:doc.is_group != 1", "fieldname": "service_unit_type", "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, "label": "Service Unit Type", "options": "Healthcare Service Unit Type" }, @@ -68,6 +77,8 @@ "fetch_from": "service_unit_type.allow_appointments", "fieldname": "allow_appointments", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "in_list_view": 1, "label": "Allow Appointments", "no_copy": 1, @@ -79,6 +90,8 @@ "fetch_from": "service_unit_type.overlap_appointments", "fieldname": "overlap_appointments", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Allow Overlap", "no_copy": 1, "read_only": 1 @@ -90,6 +103,8 @@ "fetch_from": "service_unit_type.inpatient_occupancy", "fieldname": "inpatient_occupancy", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "in_list_view": 1, "label": "Inpatient Occupancy", "no_copy": 1, @@ -100,6 +115,8 @@ "depends_on": "eval:doc.inpatient_occupancy == 1", "fieldname": "occupancy_status", "fieldtype": "Select", + "hide_days": 1, + "hide_seconds": 1, "label": "Occupancy Status", "no_copy": 1, "options": "Vacant\nOccupied", @@ -107,13 +124,17 @@ }, { "fieldname": "column_break_9", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "hide_days": 1, + "hide_seconds": 1 }, { "bold": 1, "depends_on": "eval:doc.is_group != 1", "fieldname": "warehouse", "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, "label": "Warehouse", "no_copy": 1, "options": "Warehouse" @@ -121,6 +142,8 @@ { "fieldname": "company", "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, "ignore_user_permissions": 1, "in_list_view": 1, "in_standard_filter": 1, @@ -134,6 +157,8 @@ "fieldname": "lft", "fieldtype": "Int", "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, "label": "lft", "no_copy": 1, "print_hide": 1, @@ -143,6 +168,8 @@ "fieldname": "rgt", "fieldtype": "Int", "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, "label": "rgt", "no_copy": 1, "print_hide": 1, @@ -152,6 +179,8 @@ "fieldname": "old_parent", "fieldtype": "Link", "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, "ignore_user_permissions": 1, "label": "Old Parent", "no_copy": 1, @@ -163,14 +192,26 @@ "collapsible": 1, "fieldname": "tree_details_section", "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1, "label": "Tree Details" + }, + { + "depends_on": "eval:doc.overlap_appointments == 1", + "fieldname": "service_unit_capacity", + "fieldtype": "Int", + "label": "Service Unit Capacity", + "mandatory_depends_on": "eval:doc.overlap_appointments == 1", + "non_negative": 1 } ], + "is_tree": 1, "links": [], - "modified": "2020-05-20 18:26:56.065543", + "modified": "2021-08-19 14:09:11.643464", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare Service Unit", + "nsm_parent_field": "parent_healthcare_service_unit", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py index 9e0417a2bef..989d4267897 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py +++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py @@ -5,14 +5,21 @@ from __future__ import unicode_literals from frappe.utils.nestedset import NestedSet +from frappe.utils import cint, cstr import frappe +from frappe import _ +import json + class HealthcareServiceUnit(NestedSet): nsm_parent_field = 'parent_healthcare_service_unit' + def validate(self): + self.set_service_unit_properties() + def autoname(self): if self.company: - suffix = " - " + frappe.get_cached_value('Company', self.company, "abbr") + suffix = " - " + frappe.get_cached_value('Company', self.company, 'abbr') if not self.healthcare_service_unit_name.endswith(suffix): self.name = self.healthcare_service_unit_name + suffix else: @@ -22,16 +29,86 @@ class HealthcareServiceUnit(NestedSet): super(HealthcareServiceUnit, self).on_update() self.validate_one_root() - def after_insert(self): + def set_service_unit_properties(self): if self.is_group: - self.allow_appointments = 0 - self.overlap_appointments = 0 - self.inpatient_occupancy = 0 - elif self.service_unit_type: + self.allow_appointments = False + self.overlap_appointments = False + self.inpatient_occupancy = False + self.service_unit_capacity = 0 + self.occupancy_status = '' + self.service_unit_type = '' + elif self.service_unit_type != '': service_unit_type = frappe.get_doc('Healthcare Service Unit Type', self.service_unit_type) self.allow_appointments = service_unit_type.allow_appointments - self.overlap_appointments = service_unit_type.overlap_appointments self.inpatient_occupancy = service_unit_type.inpatient_occupancy - if self.inpatient_occupancy: + + if self.inpatient_occupancy and self.occupancy_status != '': self.occupancy_status = 'Vacant' - self.overlap_appointments = 0 + + if service_unit_type.overlap_appointments: + self.overlap_appointments = True + else: + self.overlap_appointments = False + self.service_unit_capacity = 0 + + if self.overlap_appointments: + if not self.service_unit_capacity: + frappe.throw(_('Please set a valid Service Unit Capacity to enable Overlapping Appointments'), + title=_('Mandatory')) + + +@frappe.whitelist() +def add_multiple_service_units(parent, data): + ''' + parent - parent service unit under which the service units are to be created + data (dict) - company, healthcare_service_unit_name, count, service_unit_type, warehouse, service_unit_capacity + ''' + if not parent or not data: + return + + data = json.loads(data) + company = data.get('company') or \ + frappe.defaults.get_defaults().get('company') or \ + frappe.db.get_single_value('Global Defaults', 'default_company') + + if not data.get('healthcare_service_unit_name') or not company: + frappe.throw(_('Service Unit Name and Company are mandatory to create Healthcare Service Units'), + title=_('Missing Required Fields')) + + count = cint(data.get('count') or 0) + if count <= 0: + frappe.throw(_('Number of Service Units to be created should at least be 1'), + title=_('Invalid Number of Service Units')) + + capacity = cint(data.get('service_unit_capacity') or 1) + + service_unit = { + 'doctype': 'Healthcare Service Unit', + 'parent_healthcare_service_unit': parent, + 'service_unit_type': data.get('service_unit_type') or None, + 'service_unit_capacity': capacity if capacity > 0 else 1, + 'warehouse': data.get('warehouse') or None, + 'company': company + } + + service_unit_name = '{}'.format(data.get('healthcare_service_unit_name').strip(' -')) + + last_suffix = frappe.db.sql("""SELECT + IFNULL(MAX(CAST(SUBSTRING(name FROM %(start)s FOR 4) AS UNSIGNED)), 0) + FROM `tabHealthcare Service Unit` + WHERE name like %(prefix)s AND company=%(company)s""", + {'start': len(service_unit_name)+2, 'prefix': '{}-%'.format(service_unit_name), 'company': company}, + as_list=1)[0][0] + start_suffix = cint(last_suffix) + 1 + + failed_list = [] + for i in range(start_suffix, count + start_suffix): + # name to be in the form WARD-#### + service_unit['healthcare_service_unit_name'] = '{}-{}'.format(service_unit_name, cstr('%0*d' % (4, i))) + service_unit_doc = frappe.get_doc(service_unit) + try: + service_unit_doc.insert() + except Exception: + failed_list.append(service_unit['healthcare_service_unit_name']) + + return failed_list diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js index b75f2718271..ea3fea6b7a5 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js +++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js @@ -1,35 +1,185 @@ -frappe.treeview_settings["Healthcare Service Unit"] = { - breadcrumbs: "Healthcare Service Unit", - title: __("Healthcare Service Unit"), +frappe.provide("frappe.treeview_settings"); + +frappe.treeview_settings['Healthcare Service Unit'] = { + breadcrumbs: 'Healthcare Service Unit', + title: __('Service Unit Tree'), get_tree_root: false, - filters: [{ - fieldname: "company", - fieldtype: "Select", - options: erpnext.utils.get_tree_options("company"), - label: __("Company"), - default: erpnext.utils.get_tree_default("company") - }], get_tree_nodes: 'erpnext.healthcare.utils.get_children', - ignore_fields:["parent_healthcare_service_unit"], - onrender: function(node) { - if (node.data.occupied_out_of_vacant!==undefined) { - $('' - + " " + node.data.occupied_out_of_vacant + filters: [{ + fieldname: 'company', + fieldtype: 'Select', + options: erpnext.utils.get_tree_options('company'), + label: __('Company'), + default: erpnext.utils.get_tree_default('company') + }], + fields: [ + { + fieldtype: 'Data', fieldname: 'healthcare_service_unit_name', label: __('New Service Unit Name'), + reqd: true + }, + { + fieldtype: 'Check', fieldname: 'is_group', label: __('Is Group'), + description: __("Child nodes can be only created under 'Group' type nodes") + }, + { + fieldtype: 'Link', fieldname: 'service_unit_type', label: __('Service Unit Type'), + options: 'Healthcare Service Unit Type', description: __('Type of the new Service Unit'), + depends_on: 'eval:!doc.is_group', default: '', + onchange: () => { + if (cur_dialog) { + if (cur_dialog.fields_dict.service_unit_type.value) { + frappe.db.get_value('Healthcare Service Unit Type', + cur_dialog.fields_dict.service_unit_type.value, 'overlap_appointments') + .then(r => { + if (r.message.overlap_appointments) { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', false); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', true); + } else { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', true); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', false); + } + }); + } else { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', true); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', false); + } + } + } + }, + { + fieldtype: 'Int', fieldname: 'service_unit_capacity', label: __('Service Unit Capacity'), + description: __('Sets the number of concurrent appointments allowed'), reqd: false, + depends_on: "eval:!doc.is_group && doc.service_unit_type != ''", hidden: true + }, + { + fieldtype: 'Link', fieldname: 'warehouse', label: __('Warehouse'), options: 'Warehouse', + description: __('Optional, if you want to manage stock separately for this Service Unit'), + depends_on: 'eval:!doc.is_group' + }, + { + fieldtype: 'Link', fieldname: 'company', label: __('Company'), options: 'Company', reqd: true, + default: () => { + return cur_page.page.page.fields_dict.company.value; + } + } + ], + ignore_fields: ['parent_healthcare_service_unit'], + onrender: function (node) { + if (node.data.occupied_of_available !== undefined) { + $("" + + ' ' + node.data.occupied_of_available + '').insertBefore(node.$ul); } - if (node.data && node.data.inpatient_occupancy!==undefined) { + if (node.data && node.data.inpatient_occupancy !== undefined) { if (node.data.inpatient_occupancy == 1) { - if (node.data.occupancy_status == "Occupied") { - $('' - + " " + node.data.occupancy_status + if (node.data.occupancy_status == 'Occupied') { + $("" + + ' ' + node.data.occupancy_status + '').insertBefore(node.$ul); } - if (node.data.occupancy_status == "Vacant") { - $('' - + " " + node.data.occupancy_status + if (node.data.occupancy_status == 'Vacant') { + $("" + + ' ' + node.data.occupancy_status + '').insertBefore(node.$ul); } } } }, + post_render: function (treeview) { + frappe.treeview_settings['Healthcare Service Unit'].treeview = {}; + $.extend(frappe.treeview_settings['Healthcare Service Unit'].treeview, treeview); + }, + toolbar: [ + { + label: __('Add Multiple'), + condition: function (node) { + return node.expandable; + }, + click: function (node) { + const dialog = new frappe.ui.Dialog({ + title: __('Add Multiple Service Units'), + fields: [ + { + fieldtype: 'Data', fieldname: 'healthcare_service_unit_name', label: __('Service Unit Name'), + reqd: true, description: __("Will be serially suffixed to maintain uniquness. Example: 'Ward' will be named as 'Ward-####'"), + }, + { + fieldtype: 'Int', fieldname: 'count', label: __('Number of Service Units'), + reqd: true + }, + { + fieldtype: 'Link', fieldname: 'service_unit_type', label: __('Service Unit Type'), + options: 'Healthcare Service Unit Type', description: __('Type of the new Service Unit'), + depends_on: 'eval:!doc.is_group', default: '', reqd: true, + onchange: () => { + if (cur_dialog) { + if (cur_dialog.fields_dict.service_unit_type.value) { + frappe.db.get_value('Healthcare Service Unit Type', + cur_dialog.fields_dict.service_unit_type.value, 'overlap_appointments') + .then(r => { + if (r.message.overlap_appointments) { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', false); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', true); + } else { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', true); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', false); + } + }); + } else { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', true); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', false); + } + } + } + }, + { + fieldtype: 'Int', fieldname: 'service_unit_capacity', label: __('Service Unit Capacity'), + description: __('Sets the number of concurrent appointments allowed'), reqd: false, + depends_on: "eval:!doc.is_group && doc.service_unit_type != ''", hidden: true + }, + { + fieldtype: 'Link', fieldname: 'warehouse', label: __('Warehouse'), options: 'Warehouse', + description: __('Optional, if you want to manage stock separately for this Service Unit'), + }, + { + fieldtype: 'Link', fieldname: 'company', label: __('Company'), options: 'Company', reqd: true, + default: () => { + return cur_page.page.page.fields_dict.company.get_value(); + } + } + ], + primary_action: () => { + dialog.hide(); + let vals = dialog.get_values(); + if (!vals) return; + + return frappe.call({ + method: 'erpnext.healthcare.doctype.healthcare_service_unit.healthcare_service_unit.add_multiple_service_units', + args: { + parent: node.data.value, + data: vals + }, + callback: function (r) { + if (!r.exc && r.message) { + frappe.treeview_settings['Healthcare Service Unit'].treeview.tree.load_children(node, true); + + frappe.show_alert({ + message: __('{0} Service Units created', [vals.count - r.message.length]), + indicator: 'green' + }); + } else { + frappe.msgprint(__('Could not create Service Units')); + } + }, + freeze: true, + freeze_message: __('Creating {0} Service Units', [vals.count]) + }); + }, + primary_action_label: __('Create') + }); + dialog.show(); + } + } + ], + extend_toolbar: true }; diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js index eb33ab68c0d..ecf4aa1a4bf 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js +++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js @@ -68,8 +68,8 @@ let change_item_code = function(frm, doc) { if (values) { frappe.call({ "method": "erpnext.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.change_item_code", - "args": {item: doc.item, item_code: values['item_code'], doc_name: doc.name}, - callback: function () { + "args": { item: doc.item, item_code: values['item_code'], doc_name: doc.name }, + callback: function() { frm.reload_doc(); } }); diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json index 4b8503d0286..9c81c65f6b8 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json +++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json @@ -29,6 +29,8 @@ { "fieldname": "service_unit_type", "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, "in_list_view": 1, "label": "Service Unit Type", "no_copy": 1, @@ -41,6 +43,8 @@ "depends_on": "eval:doc.inpatient_occupancy != 1", "fieldname": "allow_appointments", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Allow Appointments" }, { @@ -49,6 +53,8 @@ "depends_on": "eval:doc.allow_appointments == 1 && doc.inpatient_occupany != 1", "fieldname": "overlap_appointments", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Allow Overlap" }, { @@ -57,6 +63,8 @@ "depends_on": "eval:doc.allow_appointments != 1", "fieldname": "inpatient_occupancy", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Inpatient Occupancy" }, { @@ -65,17 +73,23 @@ "depends_on": "eval:doc.inpatient_occupancy == 1 && doc.allow_appointments != 1", "fieldname": "is_billable", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Is Billable" }, { "depends_on": "is_billable", "fieldname": "item_details", "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1, "label": "Item Details" }, { "fieldname": "item", "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, "label": "Item", "no_copy": 1, "options": "Item", @@ -84,6 +98,8 @@ { "fieldname": "item_code", "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, "label": "Item Code", "mandatory_depends_on": "eval: doc.is_billable == 1", "no_copy": 1 @@ -91,6 +107,8 @@ { "fieldname": "item_group", "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, "label": "Item Group", "mandatory_depends_on": "eval: doc.is_billable == 1", "options": "Item Group" @@ -98,6 +116,8 @@ { "fieldname": "uom", "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, "label": "UOM", "mandatory_depends_on": "eval: doc.is_billable == 1", "options": "UOM" @@ -105,28 +125,38 @@ { "fieldname": "no_of_hours", "fieldtype": "Int", + "hide_days": 1, + "hide_seconds": 1, "label": "UOM Conversion in Hours", "mandatory_depends_on": "eval: doc.is_billable == 1" }, { "fieldname": "column_break_11", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "hide_days": 1, + "hide_seconds": 1 }, { "fieldname": "rate", "fieldtype": "Currency", + "hide_days": 1, + "hide_seconds": 1, "label": "Rate / UOM" }, { "default": "0", "fieldname": "disabled", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Disabled", "no_copy": 1 }, { "fieldname": "description", "fieldtype": "Small Text", + "hide_days": 1, + "hide_seconds": 1, "label": "Description" }, { @@ -134,11 +164,13 @@ "fieldname": "change_in_item", "fieldtype": "Check", "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, "label": "Change in Item" } ], "links": [], - "modified": "2020-05-20 15:31:09.627516", + "modified": "2021-08-19 17:52:30.266667", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare Service Unit Type", diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.py b/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.py index 01cf4b0a494..3ee3377b004 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.py +++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.py @@ -30,4 +30,4 @@ def get_unit_type(): unit_type.no_of_hours = 1 unit_type.rate = 4000 unit_type.save() - return unit_type \ No newline at end of file + return unit_type diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py b/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py index 7cb5a4814e8..ff9e21252a1 100644 --- a/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py +++ b/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py @@ -153,4 +153,4 @@ def make_stock_entry(warehouse=None): # in stock uom se_child.conversion_factor = 1.0 se_child.expense_account = expense_account - stock_entry.submit() \ No newline at end of file + stock_entry.submit() diff --git a/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py b/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py index 21776d2380a..798976283b3 100644 --- a/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py +++ b/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py @@ -140,4 +140,3 @@ def create_ipme(filters, update_stock=0): ipme = ipme.get_medication_orders() return ipme - diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py index a8c7720a0a4..b4a961264f2 100644 --- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py @@ -151,7 +151,7 @@ def get_healthcare_service_unit(unit_name=None): if not service_unit: service_unit = frappe.new_doc("Healthcare Service Unit") - service_unit.healthcare_service_unit_name = unit_name or "Test Service Unit Ip Occupancy" + service_unit.healthcare_service_unit_name = unit_name or "_Test Service Unit Ip Occupancy" service_unit.company = "_Test Company" service_unit.service_unit_type = get_service_unit_type() service_unit.inpatient_occupancy = 1 @@ -159,12 +159,12 @@ def get_healthcare_service_unit(unit_name=None): service_unit.is_group = 0 service_unit_parent_name = frappe.db.exists({ "doctype": "Healthcare Service Unit", - "healthcare_service_unit_name": "All Healthcare Service Units", + "healthcare_service_unit_name": "_Test All Healthcare Service Units", "is_group": 1 }) if not service_unit_parent_name: parent_service_unit = frappe.new_doc("Healthcare Service Unit") - parent_service_unit.healthcare_service_unit_name = "All Healthcare Service Units" + parent_service_unit.healthcare_service_unit_name = "_Test All Healthcare Service Units" parent_service_unit.is_group = 1 parent_service_unit.save(ignore_permissions = True) service_unit.parent_healthcare_service_unit = parent_service_unit.name @@ -180,7 +180,7 @@ def get_service_unit_type(): if not service_unit_type: service_unit_type = frappe.new_doc("Healthcare Service Unit Type") - service_unit_type.service_unit_type = "Test Service Unit Type Ip Occupancy" + service_unit_type.service_unit_type = "_Test Service Unit Type Ip Occupancy" service_unit_type.inpatient_occupancy = 1 service_unit_type.save(ignore_permissions = True) return service_unit_type.name diff --git a/erpnext/healthcare/doctype/patient/patient.js b/erpnext/healthcare/doctype/patient/patient.js index bce42e51d07..9266467155f 100644 --- a/erpnext/healthcare/doctype/patient/patient.js +++ b/erpnext/healthcare/doctype/patient/patient.js @@ -26,31 +26,39 @@ frappe.ui.form.on('Patient', { } if (frm.doc.patient_name && frappe.user.has_role('Physician')) { + frm.add_custom_button(__('Patient Progress'), function() { + frappe.route_options = {'patient': frm.doc.name}; + frappe.set_route('patient-progress'); + }, __('View')); + frm.add_custom_button(__('Patient History'), function() { frappe.route_options = {'patient': frm.doc.name}; frappe.set_route('patient_history'); - },'View'); + }, __('View')); } - if (!frm.doc.__islocal && (frappe.user.has_role('Nursing User') || frappe.user.has_role('Physician'))) { - frm.add_custom_button(__('Vital Signs'), function () { - create_vital_signs(frm); - }, 'Create'); - frm.add_custom_button(__('Medical Record'), function () { - create_medical_record(frm); - }, 'Create'); - frm.add_custom_button(__('Patient Encounter'), function () { - create_encounter(frm); - }, 'Create'); - frm.toggle_enable(['customer'], 0); // ToDo, allow change only if no transactions booked or better, add merge option + frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Patient'}; + frm.toggle_display(['address_html', 'contact_html'], !frm.is_new()); + + if (!frm.is_new()) { + if ((frappe.user.has_role('Nursing User') || frappe.user.has_role('Physician'))) { + frm.add_custom_button(__('Medical Record'), function () { + create_medical_record(frm); + }, 'Create'); + frm.toggle_enable(['customer'], 0); + } + frappe.contacts.render_address_and_contact(frm); + erpnext.utils.set_party_dashboard_indicators(frm); + } else { + frappe.contacts.clear_address_and_contact(frm); } }, + onload: function (frm) { - if (!frm.doc.dob) { - $(frm.fields_dict['age_html'].wrapper).html(''); - } if (frm.doc.dob) { $(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${get_age(frm.doc.dob)}`); + } else { + $(frm.fields_dict['age_html'].wrapper).html(''); } } }); @@ -59,16 +67,14 @@ frappe.ui.form.on('Patient', 'dob', function(frm) { if (frm.doc.dob) { let today = new Date(); let birthDate = new Date(frm.doc.dob); - if (today < birthDate){ + if (today < birthDate) { frappe.msgprint(__('Please select a valid Date')); frappe.model.set_value(frm.doctype,frm.docname, 'dob', ''); - } - else { + } else { let age_str = get_age(frm.doc.dob); $(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${age_str}`); } - } - else { + } else { $(frm.fields_dict['age_html'].wrapper).html(''); } }); diff --git a/erpnext/healthcare/doctype/patient/patient.json b/erpnext/healthcare/doctype/patient/patient.json index 8af1a9ccd75..4092a6a7681 100644 --- a/erpnext/healthcare/doctype/patient/patient.json +++ b/erpnext/healthcare/doctype/patient/patient.json @@ -1,6 +1,6 @@ { "actions": [], - "allow_copy": 1, + "allow_events_in_timeline": 1, "allow_import": 1, "allow_rename": 1, "autoname": "naming_series:", @@ -24,12 +24,19 @@ "image", "column_break_14", "status", + "uid", "inpatient_record", "inpatient_status", "report_preference", "mobile", - "email", "phone", + "email", + "invite_user", + "user_id", + "address_contacts", + "address_html", + "column_break_22", + "contact_html", "customer_details_section", "customer", "customer_group", @@ -74,6 +81,7 @@ "fieldtype": "Select", "in_preview": 1, "label": "Inpatient Status", + "no_copy": 1, "options": "\nAdmission Scheduled\nAdmitted\nDischarge Scheduled", "read_only": 1 }, @@ -81,6 +89,7 @@ "fieldname": "inpatient_record", "fieldtype": "Link", "label": "Inpatient Record", + "no_copy": 1, "options": "Inpatient Record", "read_only": 1 }, @@ -101,6 +110,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Full Name", + "no_copy": 1, "read_only": 1, "search_index": 1 }, @@ -118,6 +128,7 @@ "fieldtype": "Select", "in_preview": 1, "label": "Blood Group", + "no_copy": 1, "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative" }, { @@ -125,7 +136,8 @@ "fieldname": "dob", "fieldtype": "Date", "in_preview": 1, - "label": "Date of birth" + "label": "Date of birth", + "no_copy": 1 }, { "fieldname": "age_html", @@ -167,6 +179,7 @@ "fieldtype": "Link", "ignore_user_permissions": 1, "label": "Customer", + "no_copy": 1, "options": "Customer", "set_only_once": 1 }, @@ -183,6 +196,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Mobile", + "no_copy": 1, "options": "Phone" }, { @@ -192,6 +206,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Email", + "no_copy": 1, "options": "Email" }, { @@ -199,6 +214,7 @@ "fieldtype": "Data", "in_filter": 1, "label": "Phone", + "no_copy": 1, "options": "Phone" }, { @@ -230,7 +246,8 @@ "fieldname": "medication", "fieldtype": "Small Text", "ignore_xss_filter": 1, - "label": "Medication" + "label": "Medication", + "no_copy": 1 }, { "fieldname": "column_break_20", @@ -240,13 +257,15 @@ "fieldname": "medical_history", "fieldtype": "Small Text", "ignore_xss_filter": 1, - "label": "Medical History" + "label": "Medical History", + "no_copy": 1 }, { "fieldname": "surgical_history", "fieldtype": "Small Text", "ignore_xss_filter": 1, - "label": "Surgical History" + "label": "Surgical History", + "no_copy": 1 }, { "collapsible": 1, @@ -258,8 +277,8 @@ "fieldname": "occupation", "fieldtype": "Data", "ignore_xss_filter": 1, - "in_standard_filter": 1, - "label": "Occupation" + "label": "Occupation", + "no_copy": 1 }, { "fieldname": "column_break_25", @@ -269,6 +288,7 @@ "fieldname": "marital_status", "fieldtype": "Select", "label": "Marital Status", + "no_copy": 1, "options": "\nSingle\nMarried\nDivorced\nWidow" }, { @@ -281,25 +301,29 @@ "fieldname": "tobacco_past_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Tobacco Consumption (Past)" + "label": "Tobacco Consumption (Past)", + "no_copy": 1 }, { "fieldname": "tobacco_current_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Tobacco Consumption (Present)" + "label": "Tobacco Consumption (Present)", + "no_copy": 1 }, { "fieldname": "alcohol_past_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Alcohol Consumption (Past)" + "label": "Alcohol Consumption (Past)", + "no_copy": 1 }, { "fieldname": "alcohol_current_use", "fieldtype": "Data", "ignore_user_permissions": 1, - "label": "Alcohol Consumption (Present)" + "label": "Alcohol Consumption (Present)", + "no_copy": 1 }, { "fieldname": "column_break_32", @@ -309,13 +333,15 @@ "fieldname": "surrounding_factors", "fieldtype": "Small Text", "ignore_xss_filter": 1, - "label": "Occupational Hazards and Environmental Factors" + "label": "Occupational Hazards and Environmental Factors", + "no_copy": 1 }, { "fieldname": "other_risk_factors", "fieldtype": "Small Text", "ignore_xss_filter": 1, - "label": "Other Risk Factors" + "label": "Other Risk Factors", + "no_copy": 1 }, { "collapsible": 1, @@ -331,7 +357,8 @@ "fieldname": "patient_details", "fieldtype": "Text", "ignore_xss_filter": 1, - "label": "Patient Details" + "label": "Patient Details", + "no_copy": 1 }, { "fieldname": "default_currency", @@ -342,19 +369,22 @@ { "fieldname": "last_name", "fieldtype": "Data", - "label": "Last Name" + "label": "Last Name", + "no_copy": 1 }, { "fieldname": "first_name", "fieldtype": "Data", "label": "First Name", + "no_copy": 1, "oldfieldtype": "Data", "reqd": 1 }, { "fieldname": "middle_name", "fieldtype": "Data", - "label": "Middle Name (optional)" + "label": "Middle Name (optional)", + "no_copy": 1 }, { "collapsible": 1, @@ -389,13 +419,63 @@ "fieldtype": "Link", "label": "Print Language", "options": "Language" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "address_contacts", + "fieldtype": "Section Break", + "label": "Address and Contact", + "options": "fa fa-map-marker" + }, + { + "fieldname": "address_html", + "fieldtype": "HTML", + "label": "Address HTML", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_22", + "fieldtype": "Column Break" + }, + { + "fieldname": "contact_html", + "fieldtype": "HTML", + "label": "Contact HTML", + "no_copy": 1, + "read_only": 1 + }, + { + "allow_in_quick_entry": 1, + "default": "1", + "fieldname": "invite_user", + "fieldtype": "Check", + "label": "Invite as User", + "no_copy": 1, + "read_only_depends_on": "eval: doc.user_id" + }, + { + "fieldname": "user_id", + "fieldtype": "Read Only", + "label": "User ID", + "no_copy": 1, + "options": "User" + }, + { + "allow_in_quick_entry": 1, + "bold": 1, + "fieldname": "uid", + "fieldtype": "Data", + "in_standard_filter": 1, + "label": "Identification Number (UID)", + "unique": 1 } ], "icon": "fa fa-user", "image_field": "image", "links": [], "max_attachments": 50, - "modified": "2020-04-25 17:24:32.146415", + "modified": "2021-03-14 13:21:09.759906", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient", @@ -453,7 +533,7 @@ ], "quick_entry": 1, "restrict_to_domain": "Healthcare", - "search_fields": "patient_name,mobile,email,phone", + "search_fields": "patient_name,mobile,email,phone,uid", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "ASC", diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py index cebcb2068ea..4c3da82a1ca 100644 --- a/erpnext/healthcare/doctype/patient/patient.py +++ b/erpnext/healthcare/doctype/patient/patient.py @@ -8,24 +8,27 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import cint, cstr, getdate import dateutil +from frappe.contacts.address_and_contact import load_address_and_contact +from frappe.contacts.doctype.contact.contact import get_default_contact from frappe.model.naming import set_name_by_naming_series from frappe.utils.nestedset import get_root_of from erpnext import get_default_currency from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account, send_registration_sms +from erpnext.accounts.party import get_dashboard_info class Patient(Document): + def onload(self): + '''Load address and contacts in `__onload`''' + load_address_and_contact(self) + self.load_dashboard_info() + def validate(self): self.set_full_name() - self.add_as_website_user() def before_insert(self): self.set_missing_customer_details() def after_insert(self): - self.add_as_website_user() - self.reload() - if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient') and not self.customer: - create_customer(self) if frappe.db.get_single_value('Healthcare Settings', 'collect_registration_fee'): frappe.db.set_value('Patient', self.name, 'status', 'Disabled') else: @@ -50,6 +53,16 @@ class Patient(Document): else: create_customer(self) + self.set_contact() # add or update contact + + if not self.user_id and self.email and self.invite_user: + self.create_website_user() + + def load_dashboard_info(self): + if self.customer: + info = get_dashboard_info('Customer', self.customer, None) + self.set_onload('dashboard_info', info) + def set_full_name(self): if self.last_name: self.patient_name = ' '.join(filter(None, [self.first_name, self.last_name])) @@ -72,18 +85,24 @@ class Patient(Document): if not self.language: self.language = frappe.db.get_single_value('System Settings', 'language') - def add_as_website_user(self): - if self.email: - if not frappe.db.exists ('User', self.email): - user = frappe.get_doc({ - 'doctype': 'User', - 'first_name': self.first_name, - 'last_name': self.last_name, - 'email': self.email, - 'user_type': 'Website User' - }) - user.flags.ignore_permissions = True - user.add_roles('Patient') + def create_website_user(self): + if self.email and not frappe.db.exists('User', self.email): + user = frappe.get_doc({ + 'doctype': 'User', + 'first_name': self.first_name, + 'last_name': self.last_name, + 'email': self.email, + 'user_type': 'Website User', + 'gender': self.sex, + 'phone': self.phone, + 'mobile_no': self.mobile, + 'birth_date': self.dob + }) + user.flags.ignore_permissions = True + user.enabled = True + user.send_welcome_email = True + user.add_roles('Patient') + frappe.db.set_value(self.doctype, self.name, 'user_id', user.name) def autoname(self): patient_name_by = frappe.db.get_single_value('Healthcare Settings', 'patient_name_by') @@ -108,7 +127,8 @@ class Patient(Document): if self.dob: dob = getdate(self.dob) age = dateutil.relativedelta.relativedelta(getdate(), dob) - age_str = str(age.years) + ' ' + _("Years(s)") + ' ' + str(age.months) + ' ' + _("Month(s)") + ' ' + str(age.days) + ' ' + _("Day(s)") + age_str = f'{str(age.years)} {_("Years(s)")} {str(age.months)} {_("Month(s)")} {str(age.days)} {_("Day(s)")}' + return age_str @frappe.whitelist() @@ -125,6 +145,58 @@ class Patient(Document): return {'invoice': sales_invoice.name} + def set_contact(self): + if frappe.db.exists('Dynamic Link', {'parenttype':'Contact', 'link_doctype':'Patient', 'link_name':self.name}): + old_doc = self.get_doc_before_save() + if old_doc.email != self.email or old_doc.mobile != self.mobile or old_doc.phone != self.phone: + self.update_contact() + else: + self.reload() + if self.email or self.mobile or self.phone: + contact = frappe.get_doc({ + 'doctype': 'Contact', + 'first_name': self.first_name, + 'middle_name': self.middle_name, + 'last_name': self.last_name, + 'gender': self.sex, + 'is_primary_contact': 1 + }) + contact.append('links', dict(link_doctype='Patient', link_name=self.name)) + if self.customer: + contact.append('links', dict(link_doctype='Customer', link_name=self.customer)) + + contact.insert(ignore_permissions=True) + self.update_contact(contact) # update email, mobile and phone + + def update_contact(self, contact=None): + if not contact: + contact_name = get_default_contact(self.doctype, self.name) + if contact_name: + contact = frappe.get_doc('Contact', contact_name) + + if contact: + if self.email and self.email != contact.email_id: + for email in contact.email_ids: + email.is_primary = True if email.email_id == self.email else False + contact.add_email(self.email, is_primary=True) + contact.set_primary_email() + + if self.mobile and self.mobile != contact.mobile_no: + for mobile in contact.phone_nos: + mobile.is_primary_mobile_no = True if mobile.phone == self.mobile else False + contact.add_phone(self.mobile, is_primary_mobile_no=True) + contact.set_primary('mobile_no') + + if self.phone and self.phone != contact.phone: + for phone in contact.phone_nos: + phone.is_primary_phone = True if phone.phone == self.phone else False + contact.add_phone(self.phone, is_primary_phone=True) + contact.set_primary('phone') + + contact.flags.ignore_validate = True # disable hook TODO: safe? + contact.save(ignore_permissions=True) + + def create_customer(doc): customer = frappe.get_doc({ 'doctype': 'Customer', @@ -150,8 +222,8 @@ def make_invoice(patient, company): sales_invoice.debit_to = get_receivable_account(company) item_line = sales_invoice.append('items') - item_line.item_name = 'Registeration Fee' - item_line.description = 'Registeration Fee' + item_line.item_name = 'Registration Fee' + item_line.description = 'Registration Fee' item_line.qty = 1 item_line.uom = uom item_line.conversion_factor = 1 @@ -175,8 +247,11 @@ def get_patient_detail(patient): return details def get_timeline_data(doctype, name): - """Return timeline data from medical records""" - return dict(frappe.db.sql(''' + ''' + Return Patient's timeline data from medical records + Also include the associated Customer timeline data + ''' + patient_timeline_data = dict(frappe.db.sql(''' SELECT unix_timestamp(communication_date), count(*) FROM @@ -185,3 +260,11 @@ def get_timeline_data(doctype, name): patient=%s and `communication_date` > date_sub(curdate(), interval 1 year) GROUP BY communication_date''', name)) + + customer = frappe.db.get_value(doctype, name, 'customer') + if customer: + from erpnext.accounts.party import get_timeline_data + customer_timeline_data = get_timeline_data('Customer', customer) + patient_timeline_data.update(customer_timeline_data) + + return patient_timeline_data diff --git a/erpnext/healthcare/doctype/patient/patient_dashboard.py b/erpnext/healthcare/doctype/patient/patient_dashboard.py index 39603f77a06..7f7cfa8e5b9 100644 --- a/erpnext/healthcare/doctype/patient/patient_dashboard.py +++ b/erpnext/healthcare/doctype/patient/patient_dashboard.py @@ -6,22 +6,33 @@ def get_data(): 'heatmap': True, 'heatmap_message': _('This is based on transactions against this Patient. See timeline below for details'), 'fieldname': 'patient', + 'non_standard_fieldnames': { + 'Payment Entry': 'party' + }, 'transactions': [ { - 'label': _('Appointments and Patient Encounters'), - 'items': ['Patient Appointment', 'Patient Encounter'] + 'label': _('Appointments and Encounters'), + 'items': ['Patient Appointment', 'Vital Signs', 'Patient Encounter'] }, { 'label': _('Lab Tests and Vital Signs'), - 'items': ['Lab Test', 'Sample Collection', 'Vital Signs'] + 'items': ['Lab Test', 'Sample Collection'] }, { - 'label': _('Billing'), - 'items': ['Sales Invoice'] + 'label': _('Rehab and Physiotherapy'), + 'items': ['Patient Assessment', 'Therapy Session', 'Therapy Plan'] }, { - 'label': _('Orders'), - 'items': ['Inpatient Medication Order'] + 'label': _('Surgery'), + 'items': ['Clinical Procedure'] + }, + { + 'label': _('Admissions'), + 'items': ['Inpatient Record', 'Inpatient Medication Order'] + }, + { + 'label': _('Billing and Payments'), + 'items': ['Sales Invoice', 'Payment Entry'] } ] } diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index 2976ef13a1d..8923e014452 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -17,9 +17,9 @@ frappe.ui.form.on('Patient Appointment', { }, refresh: function(frm) { - frm.set_query('patient', function () { + frm.set_query('patient', function() { return { - filters: {'status': 'Active'} + filters: { 'status': 'Active' } }; }); @@ -64,7 +64,7 @@ frappe.ui.form.on('Patient Appointment', { } else { frappe.call({ method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.check_payment_fields_reqd', - args: {'patient': frm.doc.patient}, + args: { 'patient': frm.doc.patient }, callback: function(data) { if (data.message == true) { if (frm.doc.mode_of_payment && frm.doc.paid_amount) { @@ -97,7 +97,7 @@ frappe.ui.form.on('Patient Appointment', { if (frm.doc.patient) { frm.add_custom_button(__('Patient History'), function() { - frappe.route_options = {'patient': frm.doc.patient}; + frappe.route_options = { 'patient': frm.doc.patient }; frappe.set_route('patient_history'); }, __('View')); } @@ -111,14 +111,14 @@ frappe.ui.form.on('Patient Appointment', { }); if (frm.doc.procedure_template) { - frm.add_custom_button(__('Clinical Procedure'), function(){ + frm.add_custom_button(__('Clinical Procedure'), function() { frappe.model.open_mapped_doc({ method: 'erpnext.healthcare.doctype.clinical_procedure.clinical_procedure.make_procedure', frm: frm, }); }, __('Create')); } else if (frm.doc.therapy_type) { - frm.add_custom_button(__('Therapy Session'),function(){ + frm.add_custom_button(__('Therapy Session'), function() { frappe.model.open_mapped_doc({ method: 'erpnext.healthcare.doctype.therapy_session.therapy_session.create_therapy_session', frm: frm, @@ -148,7 +148,7 @@ frappe.ui.form.on('Patient Appointment', { doctype: 'Patient', name: frm.doc.patient }, - callback: function (data) { + callback: function(data) { let age = null; if (data.message.dob) { age = calculate_age(data.message.dob); @@ -165,7 +165,7 @@ frappe.ui.form.on('Patient Appointment', { }, practitioner: function(frm) { - if (frm.doc.practitioner ) { + if (frm.doc.practitioner) { frm.events.set_payment_details(frm); } }, @@ -230,7 +230,7 @@ frappe.ui.form.on('Patient Appointment', { toggle_payment_fields: function(frm) { frappe.call({ method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.check_payment_fields_reqd', - args: {'patient': frm.doc.patient}, + args: { 'patient': frm.doc.patient }, callback: function(data) { if (data.message.fee_validity) { // if fee validity exists and automated appointment invoicing is enabled, @@ -247,7 +247,7 @@ frappe.ui.form.on('Patient Appointment', { frm.toggle_display('paid_amount', data.message ? 1 : 0); frm.toggle_display('billing_item', data.message ? 1 : 0); frm.toggle_reqd('mode_of_payment', data.message ? 1 : 0); - frm.toggle_reqd('paid_amount', data.message ? 1 :0); + frm.toggle_reqd('paid_amount', data.message ? 1 : 0); frm.toggle_reqd('billing_item', data.message ? 1 : 0); } } @@ -258,7 +258,7 @@ frappe.ui.form.on('Patient Appointment', { if (frm.doc.patient) { frappe.call({ method: "erpnext.healthcare.doctype.patient_appointment.patient_appointment.get_prescribed_therapies", - args: {patient: frm.doc.patient}, + args: { patient: frm.doc.patient }, callback: function(r) { if (r.message) { show_therapy_types(frm, r.message); @@ -295,13 +295,13 @@ let check_and_set_availability = function(frm) { let d = new frappe.ui.Dialog({ title: __('Available slots'), fields: [ - { fieldtype: 'Link', options: 'Medical Department', reqd: 1, fieldname: 'department', label: 'Medical Department'}, - { fieldtype: 'Column Break'}, - { fieldtype: 'Link', options: 'Healthcare Practitioner', reqd: 1, fieldname: 'practitioner', label: 'Healthcare Practitioner'}, - { fieldtype: 'Column Break'}, - { fieldtype: 'Date', reqd: 1, fieldname: 'appointment_date', label: 'Date'}, - { fieldtype: 'Section Break'}, - { fieldtype: 'HTML', fieldname: 'available_slots'} + { fieldtype: 'Link', options: 'Medical Department', reqd: 1, fieldname: 'department', label: 'Medical Department' }, + { fieldtype: 'Column Break' }, + { fieldtype: 'Link', options: 'Healthcare Practitioner', reqd: 1, fieldname: 'practitioner', label: 'Healthcare Practitioner' }, + { fieldtype: 'Column Break' }, + { fieldtype: 'Date', reqd: 1, fieldname: 'appointment_date', label: 'Date' }, + { fieldtype: 'Section Break' }, + { fieldtype: 'HTML', fieldname: 'available_slots' } ], primary_action_label: __('Book'), @@ -379,59 +379,22 @@ let check_and_set_availability = function(frm) { let $wrapper = d.fields_dict.available_slots.$wrapper; // make buttons for each slot - let slot_details = data.slot_details; - let slot_html = ''; - for (let i = 0; i < slot_details.length; i++) { - slot_html = slot_html + ``; - slot_html = slot_html + `
` + slot_details[i].avail_slot.map(slot => { - let disabled = ''; - let start_str = slot.from_time; - let slot_start_time = moment(slot.from_time, 'HH:mm:ss'); - let slot_to_time = moment(slot.to_time, 'HH:mm:ss'); - let interval = (slot_to_time - slot_start_time)/60000 | 0; - // iterate in all booked appointments, update the start time and duration - slot_details[i].appointments.forEach(function(booked) { - let booked_moment = moment(booked.appointment_time, 'HH:mm:ss'); - let end_time = booked_moment.clone().add(booked.duration, 'minutes'); - // Deal with 0 duration appointments - if (booked_moment.isSame(slot_start_time) || booked_moment.isBetween(slot_start_time, slot_to_time)) { - if(booked.duration == 0){ - disabled = 'disabled="disabled"'; - return false; - } - } - // Check for overlaps considering appointment duration - if (slot_start_time.isBefore(end_time) && slot_to_time.isAfter(booked_moment)) { - // There is an overlap - disabled = 'disabled="disabled"'; - return false; - } - }); - return ``; - }).join(""); - slot_html = slot_html + `
`; - } + let slot_html = get_slots(data.slot_details); $wrapper .css('margin-bottom', 0) .addClass('text-center') .html(slot_html); - // blue button when clicked + // highlight button when clicked $wrapper.on('click', 'button', function() { let $btn = $(this); - $wrapper.find('button').removeClass('btn-primary'); - $btn.addClass('btn-primary'); + $wrapper.find('button').removeClass('btn-outline-primary'); + $btn.addClass('btn-outline-primary'); selected_slot = $btn.attr('data-name'); service_unit = $btn.attr('data-service-unit'); duration = $btn.attr('data-duration'); - // enable dialog action + // enable primary action 'Book' d.get_primary_btn().attr('disabled', null); }); @@ -441,19 +404,102 @@ let check_and_set_availability = function(frm) { } }, freeze: true, - freeze_message: __('Fetching records......') + freeze_message: __('Fetching Schedule...') }); } else { fd.available_slots.html(__('Appointment date and Healthcare Practitioner are Mandatory').bold()); } } + + function get_slots(slot_details) { + let slot_html = ''; + let appointment_count = 0; + let disabled = false; + let start_str, slot_start_time, slot_end_time, interval, count, count_class, tool_tip, available_slots; + + slot_details.forEach((slot_info) => { + slot_html += `
+ ${__('Practitioner Schedule:')} ${slot_info.slot_name}
+ ${__('Service Unit:')} ${slot_info.service_unit} `; + + if (slot_info.service_unit_capacity) { + slot_html += `
${__('Maximum Capacity:')} ${slot_info.service_unit_capacity} `; + } + + slot_html += '


'; + + slot_html += slot_info.avail_slot.map(slot => { + appointment_count = 0; + disabled = false; + start_str = slot.from_time; + slot_start_time = moment(slot.from_time, 'HH:mm:ss'); + slot_end_time = moment(slot.to_time, 'HH:mm:ss'); + interval = (slot_end_time - slot_start_time) / 60000 | 0; + + // iterate in all booked appointments, update the start time and duration + slot_info.appointments.forEach((booked) => { + let booked_moment = moment(booked.appointment_time, 'HH:mm:ss'); + let end_time = booked_moment.clone().add(booked.duration, 'minutes'); + + // Deal with 0 duration appointments + if (booked_moment.isSame(slot_start_time) || booked_moment.isBetween(slot_start_time, slot_end_time)) { + if (booked.duration == 0) { + disabled = true; + return false; + } + } + + // Check for overlaps considering appointment duration + if (slot_info.allow_overlap != 1) { + if (slot_start_time.isBefore(end_time) && slot_end_time.isAfter(booked_moment)) { + // There is an overlap + disabled = true; + return false; + } + } else { + if (slot_start_time.isBefore(end_time) && slot_end_time.isAfter(booked_moment)) { + appointment_count++; + } + if (appointment_count >= slot_info.service_unit_capacity) { + // There is an overlap + disabled = true; + return false; + } + } + }); + + if (slot_info.allow_overlap == 1 && slot_info.service_unit_capacity > 1) { + available_slots = slot_info.service_unit_capacity - appointment_count; + count = `${(available_slots > 0 ? available_slots : __('Full'))}`; + count_class = `${(available_slots > 0 ? 'badge-success' : 'badge-danger')}`; + tool_tip =`${available_slots} ${__('slots available for booking')}`; + } + return ` + `; + }).join(""); + + if (slot_info.service_unit_capacity) { + slot_html += `
${__('Each slot indicates the capacity currently available for booking')}`; + } + slot_html += `

`; + }); + + return slot_html; + } }; let get_prescribed_procedure = function(frm) { if (frm.doc.patient) { frappe.call({ method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.get_procedure_prescribed', - args: {patient: frm.doc.patient}, + args: { patient: frm.doc.patient }, callback: function(r) { if (r.message && r.message.length) { show_procedure_templates(frm, r.message); @@ -473,7 +519,7 @@ let get_prescribed_procedure = function(frm) { } }; -let show_procedure_templates = function(frm, result){ +let show_procedure_templates = function(frm, result) { let d = new frappe.ui.Dialog({ title: __('Prescribed Procedures'), fields: [ @@ -493,9 +539,11 @@ let show_procedure_templates = function(frm, result){ data-encounter="%(encounter)s" data-practitioner="%(practitioner)s"\ data-date="%(date)s" data-department="%(department)s">\

', {name:y[0], procedure_template: y[1], - encounter:y[2], consulting_practitioner:y[3], encounter_date:y[4], - practitioner:y[5]? y[5]:'', date: y[6]? y[6]:'', department: y[7]? y[7]:''})).appendTo(html_field); +

', { + name: y[0], procedure_template: y[1], + encounter: y[2], consulting_practitioner: y[3], encounter_date: y[4], + practitioner: y[5] ? y[5] : '', date: y[6] ? y[6] : '', department: y[7] ? y[7] : '' + })).appendTo(html_field); row.find("a").click(function() { frm.doc.procedure_template = $(this).attr('data-procedure-template'); frm.doc.procedure_prescription = $(this).attr('data-name'); @@ -513,7 +561,7 @@ let show_procedure_templates = function(frm, result){ }); if (!result) { let msg = __('There are no procedure prescribed for ') + frm.doc.patient; - $(repl('
%(msg)s
', {msg: msg})).appendTo(html_field); + $(repl('
%(msg)s
', { msg: msg })).appendTo(html_field); } d.show(); }; @@ -528,7 +576,7 @@ let show_therapy_types = function(frm, result) { ] }); var html_field = d.fields_dict.therapy_type.$wrapper; - $.each(result, function(x, y){ + $.each(result, function(x, y) { var row = $(repl('
\
%(encounter)s
%(practitioner)s
%(date)s
\
%(therapy)s
\ @@ -537,9 +585,11 @@ let show_therapy_types = function(frm, result) { data-encounter="%(encounter)s" data-practitioner="%(practitioner)s"\ data-date="%(date)s" data-department="%(department)s">\

', {therapy:y[0], - name: y[1], encounter:y[2], practitioner:y[3], date:y[4], - department:y[6]? y[6]:'', therapy_plan:y[5]})).appendTo(html_field); +

', { + therapy: y[0], + name: y[1], encounter: y[2], practitioner: y[3], date: y[4], + department: y[6] ? y[6] : '', therapy_plan: y[5] + })).appendTo(html_field); row.find("a").click(function() { frm.doc.therapy_type = $(this).attr("data-therapy"); @@ -574,13 +624,13 @@ let create_vital_signs = function(frm) { frappe.new_doc('Vital Signs'); }; -let update_status = function(frm, status){ +let update_status = function(frm, status) { let doc = frm.doc; frappe.confirm(__('Are you sure you want to cancel this appointment?'), function() { frappe.call({ method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_status', - args: {appointment_id: doc.name, status:status}, + args: { appointment_id: doc.name, status: status }, callback: function(data) { if (!data.exc) { frm.reload_doc(); diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json index 83c92af36ac..7654e0d249f 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json @@ -131,7 +131,7 @@ "fieldtype": "Link", "label": "Service Unit", "options": "Healthcare Service Unit", - "set_only_once": 1 + "read_only": 1 }, { "fieldname": "section_break_12", @@ -349,7 +349,7 @@ } ], "links": [], - "modified": "2021-02-08 13:13:15.116833", + "modified": "2021-08-19 17:28:41.329387", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Appointment", diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index cdd4ad39c88..7d7e078647f 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -15,6 +15,11 @@ from erpnext.hr.doctype.employee.employee import is_holiday from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account from erpnext.healthcare.utils import check_fee_validity, get_service_item_and_practitioner_charge, manage_fee_validity +class MaximumCapacityError(frappe.ValidationError): + pass +class OverlapError(frappe.ValidationError): + pass + class PatientAppointment(Document): def validate(self): self.validate_overlaps() @@ -49,26 +54,49 @@ class PatientAppointment(Document): end_time = datetime.datetime.combine(getdate(self.appointment_date), get_time(self.appointment_time)) \ + datetime.timedelta(minutes=flt(self.duration)) - overlaps = frappe.db.sql(""" - select - name, practitioner, patient, appointment_time, duration - from - `tabPatient Appointment` - where - appointment_date=%s and name!=%s and status NOT IN ("Closed", "Cancelled") - and (practitioner=%s or patient=%s) and - ((appointment_time<%s and appointment_time + INTERVAL duration MINUTE>%s) or - (appointment_time>%s and appointment_time<%s) or - (appointment_time=%s)) - """, (self.appointment_date, self.name, self.practitioner, self.patient, - self.appointment_time, end_time.time(), self.appointment_time, end_time.time(), self.appointment_time)) + # all appointments for both patient and practitioner overlapping the duration of this appointment + overlapping_appointments = frappe.db.sql(""" + SELECT + name, practitioner, patient, appointment_time, duration, service_unit + FROM + `tabPatient Appointment` + WHERE + appointment_date=%(appointment_date)s AND name!=%(name)s AND status NOT IN ("Closed", "Cancelled") AND + (practitioner=%(practitioner)s OR patient=%(patient)s) AND + ((appointment_time<%(appointment_time)s AND appointment_time + INTERVAL duration MINUTE>%(appointment_time)s) OR + (appointment_time>%(appointment_time)s AND appointment_time<%(end_time)s) OR + (appointment_time=%(appointment_time)s)) + """, + { + 'appointment_date': self.appointment_date, + 'name': self.name, + 'practitioner': self.practitioner, + 'patient': self.patient, + 'appointment_time': self.appointment_time, + 'end_time':end_time.time() + }, + as_dict = True + ) + + if not overlapping_appointments: + return # No overlaps, nothing to validate! + + if self.service_unit: # validate service unit capacity if overlap enabled + allow_overlap, service_unit_capacity = frappe.get_value('Healthcare Service Unit', self.service_unit, + ['overlap_appointments', 'service_unit_capacity']) + if allow_overlap: + service_unit_appointments = list(filter(lambda appointment: appointment['service_unit'] == self.service_unit and + appointment['patient'] != self.patient, overlapping_appointments)) # if same patient already booked, it should be an overlap + if len(service_unit_appointments) >= (service_unit_capacity or 1): + frappe.throw(_("Not allowed, {} cannot exceed maximum capacity {}") + .format(frappe.bold(self.service_unit), frappe.bold(service_unit_capacity or 1)), MaximumCapacityError) + else: # service_unit_appointments within capacity, remove from overlapping_appointments + overlapping_appointments = [appointment for appointment in overlapping_appointments if appointment not in service_unit_appointments] + + if overlapping_appointments: + frappe.throw(_("Not allowed, cannot overlap appointment {}") + .format(frappe.bold(', '.join([appointment['name'] for appointment in overlapping_appointments]))), OverlapError) - if overlaps: - overlapping_details = _('Appointment overlaps with ') - overlapping_details += "{0}
".format(overlaps[0][0]) - overlapping_details += _('{0} has appointment scheduled with {1} at {2} having {3} minute(s) duration.').format( - overlaps[0][1], overlaps[0][2], overlaps[0][3], overlaps[0][4]) - frappe.throw(overlapping_details, title=_('Appointments Overlapping')) def validate_service_unit(self): if self.inpatient_record and self.service_unit: @@ -327,6 +355,8 @@ def get_available_slots(practitioner_doc, date): if available_slots: appointments = [] + allow_overlap = 0 + service_unit_capacity = 0 # fetch all appointments to practitioner by service unit filters = { 'practitioner': practitioner, @@ -336,8 +366,8 @@ def get_available_slots(practitioner_doc, date): } if schedule_entry.service_unit: - slot_name = schedule_entry.schedule + ' - ' + schedule_entry.service_unit - allow_overlap = frappe.get_value('Healthcare Service Unit', schedule_entry.service_unit, 'overlap_appointments') + slot_name = f'{schedule_entry.schedule}' + allow_overlap, service_unit_capacity = frappe.get_value('Healthcare Service Unit', schedule_entry.service_unit, ['overlap_appointments', 'service_unit_capacity']) if not allow_overlap: # fetch all appointments to service unit filters.pop('practitioner') @@ -352,8 +382,8 @@ def get_available_slots(practitioner_doc, date): filters=filters, fields=['name', 'appointment_time', 'duration', 'status']) - slot_details.append({'slot_name':slot_name, 'service_unit':schedule_entry.service_unit, - 'avail_slot':available_slots, 'appointments': appointments}) + slot_details.append({'slot_name': slot_name, 'service_unit': schedule_entry.service_unit, 'avail_slot': available_slots, + 'appointments': appointments, 'allow_overlap': allow_overlap, 'service_unit_capacity': service_unit_capacity}) return slot_details diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index 5f2dc480a1b..bb5abd53ba7 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -15,9 +15,11 @@ class TestPatientAppointment(unittest.TestCase): frappe.db.sql("""delete from `tabFee Validity`""") frappe.db.sql("""delete from `tabPatient Encounter`""") make_pos_profile() + frappe.db.sql("""delete from `tabHealthcare Service Unit` where name like '_Test %'""") + frappe.db.sql("""delete from `tabHealthcare Service Unit` where name like '_Test Service Unit Type%'""") def test_status(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0) appointment = create_appointment(patient, practitioner, nowdate()) self.assertEqual(appointment.status, 'Open') @@ -29,7 +31,7 @@ class TestPatientAppointment(unittest.TestCase): self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') def test_start_encounter(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4), invoice = 1) appointment.reload() @@ -43,7 +45,7 @@ class TestPatientAppointment(unittest.TestCase): self.assertEqual(encounter.invoiced, frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced')) def test_auto_invoicing(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0) appointment = create_appointment(patient, practitioner, nowdate()) @@ -59,7 +61,7 @@ class TestPatientAppointment(unittest.TestCase): self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount) def test_auto_invoicing_based_on_department(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) appointment_type = create_appointment_type() @@ -77,7 +79,7 @@ class TestPatientAppointment(unittest.TestCase): self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount) def test_auto_invoicing_according_to_appointment_type_charge(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) @@ -103,7 +105,7 @@ class TestPatientAppointment(unittest.TestCase): self.assertTrue(sales_invoice_name) def test_appointment_cancel(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 1) appointment = create_appointment(patient, practitioner, nowdate()) fee_validity = frappe.db.get_value('Fee Validity Reference', {'appointment': appointment.name}, 'parent') @@ -129,7 +131,7 @@ class TestPatientAppointment(unittest.TestCase): create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy frappe.db.sql("""delete from `tabInpatient Record`""") - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() patient = create_patient() # Schedule Admission ip_record = create_inpatient(patient) @@ -137,7 +139,7 @@ class TestPatientAppointment(unittest.TestCase): ip_record.save(ignore_permissions = True) # Admit - service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy') + service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy') admit_patient(ip_record, service_unit, now_datetime()) appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit) @@ -155,7 +157,7 @@ class TestPatientAppointment(unittest.TestCase): create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy frappe.db.sql("""delete from `tabInpatient Record`""") - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() patient = create_patient() # Schedule Admission ip_record = create_inpatient(patient) @@ -163,10 +165,10 @@ class TestPatientAppointment(unittest.TestCase): ip_record.save(ignore_permissions = True) # Admit - service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy') + service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy') admit_patient(ip_record, service_unit, now_datetime()) - appointment_service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy for Appointment') + appointment_service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy for Appointment') appointment = create_appointment(patient, practitioner, nowdate(), service_unit=appointment_service_unit, save=0) self.assertRaises(frappe.exceptions.ValidationError, appointment.save) @@ -176,39 +178,102 @@ class TestPatientAppointment(unittest.TestCase): mark_invoiced_inpatient_occupancy(ip_record1) discharge_patient(ip_record1) + def test_overlap_appointment(self): + from erpnext.healthcare.doctype.patient_appointment.patient_appointment import OverlapError + patient, practitioner = create_healthcare_docs(id=1) + patient_1, practitioner_1 = create_healthcare_docs(id=2) + service_unit = create_service_unit(id=0) + service_unit_1 = create_service_unit(id=1) + appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit) # valid -def create_healthcare_docs(): - patient = create_patient() - practitioner = frappe.db.exists('Healthcare Practitioner', '_Test Healthcare Practitioner') - medical_department = frappe.db.exists('Medical Department', '_Test Medical Department') + # patient and practitioner cannot have overlapping appointments + appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit, save=0) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit_1, save=0) # diff service unit + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment(patient, practitioner, nowdate(), save=0) # with no service unit link + self.assertRaises(OverlapError, appointment.save) - if not medical_department: - medical_department = frappe.new_doc('Medical Department') - medical_department.department = '_Test Medical Department' - medical_department.save(ignore_permissions=True) - medical_department = medical_department.name + # patient cannot have overlapping appointments with other practitioners + appointment = create_appointment(patient, practitioner_1, nowdate(), service_unit=service_unit, save=0) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment(patient, practitioner_1, nowdate(), service_unit=service_unit_1, save=0) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment(patient, practitioner_1, nowdate(), save=0) + self.assertRaises(OverlapError, appointment.save) - if not practitioner: - practitioner = frappe.new_doc('Healthcare Practitioner') - practitioner.first_name = '_Test Healthcare Practitioner' - practitioner.gender = 'Female' - practitioner.department = medical_department - practitioner.op_consulting_charge = 500 - practitioner.inpatient_visit_charge = 500 - practitioner.save(ignore_permissions=True) - practitioner = practitioner.name + # practitioner cannot have overlapping appointments with other patients + appointment = create_appointment(patient_1, practitioner, nowdate(), service_unit=service_unit, save=0) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment(patient_1, practitioner, nowdate(), service_unit=service_unit_1, save=0) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment(patient_1, practitioner, nowdate(), save=0) + self.assertRaises(OverlapError, appointment.save) - return patient, medical_department, practitioner + def test_service_unit_capacity(self): + from erpnext.healthcare.doctype.patient_appointment.patient_appointment import MaximumCapacityError, OverlapError + practitioner = create_practitioner() + capacity = 3 + overlap_service_unit_type = create_service_unit_type(id=10, allow_appointments=1, overlap_appointments=1) + overlap_service_unit = create_service_unit(id=100, service_unit_type=overlap_service_unit_type, service_unit_capacity=capacity) + + for i in range(0, capacity): + patient = create_patient(id=i) + create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit) # valid + appointment = create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0) # overlap + self.assertRaises(OverlapError, appointment.save) + + patient = create_patient(id=capacity) + appointment = create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0) + self.assertRaises(MaximumCapacityError, appointment.save) + + +def create_healthcare_docs(id=0): + patient = create_patient(id) + practitioner = create_practitioner(id) + + return patient, practitioner + + +def create_patient(id=0): + if frappe.db.exists('Patient', {'firstname':f'_Test Patient {str(id)}'}): + patient = frappe.db.get_value('Patient', {'first_name': f'_Test Patient {str(id)}'}, ['name']) + return patient + + patient = frappe.new_doc('Patient') + patient.first_name = f'_Test Patient {str(id)}' + patient.sex = 'Female' + patient.save(ignore_permissions=True) + + return patient.name + + +def create_medical_department(id=0): + if frappe.db.exists('Medical Department', f'_Test Medical Department {str(id)}'): + return f'_Test Medical Department {str(id)}' + + medical_department = frappe.new_doc('Medical Department') + medical_department.department = f'_Test Medical Department {str(id)}' + medical_department.save(ignore_permissions=True) + + return medical_department.name + + +def create_practitioner(id=0, medical_department=None): + if frappe.db.exists('Healthcare Practitioner', {'firstname':f'_Test Healthcare Practitioner {str(id)}'}): + practitioner = frappe.db.get_value('Healthcare Practitioner', {'firstname':f'_Test Healthcare Practitioner {str(id)}'}, ['name']) + return practitioner + + practitioner = frappe.new_doc('Healthcare Practitioner') + practitioner.first_name = f'_Test Healthcare Practitioner {str(id)}' + practitioner.gender = 'Female' + practitioner.department = medical_department or create_medical_department(id) + practitioner.op_consulting_charge = 500 + practitioner.inpatient_visit_charge = 500 + practitioner.save(ignore_permissions=True) + + return practitioner.name -def create_patient(): - patient = frappe.db.exists('Patient', '_Test Patient') - if not patient: - patient = frappe.new_doc('Patient') - patient.first_name = '_Test Patient' - patient.sex = 'Female' - patient.save(ignore_permissions=True) - patient = patient.name - return patient def create_encounter(appointment): if appointment: @@ -221,8 +286,10 @@ def create_encounter(appointment): encounter.company = appointment.company encounter.save() encounter.submit() + return encounter + def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0, service_unit=None, appointment_type=None, save=1, department=None): item = create_healthcare_service_items() @@ -235,6 +302,7 @@ def create_appointment(patient, practitioner, appointment_date, invoice=0, proce appointment.appointment_date = appointment_date appointment.company = '_Test Company' appointment.duration = 15 + if service_unit: appointment.service_unit = service_unit if invoice: @@ -245,11 +313,14 @@ def create_appointment(patient, practitioner, appointment_date, invoice=0, proce appointment.procedure_template = create_clinical_procedure_template().get('name') if save: appointment.save(ignore_permissions=True) + return appointment + def create_healthcare_service_items(): if frappe.db.exists('Item', 'HLC-SI-001'): return 'HLC-SI-001' + item = frappe.new_doc('Item') item.item_code = 'HLC-SI-001' item.item_name = 'Consulting Charges' @@ -257,11 +328,14 @@ def create_healthcare_service_items(): item.is_stock_item = 0 item.stock_uom = 'Nos' item.save() + return item.name + def create_clinical_procedure_template(): if frappe.db.exists('Clinical Procedure Template', 'Knee Surgery and Rehab'): return frappe.get_doc('Clinical Procedure Template', 'Knee Surgery and Rehab') + template = frappe.new_doc('Clinical Procedure Template') template.template = 'Knee Surgery and Rehab' template.item_code = 'Knee Surgery and Rehab' @@ -270,8 +344,10 @@ def create_clinical_procedure_template(): template.description = 'Knee Surgery and Rehab' template.rate = 50000 template.save() + return template + def create_appointment_type(args=None): if not args: args = frappe.local.form_dict @@ -295,4 +371,29 @@ def create_appointment_type(args=None): 'color': args.get('color') or '#7575ff', 'price_list': args.get('price_list') or frappe.db.get_value("Price List", {"selling": 1}), 'items': args.get('items') or items - }).insert() \ No newline at end of file + }).insert() +def create_service_unit_type(id=0, allow_appointments=1, overlap_appointments=0): + if frappe.db.exists('Healthcare Service Unit Type', f'_Test Service Unit Type {str(id)}'): + return f'_Test Service Unit Type {str(id)}' + + service_unit_type = frappe.new_doc('Healthcare Service Unit Type') + service_unit_type.service_unit_type = f'_Test Service Unit Type {str(id)}' + service_unit_type.allow_appointments = allow_appointments + service_unit_type.overlap_appointments = overlap_appointments + service_unit_type.save(ignore_permissions=True) + + return service_unit_type.name + + +def create_service_unit(id=0, service_unit_type=None, service_unit_capacity=0): + if frappe.db.exists('Healthcare Service Unit', f'_Test Service Unit {str(id)}'): + return f'_Test service_unit {str(id)}' + + service_unit = frappe.new_doc('Healthcare Service Unit') + service_unit.is_group = 0 + service_unit.healthcare_service_unit_name= f'_Test Service Unit {str(id)}' + service_unit.service_unit_type = service_unit_type or create_service_unit_type(id) + service_unit.service_unit_capacity = service_unit_capacity + service_unit.save(ignore_permissions=True) + + return service_unit.name diff --git a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.py b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.py index 3033a3e6ac9..7bad20dffdc 100644 --- a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.py +++ b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.py @@ -31,6 +31,3 @@ def create_patient_assessment(source_name, target_doc=None): }, target_doc) return doc - - - diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py index cc2141790f7..2b3029efdeb 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py @@ -99,4 +99,4 @@ def create_therapy_plan(encounter): def delete_ip_medication_order(encounter): record = frappe.db.exists('Inpatient Medication Order', {'patient_encounter': encounter.name}) if record: - frappe.delete_doc('Inpatient Medication Order', record, force=1) \ No newline at end of file + frappe.delete_doc('Inpatient Medication Order', record, force=1) diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py index 887d58a2e04..63b00859d71 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py @@ -187,4 +187,4 @@ def get_module(doc): if not module: module = frappe.db.get_value('DocType', doc.doctype, 'module') - return module \ No newline at end of file + return module diff --git a/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py index c93b788aed7..f523cd5edea 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py +++ b/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py @@ -101,4 +101,4 @@ def create_doc(patient): }).insert() doc.submit() - return doc \ No newline at end of file + return doc diff --git a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py index c1d9872a019..5b7d8d62c83 100644 --- a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py +++ b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import unittest import frappe from frappe.utils import nowdate -from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_encounter, create_healthcare_docs, create_appointment +from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_encounter, create_healthcare_docs, create_appointment, create_medical_department from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile class TestPatientMedicalRecord(unittest.TestCase): @@ -15,7 +15,8 @@ class TestPatientMedicalRecord(unittest.TestCase): make_pos_profile() def test_medical_record(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() + medical_department = create_medical_department() appointment = create_appointment(patient, practitioner, nowdate(), invoice=1) encounter = create_encounter(appointment) @@ -88,4 +89,4 @@ def create_lab_test(template, patient): lab_test.template = template lab_test.save() lab_test.submit() - return lab_test \ No newline at end of file + return lab_test diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py index 113fa513f98..983fba9f5ff 100644 --- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py @@ -8,11 +8,13 @@ import unittest from frappe.utils import getdate, flt, nowdate from erpnext.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type from erpnext.healthcare.doctype.therapy_plan.therapy_plan import make_therapy_session, make_sales_invoice -from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_patient, create_appointment +from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import \ + create_healthcare_docs, create_patient, create_appointment, create_medical_department class TestTherapyPlan(unittest.TestCase): def test_creation_on_encounter_submission(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() + medical_department = create_medical_department() encounter = create_encounter(patient, medical_department, practitioner) self.assertTrue(frappe.db.exists('Therapy Plan', encounter.therapy_plan)) @@ -28,8 +30,9 @@ class TestTherapyPlan(unittest.TestCase): frappe.get_doc(session).submit() self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed') - patient, medical_department, practitioner = create_healthcare_docs() - appointment = create_appointment(patient, practitioner, nowdate()) + patient, practitioner = create_healthcare_docs() + appointment = create_appointment(patient, practitioner, nowdate()) + session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name) session = frappe.get_doc(session) session.submit() diff --git a/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.py b/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.py index 748c12c6896..635d4beb8df 100644 --- a/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.py +++ b/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.py @@ -70,4 +70,4 @@ class TherapyPlanTemplate(Document): item_price.item_name = self.item_name item_price.price_list_rate = self.total_amount item_price.ignore_mandatory = True - item_price.save(ignore_permissions=True) \ No newline at end of file + item_price.save(ignore_permissions=True) diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.js b/erpnext/healthcare/doctype/therapy_session/therapy_session.js index fd200036935..fbfa774c91c 100644 --- a/erpnext/healthcare/doctype/therapy_session/therapy_session.js +++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.js @@ -168,4 +168,4 @@ frappe.ui.form.on('Therapy Session', { }); } } -}); \ No newline at end of file +}); diff --git a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py index 21f63699753..80fc83fd6ce 100644 --- a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py +++ b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py @@ -34,7 +34,8 @@ def create_therapy_type(): }) therapy_type.save() else: - therapy_type = frappe.get_doc('Therapy Type', 'Basic Rehab') + therapy_type = frappe.get_doc('Therapy Type', therapy_type) + return therapy_type def create_exercise_type(): @@ -47,4 +48,7 @@ def create_exercise_type(): 'description': 'Squat and Rise' }) exercise_type.save() - return exercise_type \ No newline at end of file + else: + exercise_type = frappe.get_doc('Exercise Type', exercise_type) + + return exercise_type diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.py b/erpnext/healthcare/doctype/vital_signs/vital_signs.py index 35c823d739c..4bb3940ae0f 100644 --- a/erpnext/healthcare/doctype/vital_signs/vital_signs.py +++ b/erpnext/healthcare/doctype/vital_signs/vital_signs.py @@ -15,4 +15,3 @@ class VitalSigns(Document): def set_title(self): self.title = _('{0} on {1}').format(self.patient_name or self.patient, frappe.utils.format_date(self.signs_date))[:100] - diff --git a/erpnext/healthcare/page/patient_history/patient_history.html b/erpnext/healthcare/page/patient_history/patient_history.html index be486c62d1e..f1706557f45 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.html +++ b/erpnext/healthcare/page/patient_history/patient_history.html @@ -23,4 +23,4 @@
-
\ No newline at end of file +
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.html b/erpnext/healthcare/page/patient_progress/patient_progress.html index c20537ea81d..30064bd1654 100644 --- a/erpnext/healthcare/page/patient_progress/patient_progress.html +++ b/erpnext/healthcare/page/patient_progress/patient_progress.html @@ -65,4 +65,4 @@ - \ No newline at end of file + diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.js b/erpnext/healthcare/page/patient_progress/patient_progress.js index 2410b0ce845..4b7599df296 100644 --- a/erpnext/healthcare/page/patient_progress/patient_progress.js +++ b/erpnext/healthcare/page/patient_progress/patient_progress.js @@ -528,4 +528,4 @@ class PatientProgress { } $(parent).find('.chart-container').hide(); } -} \ No newline at end of file +} diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.py b/erpnext/healthcare/page/patient_progress/patient_progress.py index a04fb2b592a..46bfb3db5d4 100644 --- a/erpnext/healthcare/page/patient_progress/patient_progress.py +++ b/erpnext/healthcare/page/patient_progress/patient_progress.py @@ -194,4 +194,3 @@ def get_date_range(time_span): return time_span except json.decoder.JSONDecodeError: return get_timespan_date_range(time_span.lower()) - diff --git a/erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html b/erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html index cd62dd39035..4ee65738ba3 100644 --- a/erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html +++ b/erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html @@ -26,4 +26,4 @@

{%=__("Therapy Plan") %}

{%=__("Patient History") %}

- \ No newline at end of file + diff --git a/erpnext/healthcare/print_format/encounter_print/encounter_print.json b/erpnext/healthcare/print_format/encounter_print/encounter_print.json index ec1e0f202a2..3c90adb0a1c 100644 --- a/erpnext/healthcare/print_format/encounter_print/encounter_print.json +++ b/erpnext/healthcare/print_format/encounter_print/encounter_print.json @@ -16,7 +16,7 @@ "name": "Encounter Print", "owner": "Administrator", "print_format_builder": 0, - "print_format_type": "Server", + "print_format_type": "Jinja", "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/healthcare/print_format/sample_id_print/sample_id_print.json b/erpnext/healthcare/print_format/sample_id_print/sample_id_print.json index e99ce708f46..4819e6d57ac 100644 --- a/erpnext/healthcare/print_format/sample_id_print/sample_id_print.json +++ b/erpnext/healthcare/print_format/sample_id_print/sample_id_print.json @@ -16,7 +16,7 @@ "name": "Sample ID Print", "owner": "Administrator", "print_format_builder": 0, - "print_format_type": "Server", + "print_format_type": "Jinja", "show_section_headings": 0, "standard": "Yes" } diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py index b9077301bad..28b60bdcc92 100644 --- a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py +++ b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py @@ -195,4 +195,4 @@ def get_chart_data(data): chart["fieldtype"] = "Data" - return chart \ No newline at end of file + return chart diff --git a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py index 4b461f1a97d..fae5ecef843 100644 --- a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py +++ b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py @@ -25,7 +25,7 @@ class TestInpatientMedicationOrders(unittest.TestCase): 'from_date': getdate(), 'to_date': getdate(), 'patient': '_Test IPD Patient', - 'service_unit': 'Test Service Unit Ip Occupancy - _TC' + 'service_unit': '_Test Service Unit Ip Occupancy - _TC' } report = execute(filters) @@ -42,7 +42,7 @@ class TestInpatientMedicationOrders(unittest.TestCase): 'date': getdate(), 'time': datetime.timedelta(seconds=32400), 'is_completed': 0, - 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC' + 'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC' }, { 'patient': '_Test IPD Patient', @@ -55,7 +55,7 @@ class TestInpatientMedicationOrders(unittest.TestCase): 'date': getdate(), 'time': datetime.timedelta(seconds=50400), 'is_completed': 0, - 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC' + 'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC' }, { 'patient': '_Test IPD Patient', @@ -68,7 +68,7 @@ class TestInpatientMedicationOrders(unittest.TestCase): 'date': getdate(), 'time': datetime.timedelta(seconds=75600), 'is_completed': 0, - 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC' + 'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC' } ] @@ -83,7 +83,7 @@ class TestInpatientMedicationOrders(unittest.TestCase): 'from_date': getdate(), 'to_date': getdate(), 'patient': '_Test IPD Patient', - 'service_unit': 'Test Service Unit Ip Occupancy - _TC', + 'service_unit': '_Test Service Unit Ip Occupancy - _TC', 'show_completed_orders': 0 } @@ -119,7 +119,7 @@ def create_records(patient): ip_record.expected_length_of_stay = 0 ip_record.save() ip_record.reload() - service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy') + service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy') admit_patient(ip_record, service_unit, now_datetime()) ipmo = create_ipmo(patient) diff --git a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py b/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py index 9c35dbb3ea5..9a4840acfea 100644 --- a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py +++ b/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py @@ -191,4 +191,4 @@ class Analytics(object): 'datasets': [] }, "type": "line" - } \ No newline at end of file + } diff --git a/erpnext/healthcare/setup.py b/erpnext/healthcare/setup.py index bf4df7e4c88..891272ddf81 100644 --- a/erpnext/healthcare/setup.py +++ b/erpnext/healthcare/setup.py @@ -292,4 +292,4 @@ def get_patient_history_config(): {"label": "Medication Orders", "fieldname": "medication_orders", "fieldtype": "Table"}, {"label": "Total Orders", "fieldname": "total_orders", "fieldtype": "Float"} ]) - } \ No newline at end of file + } diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py index d3d22c80b67..ffecf4dce29 100644 --- a/erpnext/healthcare/utils.py +++ b/erpnext/healthcare/utils.py @@ -543,58 +543,43 @@ def get_drugs_to_invoice(encounter): @frappe.whitelist() -def get_children(doctype, parent, company, is_root=False): - parent_fieldname = "parent_" + doctype.lower().replace(" ", "_") +def get_children(doctype, parent=None, company=None, is_root=False): + parent_fieldname = 'parent_' + doctype.lower().replace(' ', '_') fields = [ - "name as value", - "is_group as expandable", - "lft", - "rgt" + 'name as value', + 'is_group as expandable', + 'lft', + 'rgt' ] - # fields = [ "name", "is_group", "lft", "rgt" ] - filters = [["ifnull(`{0}`,'')".format(parent_fieldname), "=", "" if is_root else parent]] + + filters = [["ifnull(`{0}`,'')".format(parent_fieldname), + '=', '' if is_root else parent]] if is_root: - fields += ["service_unit_type"] if doctype == "Healthcare Service Unit" else [] - filters.append(["company", "=", company]) - + fields += ['service_unit_type'] if doctype == 'Healthcare Service Unit' else [] + filters.append(['company', '=', company]) else: - fields += ["service_unit_type", "allow_appointments", "inpatient_occupancy", "occupancy_status"] if doctype == "Healthcare Service Unit" else [] - fields += [parent_fieldname + " as parent"] + fields += ['service_unit_type', 'allow_appointments', 'inpatient_occupancy', + 'occupancy_status'] if doctype == 'Healthcare Service Unit' else [] + fields += [parent_fieldname + ' as parent'] - hc_service_units = frappe.get_list(doctype, fields=fields, filters=filters) + service_units = frappe.get_list(doctype, fields=fields, filters=filters) + for each in service_units: + if each['expandable'] == 1: # group node + available_count = frappe.db.count('Healthcare Service Unit', filters={ + 'parent_healthcare_service_unit': each['value'], + 'inpatient_occupancy': 1}) - if doctype == "Healthcare Service Unit": - for each in hc_service_units: - occupancy_msg = "" - if each["expandable"] == 1: - occupied = False - vacant = False - child_list = frappe.db.sql( - ''' - SELECT - name, occupancy_status - FROM - `tabHealthcare Service Unit` - WHERE - inpatient_occupancy = 1 - and lft > %s and rgt < %s - ''', (each['lft'], each['rgt'])) + if available_count > 0: + occupied_count = frappe.db.count('Healthcare Service Unit', { + 'parent_healthcare_service_unit': each['value'], + 'inpatient_occupancy': 1, + 'occupancy_status': 'Occupied'}) + # set occupancy status of group node + each['occupied_of_available'] = str( + occupied_count) + ' Occupied of ' + str(available_count) - for child in child_list: - if not occupied: - occupied = 0 - if child[1] == "Occupied": - occupied += 1 - if not vacant: - vacant = 0 - if child[1] == "Vacant": - vacant += 1 - if vacant and occupied: - occupancy_total = vacant + occupied - occupancy_msg = str(occupied) + " Occupied out of " + str(occupancy_total) - each["occupied_out_of_vacant"] = occupancy_msg - return hc_service_units + return service_units @frappe.whitelist() @@ -717,3 +702,40 @@ def render_doc_as_html(doctype, docname, exclude_fields = []): doc_html = "
" %(doctype, docname) + doc_html + '
' return {'html': doc_html} + + +def update_address_links(address, method): + ''' + Hook validate Address + If Patient is linked in Address, also link the associated Customer + ''' + if 'Healthcare' not in frappe.get_active_domains(): + return + + patient_links = list(filter(lambda link: link.get('link_doctype') == 'Patient', address.links)) + + for link in patient_links: + customer = frappe.db.get_value('Patient', link.get('link_name'), 'customer') + if customer and not address.has_link('Customer', customer): + address.append('links', dict(link_doctype = 'Customer', link_name = customer)) + + +def update_patient_email_and_phone_numbers(contact, method): + ''' + Hook validate Contact + Update linked Patients' primary mobile and phone numbers + ''' + if 'Healthcare' not in frappe.get_active_domains(): + return + + if contact.is_primary_contact and (contact.email_id or contact.mobile_no or contact.phone): + patient_links = list(filter(lambda link: link.get('link_doctype') == 'Patient', contact.links)) + + for link in patient_links: + contact_details = frappe.db.get_value('Patient', link.get('link_name'), ['email', 'mobile', 'phone'], as_dict=1) + if contact.email_id and contact.email_id != contact_details.get('email'): + frappe.db.set_value('Patient', link.get('link_name'), 'email', contact.email_id) + if contact.mobile_no and contact.mobile_no != contact_details.get('mobile'): + frappe.db.set_value('Patient', link.get('link_name'), 'mobile', contact.mobile_no) + if contact.phone and contact.phone != contact_details.get('phone'): + frappe.db.set_value('Patient', link.get('link_name'), 'phone', contact.phone) diff --git a/erpnext/healthcare/web_form/patient_registration/patient_registration.js b/erpnext/healthcare/web_form/patient_registration/patient_registration.js index 7da3f1fb41c..f09e5409192 100644 --- a/erpnext/healthcare/web_form/patient_registration/patient_registration.js +++ b/erpnext/healthcare/web_form/patient_registration/patient_registration.js @@ -1,3 +1,3 @@ frappe.ready(function() { // bind events here -}); \ No newline at end of file +}); diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 52daec91805..811004767d5 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -24,7 +24,8 @@ doctype_js = { "Address": "public/js/address.js", "Communication": "public/js/communication.js", "Event": "public/js/event.js", - "Newsletter": "public/js/newsletter.js" + "Newsletter": "public/js/newsletter.js", + "Contact": "public/js/contact.js" } override_doctype_class = { @@ -281,7 +282,12 @@ doc_events = { "on_trash": "erpnext.regional.check_deletion_permission" }, 'Address': { - 'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code', 'erpnext.regional.india.utils.update_gst_category'] + 'validate': [ + 'erpnext.regional.india.utils.validate_gstin_for_india', + 'erpnext.regional.italy.utils.set_state_code', + 'erpnext.regional.india.utils.update_gst_category', + 'erpnext.healthcare.utils.update_address_links' + ], }, 'Supplier': { 'validate': 'erpnext.regional.india.utils.validate_pan_for_india' @@ -292,13 +298,16 @@ doc_events = { "Contact": { "on_trash": "erpnext.support.doctype.issue.issue.update_issue", "after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations", - "validate": "erpnext.crm.utils.update_lead_phone_numbers" + "validate": ["erpnext.crm.utils.update_lead_phone_numbers", "erpnext.healthcare.utils.update_patient_email_and_phone_numbers"] }, "Email Unsubscribe": { "after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient" }, ('Quotation', 'Sales Order', 'Sales Invoice'): { 'validate': ["erpnext.erpnext_integrations.taxjar_integration.set_sales_tax"] + }, + "Company": { + "on_trash": "erpnext.regional.india.utils.delete_gst_settings_for_company" } } @@ -429,7 +438,8 @@ regional_overrides = { 'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption', 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period', 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields', - 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount' + 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount', + 'erpnext.stock.doctype.item.item.set_item_tax_from_hsn_code': 'erpnext.regional.india.utils.set_item_tax_from_hsn_code' }, 'United Arab Emirates': { 'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data', diff --git a/erpnext/hotels/doctype/hotel_room/hotel_room.py b/erpnext/hotels/doctype/hotel_room/hotel_room.py index 8471aee4a03..6a2fc02574f 100644 --- a/erpnext/hotels/doctype/hotel_room/hotel_room.py +++ b/erpnext/hotels/doctype/hotel_room/hotel_room.py @@ -10,4 +10,4 @@ class HotelRoom(Document): def validate(self): if not self.capacity: self.capacity, self.extra_bed_capacity = frappe.db.get_value('Hotel Room Type', - self.hotel_room_type, ['capacity', 'extra_bed_capacity']) \ No newline at end of file + self.hotel_room_type, ['capacity', 'extra_bed_capacity']) diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js index 7f7322cf4b6..7bde292a2bc 100644 --- a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js +++ b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js @@ -6,4 +6,4 @@ frappe.views.calendar["Hotel Room Reservation"] = { "title": "guest_name", "status": "status" } -} \ No newline at end of file +} diff --git a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py index f77d43b3143..259edb9c06d 100644 --- a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py +++ b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py @@ -30,4 +30,4 @@ def get_data(filters): out.append([room_type.name, total_booked]) - return out \ No newline at end of file + return out diff --git a/erpnext/hr/doctype/appraisal/appraisal.js b/erpnext/hr/doctype/appraisal/appraisal.js index 1a30ceac6d3..50612b923ef 100644 --- a/erpnext/hr/doctype/appraisal/appraisal.js +++ b/erpnext/hr/doctype/appraisal/appraisal.js @@ -15,7 +15,7 @@ frappe.ui.form.on('Appraisal', { frm.set_value('status', 'Draft'); } }, - + kra_template: function(frm) { frm.doc.goals = []; erpnext.utils.map_current_doc({ diff --git a/erpnext/hr/doctype/appraisal/appraisal.py b/erpnext/hr/doctype/appraisal/appraisal.py index f7601870fac..c2ed4579844 100644 --- a/erpnext/hr/doctype/appraisal/appraisal.py +++ b/erpnext/hr/doctype/appraisal/appraisal.py @@ -9,7 +9,7 @@ from frappe.utils import flt, getdate from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.model.document import Document -from erpnext.hr.utils import set_employee_name +from erpnext.hr.utils import set_employee_name, validate_active_employee class Appraisal(Document): def validate(self): @@ -19,6 +19,7 @@ class Appraisal(Document): if not self.goals: frappe.throw(_("Goals cannot be empty")) + validate_active_employee(self.employee) set_employee_name(self) self.validate_dates() self.validate_existing_appraisal() diff --git a/erpnext/hr/doctype/appraisal/test_appraisal.js b/erpnext/hr/doctype/appraisal/test_appraisal.js index 9ca17e2e226..fb1354c3f6b 100644 --- a/erpnext/hr/doctype/appraisal/test_appraisal.js +++ b/erpnext/hr/doctype/appraisal/test_appraisal.js @@ -55,4 +55,3 @@ QUnit.test("Test: Expense Claim [HR]", function (assert) { () => done() ]); }); - diff --git a/erpnext/hr/doctype/appraisal_goal/appraisal_goal.py b/erpnext/hr/doctype/appraisal_goal/appraisal_goal.py index a6868ee2b1c..11d9f3944d5 100644 --- a/erpnext/hr/doctype/appraisal_goal/appraisal_goal.py +++ b/erpnext/hr/doctype/appraisal_goal/appraisal_goal.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class AppraisalGoal(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py b/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py index 309427e30c2..392b370e6c3 100644 --- a/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py +++ b/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Appraisal'] }, ], - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/appraisal_template/test_appraisal_template.js b/erpnext/hr/doctype/appraisal_template/test_appraisal_template.js index 0403cad0683..3eb64e08501 100644 --- a/erpnext/hr/doctype/appraisal_template/test_appraisal_template.js +++ b/erpnext/hr/doctype/appraisal_template/test_appraisal_template.js @@ -27,4 +27,3 @@ QUnit.test("Test: Appraisal Template [HR]", function (assert) { () => done() ]); }); - diff --git a/erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.py b/erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.py index ca58e0c3202..b3c5704fa53 100644 --- a/erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.py +++ b/erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class AppraisalTemplateGoal(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index 3412675d811..c1a7c8f88a5 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -8,11 +8,13 @@ from frappe.utils import getdate, nowdate from frappe import _ from frappe.model.document import Document from frappe.utils import cstr, get_datetime, formatdate +from erpnext.hr.utils import validate_active_employee class Attendance(Document): def validate(self): from erpnext.controllers.status_updater import validate_status validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"]) + validate_active_employee(self.employee) self.validate_attendance_date() self.validate_duplicate_record() self.validate_employee_status() @@ -133,7 +135,7 @@ def mark_attendance(employee, attendance_date, status, shift=None, leave_type=No def mark_bulk_attendance(data): import json from pprint import pprint - if isinstance(data, frappe.string_types): + if isinstance(data, str): data = json.loads(data) data = frappe._dict(data) company = frappe.get_value('Employee', data.employee, 'company') diff --git a/erpnext/hr/doctype/attendance/attendance_calendar.js b/erpnext/hr/doctype/attendance/attendance_calendar.js index 45664896965..d9f6d2eb3eb 100644 --- a/erpnext/hr/doctype/attendance/attendance_calendar.js +++ b/erpnext/hr/doctype/attendance/attendance_calendar.js @@ -9,4 +9,4 @@ frappe.views.calendar["Attendance"] = { } }, get_events_method: "erpnext.hr.doctype.attendance.attendance.get_events" -}; \ No newline at end of file +}; diff --git a/erpnext/hr/doctype/attendance/test_attendance.js b/erpnext/hr/doctype/attendance/test_attendance.js index 8f30e8cc161..b3e7fef02a0 100644 --- a/erpnext/hr/doctype/attendance/test_attendance.js +++ b/erpnext/hr/doctype/attendance/test_attendance.js @@ -36,4 +36,4 @@ QUnit.test("Test: Attendance [HR]", function (assert) { "attendance for Present day is marked"), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/attendance_request/attendance_request.py b/erpnext/hr/doctype/attendance_request/attendance_request.py index 090d53262cf..7f88fed73a2 100644 --- a/erpnext/hr/doctype/attendance_request/attendance_request.py +++ b/erpnext/hr/doctype/attendance_request/attendance_request.py @@ -8,10 +8,11 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import date_diff, add_days, getdate from erpnext.hr.doctype.employee.employee import is_holiday -from erpnext.hr.utils import validate_dates +from erpnext.hr.utils import validate_dates, validate_active_employee class AttendanceRequest(Document): def validate(self): + validate_active_employee(self.employee) validate_dates(self, self.from_date, self.to_date) if self.half_day: if not getdate(self.from_date)<=getdate(self.half_day_date)<=getdate(self.to_date): diff --git a/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py b/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py index cfdd6d3aefb..2d3eb000119 100644 --- a/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py +++ b/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py @@ -8,4 +8,4 @@ def get_data(): 'items': ['Attendance'] } ] - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/attendance_request/test_attendance_request.py b/erpnext/hr/doctype/attendance_request/test_attendance_request.py index 3c42bd9fc35..9e668aa72fb 100644 --- a/erpnext/hr/doctype/attendance_request/test_attendance_request.py +++ b/erpnext/hr/doctype/attendance_request/test_attendance_request.py @@ -15,7 +15,11 @@ class TestAttendanceRequest(unittest.TestCase): for doctype in ["Attendance Request", "Attendance"]: frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype)) + def tearDown(self): + frappe.db.rollback() + def test_on_duty_attendance_request(self): + "Test creation/updation of Attendace from Attendance Request, on duty." today = nowdate() employee = get_employee() attendance_request = frappe.new_doc("Attendance Request") @@ -26,17 +30,36 @@ class TestAttendanceRequest(unittest.TestCase): attendance_request.company = "_Test Company" attendance_request.insert() attendance_request.submit() - attendance = frappe.get_doc('Attendance', { - 'employee': employee.name, - 'attendance_date': date(date.today().year, 1, 1), - 'docstatus': 1 - }) - self.assertEqual(attendance.status, 'Present') + + attendance = frappe.db.get_value( + "Attendance", + filters={ + "attendance_request": attendance_request.name, + "attendance_date": date(date.today().year, 1, 1) + }, + fieldname=["status", "docstatus"], + as_dict=True + ) + self.assertEqual(attendance.status, "Present") + self.assertEqual(attendance.docstatus, 1) + + # cancelling attendance request cancels linked attendances attendance_request.cancel() - attendance.reload() - self.assertEqual(attendance.docstatus, 2) + + # cancellation alters docname + # fetch attendance value again to avoid stale docname + attendance_docstatus = frappe.db.get_value( + "Attendance", + filters={ + "attendance_request": attendance_request.name, + "attendance_date": date(date.today().year, 1, 1) + }, + fieldname="docstatus" + ) + self.assertEqual(attendance_docstatus, 2) def test_work_from_home_attendance_request(self): + "Test creation/updation of Attendace from Attendance Request, work from home." today = nowdate() employee = get_employee() attendance_request = frappe.new_doc("Attendance Request") @@ -47,15 +70,30 @@ class TestAttendanceRequest(unittest.TestCase): attendance_request.company = "_Test Company" attendance_request.insert() attendance_request.submit() - attendance = frappe.get_doc('Attendance', { - 'employee': employee.name, - 'attendance_date': date(date.today().year, 1, 1), - 'docstatus': 1 - }) - self.assertEqual(attendance.status, 'Work From Home') + + attendance_status = frappe.db.get_value( + "Attendance", + filters={ + "attendance_request": attendance_request.name, + "attendance_date": date(date.today().year, 1, 1) + }, + fieldname="status" + ) + self.assertEqual(attendance_status, 'Work From Home') + attendance_request.cancel() - attendance.reload() - self.assertEqual(attendance.docstatus, 2) + + # cancellation alters docname + # fetch attendance value again to avoid stale docname + attendance_docstatus = frappe.db.get_value( + "Attendance", + filters={ + "attendance_request": attendance_request.name, + "attendance_date": date(date.today().year, 1, 1) + }, + fieldname="docstatus" + ) + self.assertEqual(attendance_docstatus, 2) def get_employee(): return frappe.get_doc("Employee", "_T-Employee-00001") diff --git a/erpnext/hr/doctype/branch/branch.py b/erpnext/hr/doctype/branch/branch.py index fab2ffc1a37..a847c8e2174 100644 --- a/erpnext/hr/doctype/branch/branch.py +++ b/erpnext/hr/doctype/branch/branch.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class Branch(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/hr/doctype/branch/test_branch.js b/erpnext/hr/doctype/branch/test_branch.js index c315385f116..82a6ae103ee 100644 --- a/erpnext/hr/doctype/branch/test_branch.js +++ b/erpnext/hr/doctype/branch/test_branch.js @@ -20,4 +20,4 @@ QUnit.test("Test: Branch [HR]", function (assert) { 'name of branch correctly saved'), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/branch/test_branch.py b/erpnext/hr/doctype/branch/test_branch.py index 5ba02b36b8a..807698ba0a2 100644 --- a/erpnext/hr/doctype/branch/test_branch.py +++ b/erpnext/hr/doctype/branch/test_branch.py @@ -4,4 +4,4 @@ from __future__ import unicode_literals import frappe -test_records = frappe.get_test_records('Branch') \ No newline at end of file +test_records = frappe.get_test_records('Branch') diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py index a6fe429be17..0d7fded921b 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py @@ -7,12 +7,13 @@ import frappe from frappe import _ from frappe.utils import date_diff, add_days, getdate, cint, format_date from frappe.model.document import Document -from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \ +from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, validate_active_employee, \ get_holidays_for_employee, create_additional_leave_ledger_entry class CompensatoryLeaveRequest(Document): def validate(self): + validate_active_employee(self.employee) validate_dates(self, self.work_from_date, self.work_end_date) if self.half_day: if not self.half_day_date: diff --git a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js index d2ceb8bd527..15335171473 100644 --- a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js +++ b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js @@ -20,4 +20,4 @@ QUnit.test("test: Daily Work Summary", function (assert) { () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/department/department_tree.js b/erpnext/hr/doctype/department/department_tree.js index 52d864bc0e6..5c7726de6a6 100644 --- a/erpnext/hr/doctype/department/department_tree.js +++ b/erpnext/hr/doctype/department/department_tree.js @@ -25,4 +25,4 @@ frappe.treeview_settings["Department"] = { onload: function(treeview) { treeview.make_tree(); } -}; \ No newline at end of file +}; diff --git a/erpnext/hr/doctype/department/test_department.js b/erpnext/hr/doctype/department/test_department.js index 3a571f76535..e73779c97c6 100644 --- a/erpnext/hr/doctype/department/test_department.js +++ b/erpnext/hr/doctype/department/test_department.js @@ -20,4 +20,4 @@ QUnit.test("Test: Department [HR]", function (assert) { 'name of department correctly saved'), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/department/test_department.py b/erpnext/hr/doctype/department/test_department.py index 2eeca26e303..e4f6645ee42 100644 --- a/erpnext/hr/doctype/department/test_department.py +++ b/erpnext/hr/doctype/department/test_department.py @@ -21,4 +21,4 @@ def create_department(department_name, parent_department=None): return doc -test_records = frappe.get_test_records('Department') \ No newline at end of file +test_records = frappe.get_test_records('Department') diff --git a/erpnext/hr/doctype/designation/designation.py b/erpnext/hr/doctype/designation/designation.py index efd864ad593..a3f84aab5f0 100644 --- a/erpnext/hr/doctype/designation/designation.py +++ b/erpnext/hr/doctype/designation/designation.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class Designation(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/hr/doctype/designation/test_designation.js b/erpnext/hr/doctype/designation/test_designation.js index 45c34171911..00adf8293f7 100644 --- a/erpnext/hr/doctype/designation/test_designation.js +++ b/erpnext/hr/doctype/designation/test_designation.js @@ -20,4 +20,4 @@ QUnit.test("Test: Designation [HR]", function (assert) { 'name of designation correctly saved'), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/designation/test_designation.py b/erpnext/hr/doctype/designation/test_designation.py index 3b300941a65..2778862a1c2 100644 --- a/erpnext/hr/doctype/designation/test_designation.py +++ b/erpnext/hr/doctype/designation/test_designation.py @@ -17,4 +17,4 @@ def create_designation(**args): "description": args.description or "_Test description" }) designation.save() - return designation \ No newline at end of file + return designation diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index fa017d9d4c2..f4280152c5c 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -13,8 +13,10 @@ from frappe.model.document import Document from erpnext.utilities.transaction_base import delete_events from frappe.utils.nestedset import NestedSet -class EmployeeUserDisabledError(frappe.ValidationError): pass -class EmployeeLeftValidationError(frappe.ValidationError): pass +class EmployeeUserDisabledError(frappe.ValidationError): + pass +class InactiveEmployeeStatusError(frappe.ValidationError): + pass class Employee(NestedSet): nsm_parent_field = 'reports_to' @@ -196,7 +198,7 @@ class Employee(NestedSet): message += "


" message += _("Please make sure the employees above report to another Active employee.") - throw(message, EmployeeLeftValidationError, _("Cannot Relieve Employee")) + throw(message, InactiveEmployeeStatusError, _("Cannot Relieve Employee")) if not self.relieving_date: throw(_("Please enter relieving date.")) @@ -518,4 +520,4 @@ def has_upload_permission(doc, ptype='read', user=None): user = frappe.session.user if get_doc_permissions(doc, user=user, ptype=ptype).get(ptype): return True - return doc.user_id == user \ No newline at end of file + return doc.user_id == user diff --git a/erpnext/hr/doctype/employee/employee_tree.js b/erpnext/hr/doctype/employee/employee_tree.js index 9ab091a1eb6..7d6a70013d4 100644 --- a/erpnext/hr/doctype/employee/employee_tree.js +++ b/erpnext/hr/doctype/employee/employee_tree.js @@ -33,4 +33,4 @@ frappe.treeview_settings['Employee'] = { condition: 'frappe.boot.user.can_create.indexOf("Employee") !== -1' } ], -}; \ No newline at end of file +}; diff --git a/erpnext/hr/doctype/employee/test_employee.js b/erpnext/hr/doctype/employee/test_employee.js index 200dcd79666..3a414584804 100644 --- a/erpnext/hr/doctype/employee/test_employee.js +++ b/erpnext/hr/doctype/employee/test_employee.js @@ -37,4 +37,4 @@ QUnit.test("Test: Employee [HR]", function (assert) { () => frappe.timeout(10), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index 7d652a7366a..8fc7cf19343 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -7,7 +7,7 @@ import frappe import erpnext import unittest import frappe.utils -from erpnext.hr.doctype.employee.employee import EmployeeLeftValidationError +from erpnext.hr.doctype.employee.employee import InactiveEmployeeStatusError test_records = frappe.get_test_records('Employee') @@ -45,10 +45,33 @@ class TestEmployee(unittest.TestCase): employee2_doc.save() employee1_doc.reload() employee1_doc.status = 'Left' - self.assertRaises(EmployeeLeftValidationError, employee1_doc.save) + self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save) + + def test_employee_status_inactive(self): + from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure + from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip + from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list + + employee = make_employee("test_employee_status@company.com") + employee_doc = frappe.get_doc("Employee", employee) + employee_doc.status = "Inactive" + employee_doc.save() + employee_doc.reload() + + make_holiday_list() + frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List") + + frappe.db.sql("""delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'""") + salary_structure = make_salary_structure("Test Inactive Employee Salary Slip", "Monthly", + employee=employee_doc.name, company=employee_doc.company) + salary_slip = make_salary_slip(salary_structure.name, employee=employee_doc.name) + + self.assertRaises(InactiveEmployeeStatusError, salary_slip.save) + + def tearDown(self): + frappe.db.rollback() def make_employee(user, company=None, **kwargs): - "" if not frappe.db.get_value("User", user): frappe.get_doc({ "doctype": "User", @@ -80,4 +103,5 @@ def make_employee(user, company=None, **kwargs): employee.insert() return employee.name else: + frappe.db.set_value("Employee", {"employee_name":user}, "status", "Active") return frappe.get_value("Employee", {"employee_name":user}, "name") diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index cb72f6b6d96..cbb3cc813b4 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -8,6 +8,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import flt, nowdate from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account +from erpnext.hr.utils import validate_active_employee class EmployeeAdvanceOverPayment(frappe.ValidationError): pass @@ -18,11 +19,11 @@ class EmployeeAdvance(Document): 'make_payment_via_journal_entry') def validate(self): + validate_active_employee(self.employee) self.set_status() def on_cancel(self): self.ignore_linked_doctypes = ('GL Entry') - self.set_status() def set_status(self): if self.docstatus == 0: @@ -183,9 +184,9 @@ def make_return_entry(employee, company, employee_advance_name, return_amount, bank_cash_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment) if not bank_cash_account: frappe.throw(_("Please set a Default Cash Account in Company defaults")) - + advance_account_currency = frappe.db.get_value('Account', advance_account, 'account_currency') - + je = frappe.new_doc('Journal Entry') je.posting_date = nowdate() je.voucher_type = get_voucher_type(mode_of_payment) @@ -229,4 +230,4 @@ def get_voucher_type(mode_of_payment=None): if mode_of_payment_type == "Bank": voucher_type = "Bank Entry" - return voucher_type \ No newline at end of file + return voucher_type diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py index c88b2b8e49e..100968bb7aa 100644 --- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py @@ -48,4 +48,4 @@ def make_employee_advance(employee_name): doc.insert() doc.submit() - return doc \ No newline at end of file + return doc diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.css b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.css index d25fb2247ed..c8d6644b2f8 100644 --- a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.css +++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.css @@ -18,4 +18,4 @@ .checkbox{ margin-top: -3px; -} \ No newline at end of file +} diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js index 3205a92b1b6..2de5eb0d64e 100644 --- a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js +++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js @@ -267,5 +267,3 @@ erpnext.EmployeeSelector = Class.extend({ mark_employee_toolbar.appendTo($(this.wrapper)); } }); - - diff --git a/erpnext/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.js b/erpnext/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.js index 2827d4ba289..48d4344df22 100644 --- a/erpnext/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.js +++ b/erpnext/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.js @@ -58,4 +58,4 @@ QUnit.test("Test: Employee attendance tool [HR]", function (assert) { }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py index 15fbd4e0153..6c0cd4f963b 100644 --- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py @@ -9,9 +9,11 @@ from frappe.model.document import Document from frappe import _ from erpnext.hr.doctype.shift_assignment.shift_assignment import get_actual_start_end_datetime_of_shift +from erpnext.hr.utils import validate_active_employee class EmployeeCheckin(Document): def validate(self): + validate_active_employee(self.employee) self.validate_duplicate_log() self.fetch_shift() @@ -122,7 +124,7 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type): """Given a set of logs in chronological order calculates the total working hours based on the parameters. Zero is returned for all invalid cases. - + :param logs: The List of 'Employee Checkin'. :param check_in_out_type: One of: 'Alternating entries as IN and OUT during the same shift', 'Strictly based on Log Type in Employee Checkin' :param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out' @@ -174,4 +176,3 @@ def time_diff_in_hours(start, end): def find_index_in_dict(dict_list, key, value): return next((index for (index, d) in enumerate(dict_list) if d[key] == value), None) - diff --git a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py index 9f12ef24e62..7ba511f08d5 100644 --- a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py @@ -42,11 +42,11 @@ class TestEmployeeCheckin(unittest.TestCase): self.assertEqual(logs_count, 4) attendance_count = frappe.db.count('Attendance', {'status':'Present', 'working_hours':8.2, 'employee':employee, 'attendance_date':now_date}) - self.assertEqual(attendance_count, 1) + self.assertEqual(attendance_count, 1) def test_calculate_working_hours(self): check_in_out_type = ['Alternating entries as IN and OUT during the same shift', - 'Strictly based on Log Type in Employee Checkin'] + 'Strictly based on Log Type in Employee Checkin'] working_hours_calc_type = ['First Check-in and Last Check-out', 'Every Valid Check-in and Check-out'] logs_type_1 = [ diff --git a/erpnext/hr/doctype/employee_education/employee_education.py b/erpnext/hr/doctype/employee_education/employee_education.py index a1d449291c9..f0a76172b2c 100644 --- a/erpnext/hr/doctype/employee_education/employee_education.py +++ b/erpnext/hr/doctype/employee_education/employee_education.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class EmployeeEducation(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/hr/doctype/employee_external_work_history/employee_external_work_history.py b/erpnext/hr/doctype/employee_external_work_history/employee_external_work_history.py index c7166309f37..517ef57be85 100644 --- a/erpnext/hr/doctype/employee_external_work_history/employee_external_work_history.py +++ b/erpnext/hr/doctype/employee_external_work_history/employee_external_work_history.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class EmployeeExternalWorkHistory(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py b/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py index f2656e9a2b2..df679104185 100644 --- a/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py +++ b/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py @@ -10,4 +10,4 @@ def get_data(): 'items': ['Employee Onboarding Template', 'Employee Separation Template'] } ] - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.py b/erpnext/hr/doctype/employee_grievance/employee_grievance.py index 503b5ea4449..17055829efb 100644 --- a/erpnext/hr/doctype/employee_grievance/employee_grievance.py +++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.py @@ -12,4 +12,3 @@ class EmployeeGrievance(Document): bold("Invalid"), bold("Resolved")) ) - diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js b/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js index fc08e216099..11672ca4e0e 100644 --- a/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js +++ b/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js @@ -9,4 +9,4 @@ frappe.listview_settings["Employee Grievance"] = { }; return [__(doc.status), colors[doc.status], "status,=," + doc.status]; } -}; \ No newline at end of file +}; diff --git a/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py index a615b20a5a2..ed897ee1032 100644 --- a/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py +++ b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py @@ -48,4 +48,3 @@ def create_grievance_type(): grievance_type.save() return grievance_type.name - diff --git a/erpnext/hr/doctype/employee_group/test_employee_group.py b/erpnext/hr/doctype/employee_group/test_employee_group.py index 3a6bf8594b3..26a61c407b2 100644 --- a/erpnext/hr/doctype/employee_group/test_employee_group.py +++ b/erpnext/hr/doctype/employee_group/test_employee_group.py @@ -29,4 +29,4 @@ def make_employee_group(): def get_employee_group(): employee_group = frappe.db.exists("Employee Group", "_Test Employee Group") - return employee_group \ No newline at end of file + return employee_group diff --git a/erpnext/hr/doctype/employee_internal_work_history/employee_internal_work_history.py b/erpnext/hr/doctype/employee_internal_work_history/employee_internal_work_history.py index d0f3d8d016a..2f385a8113e 100644 --- a/erpnext/hr/doctype/employee_internal_work_history/employee_internal_work_history.py +++ b/erpnext/hr/doctype/employee_internal_work_history/employee_internal_work_history.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class EmployeeInternalWorkHistory(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py index 6cc2bf5cd85..0cb50475bf8 100644 --- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py +++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py @@ -57,4 +57,3 @@ def make_employee(source_name, target_doc=None): }} }, target_doc, set_missing_values) return doc - diff --git a/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py index 837da530162..ab0eb2f5dce 100644 --- a/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py +++ b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Employee Onboarding'] }, ], - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/employee_promotion/employee_promotion.py b/erpnext/hr/doctype/employee_promotion/employee_promotion.py index 83fb235f92c..a3a61834c8c 100644 --- a/erpnext/hr/doctype/employee_promotion/employee_promotion.py +++ b/erpnext/hr/doctype/employee_promotion/employee_promotion.py @@ -7,12 +7,11 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import getdate -from erpnext.hr.utils import update_employee +from erpnext.hr.utils import update_employee, validate_active_employee class EmployeePromotion(Document): def validate(self): - if frappe.get_value("Employee", self.employee, "status") != "Active": - frappe.throw(_("Cannot promote Employee with status Left or Inactive")) + validate_active_employee(self.employee) def before_submit(self): if getdate(self.promotion_date) > getdate(): diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py index 45d68729ce6..547a95e3bdf 100644 --- a/erpnext/hr/doctype/employee_referral/employee_referral.py +++ b/erpnext/hr/doctype/employee_referral/employee_referral.py @@ -7,9 +7,11 @@ import frappe from frappe import _ from frappe.utils import get_link_to_form from frappe.model.document import Document +from erpnext.hr.utils import validate_active_employee class EmployeeReferral(Document): def validate(self): + validate_active_employee(self.referrer) self.set_full_name() self.set_referral_bonus_payment_status() @@ -68,4 +70,3 @@ def create_additional_salary(doc): additional_salary.ref_docname = doc.name return additional_salary - diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py index afa2a1ff1fc..caca2961a1a 100644 --- a/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py +++ b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py @@ -12,4 +12,4 @@ def get_data(): }, ] - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_list.js b/erpnext/hr/doctype/employee_referral/employee_referral_list.js index 7533ab635f5..38dfc4d4c86 100644 --- a/erpnext/hr/doctype/employee_referral/employee_referral_list.js +++ b/erpnext/hr/doctype/employee_referral/employee_referral_list.js @@ -11,4 +11,4 @@ frappe.listview_settings['Employee Referral'] = { return [__(doc.status), "red", "status,=," + doc.status]; } }, -}; \ No newline at end of file +}; diff --git a/erpnext/hr/doctype/employee_referral/test_employee_referral.py b/erpnext/hr/doctype/employee_referral/test_employee_referral.py index a674f390265..599f3262240 100644 --- a/erpnext/hr/doctype/employee_referral/test_employee_referral.py +++ b/erpnext/hr/doctype/employee_referral/test_employee_referral.py @@ -57,4 +57,4 @@ def create_employee_referral(): emp_ref.save() emp_ref.submit() - return emp_ref \ No newline at end of file + return emp_ref diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py index 713fcf526b5..0b72efa1378 100644 --- a/erpnext/hr/doctype/employee_separation/test_employee_separation.py +++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py @@ -23,4 +23,4 @@ class TestEmployeeSeparation(unittest.TestCase): separation.submit() self.assertEqual(separation.docstatus, 1) separation.cancel() - self.assertEqual(separation.project, "") \ No newline at end of file + self.assertEqual(separation.project, "") diff --git a/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py b/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py index 39345f07663..75f985cec39 100644 --- a/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py +++ b/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Employee Separation'] }, ], - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.py b/erpnext/hr/doctype/employee_transfer/employee_transfer.py index 6eec9fa12a9..c2007747fb3 100644 --- a/erpnext/hr/doctype/employee_transfer/employee_transfer.py +++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.py @@ -10,10 +10,6 @@ from frappe.utils import getdate from erpnext.hr.utils import update_employee class EmployeeTransfer(Document): - def validate(self): - if frappe.get_value("Employee", self.employee, "status") != "Active": - frappe.throw(_("Cannot transfer Employee with status Left or Inactive")) - def before_submit(self): if getdate(self.transfer_date) > getdate(): frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date"), diff --git a/erpnext/hr/doctype/employment_type/employment_type.py b/erpnext/hr/doctype/employment_type/employment_type.py index fb306b65d28..00aa6bb9bc4 100644 --- a/erpnext/hr/doctype/employment_type/employment_type.py +++ b/erpnext/hr/doctype/employment_type/employment_type.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class EmploymentType(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/hr/doctype/employment_type/test_employment_type.js b/erpnext/hr/doctype/employment_type/test_employment_type.js index 9835aabd481..fd7c6a1ce33 100644 --- a/erpnext/hr/doctype/employment_type/test_employment_type.js +++ b/erpnext/hr/doctype/employment_type/test_employment_type.js @@ -19,4 +19,4 @@ QUnit.test("Test: Employment type [HR]", function (assert) { 'name of employment type correctly saved'), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/employment_type/test_employment_type.py b/erpnext/hr/doctype/employment_type/test_employment_type.py index e138136605c..0297ffa01a3 100644 --- a/erpnext/hr/doctype/employment_type/test_employment_type.py +++ b/erpnext/hr/doctype/employment_type/test_employment_type.py @@ -4,4 +4,4 @@ from __future__ import unicode_literals import frappe -test_records = frappe.get_test_records('Employment Type') \ No newline at end of file +test_records = frappe.get_test_records('Employment Type') diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 629341ff2a5..3c4c672816c 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -442,4 +442,4 @@ frappe.ui.form.on("Expense Taxes and Charges", { tax_amount: function(frm, cdt, cdn) { frm.trigger("calculate_total_tax", cdt, cdn); } -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 5010fc3f75c..95e2806aedc 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -6,7 +6,7 @@ import frappe, erpnext from frappe import _ from frappe.utils import get_fullname, flt, cstr, get_link_to_form from frappe.model.document import Document -from erpnext.hr.utils import set_employee_name, share_doc_with_approver +from erpnext.hr.utils import set_employee_name, share_doc_with_approver, validate_active_employee from erpnext.accounts.party import get_party_account from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account @@ -23,6 +23,7 @@ class ExpenseClaim(AccountsController): 'make_payment_via_journal_entry') def validate(self): + validate_active_employee(self.employee) self.validate_advances() self.validate_sanctioned_amount() self.calculate_total_amount() @@ -35,8 +36,8 @@ class ExpenseClaim(AccountsController): if self.task and not self.project: self.project = frappe.db.get_value("Task", self.task, "project") - def set_status(self): - self.status = { + def set_status(self, update=False): + status = { "0": "Draft", "1": "Submitted", "2": "Cancelled" @@ -44,14 +45,18 @@ class ExpenseClaim(AccountsController): paid_amount = flt(self.total_amount_reimbursed) + flt(self.total_advance_amount) precision = self.precision("grand_total") - if (self.is_paid or (flt(self.total_sanctioned_amount) > 0 - and flt(self.grand_total, precision) == flt(paid_amount, precision))) \ - and self.docstatus == 1 and self.approval_status == 'Approved': - self.status = "Paid" + if (self.is_paid or (flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1 + and flt(self.grand_total, precision) == flt(paid_amount, precision))) and self.approval_status == 'Approved': + status = "Paid" elif flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1 and self.approval_status == 'Approved': - self.status = "Unpaid" + status = "Unpaid" elif self.docstatus == 1 and self.approval_status == 'Rejected': - self.status = 'Rejected' + status = 'Rejected' + + if update: + self.db_set("status", status) + else: + self.status = status def on_update(self): share_doc_with_approver(self, self.expense_approver) @@ -74,7 +79,7 @@ class ExpenseClaim(AccountsController): if self.is_paid: update_reimbursed_amount(self) - self.set_status() + self.set_status(update=True) self.update_claimed_amount_in_employee_advance() def on_cancel(self): @@ -86,7 +91,6 @@ class ExpenseClaim(AccountsController): if self.is_paid: update_reimbursed_amount(self) - self.set_status() self.update_claimed_amount_in_employee_advance() def update_claimed_amount_in_employee_advance(self): diff --git a/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py b/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py index 7de8f4fc13a..fe973507019 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py @@ -17,4 +17,4 @@ def get_data(): 'items': ['Employee Advance'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.js b/erpnext/hr/doctype/expense_claim/test_expense_claim.js index d0c43d3be47..2529faec983 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.js @@ -42,4 +42,3 @@ QUnit.test("Test: Expense Claim [HR]", function (assert) { () => done() ]); }); - diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 96ea686706c..c2bd1e9f9f1 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -72,7 +72,7 @@ class TestExpenseClaim(unittest.TestCase): def test_expense_claim_gl_entry(self): payable_account = get_payable_account(company_name) taxes = generate_taxes() - expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4", + expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4", do_not_submit=True, taxes=taxes) expense_claim.submit() diff --git a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.py b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.py index 8bfa1ade072..5d48990c5ce 100644 --- a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.py +++ b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class ExpenseClaimDetail(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py index 2595506486d..a637a540213 100644 --- a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py +++ b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py @@ -25,4 +25,4 @@ class ExpenseClaimType(Document): """Error when Company of Ledger account doesn't match with Company Selected""" if frappe.db.get_value("Account", entry.default_account, "company") != entry.company: frappe.throw(_("Account {0} does not match with Company {1}" - ).format(entry.default_account, entry.company)) \ No newline at end of file + ).format(entry.default_account, entry.company)) diff --git a/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js b/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js index 62234e08a04..3c9ed35313d 100644 --- a/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js +++ b/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js @@ -27,4 +27,3 @@ QUnit.test("Test: Expense Claim Type [HR]", function (assert) { () => done() ]); }); - diff --git a/erpnext/hr/doctype/holiday/holiday.py b/erpnext/hr/doctype/holiday/holiday.py index aabab0b0d35..78a95b9b741 100644 --- a/erpnext/hr/doctype/holiday/holiday.py +++ b/erpnext/hr/doctype/holiday/holiday.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class Holiday(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py b/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py index 22e1de0c342..05641c7dc26 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py +++ b/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py @@ -18,4 +18,4 @@ def get_data(): 'items': ['Service Level', 'Service Level Agreement'] } ] - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/holiday_list/test_holiday_list.js b/erpnext/hr/doctype/holiday_list/test_holiday_list.js index bfcafa9460c..ce766143a62 100644 --- a/erpnext/hr/doctype/holiday_list/test_holiday_list.js +++ b/erpnext/hr/doctype/holiday_list/test_holiday_list.js @@ -39,4 +39,4 @@ QUnit.test("Test: Holiday list [HR]", function (assert) { }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.js b/erpnext/hr/doctype/hr_settings/hr_settings.js index fd082fda09b..ec99472d9bc 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.js +++ b/erpnext/hr/doctype/hr_settings/hr_settings.js @@ -5,4 +5,4 @@ frappe.ui.form.on('HR Settings', { restrict_backdated_leave_application: function(frm) { frm.toggle_reqd("role_allowed_to_create_backdated_leave_application", frm.doc.restrict_backdated_leave_application); } -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.py b/erpnext/hr/doctype/hr_settings/hr_settings.py index ced98fb9a58..c99df269cc9 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.py +++ b/erpnext/hr/doctype/hr_settings/hr_settings.py @@ -15,4 +15,3 @@ class HRSettings(Document): from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series set_by_naming_series("Employee", "employee_number", self.get("emp_created_by")=="Naming Series", hide_name_field=True) - diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.js b/erpnext/hr/doctype/job_applicant/job_applicant.js index c62515597ce..7658bc93539 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.js +++ b/erpnext/hr/doctype/job_applicant/job_applicant.js @@ -38,4 +38,4 @@ frappe.ui.form.on("Job Applicant", { }); } -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py index 0594ba395ba..14aeb03a87e 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.py +++ b/erpnext/hr/doctype/job_applicant/job_applicant.py @@ -50,4 +50,3 @@ class JobApplicant(Document): if names: frappe.throw(_("Email Address must be unique, already exists for {0}").format(comma_and(names)), frappe.DuplicateEntryError) - diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py index 7f131151e18..ed97978a8ad 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py +++ b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py @@ -12,4 +12,4 @@ def get_data(): 'items': ['Job Offer'] }, ], - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/job_applicant/test_job_applicant.js b/erpnext/hr/doctype/job_applicant/test_job_applicant.js index b5391c8bf36..741a182addc 100644 --- a/erpnext/hr/doctype/job_applicant/test_job_applicant.js +++ b/erpnext/hr/doctype/job_applicant/test_job_applicant.js @@ -26,4 +26,3 @@ QUnit.test("Test: Job Opening [HR]", function (assert) { () => done() ]); }); - diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.js b/erpnext/hr/doctype/job_offer/test_job_offer.js index c9d7d2bef79..5339b9c3d67 100644 --- a/erpnext/hr/doctype/job_offer/test_job_offer.js +++ b/erpnext/hr/doctype/job_offer/test_job_offer.js @@ -48,4 +48,4 @@ QUnit.test("Test: Job Offer [HR]", function (assert) { () => frappe.timeout(2), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.py b/erpnext/hr/doctype/job_offer/test_job_offer.py index b3e1dc8d87b..edb21321fcc 100644 --- a/erpnext/hr/doctype/job_offer/test_job_offer.py +++ b/erpnext/hr/doctype/job_offer/test_job_offer.py @@ -79,4 +79,4 @@ def create_staffing_plan(**args): }) staffing_plan.insert() staffing_plan.submit() - return staffing_plan \ No newline at end of file + return staffing_plan diff --git a/erpnext/hr/doctype/job_opening/job_opening_dashboard.py b/erpnext/hr/doctype/job_opening/job_opening_dashboard.py index c0890b4f57c..31ef33ef2ce 100644 --- a/erpnext/hr/doctype/job_opening/job_opening_dashboard.py +++ b/erpnext/hr/doctype/job_opening/job_opening_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Job Applicant'] } ], - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/job_opening/templates/job_opening_row.html b/erpnext/hr/doctype/job_opening/templates/job_opening_row.html index c015101600a..69bf49bef77 100644 --- a/erpnext/hr/doctype/job_opening/templates/job_opening_row.html +++ b/erpnext/hr/doctype/job_opening/templates/job_opening_row.html @@ -1,16 +1,16 @@

{{ doc.job_title }}

{{ doc.description }}

- {%- if doc.publish_salary_range -%} + {%- if doc.publish_salary_range -%}

{{_("Salary range per month")}}: {{ frappe.format_value(frappe.utils.flt(doc.lower_range), currency=doc.currency) }} - {{ frappe.format_value(frappe.utils.flt(doc.upper_range), currency=doc.currency) }}

{% endif %}
{%- if doc.job_application_route -%} - {{ _("Apply Now") }} {% else %} - {{ _("Apply Now") }} {% endif %} diff --git a/erpnext/hr/doctype/job_opening/test_job_opening.js b/erpnext/hr/doctype/job_opening/test_job_opening.js index b9e6c0a8b2d..cc2f027e85b 100644 --- a/erpnext/hr/doctype/job_opening/test_job_opening.js +++ b/erpnext/hr/doctype/job_opening/test_job_opening.js @@ -24,4 +24,3 @@ QUnit.test("Test: Job Opening [HR]", function (assert) { () => done() ]); }); - diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index e9e129cdd24..d94764104d0 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -100,4 +100,4 @@ frappe.ui.form.on("Leave Allocation", { frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated)); } } -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py b/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py index 7456aebb457..7a063d92eac 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py @@ -17,4 +17,4 @@ def get_data(): 'items': ['Employee Leave Balance'] } ] - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.js b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.js index 0ef78f2f883..d5364fc8b2e 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.js @@ -38,4 +38,4 @@ QUnit.test("Test: Leave allocation [HR]", function (assert) { "total leave calculation is correctly set"), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index cee6f374fdc..93fb19f4a19 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, get_fullname, add_days, nowdate -from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver +from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver, validate_active_employee from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange @@ -22,6 +22,7 @@ class LeaveApplication(Document): return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type) def validate(self): + validate_active_employee(self.employee) set_employee_name(self) self.validate_dates() self.validate_balance_leaves() diff --git a/erpnext/hr/doctype/leave_application/leave_application_calendar.js b/erpnext/hr/doctype/leave_application/leave_application_calendar.js index 0286f300646..31faadb1079 100644 --- a/erpnext/hr/doctype/leave_application/leave_application_calendar.js +++ b/erpnext/hr/doctype/leave_application/leave_application_calendar.js @@ -17,4 +17,4 @@ frappe.views.calendar["Leave Application"] = { } }, get_events_method: "erpnext.hr.doctype.leave_application.leave_application.get_events" -} \ No newline at end of file +} diff --git a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py index c1d6a6665b6..c45717f5870 100644 --- a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py +++ b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py @@ -17,4 +17,4 @@ def get_data(): 'items': ['Employee Leave Balance'] } ] - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/leave_application/leave_application_email_template.html b/erpnext/hr/doctype/leave_application/leave_application_email_template.html index 209302e8f30..14ca41bebca 100644 --- a/erpnext/hr/doctype/leave_application/leave_application_email_template.html +++ b/erpnext/hr/doctype/leave_application/leave_application_email_template.html @@ -21,5 +21,5 @@ Status {{status}} - + diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.js b/erpnext/hr/doctype/leave_application/test_leave_application.js index 6d7b6a70588..0866b0b6d2a 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.js +++ b/erpnext/hr/doctype/leave_application/test_leave_application.js @@ -39,4 +39,4 @@ QUnit.test("Test: Leave application [HR]", function (assert) { // "leave for correct employee is submitted"), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py b/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py index 2aa54984ec5..45aa4915bc6 100644 --- a/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py +++ b/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py @@ -8,4 +8,4 @@ def get_data(): 'items': ['Department'] } ] - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/leave_block_list/test_leave_block_list.js b/erpnext/hr/doctype/leave_block_list/test_leave_block_list.js index 453787865c7..b39601b490d 100644 --- a/erpnext/hr/doctype/leave_block_list/test_leave_block_list.js +++ b/erpnext/hr/doctype/leave_block_list/test_leave_block_list.js @@ -24,4 +24,4 @@ QUnit.test("Test: Leave block list [HR]", function (assert) { 'name of blocked leave list correctly saved'), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/leave_block_list_allow/leave_block_list_allow.py b/erpnext/hr/doctype/leave_block_list_allow/leave_block_list_allow.py index be06b768bf7..8e5a09e01ec 100644 --- a/erpnext/hr/doctype/leave_block_list_allow/leave_block_list_allow.py +++ b/erpnext/hr/doctype/leave_block_list_allow/leave_block_list_allow.py @@ -9,4 +9,4 @@ import frappe from frappe.model.document import Document class LeaveBlockListAllow(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/hr/doctype/leave_block_list_date/leave_block_list_date.py b/erpnext/hr/doctype/leave_block_list_date/leave_block_list_date.py index f4028f54eba..54978a1e83a 100644 --- a/erpnext/hr/doctype/leave_block_list_date/leave_block_list_date.py +++ b/erpnext/hr/doctype/leave_block_list_date/leave_block_list_date.py @@ -9,4 +9,4 @@ import frappe from frappe.model.document import Document class LeaveBlockListDate(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.js b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.js index b60e225a727..4a450807ccf 100644 --- a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.js +++ b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.js @@ -21,4 +21,4 @@ frappe.ui.form.on("Leave Control Panel", { }); } } -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js b/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js index 2b5cec1c1e1..9d373277175 100644 --- a/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js +++ b/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js @@ -47,4 +47,4 @@ QUnit.test("Test: Leave control panel [HR]", function (assert) { }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index e041b7fb8f8..d136210a043 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -7,7 +7,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import getdate, nowdate, flt -from erpnext.hr.utils import set_employee_name +from erpnext.hr.utils import set_employee_name, validate_active_employee from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves @@ -15,6 +15,7 @@ from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leav class LeaveEncashment(Document): def validate(self): set_employee_name(self) + validate_active_employee(self.employee) self.get_leave_details_for_encashment() self.validate_salary_structure() @@ -133,4 +134,4 @@ def create_leave_encashment(leave_allocation): leave_type=allocation.leave_type, encashment_date=allocation.to_date )) - leave_encashment.insert(ignore_permissions=True) \ No newline at end of file + leave_encashment.insert(ignore_permissions=True) diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index cf130361810..33a6243e609 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -185,4 +185,4 @@ def expire_carried_forward_allocation(allocation): from_date=allocation.to_date, to_date=allocation.to_date ) - create_leave_ledger_entry(allocation, args) \ No newline at end of file + create_leave_ledger_entry(allocation, args) diff --git a/erpnext/hr/doctype/leave_period/leave_period_dashboard.py b/erpnext/hr/doctype/leave_period/leave_period_dashboard.py index 1572de3cb72..7c2c9632d85 100644 --- a/erpnext/hr/doctype/leave_period/leave_period_dashboard.py +++ b/erpnext/hr/doctype/leave_period/leave_period_dashboard.py @@ -10,4 +10,4 @@ def get_data(): 'items': ['Leave Allocation'] } ] - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.py b/erpnext/hr/doctype/leave_period/test_leave_period.py index b5857bcd8fe..cbb34371fc9 100644 --- a/erpnext/hr/doctype/leave_period/test_leave_period.py +++ b/erpnext/hr/doctype/leave_period/test_leave_period.py @@ -27,4 +27,4 @@ def create_leave_period(from_date, to_date, company=None): "to_date": to_date, "is_active": 1 }).insert() - return leave_period \ No newline at end of file + return leave_period diff --git a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py index ff7f0422e03..474f3a77ad0 100644 --- a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py +++ b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py @@ -10,4 +10,4 @@ def get_data(): 'items': ['Leave Policy Assignment', 'Leave Allocation'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/leave_policy/test_leave_policy.py b/erpnext/hr/doctype/leave_policy/test_leave_policy.py index fc868ea15a6..af7567b5bc7 100644 --- a/erpnext/hr/doctype/leave_policy/test_leave_policy.py +++ b/erpnext/hr/doctype/leave_policy/test_leave_policy.py @@ -28,4 +28,4 @@ def create_leave_policy(**args): "leave_type": args.leave_type or "_Test Leave Type", "annual_allocation": args.annual_allocation or 10 }] - }) \ No newline at end of file + }) diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py index 4bb0535cf8c..a2f7f5866b7 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py @@ -10,4 +10,4 @@ def get_data(): 'items': ['Leave Allocation'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js index 8fe4b8f8efa..8b954c46a10 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js @@ -105,4 +105,4 @@ frappe.listview_settings['Leave Policy Assignment'] = { }); } } -}; \ No newline at end of file +}; diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py index 9a14e3588d0..0089804f512 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py +++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py @@ -99,5 +99,3 @@ class TestLeavePolicyAssignment(unittest.TestCase): def tearDown(self): for doctype in ["Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]: frappe.db.sql("delete from `tab{0}`".format(doctype)) #nosec - - diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index fc577ef1d3d..8f2ae6eb15d 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -214,7 +214,7 @@ "icon": "fa fa-flag", "idx": 1, "links": [], - "modified": "2021-03-02 11:22:33.776320", + "modified": "2021-08-12 16:10:36.464690", "modified_by": "Administrator", "module": "HR", "name": "Leave Type", @@ -248,5 +248,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_type/leave_type_dashboard.py b/erpnext/hr/doctype/leave_type/leave_type_dashboard.py index 5cae9a8809c..c8944fcb9e2 100644 --- a/erpnext/hr/doctype/leave_type/leave_type_dashboard.py +++ b/erpnext/hr/doctype/leave_type/leave_type_dashboard.py @@ -11,4 +11,4 @@ def get_data(): 'items': ['Attendance', 'Leave Encashment'] } ] - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/leave_type/test_leave_type.js b/erpnext/hr/doctype/leave_type/test_leave_type.js index d939a248102..db910cde512 100644 --- a/erpnext/hr/doctype/leave_type/test_leave_type.js +++ b/erpnext/hr/doctype/leave_type/test_leave_type.js @@ -19,4 +19,4 @@ QUnit.test("Test: Leave type [HR]", function (assert) { 'leave type correctly saved'), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/leave_type/test_leave_type.py b/erpnext/hr/doctype/leave_type/test_leave_type.py index 7fef2975c8a..048dddd3ef9 100644 --- a/erpnext/hr/doctype/leave_type/test_leave_type.py +++ b/erpnext/hr/doctype/leave_type/test_leave_type.py @@ -28,4 +28,4 @@ def create_leave_type(**args): if leave_type.is_ppl: leave_type.fraction_of_daily_salary_per_leave = args.fraction_of_daily_salary_per_leave or 0.5 - return leave_type \ No newline at end of file + return leave_type diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index ab65260c091..89ae4d535d4 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -9,10 +9,12 @@ from frappe.model.document import Document from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, now_datetime, nowdate from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday +from erpnext.hr.utils import validate_active_employee from datetime import timedelta, datetime class ShiftAssignment(Document): def validate(self): + validate_active_employee(self.employee) self.validate_overlapping_dates() if self.end_date and self.end_date <= self.start_date: diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js b/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js index bb692e1402e..5d2360f10fa 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js @@ -10,4 +10,4 @@ frappe.views.calendar["Shift Assignment"] = { "allDay": "allDay", }, get_events_method: "erpnext.hr.doctype.shift_assignment.shift_assignment.get_events" -} \ No newline at end of file +} diff --git a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py index 4c3c1ed579e..07d92fe61d6 100644 --- a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py @@ -77,4 +77,4 @@ class TestShiftAssignment(unittest.TestCase): "status": 'Active' }) - self.assertRaises(frappe.ValidationError, shift_assignment_3.save) \ No newline at end of file + self.assertRaises(frappe.ValidationError, shift_assignment_3.save) diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py index 177c45edc65..2731da125a8 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.py +++ b/erpnext/hr/doctype/shift_request/shift_request.py @@ -7,12 +7,13 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import formatdate, getdate -from erpnext.hr.utils import share_doc_with_approver +from erpnext.hr.utils import share_doc_with_approver, validate_active_employee class OverlapError(frappe.ValidationError): pass class ShiftRequest(Document): def validate(self): + validate_active_employee(self.employee) self.validate_dates() self.validate_shift_request_overlap_dates() self.validate_approver() @@ -93,4 +94,4 @@ class ShiftRequest(Document): msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee, d['shift_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \ + """ {0}""".format(d["name"]) - frappe.throw(msg, OverlapError) \ No newline at end of file + frappe.throw(msg, OverlapError) diff --git a/erpnext/hr/doctype/shift_request/shift_request_dashboard.py b/erpnext/hr/doctype/shift_request/shift_request_dashboard.py index e3bf5df9490..f70b61a20a6 100644 --- a/erpnext/hr/doctype/shift_request/shift_request_dashboard.py +++ b/erpnext/hr/doctype/shift_request/shift_request_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Shift Assignment'] }, ], - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.py b/erpnext/hr/doctype/shift_request/test_shift_request.py index 9c0d8e31985..60b7676e251 100644 --- a/erpnext/hr/doctype/shift_request/test_shift_request.py +++ b/erpnext/hr/doctype/shift_request/test_shift_request.py @@ -15,24 +15,35 @@ class TestShiftRequest(unittest.TestCase): for doctype in ["Shift Request", "Shift Assignment"]: frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype)) + def tearDown(self): + frappe.db.rollback() + def test_make_shift_request(self): + "Test creation/updation of Shift Assignment from Shift Request." department = frappe.get_value("Employee", "_T-Employee-00001", 'department') set_shift_approver(department) approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0] shift_request = make_shift_request(approver) - shift_assignments = frappe.db.sql(''' - SELECT shift_request, employee - FROM `tabShift Assignment` - WHERE shift_request = '{0}' - '''.format(shift_request.name), as_dict=1) - for d in shift_assignments: - employee = d.get('employee') - self.assertEqual(shift_request.employee, employee) - shift_request.cancel() - shift_assignment_doc = frappe.get_doc("Shift Assignment", {"shift_request": d.get('shift_request')}) - self.assertEqual(shift_assignment_doc.docstatus, 2) + # Only one shift assignment is created against a shift request + shift_assignment = frappe.db.get_value( + "Shift Assignment", + filters={"shift_request": shift_request.name}, + fieldname=["employee", "docstatus"], + as_dict=True + ) + self.assertEqual(shift_request.employee, shift_assignment.employee) + self.assertEqual(shift_assignment.docstatus, 1) + + shift_request.cancel() + + shift_assignment_docstatus = frappe.db.get_value( + "Shift Assignment", + filters={"shift_request": shift_request.name}, + fieldname="docstatus" + ) + self.assertEqual(shift_assignment_docstatus, 2) def test_shift_request_approver_perms(self): employee = frappe.get_doc("Employee", "_T-Employee-00001") @@ -95,4 +106,4 @@ def make_shift_request(approver, do_not_submit=0): return shift_request shift_request.submit() - return shift_request \ No newline at end of file + return shift_request diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.js b/erpnext/hr/doctype/staffing_plan/staffing_plan.js index 04af2323c72..597ecceaa0f 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan.js +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.js @@ -103,4 +103,4 @@ var set_total_estimated_budget = function(frm) { }) frm.set_value('total_estimated_budget', estimated_budget); } -} \ No newline at end of file +} diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py b/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py index 35a303f0fb2..8e89d53c8e0 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Job Opening'] } ], - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py index 628255b11f5..1c6218e9a70 100644 --- a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py +++ b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py @@ -94,4 +94,4 @@ def make_company(): company.parent_company = "_Test Company 3" company.default_currency = "INR" company.country = "Pakistan" - company.insert() \ No newline at end of file + company.insert() diff --git a/erpnext/hr/doctype/training_event/test_training_event.py b/erpnext/hr/doctype/training_event/test_training_event.py index 313f90eba85..6a275b330c0 100644 --- a/erpnext/hr/doctype/training_event/test_training_event.py +++ b/erpnext/hr/doctype/training_event/test_training_event.py @@ -11,21 +11,34 @@ from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_ class TestTrainingEvent(unittest.TestCase): def setUp(self): create_training_program("Basic Training") - self.employee = make_employee("robert_loan@trainig.com") - self.employee2 = make_employee("suzie.tan@trainig.com") + employee = make_employee("robert_loan@trainig.com") + employee2 = make_employee("suzie.tan@trainig.com") + self.attendees = [ + {"employee": employee}, + {"employee": employee2} + ] + + def test_training_event_status_update(self): + training_event = create_training_event(self.attendees) + training_event.submit() + + training_event.event_status = "Completed" + training_event.save() + training_event.reload() + + for entry in training_event.employees: + self.assertEqual(entry.status, "Completed") + + training_event.event_status = "Scheduled" + training_event.save() + training_event.reload() + + for entry in training_event.employees: + self.assertEqual(entry.status, "Open") + + def tearDown(self): + frappe.db.rollback() - def test_create_training_event(self): - if not frappe.db.get_value("Training Event", "Basic Training Event"): - frappe.get_doc({ - "doctype": "Training Event", - "event_name": "Basic Training Event", - "training_program": "Basic Training", - "location": "Union Square", - "start_time": add_days(today(), 5), - "end_time": add_days(today(), 6), - "introduction": "Welcome to the Basic Training Event", - "employees": get_attendees(self.employee, self.employee2) - }).insert() def create_training_program(training_program): if not frappe.db.get_value("Training Program", training_program): @@ -35,8 +48,14 @@ def create_training_program(training_program): "description": training_program }).insert() -def get_attendees(employee, employee2): - return [ - {"employee": employee}, - {"employee": employee2} - ] \ No newline at end of file +def create_training_event(attendees): + return frappe.get_doc({ + "doctype": "Training Event", + "event_name": "Basic Training Event", + "training_program": "Basic Training", + "location": "Union Square", + "start_time": add_days(today(), 5), + "end_time": add_days(today(), 6), + "introduction": "Welcome to the Basic Training Event", + "employees": attendees + }).insert() diff --git a/erpnext/hr/doctype/training_event/tests/test_training_event.js b/erpnext/hr/doctype/training_event/tests/test_training_event.js index 8ff4fecd6ee..08031a1963e 100644 --- a/erpnext/hr/doctype/training_event/tests/test_training_event.js +++ b/erpnext/hr/doctype/training_event/tests/test_training_event.js @@ -56,4 +56,4 @@ QUnit.test("Test: Training Event [HR]", function (assert) { () => frappe.timeout(2), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/training_event/training_event.js b/erpnext/hr/doctype/training_event/training_event.js index d5f6e5f573e..642e6a1fd75 100644 --- a/erpnext/hr/doctype/training_event/training_event.js +++ b/erpnext/hr/doctype/training_event/training_event.js @@ -46,4 +46,3 @@ frappe.ui.form.on("Training Event Employee", { frm.events.set_employee_query(frm); } }); - diff --git a/erpnext/hr/doctype/training_event/training_event.py b/erpnext/hr/doctype/training_event/training_event.py index 5064f033081..e2c30cb3145 100644 --- a/erpnext/hr/doctype/training_event/training_event.py +++ b/erpnext/hr/doctype/training_event/training_event.py @@ -14,10 +14,25 @@ class TrainingEvent(Document): self.set_employee_emails() self.validate_period() + def on_update_after_submit(self): + self.set_status_for_attendees() + def set_employee_emails(self): self.employee_emails = ', '.join(get_employee_emails([d.employee for d in self.employees])) def validate_period(self): if time_diff_in_seconds(self.end_time, self.start_time) <= 0: - frappe.throw(_('End time cannot be before start time')) \ No newline at end of file + frappe.throw(_('End time cannot be before start time')) + + def set_status_for_attendees(self): + if self.event_status == 'Completed': + for employee in self.employees: + if employee.attendance == 'Present' and employee.status != 'Feedback Submitted': + employee.status = 'Completed' + + elif self.event_status == 'Scheduled': + for employee in self.employees: + employee.status = 'Open' + + self.db_update_all() diff --git a/erpnext/hr/doctype/training_event/training_event_dashboard.py b/erpnext/hr/doctype/training_event/training_event_dashboard.py index 1c1645c766f..19afd8dd6e1 100644 --- a/erpnext/hr/doctype/training_event/training_event_dashboard.py +++ b/erpnext/hr/doctype/training_event/training_event_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Training Result', 'Training Feedback'] }, ], - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/training_feedback/test_training_feedback.js b/erpnext/hr/doctype/training_feedback/test_training_feedback.js index 9daa51f9275..5c825aea7fb 100644 --- a/erpnext/hr/doctype/training_feedback/test_training_feedback.js +++ b/erpnext/hr/doctype/training_feedback/test_training_feedback.js @@ -49,4 +49,3 @@ QUnit.test("Test: Training Feedback [HR]", function (assert) { () => done() ]); }); - diff --git a/erpnext/hr/doctype/training_feedback/test_training_feedback.py b/erpnext/hr/doctype/training_feedback/test_training_feedback.py index 34559982a24..4c0c18029d0 100644 --- a/erpnext/hr/doctype/training_feedback/test_training_feedback.py +++ b/erpnext/hr/doctype/training_feedback/test_training_feedback.py @@ -5,8 +5,63 @@ from __future__ import unicode_literals import frappe import unittest - -# test_records = frappe.get_test_records('Training Feedback') - +from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_employee +from erpnext.hr.doctype.training_event.test_training_event import create_training_program, create_training_event class TestTrainingFeedback(unittest.TestCase): - pass + def setUp(self): + create_training_program("Basic Training") + self.employee = make_employee("robert_loan@trainig.com") + self.employee2 = make_employee("suzie.tan@trainig.com") + self.attendees = [{"employee": self.employee}] + + def test_employee_validations_for_feedback(self): + training_event = create_training_event(self.attendees) + training_event.submit() + + training_event.event_status = "Completed" + training_event.save() + training_event.reload() + + # should not allow creating feedback since employee2 was not part of the event + feedback = create_training_feedback(training_event.name, self.employee2) + self.assertRaises(frappe.ValidationError, feedback.save) + + # cannot record feedback for absent employee + employee = frappe.db.get_value("Training Event Employee", { + "parent": training_event.name, + "employee": self.employee + }, "name") + + frappe.db.set_value("Training Event Employee", employee, "attendance", "Absent") + feedback = create_training_feedback(training_event.name, self.employee) + self.assertRaises(frappe.ValidationError, feedback.save) + + def test_training_feedback_status(self): + training_event = create_training_event(self.attendees) + training_event.submit() + + training_event.event_status = "Completed" + training_event.save() + training_event.reload() + + feedback = create_training_feedback(training_event.name, self.employee) + feedback.submit() + + status = frappe.db.get_value("Training Event Employee", { + "parent": training_event.name, + "employee": self.employee + }, "status") + + self.assertEqual(status, "Feedback Submitted") + + def tearDown(self): + frappe.db.rollback() + + +def create_training_feedback(event, employee): + return frappe.get_doc({ + "doctype": "Training Feedback", + "training_event": event, + "employee": employee, + "feedback": "Test" + }) diff --git a/erpnext/hr/doctype/training_feedback/training_feedback.js b/erpnext/hr/doctype/training_feedback/training_feedback.js index 0dea098a67e..5e875c1b434 100644 --- a/erpnext/hr/doctype/training_feedback/training_feedback.js +++ b/erpnext/hr/doctype/training_feedback/training_feedback.js @@ -7,4 +7,4 @@ frappe.ui.form.on('Training Feedback', { frm.add_fetch("training_event", "event_name", "event_name"); frm.add_fetch("training_event", "trainer_name", "trainer_name"); } -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/training_feedback/training_feedback.py b/erpnext/hr/doctype/training_feedback/training_feedback.py index 1a334507917..3d4b9b3ea96 100644 --- a/erpnext/hr/doctype/training_feedback/training_feedback.py +++ b/erpnext/hr/doctype/training_feedback/training_feedback.py @@ -11,15 +11,34 @@ class TrainingFeedback(Document): def validate(self): training_event = frappe.get_doc("Training Event", self.training_event) if training_event.docstatus != 1: - frappe.throw(_('{0} must be submitted').format(_('Training Event'))) + frappe.throw(_("{0} must be submitted").format(_("Training Event"))) + + emp_event_details = frappe.db.get_value("Training Event Employee", { + "parent": self.training_event, + "employee": self.employee + }, ["name", "attendance"], as_dict=True) + + if not emp_event_details: + frappe.throw(_("Employee {0} not found in Training Event Participants.").format( + frappe.bold(self.employee_name))) + + if emp_event_details.attendance == "Absent": + frappe.throw(_("Feedback cannot be recorded for an absent Employee.")) def on_submit(self): - training_event = frappe.get_doc("Training Event", self.training_event) - event_status = None - for e in training_event.employees: - if e.employee == self.employee: - event_status = 'Feedback Submitted' - break + employee = frappe.db.get_value("Training Event Employee", { + "parent": self.training_event, + "employee": self.employee + }) - if event_status: - frappe.db.set_value("Training Event", self.training_event, "event_status", event_status) + if employee: + frappe.db.set_value("Training Event Employee", employee, "status", "Feedback Submitted") + + def on_cancel(self): + employee = frappe.db.get_value("Training Event Employee", { + "parent": self.training_event, + "employee": self.employee + }) + + if employee: + frappe.db.set_value("Training Event Employee", employee, "status", "Completed") diff --git a/erpnext/hr/doctype/training_program/training_program.js b/erpnext/hr/doctype/training_program/training_program.js index 7d85cab59dc..a4ccf540636 100644 --- a/erpnext/hr/doctype/training_program/training_program.js +++ b/erpnext/hr/doctype/training_program/training_program.js @@ -2,4 +2,4 @@ // For license information, please see license.txt frappe.ui.form.on('Training Program', { -}); \ No newline at end of file +}); diff --git a/erpnext/hr/doctype/training_program/training_program_dashboard.py b/erpnext/hr/doctype/training_program/training_program_dashboard.py index 441a71bba77..0fc18a80298 100644 --- a/erpnext/hr/doctype/training_program/training_program_dashboard.py +++ b/erpnext/hr/doctype/training_program/training_program_dashboard.py @@ -10,4 +10,4 @@ def get_data(): 'items': ['Training Event'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/training_result/training_result.js b/erpnext/hr/doctype/training_result/training_result.js index 62ac383ab78..5cdbcad8058 100644 --- a/erpnext/hr/doctype/training_result/training_result.js +++ b/erpnext/hr/doctype/training_result/training_result.js @@ -11,7 +11,7 @@ frappe.ui.form.on('Training Result', { }, training_event: function(frm) { - if (frm.doc.training_event && !frm.doc.docstatus && !frm.doc.employees) { + if (frm.doc.training_event && !frm.doc.docstatus && !frm.doc.employees) { frappe.call({ method: "erpnext.hr.doctype.training_result.training_result.get_employees", args: { diff --git a/erpnext/hr/doctype/training_result_employee/test_training_result.js b/erpnext/hr/doctype/training_result_employee/test_training_result.js index 2ebf8962ee2..3f397508357 100644 --- a/erpnext/hr/doctype/training_result_employee/test_training_result.js +++ b/erpnext/hr/doctype/training_result_employee/test_training_result.js @@ -50,4 +50,3 @@ QUnit.test("Test: Training Result [HR]", function (assert) { () => done() ]); }); - diff --git a/erpnext/hr/doctype/travel_request/travel_request.py b/erpnext/hr/doctype/travel_request/travel_request.py index 01d3f347061..60834d3f4a6 100644 --- a/erpnext/hr/doctype/travel_request/travel_request.py +++ b/erpnext/hr/doctype/travel_request/travel_request.py @@ -5,6 +5,8 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from erpnext.hr.utils import validate_active_employee class TravelRequest(Document): - pass + def validate(self): + validate_active_employee(self.employee) diff --git a/erpnext/hr/doctype/vehicle/vehicle_dashboard.py b/erpnext/hr/doctype/vehicle/vehicle_dashboard.py index 761c70182b2..628c8972cec 100644 --- a/erpnext/hr/doctype/vehicle/vehicle_dashboard.py +++ b/erpnext/hr/doctype/vehicle/vehicle_dashboard.py @@ -17,4 +17,4 @@ def get_data(): 'items': ['Delivery Trip'] } ] - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py index ed52c4e1222..ed02120cca3 100644 --- a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py +++ b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py @@ -115,4 +115,4 @@ def make_vehicle_log(license_plate, employee_id, with_services=False): vehicle_log.save() vehicle_log.submit() - return vehicle_log \ No newline at end of file + return vehicle_log diff --git a/erpnext/hr/doctype/vehicle_log/vehicle_log.js b/erpnext/hr/doctype/vehicle_log/vehicle_log.js index 6f3a0dc40eb..14fe9a02da2 100644 --- a/erpnext/hr/doctype/vehicle_log/vehicle_log.js +++ b/erpnext/hr/doctype/vehicle_log/vehicle_log.js @@ -24,4 +24,3 @@ frappe.ui.form.on("Vehicle Log", { }); } }); - diff --git a/erpnext/hr/notification/training_feedback/training_feedback.html b/erpnext/hr/notification/training_feedback/training_feedback.html index fd8fef9e82c..b49662a6eb9 100644 --- a/erpnext/hr/notification/training_feedback/training_feedback.html +++ b/erpnext/hr/notification/training_feedback/training_feedback.html @@ -3,4 +3,4 @@

You attended training {{ frappe.utils.get_link_to_form( "Training Event", doc.training_event) }}

-

{{ _("Please share your feedback to the training by clicking on 'Training Feedback' and then 'New'") }}

\ No newline at end of file +

{{ _("Please share your feedback to the training by clicking on 'Training Feedback' and then 'New'") }}

diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.html b/erpnext/hr/notification/training_scheduled/training_scheduled.html index 374038ac202..50f6d07a470 100644 --- a/erpnext/hr/notification/training_scheduled/training_scheduled.html +++ b/erpnext/hr/notification/training_scheduled/training_scheduled.html @@ -41,4 +41,4 @@ - \ No newline at end of file + diff --git a/erpnext/hr/page/organizational_chart/__init__.py b/erpnext/hr/page/organizational_chart/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/hr/page/organizational_chart/organizational_chart.js b/erpnext/hr/page/organizational_chart/organizational_chart.js new file mode 100644 index 00000000000..b6fcc6014bd --- /dev/null +++ b/erpnext/hr/page/organizational_chart/organizational_chart.js @@ -0,0 +1,21 @@ +frappe.pages['organizational-chart'].on_page_load = function(wrapper) { + frappe.ui.make_app_page({ + parent: wrapper, + title: __('Organizational Chart'), + single_column: true + }); + + $(wrapper).bind('show', () => { + frappe.require('/assets/js/hierarchy-chart.min.js', () => { + let organizational_chart = undefined; + let method = 'erpnext.hr.page.organizational_chart.organizational_chart.get_children'; + + if (frappe.is_mobile()) { + organizational_chart = new erpnext.HierarchyChartMobile('Employee', wrapper, method); + } else { + organizational_chart = new erpnext.HierarchyChart('Employee', wrapper, method); + } + organizational_chart.show(); + }); + }); +}; diff --git a/erpnext/hr/page/organizational_chart/organizational_chart.json b/erpnext/hr/page/organizational_chart/organizational_chart.json new file mode 100644 index 00000000000..d802781320a --- /dev/null +++ b/erpnext/hr/page/organizational_chart/organizational_chart.json @@ -0,0 +1,26 @@ +{ + "content": null, + "creation": "2021-05-25 10:53:10.107241", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2021-05-25 10:53:18.201931", + "modified_by": "Administrator", + "module": "HR", + "name": "organizational-chart", + "owner": "Administrator", + "page_name": "Organizational Chart", + "roles": [ + { + "role": "HR User" + }, + { + "role": "HR Manager" + } + ], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0, + "title": "Organizational Chart" +} \ No newline at end of file diff --git a/erpnext/hr/page/organizational_chart/organizational_chart.py b/erpnext/hr/page/organizational_chart/organizational_chart.py new file mode 100644 index 00000000000..4423d29e402 --- /dev/null +++ b/erpnext/hr/page/organizational_chart/organizational_chart.py @@ -0,0 +1,48 @@ +from __future__ import unicode_literals +import frappe + +@frappe.whitelist() +def get_children(parent=None, company=None, exclude_node=None): + filters = [['status', '!=', 'Left']] + if company and company != 'All Companies': + filters.append(['company', '=', company]) + + if parent and company and parent != company: + filters.append(['reports_to', '=', parent]) + else: + filters.append(['reports_to', '=', '']) + + if exclude_node: + filters.append(['name', '!=', exclude_node]) + + employees = frappe.get_list('Employee', + fields=['employee_name as name', 'name as id', 'reports_to', 'image', 'designation as title'], + filters=filters, + order_by='name' + ) + + for employee in employees: + is_expandable = frappe.db.count('Employee', filters={'reports_to': employee.get('id')}) + employee.connections = get_connections(employee.id) + employee.expandable = 1 if is_expandable else 0 + + return employees + + +def get_connections(employee): + num_connections = 0 + + nodes_to_expand = frappe.get_list('Employee', filters=[ + ['reports_to', '=', employee] + ]) + num_connections += len(nodes_to_expand) + + while nodes_to_expand: + parent = nodes_to_expand.pop(0) + descendants = frappe.get_list('Employee', filters=[ + ['reports_to', '=', parent.name] + ]) + num_connections += len(descendants) + nodes_to_expand.extend(descendants) + + return num_connections diff --git a/erpnext/hr/page/team_updates/team_updates.py b/erpnext/hr/page/team_updates/team_updates.py index a6cf935985b..58cdc4b7e1d 100644 --- a/erpnext/hr/page/team_updates/team_updates.py +++ b/erpnext/hr/page/team_updates/team_updates.py @@ -17,4 +17,4 @@ def get_data(start=0): if d.text_content: d.content = frappe.utils.md_to_html(EmailReplyParser.parse_reply(d.text_content)) - return data \ No newline at end of file + return data diff --git a/erpnext/hr/print_format/job_offer/job_offer.json b/erpnext/hr/print_format/job_offer/job_offer.json index 3fc2bcf7d27..0a922306b46 100644 --- a/erpnext/hr/print_format/job_offer/job_offer.json +++ b/erpnext/hr/print_format/job_offer/job_offer.json @@ -15,7 +15,7 @@ "name": "Job Offer", "owner": "Administrator", "print_format_builder": 0, - "print_format_type": "Server", + "print_format_type": "Jinja", "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/hr/print_format/standard_appointment_letter/standard_appointment_letter.html b/erpnext/hr/print_format/standard_appointment_letter/standard_appointment_letter.html index d60582e1a18..87daafcaaee 100644 --- a/erpnext/hr/print_format/standard_appointment_letter/standard_appointment_letter.html +++ b/erpnext/hr/print_format/standard_appointment_letter/standard_appointment_letter.html @@ -35,4 +35,4 @@
________________
{{ doc.applicant_name }} -
\ No newline at end of file +
diff --git a/erpnext/hr/report/daily_work_summary_replies/daily_work_summary_replies.py b/erpnext/hr/report/daily_work_summary_replies/daily_work_summary_replies.py index aa8eea5d74e..d8691b4d025 100644 --- a/erpnext/hr/report/daily_work_summary_replies/daily_work_summary_replies.py +++ b/erpnext/hr/report/daily_work_summary_replies/daily_work_summary_replies.py @@ -54,4 +54,4 @@ def get_data(filters): user_name = frappe.get_value('User', user, 'full_name') count = len([d for d in replies if d.sender == user]) data.append([user_name, count, total]) - return data \ No newline at end of file + return data diff --git a/erpnext/hr/report/employee_advance_summary/employee_advance_summary.js b/erpnext/hr/report/employee_advance_summary/employee_advance_summary.js index 528ae4cea63..8de4af5d4fc 100644 --- a/erpnext/hr/report/employee_advance_summary/employee_advance_summary.js +++ b/erpnext/hr/report/employee_advance_summary/employee_advance_summary.js @@ -38,4 +38,3 @@ frappe.query_reports["Employee Advance Summary"] = { } ] }; - diff --git a/erpnext/hr/report/employee_analytics/employee_analytics.py b/erpnext/hr/report/employee_analytics/employee_analytics.py index 8f393889269..fe77b6abc96 100644 --- a/erpnext/hr/report/employee_analytics/employee_analytics.py +++ b/erpnext/hr/report/employee_analytics/employee_analytics.py @@ -81,4 +81,3 @@ def get_chart_data(parameters,employees, filters): } chart["type"] = "donut" return chart - diff --git a/erpnext/hr/report/employee_birthday/employee_birthday.js b/erpnext/hr/report/employee_birthday/employee_birthday.js index 60b69b409a0..bbe4a8d179e 100644 --- a/erpnext/hr/report/employee_birthday/employee_birthday.js +++ b/erpnext/hr/report/employee_birthday/employee_birthday.js @@ -8,7 +8,7 @@ frappe.query_reports["Employee Birthday"] = { "label": __("Month"), "fieldtype": "Select", "options": "Jan\nFeb\nMar\nApr\nMay\nJun\nJul\nAug\nSep\nOct\nNov\nDec", - "default": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", + "default": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][frappe.datetime.str_to_obj(frappe.datetime.get_today()).getMonth()], }, { @@ -19,4 +19,4 @@ frappe.query_reports["Employee Birthday"] = { "default": frappe.defaults.get_user_default("Company") } ] -} \ No newline at end of file +} diff --git a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py index 92715d34453..9dd7bcd8dd9 100644 --- a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py +++ b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py @@ -72,4 +72,4 @@ def get_data(filters, leave_types): data.append(row) - return data \ No newline at end of file + return data diff --git a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.js b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.js index 9620f520002..51dc7ff85b8 100644 --- a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.js +++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.js @@ -20,4 +20,4 @@ frappe.query_reports["Recruitment Analytics"] = { "reqd": 1, }, ] -}; \ No newline at end of file +}; diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js index 879acd18ef4..2d0aa0f36d3 100644 --- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js +++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js @@ -49,4 +49,3 @@ frappe.query_reports["Vehicle Expenses"] = { } ] }; - diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index ebb17343471..3022ec022bd 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -3,13 +3,12 @@ import erpnext import frappe -from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee +from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee, InactiveEmployeeStatusError from frappe import _ from frappe.desk.form import assign_to from frappe.model.document import Document from frappe.utils import (add_days, cstr, flt, format_datetime, formatdate, - get_datetime, getdate, nowdate, today, unique) - + get_datetime, getdate, nowdate, today, unique, get_link_to_form) class DuplicateDeclarationError(frappe.ValidationError): pass @@ -20,6 +19,7 @@ class EmployeeBoardingController(Document): Assign to the concerned person and roles as per the onboarding/separation template ''' def validate(self): + validate_active_employee(self.employee) # remove the task if linked before submitting the form if self.amended_from: for activity in self.activities: @@ -522,3 +522,8 @@ def share_doc_with_approver(doc, user): approver = approvers.get(doc.doctype) if doc_before_save.get(approver) != doc.get(approver): frappe.share.remove(doc.doctype, doc.name, doc_before_save.get(approver)) + +def validate_active_employee(employee): + if frappe.db.get_value("Employee", employee, "status") == "Inactive": + frappe.throw(_("Transactions cannot be created for an Inactive Employee {0}.").format( + get_link_to_form("Employee", employee)), InactiveEmployeeStatusError) diff --git a/erpnext/hr/web_form/job_application/job_application.js b/erpnext/hr/web_form/job_application/job_application.js index 699703c5792..ffc5e984253 100644 --- a/erpnext/hr/web_form/job_application/job_application.js +++ b/erpnext/hr/web_form/job_application/job_application.js @@ -1,3 +1,3 @@ frappe.ready(function() { // bind events here -}) \ No newline at end of file +}) diff --git a/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.js b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.js index cf75cc8e41a..58179416b1a 100644 --- a/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.js +++ b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.js @@ -11,4 +11,4 @@ frappe.dashboards.chart_sources["Top 10 Pledged Loan Securities"] = { default: frappe.defaults.get_user_default("Company") } ] -}; \ No newline at end of file +}; diff --git a/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py index 6bb04401bed..6ce2a54b190 100644 --- a/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py +++ b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py @@ -73,4 +73,4 @@ def get_data(chart_name = None, chart = None, no_cache = None, filters = None, f 'chartType': 'bar', 'values': values }] - } \ No newline at end of file + } diff --git a/erpnext/loan_management/doctype/loan/loan_dashboard.py b/erpnext/loan_management/doctype/loan/loan_dashboard.py index 7a8190f7450..711a7829baf 100644 --- a/erpnext/loan_management/doctype/loan/loan_dashboard.py +++ b/erpnext/loan_management/doctype/loan/loan_dashboard.py @@ -16,4 +16,4 @@ def get_data(): 'items': ['Loan Repayment', 'Loan Interest Accrual', 'Loan Write Off', 'Loan Security Unpledge'] } ] - } \ No newline at end of file + } diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 314f58dd15e..122d7236051 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -988,4 +988,4 @@ def create_demand_loan(applicant, loan_type, loan_application, posting_date=None loan.save() - return loan \ No newline at end of file + return loan diff --git a/erpnext/loan_management/doctype/loan_application/loan_application_dashboard.py b/erpnext/loan_management/doctype/loan_application/loan_application_dashboard.py index bf3f58b83ef..3975adf4431 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application_dashboard.py +++ b/erpnext/loan_management/doctype/loan_application/loan_application_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Loan', 'Loan Security Pledge'] }, ], - } \ No newline at end of file + } diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py index f341e81065f..f113c10ef71 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py @@ -203,5 +203,3 @@ def get_disbursal_amount(loan, on_current_security_price=0): disbursal_amount = loan_details.loan_amount - loan_details.disbursed_amount return disbursal_amount - - diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 7978350adf8..d75213ce78d 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -247,4 +247,3 @@ def get_per_day_interest(principal_amount, rate_of_interest, posting_date=None): posting_date = getdate() return flt((principal_amount * rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100)) - diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index b8b1a40b5fd..57aec2e5c9c 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -455,6 +455,3 @@ def calculate_amounts(against_loan, posting_date, payment_type=''): amounts['payable_amount'] = amounts['payable_principal_amount'] + amounts['interest_amount'] return amounts - - - diff --git a/erpnext/loan_management/doctype/loan_security/loan_security_dashboard.py b/erpnext/loan_management/doctype/loan_security/loan_security_dashboard.py index 878b3fd051e..3eec5660ac1 100644 --- a/erpnext/loan_management/doctype/loan_security/loan_security_dashboard.py +++ b/erpnext/loan_management/doctype/loan_security/loan_security_dashboard.py @@ -12,4 +12,4 @@ def get_data(): 'items': ['Loan Security Pledge', 'Loan Security Unpledge'] } ] - } \ No newline at end of file + } diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.js b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.js index 11c932ff1c1..48ca392edf7 100644 --- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.js +++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.js @@ -40,4 +40,4 @@ frappe.ui.form.on("Pledge", { qty: function(frm, cdt, cdn) { frm.events.calculate_amounts(frm, cdt, cdn); }, -}); \ No newline at end of file +}); diff --git a/erpnext/loan_management/doctype/loan_security_price/loan_security_price.py b/erpnext/loan_management/doctype/loan_security_price/loan_security_price.py index 32d81afed5d..9fc1fda53f4 100644 --- a/erpnext/loan_management/doctype/loan_security_price/loan_security_price.py +++ b/erpnext/loan_management/doctype/loan_security_price/loan_security_price.py @@ -40,12 +40,3 @@ def get_loan_security_price(loan_security, valid_time=None): frappe.throw(_("No valid Loan Security Price found for {0}").format(frappe.bold(loan_security))) else: return loan_security_price - - - - - - - - - diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py index 8233b7b297a..cd7694b7b17 100644 --- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py +++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py @@ -122,4 +122,3 @@ def update_pending_shortfall(shortfall): "shortfall_amount": 0, "shortfall_percentage": 0 }) - diff --git a/erpnext/loan_management/doctype/loan_security_type/loan_security_type_dashboard.py b/erpnext/loan_management/doctype/loan_security_type/loan_security_type_dashboard.py index ac33589b549..17de8c1da4d 100644 --- a/erpnext/loan_management/doctype/loan_security_type/loan_security_type_dashboard.py +++ b/erpnext/loan_management/doctype/loan_security_type/loan_security_type_dashboard.py @@ -12,4 +12,4 @@ def get_data(): 'items': ['Loan Security Pledge', 'Loan Security Unpledge'] } ] - } \ No newline at end of file + } diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py index b24dc2f7c28..4f936dd7c11 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py +++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py @@ -147,8 +147,3 @@ def get_pledged_security_qty(loan): current_pledges[security] -= unpledges.get(security, 0.0) return current_pledges - - - - - diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.py b/erpnext/loan_management/doctype/loan_type/loan_type.py index 208cb19c88e..50ef930dbbe 100644 --- a/erpnext/loan_management/doctype/loan_type/loan_type.py +++ b/erpnext/loan_management/doctype/loan_type/loan_type.py @@ -21,4 +21,3 @@ class LoanType(Document): if self.get('loan_account') == self.get('payment_account'): frappe.throw(_('Loan Account and Payment Account cannot be same')) - diff --git a/erpnext/loan_management/doctype/loan_type/loan_type_dashboard.py b/erpnext/loan_management/doctype/loan_type/loan_type_dashboard.py index 58c668948c2..95d97fdf9b0 100644 --- a/erpnext/loan_management/doctype/loan_type/loan_type_dashboard.py +++ b/erpnext/loan_management/doctype/loan_type/loan_type_dashboard.py @@ -12,4 +12,4 @@ def get_data(): 'items': ['Loan Application'] } ] - } \ No newline at end of file + } diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py index 54a3f2cbb1f..676df701cc3 100644 --- a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py +++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py @@ -84,5 +84,3 @@ class LoanWriteOff(AccountsController): ) make_gl_entries(gl_entries, cancel=cancel, merge_entries=False) - - diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py index 11333dc2aaf..8c67c0affee 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py @@ -61,4 +61,3 @@ def term_loan_accrual_pending(date): }) return pending_accrual - diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual_dashboard.py b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual_dashboard.py index 243a7a3ba66..e104c6646b0 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual_dashboard.py +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Loan Interest Accrual'] } ] - } \ No newline at end of file + } diff --git a/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall_dashboard.py b/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall_dashboard.py index dc9bd81a1dd..e67e4d4738f 100644 --- a/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall_dashboard.py +++ b/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Loan Security Shortfall'] } ] - } \ No newline at end of file + } diff --git a/erpnext/loan_management/loan_common.js b/erpnext/loan_management/loan_common.js index 50b68da30e3..43980ffef48 100644 --- a/erpnext/loan_management/loan_common.js +++ b/erpnext/loan_management/loan_common.js @@ -40,4 +40,4 @@ frappe.ui.form.on(cur_frm.doctype, { frm.set_value("applicant_name", null); } } -}); \ No newline at end of file +}); diff --git a/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py index 0ccd149e5fb..f2cbbb469f0 100644 --- a/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py +++ b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py @@ -136,4 +136,4 @@ def get_applicant_wise_total_loan_security_qty(filters, loan_security_details): total_value_map[security.applicant] += current_pledges.get((security.applicant, security.loan_security)) \ * loan_security_details.get(security.loan_security, {}).get('latest_price', 0) - return current_pledges, total_value_map, applicant_type_map \ No newline at end of file + return current_pledges, total_value_map, applicant_type_map diff --git a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py index 2a74a1eb858..a505e72c4d9 100644 --- a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py +++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py @@ -182,4 +182,4 @@ def get_loan_wise_security_value(filters, current_pledges): loan_wise_security_value[key[0]] += \ flt(qty * loan_security_details.get(key[1], {}).get('latest_price', 0)) - return loan_wise_security_value \ No newline at end of file + return loan_wise_security_value diff --git a/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py b/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py index c6f6b990cc5..65910770881 100644 --- a/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py +++ b/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py @@ -126,4 +126,4 @@ def get_data(filters): data.append(row) - return data \ No newline at end of file + return data diff --git a/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py index 887a86a46c5..34bbe5a4503 100644 --- a/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py +++ b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py @@ -79,6 +79,3 @@ def get_company_wise_loan_security_details(filters, loan_security_details): total_portfolio_value += flt(qty * loan_security_details.get(key[1], {}).get('latest_price', 0)) return security_wise_map, total_portfolio_value - - - diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index 44712d543b7..0868187e09e 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -69,10 +69,10 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ if (flag) { this.frm.add_custom_button(__('Maintenance Visit'), function () { let options = ""; - + me.frm.call('get_pending_data', {data_type: "items"}).then(r => { options = r.message; - + let schedule_id = ""; let d = new frappe.ui.Dialog({ title: __("Enter Visit Details"), @@ -86,7 +86,7 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ let field = d.get_field("scheduled_date"); me.frm.call('get_pending_data', { - item_name: this.value, + item_name: this.value, data_type: "date" }).then(r => { field.df.options = r.message; @@ -161,10 +161,9 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ let me = this; if (item.start_date && item.periodicity) { me.frm.call('validate_end_date_visits'); - + } }, }); $.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceSchedule({ frm: cur_frm })); - diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index d6e42f3ee1c..97289032d70 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -52,15 +52,15 @@ class MaintenanceSchedule(TransactionBase): item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]) else: item.end_date = add_days(item.start_date, days_in_period[item.periodicity]) - + diff = date_diff(item.end_date, item.start_date) + 1 no_of_visits = cint(diff / days_in_period[item.periodicity]) - + if not item.no_of_visits or item.no_of_visits == 0: item.end_date = add_days(item.start_date, days_in_period[item.periodicity]) diff = date_diff(item.end_date, item.start_date) + 1 item.no_of_visits = cint(diff / days_in_period[item.periodicity]) - + elif item.no_of_visits > no_of_visits: item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]) @@ -207,7 +207,7 @@ class MaintenanceSchedule(TransactionBase): def on_update(self): frappe.db.set(self, 'status', 'Draft') - + def update_amc_date(self, serial_nos, amc_expiry_date=None): for serial_no in serial_nos: serial_no_doc = frappe.get_doc("Serial No", serial_no) @@ -300,7 +300,7 @@ class MaintenanceSchedule(TransactionBase): for schedule in self.schedules: if schedule.item_name == item_name and s_date == formatdate(schedule.scheduled_date, "dd-mm-yyyy"): return schedule.name - + @frappe.whitelist() def update_serial_nos(s_id): serial_nos = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'serial_no') @@ -318,12 +318,12 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No target.maintenance_type = "Scheduled" target.maintenance_schedule = source.name target.maintenance_schedule_detail = s_id - + def update_sales(source, target, parent): sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person') target.service_person = sales_person target.serial_no = '' - + doclist = get_mapped_doc("Maintenance Schedule", source_name, { "Maintenance Schedule": { "doctype": "Maintenance Visit", diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 09981bad05f..c733dd0c92c 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -22,7 +22,7 @@ class TestMaintenanceSchedule(unittest.TestCase): ms.cancel() events_after_cancel = get_events(ms) self.assertTrue(len(events_after_cancel) == 0) - + def test_make_schedule(self): ms = make_maintenance_schedule() ms.save() @@ -72,7 +72,7 @@ class TestMaintenanceSchedule(unittest.TestCase): #checks if visit status is back updated in schedule self.assertTrue(ms.schedules[1].completion_status, "Partially Completed") - + def get_events(ms): return frappe.get_all("Event Participants", filters={ "reference_doctype": ms.doctype, diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index d6105c657ef..8e488c1ce12 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -122,4 +122,4 @@ erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({ }, }); -$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceVisit({ frm: cur_frm })); \ No newline at end of file +$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceVisit({ frm: cur_frm })); diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index 7fffc942a03..d63c7003870 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -28,11 +28,11 @@ class MaintenanceVisit(TransactionBase): def validate(self): self.validate_serial_no() self.validate_maintenance_date() - + def update_completion_status(self): if self.maintenance_schedule_detail: frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'completion_status', self.completion_status) - + def update_actual_date(self): if self.maintenance_schedule_detail: frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'actual_date', self.mntc_date) diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js index f19a1b08681..d3bb33e86e0 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js @@ -85,5 +85,3 @@ frappe.ui.form.on('Blanket Order', { frm.trigger('set_tc_name_filter'); } }); - - diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py index d7556add805..1aedb1e590f 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py @@ -76,4 +76,4 @@ def make_order(source_name): "postprocess": update_item } }) - return target_doc \ No newline at end of file + return target_doc diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py index 3171defdaea..9a0a72fb475 100644 --- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py @@ -88,4 +88,4 @@ def make_blanket_order(**args): bo.insert() bo.submit() - return bo \ No newline at end of file + return bo diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 15a7c316c91..04aa8a43da5 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -83,7 +83,7 @@ frappe.ui.form.on("BOM", { if (!frm.doc.__islocal && frm.doc.docstatus<2) { frm.add_custom_button(__("Update Cost"), function() { - frm.events.update_cost(frm); + frm.events.update_cost(frm, true); }); frm.add_custom_button(__("Browse BOM"), function() { frappe.route_options = { @@ -235,7 +235,7 @@ frappe.ui.form.on("BOM", { reqd: 1, }, { - fieldname: "varint_item_code", + fieldname: "variant_item_code", options: "Item", label: __("Variant Item"), fieldtype: "Link", @@ -287,7 +287,7 @@ frappe.ui.form.on("BOM", { let variant_items = data.items || []; variant_items.forEach(d => { - if (!d.varint_item_code) { + if (!d.variant_item_code) { frappe.throw(__("Select variant item code for the template item {0}", [d.item_code])); } }) @@ -299,7 +299,7 @@ frappe.ui.form.on("BOM", { has_template_rm.forEach(d => { dialog.fields_dict.items.df.data.push({ "item_code": d.item_code, - "varint_item_code": "", + "variant_item_code": "", "qty": d.qty, "source_warehouse": d.source_warehouse, "operation": d.operation @@ -318,14 +318,15 @@ frappe.ui.form.on("BOM", { }) }, - update_cost: function(frm) { + update_cost: function(frm, save_doc=false) { return frappe.call({ doc: frm.doc, method: "update_cost", freeze: true, args: { update_parent: true, - from_child_bom:false + save: save_doc, + from_child_bom: false }, callback: function(r) { refresh_field("items"); @@ -653,4 +654,4 @@ frappe.ui.form.on("BOM", "with_operations", function(frm) { if(!cint(frm.doc.with_operations)) { frm.set_value("operations", []); } -}); \ No newline at end of file +}); diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index c32a8a95a17..0ba85078ead 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -330,7 +330,7 @@ class BOM(WebsiteGenerator): frappe.get_doc("BOM", bom).update_cost(from_child_bom=True) if not from_child_bom: - frappe.msgprint(_("Cost Updated")) + frappe.msgprint(_("Cost Updated"), alert=True) def update_parent_cost(self): if self.total_cost: @@ -713,12 +713,12 @@ def get_bom_item_rate(args, bom_doc): "conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function "conversion_factor": args.get("conversion_factor") or 1, "plc_conversion_rate": 1, - "ignore_party": True + "ignore_party": True, + "ignore_conversion_rate": True }) item_doc = frappe.get_cached_doc("Item", args.get("item_code")) - out = frappe._dict() - get_price_list_rate(bom_args, item_doc, out) - rate = out.price_list_rate + price_list_data = get_price_list_rate(bom_args, item_doc) + rate = price_list_data.price_list_rate return rate @@ -747,7 +747,7 @@ def get_valuation_rate(args): if valuation_rate <= 0: last_valuation_rate = frappe.db.sql("""select valuation_rate from `tabStock Ledger Entry` - where item_code = %s and valuation_rate > 0 + where item_code = %s and valuation_rate > 0 and is_cancelled = 0 order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code']) valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0 @@ -773,7 +773,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite item.image, bom.project, bom_item.rate, - bom_item.amount, + sum(bom_item.{qty_field}/ifnull(bom.quantity, 1)) * bom_item.rate * %(qty)s as amount, item.stock_uom, item.item_group, item.allow_alternative_item, @@ -1068,13 +1068,6 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): if barcodes: or_cond_filters["name"] = ("in", barcodes) - for cond in get_match_cond(doctype, as_condition=False): - for key, value in cond.items(): - if key == doctype: - key = "name" - - query_filters[key] = ("in", value) - if filters and filters.get("item_code"): has_variants = frappe.get_cached_value("Item", filters.get("item_code"), "has_variants") if not has_variants: @@ -1083,7 +1076,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): if filters and filters.get("is_stock_item"): query_filters["is_stock_item"] = 1 - return frappe.get_all("Item", + return frappe.get_list("Item", fields = fields, filters=query_filters, or_filters = or_cond_filters, order_by=order_by, limit_start=start, limit_page_length=page_len, as_list=1) diff --git a/erpnext/manufacturing/doctype/bom/bom_item_preview.html b/erpnext/manufacturing/doctype/bom/bom_item_preview.html index 6088e46265b..e614a7ebaa1 100644 --- a/erpnext/manufacturing/doctype/bom/bom_item_preview.html +++ b/erpnext/manufacturing/doctype/bom/bom_item_preview.html @@ -38,4 +38,4 @@ {{ __("Open Item {0}", [data.item_code.bold()]) }} {% endif %}

-
\ No newline at end of file + diff --git a/erpnext/manufacturing/doctype/bom/bom_tree.js b/erpnext/manufacturing/doctype/bom/bom_tree.js index 60fb377f476..6e2599e41bc 100644 --- a/erpnext/manufacturing/doctype/bom/bom_tree.js +++ b/erpnext/manufacturing/doctype/bom/bom_tree.js @@ -70,4 +70,4 @@ frappe.treeview_settings["BOM"] = { } }, view_template: 'bom_item_preview' -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/bom/test_bom.js b/erpnext/manufacturing/doctype/bom/test_bom.js index 5044a284444..98a9198b79b 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.js +++ b/erpnext/manufacturing/doctype/bom/test_bom.js @@ -60,4 +60,4 @@ QUnit.test("test: item", function (assert) { () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.py b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.py index cc5a3f8cb1a..39ccbddbea2 100644 --- a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.py +++ b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class BOMExplosionItem(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.py b/erpnext/manufacturing/doctype/bom_item/bom_item.py index e7cdea290b8..220c73e1493 100644 --- a/erpnext/manufacturing/doctype/bom_item/bom_item.py +++ b/erpnext/manufacturing/doctype/bom_item/bom_item.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class BOMItem(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.py b/erpnext/manufacturing/doctype/bom_operation/bom_operation.py index ee3f877da3c..e3501eb9cf6 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.py +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class BOMOperation(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.js b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.js index e4b8a202882..bf5fe2e18de 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.js +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.js @@ -46,4 +46,4 @@ frappe.ui.form.on('BOM Update Tool', { } }); } -}); \ No newline at end of file +}); diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 81860c9fbcf..91eb4a0fa90 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -367,4 +367,4 @@ frappe.ui.form.on('Job Card Time Log', { to_time: function(frm) { frm.set_value('started_time', ''); } -}) \ No newline at end of file +}) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 420bb008039..66e2394b847 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -192,11 +192,11 @@ class JobCard(Document): "completed_qty": args.get("completed_qty") or 0.0 }) elif args.get("start_time"): - new_args = { + new_args = frappe._dict({ "from_time": get_datetime(args.get("start_time")), "operation": args.get("sub_operation"), "completed_qty": 0.0 - } + }) if employees: for name in employees: @@ -608,6 +608,11 @@ def make_stock_entry(source_name, target_doc=None): target.set_missing_values() target.set_stock_entry_type() + wo_allows_alternate_item = frappe.db.get_value("Work Order", target.work_order, "allow_alternative_item") + for item in target.items: + item.allow_alternative_item = int(wo_allows_alternate_item and + frappe.get_cached_value("Item", item.item_code, "allow_alternative_item")) + doclist = get_mapped_doc("Job Card", source_name, { "Job Card": { "doctype": "Stock Entry", @@ -698,4 +703,4 @@ def make_corrective_job_card(source_name, operation=None, for_operation=None, ta } }, target_doc, set_missing_values) - return doclist \ No newline at end of file + return doclist diff --git a/erpnext/manufacturing/doctype/job_card/job_card_list.js b/erpnext/manufacturing/doctype/job_card/job_card_list.js index ed851ebc83b..8017209e7de 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card_list.js +++ b/erpnext/manufacturing/doctype/job_card/job_card_list.js @@ -12,4 +12,4 @@ frappe.listview_settings['Job Card'] = { return [__("Open"), "red", "status,=,Open"]; } } -}; \ No newline at end of file +}; diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index b6a6c33d37f..8fa0b27fcb8 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -72,4 +72,4 @@ class TestJobCard(unittest.TestCase): doc.cancel() for d in job_cards: - frappe.delete_doc("Job Card", d.name) \ No newline at end of file + frappe.delete_doc("Job Card", d.name) diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.js b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.js index 668e981d188..a0122a47385 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.js +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.js @@ -30,4 +30,4 @@ frappe.tour["Manufacturing Settings"] = [ title: __("Update BOM Cost Automatically"), description: __("If ticked, the BOM cost will be automatically updated based on Valuation Rate / Price List Rate / last purchase rate of raw materials.") } -]; \ No newline at end of file +]; diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py index e88164f9178..149fe3e22b8 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py @@ -20,4 +20,4 @@ def is_material_consumption_enabled(): frappe.local.material_consumption = cint(frappe.db.get_single_value('Manufacturing Settings', 'material_consumption')) - return frappe.local.material_consumption \ No newline at end of file + return frappe.local.material_consumption diff --git a/erpnext/manufacturing/doctype/operation/operation.js b/erpnext/manufacturing/doctype/operation/operation.js index 102b6780e5f..2936e33b118 100644 --- a/erpnext/manufacturing/doctype/operation/operation.js +++ b/erpnext/manufacturing/doctype/operation/operation.js @@ -11,4 +11,4 @@ frappe.ui.form.on('Operation', { }; }); } -}); \ No newline at end of file +}); diff --git a/erpnext/manufacturing/doctype/operation/test_operation.py b/erpnext/manufacturing/doctype/operation/test_operation.py index 00672317018..8e7e7237263 100644 --- a/erpnext/manufacturing/doctype/operation/test_operation.py +++ b/erpnext/manufacturing/doctype/operation/test_operation.py @@ -28,4 +28,4 @@ def make_operation(*args, **kwargs): return doc except frappe.DuplicateEntryError: - return frappe.get_doc("Operation", args.operation) \ No newline at end of file + return frappe.get_doc("Operation", args.operation) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 38a0ee77ad7..b4c663507ce 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -109,6 +109,15 @@ class ProductionPlan(Document): so_mr_list = [d.get(field) for d in self.get(table) if d.get(field)] return so_mr_list + def get_bom_item(self): + """Check if Item or if its Template has a BOM.""" + bom_item = None + has_bom = frappe.db.exists({'doctype': 'BOM', 'item': self.item_code, 'docstatus': 1}) + if not has_bom: + template_item = frappe.db.get_value('Item', self.item_code, ['variant_of']) + bom_item = "bom.item = {0}".format(frappe.db.escape(template_item)) if template_item else bom_item + return bom_item + def get_so_items(self): # Check for empty table or empty rows if not self.get("sales_orders") or not self.get_so_mr_list("sales_order", "sales_orders"): @@ -117,16 +126,26 @@ class ProductionPlan(Document): so_list = self.get_so_mr_list("sales_order", "sales_orders") item_condition = "" - if self.item_code: + bom_item = "bom.item = so_item.item_code" + if self.item_code and frappe.db.exists('Item', self.item_code): + bom_item = self.get_bom_item() or bom_item item_condition = ' and so_item.item_code = {0}'.format(frappe.db.escape(self.item_code)) - items = frappe.db.sql("""select distinct parent, item_code, warehouse, - (qty - work_order_qty) * conversion_factor as pending_qty, description, name - from `tabSales Order Item` so_item - where parent in (%s) and docstatus = 1 and qty > work_order_qty - and exists (select name from `tabBOM` bom where bom.item=so_item.item_code - and bom.is_active = 1) %s""" % \ - (", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1) + items = frappe.db.sql(""" + select + distinct parent, item_code, warehouse, + (qty - work_order_qty) * conversion_factor as pending_qty, + description, name + from + `tabSales Order Item` so_item + where + parent in (%s) and docstatus = 1 and qty > work_order_qty + and exists (select name from `tabBOM` bom where %s + and bom.is_active = 1) %s""" % + (", ".join(["%s"] * len(so_list)), + bom_item, + item_condition), + tuple(so_list), as_dict=1) if self.item_code: item_condition = ' and so_item.item_code = {0}'.format(frappe.db.escape(self.item_code)) @@ -278,7 +297,7 @@ class ProductionPlan(Document): if self.total_produced_qty > 0: self.status = "In Process" - if self.total_produced_qty == self.total_planned_qty: + if self.total_produced_qty >= self.total_planned_qty: self.status = "Completed" if self.status != 'Completed': @@ -327,6 +346,7 @@ class ProductionPlan(Document): "production_plan" : self.name, "production_plan_item" : d.name, "product_bundle_item" : d.product_bundle_item, + "planned_start_date" : d.planned_start_date, "make_work_order_for_sub_assembly_items": d.get("make_work_order_for_sub_assembly_items", 0) } @@ -683,6 +703,7 @@ def get_material_request_items(row, sales_order, company, def get_sales_orders(self): so_filter = item_filter = "" + bom_item = "bom.item = so_item.item_code" if self.from_date: so_filter += " and so.transaction_date >= %(from_date)s" if self.to_date: @@ -694,7 +715,8 @@ def get_sales_orders(self): if self.sales_order_status: so_filter += "and so.status = %(sales_order_status)s" - if self.item_code: + if self.item_code and frappe.db.exists('Item', self.item_code): + bom_item = self.get_bom_item() or bom_item item_filter += " and so_item.item_code = %(item)s" open_so = frappe.db.sql(""" @@ -704,13 +726,13 @@ def get_sales_orders(self): and so.docstatus = 1 and so.status not in ("Stopped", "Closed") and so.company = %(company)s and so_item.qty > so_item.work_order_qty {0} {1} - and (exists (select name from `tabBOM` bom where bom.item=so_item.item_code + and (exists (select name from `tabBOM` bom where {2} and bom.is_active = 1) or exists (select name from `tabPacked Item` pi where pi.parent = so.name and pi.parent_item = so_item.item_code and exists (select name from `tabBOM` bom where bom.item=pi.item_code and bom.is_active = 1))) - """.format(so_filter, item_filter), { + """.format(so_filter, item_filter, bom_item), { "from_date": self.from_date, "to_date": self.to_date, "customer": self.customer, @@ -747,9 +769,8 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): group by item_code, warehouse """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1) -def get_warehouse_list(warehouses, warehouse_list=None): - if not warehouse_list: - warehouse_list = [] +def get_warehouse_list(warehouses): + warehouse_list = [] if isinstance(warehouses, str): warehouses = json.loads(warehouses) @@ -761,23 +782,19 @@ def get_warehouse_list(warehouses, warehouse_list=None): else: warehouse_list.append(row.get("warehouse")) + return warehouse_list + @frappe.whitelist() def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None): if isinstance(doc, str): doc = frappe._dict(json.loads(doc)) - warehouse_list = [] if warehouses: - get_warehouse_list(warehouses, warehouse_list) - - if warehouse_list: - warehouses = list(set(warehouse_list)) + warehouses = list(set(get_warehouse_list(warehouses))) if doc.get("for_warehouse") and not get_parent_warehouse_data and doc.get("for_warehouse") in warehouses: warehouses.remove(doc.get("for_warehouse")) - warehouse_list = None - doc['mr_items'] = [] po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items') diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py b/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py index ca597f63278..52a56af7bce 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py @@ -14,4 +14,4 @@ def get_data(): 'items': ['Purchase Order'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index cce1bb61b6b..a5b9ff845fc 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -5,12 +5,13 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import nowdate, now_datetime, flt +from frappe.utils import nowdate, now_datetime, flt, add_to_date from erpnext.stock.doctype.item.test_item import create_item from erpnext.manufacturing.doctype.production_plan.production_plan import get_sales_orders from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order -from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests +from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests, get_warehouse_list +from erpnext.controllers.item_variant import create_variant class TestProductionPlan(unittest.TestCase): def setUp(self): @@ -60,6 +61,21 @@ class TestProductionPlan(unittest.TestCase): pln = frappe.get_doc('Production Plan', pln.name) pln.cancel() + def test_production_plan_start_date(self): + planned_date = add_to_date(date=None, days=3) + plan = create_production_plan(item_code='Test Production Item 1', planned_start_date=planned_date) + plan.make_work_order() + + work_orders = frappe.get_all('Work Order', fields = ['name', 'planned_start_date'], + filters = {'production_plan': plan.name}) + + self.assertEqual(work_orders[0].planned_start_date, planned_date) + + for wo in work_orders: + frappe.delete_doc('Work Order', wo.name) + + frappe.get_doc('Production Plan', plan.name).cancel() + def test_production_plan_for_existing_ordered_qty(self): sr1 = create_stock_reconciliation(item_code="Raw Material Item 1", target="_Test Warehouse - _TC", qty=1, rate=110) @@ -251,6 +267,81 @@ class TestProductionPlan(unittest.TestCase): pln.cancel() frappe.delete_doc("Production Plan", pln.name) + def test_get_warehouse_list_group(self): + """Check if required warehouses are returned""" + warehouse_json = '[{\"warehouse\":\"_Test Warehouse Group - _TC\"}]' + + warehouses = set(get_warehouse_list(warehouse_json)) + expected_warehouses = {"_Test Warehouse Group-C1 - _TC", "_Test Warehouse Group-C2 - _TC"} + + missing_warehouse = expected_warehouses - warehouses + + self.assertTrue(len(missing_warehouse) == 0, + msg=f"Following warehouses were expected {', '.join(missing_warehouse)}") + + def test_get_warehouse_list_single(self): + warehouse_json = '[{\"warehouse\":\"_Test Scrap Warehouse - _TC\"}]' + + warehouses = set(get_warehouse_list(warehouse_json)) + expected_warehouses = {"_Test Scrap Warehouse - _TC", } + + self.assertEqual(warehouses, expected_warehouses) + + def test_get_sales_order_with_variant(self): + if not frappe.db.exists('Item', {"item_code": 'PIV'}): + item = create_item('PIV', valuation_rate = 100) + variant_settings = { + "attributes": [ + { + "attribute": "Colour" + }, + ], + "has_variants": 1 + } + item.update(variant_settings) + item.save() + parent_bom = make_bom(item = 'PIV', raw_materials = ['PIV']) + if not frappe.db.exists('BOM', {"item": 'PIV'}): + parent_bom = make_bom(item = 'PIV', raw_materials = ['PIV']) + else: + parent_bom = frappe.get_doc('BOM', {"item": 'PIV'}) + + if not frappe.db.exists('Item', {"item_code": 'PIV-RED'}): + variant = create_variant("PIV", {"Colour": "Red"}) + variant.save() + variant_bom = make_bom(item = variant.item_code, raw_materials = [variant.item_code]) + else: + variant = frappe.get_doc('Item', 'PIV-RED') + if not frappe.db.exists('BOM', {"item": 'PIV-RED'}): + variant_bom = make_bom(item = variant.item_code, raw_materials = [variant.item_code]) + + """Testing when item variant has a BOM""" + so = make_sales_order(item_code="PIV-RED", qty=5) + pln = frappe.new_doc('Production Plan') + pln.company = so.company + pln.get_items_from = 'Sales Order' + pln.item_code = 'PIV-RED' + pln.get_open_sales_orders() + self.assertEqual(pln.sales_orders[0].sales_order, so.name) + pln.get_so_items() + self.assertEqual(pln.po_items[0].item_code, 'PIV-RED') + self.assertEqual(pln.po_items[0].bom_no, variant_bom.name) + so.cancel() + frappe.delete_doc('Sales Order', so.name) + variant_bom.cancel() + frappe.delete_doc('BOM', variant_bom.name) + + """Testing when item variant doesn't have a BOM""" + so = make_sales_order(item_code="PIV-RED", qty=5) + pln.get_open_sales_orders() + self.assertEqual(pln.sales_orders[0].sales_order, so.name) + pln.po_items = [] + pln.get_so_items() + self.assertEqual(pln.po_items[0].item_code, 'PIV-RED') + self.assertEqual(pln.po_items[0].bom_no, parent_bom.name) + + frappe.db.rollback() + def create_production_plan(**args): args = frappe._dict(args) diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.py b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.py index 8b570422ddc..37cf5a49dc9 100644 --- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.py +++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class ProductionPlanItem(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/manufacturing/doctype/production_plan_sales_order/production_plan_sales_order.py b/erpnext/manufacturing/doctype/production_plan_sales_order/production_plan_sales_order.py index ef7f79e8d2c..99c7273a640 100644 --- a/erpnext/manufacturing/doctype/production_plan_sales_order/production_plan_sales_order.py +++ b/erpnext/manufacturing/doctype/production_plan_sales_order/production_plan_sales_order.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class ProductionPlanSalesOrder(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/manufacturing/doctype/routing/routing_dashboard.py b/erpnext/manufacturing/doctype/routing/routing_dashboard.py index ab309cc9d50..50a3fe62da5 100644 --- a/erpnext/manufacturing/doctype/routing/routing_dashboard.py +++ b/erpnext/manufacturing/doctype/routing/routing_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['BOM'] } ] - } \ No newline at end of file + } diff --git a/erpnext/manufacturing/doctype/sub_operation/sub_operation.json b/erpnext/manufacturing/doctype/sub_operation/sub_operation.json index f63d2b98641..10cee32398a 100644 --- a/erpnext/manufacturing/doctype/sub_operation/sub_operation.json +++ b/erpnext/manufacturing/doctype/sub_operation/sub_operation.json @@ -19,6 +19,7 @@ "options": "Operation" }, { + "default": "0", "description": "Time in mins", "fieldname": "time_in_mins", "fieldtype": "Float", @@ -38,7 +39,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-12-07 18:09:18.005578", + "modified": "2021-07-15 16:39:41.635362", "modified_by": "Administrator", "module": "Manufacturing", "name": "Sub Operation", diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 68de0b29d3e..bf1ccb71594 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -513,6 +513,60 @@ class TestWorkOrder(unittest.TestCase): work_order1.save() self.assertEqual(work_order1.operations[0].time_in_mins, 40.0) + def test_batch_size_for_fg_item(self): + fg_item = "Test Batch Size Item For BOM 3" + rm1 = "Test Batch Size Item RM 1 For BOM 3" + + frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 0) + for item in ["Test Batch Size Item For BOM 3", "Test Batch Size Item RM 1 For BOM 3"]: + item_args = { + "include_item_in_manufacturing": 1, + "is_stock_item": 1 + } + + if item == fg_item: + item_args['has_batch_no'] = 1 + item_args['create_new_batch'] = 1 + item_args['batch_number_series'] = 'TBSI3.#####' + + make_item(item, item_args) + + bom_name = frappe.db.get_value("BOM", + {"item": fg_item, "is_active": 1, "with_operations": 1}, "name") + + if not bom_name: + bom = make_bom(item=fg_item, rate=1000, raw_materials = [rm1], do_not_save=True) + bom.save() + bom.submit() + bom_name = bom.name + + work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1) + ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1)) + for row in ste1.get('items'): + if row.is_finished_item: + self.assertEqual(row.item_code, fg_item) + + work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1) + frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 1) + ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1)) + for row in ste1.get('items'): + if row.is_finished_item: + self.assertEqual(row.item_code, fg_item) + + work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), + qty=30, do_not_save = True) + work_order.batch_size = 10 + work_order.insert() + work_order.submit() + self.assertEqual(work_order.has_batch_no, 1) + ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 30)) + for row in ste1.get('items'): + if row.is_finished_item: + self.assertEqual(row.item_code, fg_item) + self.assertEqual(row.qty, 10) + + frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 0) + def test_partial_material_consumption(self): frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1) wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 779ae42d653..5fe9fec2af1 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -239,7 +239,7 @@ class WorkOrder(Document): self.create_serial_no_batch_no() def on_submit(self): - if not self.wip_warehouse: + if not self.wip_warehouse and not self.skip_transfer: frappe.throw(_("Work-in-Progress Warehouse is required before Submit")) if not self.fg_warehouse: frappe.throw(_("For Warehouse is required before Submit")) @@ -487,21 +487,20 @@ class WorkOrder(Document): return operations = [] - if not self.use_multi_level_bom: - bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity") - operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty)) - else: + + if self.use_multi_level_bom: bom_tree = frappe.get_doc("BOM", self.bom_no).get_tree_representation() - bom_traversal = list(reversed(bom_tree.level_order_traversal())) - bom_traversal.append(bom_tree) # add operation on top level item last + bom_traversal = reversed(bom_tree.level_order_traversal()) - for d in bom_traversal: - if d.is_bom: - operations.extend(_get_operations(d.name, qty=d.exploded_qty)) + for node in bom_traversal: + if node.is_bom: + operations.extend(_get_operations(node.name, qty=node.exploded_qty)) - for correct_index, operation in enumerate(operations, start=1): - operation.idx = correct_index + bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity") + operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty)) + for correct_index, operation in enumerate(operations, start=1): + operation.idx = correct_index self.set('operations', operations) self.calculate_time() @@ -603,7 +602,7 @@ class WorkOrder(Document): if self.docstatus==1: # calculate transferred qty based on submitted stock entries - self.update_transaferred_qty_for_required_items() + self.update_transferred_qty_for_required_items() # update in bin self.update_reserved_qty_for_production() @@ -656,7 +655,7 @@ class WorkOrder(Document): for item in sorted(item_dict.values(), key=lambda d: d['idx'] or 9999): self.append('required_items', { 'rate': item.rate, - 'amount': item.amount, + 'amount': item.rate * item.qty, 'operation': item.operation or operation, 'item_code': item.item_code, 'item_name': item.item_name, @@ -672,7 +671,7 @@ class WorkOrder(Document): self.set_available_qty() - def update_transaferred_qty_for_required_items(self): + def update_transferred_qty_for_required_items(self): '''update transferred qty from submitted stock entries for that item against the work order''' @@ -839,7 +838,7 @@ def add_variant_item(variant_items, wo_doc, bom_no, table_name="items"): for item in variant_items: args = frappe._dict({ - "item_code": item.get("varint_item_code"), + "item_code": item.get("variant_item_code"), "required_qty": item.get("qty"), "qty": item.get("qty"), # for bom "source_warehouse": item.get("source_warehouse"), @@ -860,7 +859,7 @@ def add_variant_item(variant_items, wo_doc, bom_no, table_name="items"): }, bom_doc) if not args.source_warehouse: - args["source_warehouse"] = get_item_defaults(item.get("varint_item_code"), + args["source_warehouse"] = get_item_defaults(item.get("variant_item_code"), wo_doc.company).default_warehouse args["amount"] = flt(args.get("required_qty")) * flt(args.get("rate")) diff --git a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py index 9aa0715e7ff..403d46d8d42 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py +++ b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py @@ -17,4 +17,4 @@ def get_data(): 'items': ['Serial No', 'Batch'] } ] - } \ No newline at end of file + } diff --git a/erpnext/manufacturing/doctype/work_order_item/work_order_item.py b/erpnext/manufacturing/doctype/work_order_item/work_order_item.py index d18f028fc6e..9aa53b5e3c3 100644 --- a/erpnext/manufacturing/doctype/work_order_item/work_order_item.py +++ b/erpnext/manufacturing/doctype/work_order_item/work_order_item.py @@ -10,4 +10,4 @@ class WorkOrderItem(Document): pass def on_doctype_update(): - frappe.db.add_index("Work Order Item", ["item_code", "source_warehouse"]) \ No newline at end of file + frappe.db.add_index("Work Order Item", ["item_code", "source_warehouse"]) diff --git a/erpnext/manufacturing/doctype/workstation/workstation.js b/erpnext/manufacturing/doctype/workstation/workstation.js index ba8e30cba07..d8d25fc6f83 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.js +++ b/erpnext/manufacturing/doctype/workstation/workstation.js @@ -16,4 +16,4 @@ frappe.ui.form.on("Workstation", { }) } } -}) \ No newline at end of file +}) diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py index e7d92658f7d..8778d9ba557 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py @@ -108,5 +108,3 @@ def get_columns(filters): "fieldtype": "Int", "width": 180 }] - - diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html index 119a4fc6292..2ae8848cc03 100644 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html +++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html @@ -24,4 +24,4 @@ {% } %} - \ No newline at end of file + diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py index 9f81e7d26a1..b4db98c3d7e 100644 --- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py @@ -124,4 +124,4 @@ def get_columns(filters): "fieldname": "total_time_in_mins", "width": "100" } - ] \ No newline at end of file + ] diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py index 093309a005b..74c794b5dd0 100644 --- a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py +++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py @@ -110,4 +110,4 @@ def get_columns(filters): "fieldtype": "Text", "width": 100 } - ] \ No newline at end of file + ] diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py index fc27d355984..9a6c764c609 100644 --- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py +++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py @@ -239,4 +239,4 @@ class ForecastingReport(ExponentialSmoothingForecast): "currency": self.company_currency, "datatype": self.fieldtype } - ] \ No newline at end of file + ] diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py index b1bff3500c6..a8939051523 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py @@ -201,4 +201,4 @@ def get_columns(filters): } ]) - return columns \ No newline at end of file + return columns diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.py b/erpnext/manufacturing/report/production_analytics/production_analytics.py index 79af8a1e39b..42c9d97cb5e 100644 --- a/erpnext/manufacturing/report/production_analytics/production_analytics.py +++ b/erpnext/manufacturing/report/production_analytics/production_analytics.py @@ -139,7 +139,3 @@ def get_chart_data(periodic_data, columns): chart["type"] = "line" return chart - - - - diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py index 6192632bda6..a12ac7f9d91 100644 --- a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py +++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py @@ -129,4 +129,4 @@ def get_columns(filters): } ]) - return columns \ No newline at end of file + return columns diff --git a/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py b/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py index 97553e699d8..599a738f6f6 100644 --- a/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py +++ b/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py @@ -10,10 +10,10 @@ def execute(filters=None): data = get_item_list(wo_list, filters) columns = get_columns() return columns, data - + def get_item_list(wo_list, filters): out = [] - + #Add a row for each item/qty for wo_details in wo_list: desc = frappe.db.get_value("BOM", wo_details.bom_no, "description") @@ -70,13 +70,13 @@ def get_item_list(wo_list, filters): out.append(row) return out - + def get_work_orders(): out = frappe.get_all("Work Order", filters={"docstatus": 1, "status": ( "!=","Completed")}, fields=["name","status", "bom_no", "qty", "produced_qty"], order_by='name') return out - + def get_columns(): columns = [{ "fieldname": "work_order", diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py index 612dad0bf51..d0766f9abe5 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py @@ -265,4 +265,4 @@ def get_columns(filters): }, ]) - return columns \ No newline at end of file + return columns diff --git a/erpnext/non_profit/doctype/chapter_member/chapter_member.py b/erpnext/non_profit/doctype/chapter_member/chapter_member.py index c4b899913b9..a1b25f2d4e2 100644 --- a/erpnext/non_profit/doctype/chapter_member/chapter_member.py +++ b/erpnext/non_profit/doctype/chapter_member/chapter_member.py @@ -7,5 +7,3 @@ from frappe.model.document import Document class ChapterMember(Document): pass - - diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py index 4fd1a30ab9e..9aa7e13433c 100644 --- a/erpnext/non_profit/doctype/donation/donation.py +++ b/erpnext/non_profit/doctype/donation/donation.py @@ -217,4 +217,3 @@ def notify_failure(log): sendmail_to_system_managers(_('[Important] [ERPNext] Razorpay donation webhook failed, please check.'), content) except Exception: pass - diff --git a/erpnext/non_profit/doctype/donation/donation_dashboard.py b/erpnext/non_profit/doctype/donation/donation_dashboard.py index 7e25c8d2173..3da89423d37 100644 --- a/erpnext/non_profit/doctype/donation/donation_dashboard.py +++ b/erpnext/non_profit/doctype/donation/donation_dashboard.py @@ -13,4 +13,4 @@ def get_data(): 'items': ['Payment Entry'] } ] - } \ No newline at end of file + } diff --git a/erpnext/non_profit/doctype/donation/test_donation.py b/erpnext/non_profit/doctype/donation/test_donation.py index bbe9bf5228d..b206f54523e 100644 --- a/erpnext/non_profit/doctype/donation/test_donation.py +++ b/erpnext/non_profit/doctype/donation/test_donation.py @@ -73,4 +73,4 @@ def create_mode_of_payment(): 'company': '_Test Company', 'default_account': 'Cash - _TC' }] - }).insert() \ No newline at end of file + }).insert() diff --git a/erpnext/non_profit/doctype/donor/donor.py b/erpnext/non_profit/doctype/donor/donor.py index fb70e59575b..ab6a197ed51 100644 --- a/erpnext/non_profit/doctype/donor/donor.py +++ b/erpnext/non_profit/doctype/donor/donor.py @@ -15,4 +15,3 @@ class Donor(Document): from frappe.utils import validate_email_address if self.email: validate_email_address(self.email.strip(), True) - diff --git a/erpnext/non_profit/doctype/grant_application/grant_application.py b/erpnext/non_profit/doctype/grant_application/grant_application.py index f0123b2e494..b810fd027af 100644 --- a/erpnext/non_profit/doctype/grant_application/grant_application.py +++ b/erpnext/non_profit/doctype/grant_application/grant_application.py @@ -55,4 +55,4 @@ def send_grant_review_emails(grant_application): grant.save() frappe.db.commit() - frappe.msgprint(_("Review Invitation Sent")) \ No newline at end of file + frappe.msgprint(_("Review Invitation Sent")) diff --git a/erpnext/non_profit/doctype/member/member.js b/erpnext/non_profit/doctype/member/member.js index 6b8f1b1deb6..e58ec0f5eea 100644 --- a/erpnext/non_profit/doctype/member/member.js +++ b/erpnext/non_profit/doctype/member/member.js @@ -61,4 +61,4 @@ frappe.ui.form.on('Member', { } }); } -}); \ No newline at end of file +}); diff --git a/erpnext/non_profit/doctype/member/member.json b/erpnext/non_profit/doctype/member/member.json index f190cfae755..7c1baf1a8d1 100644 --- a/erpnext/non_profit/doctype/member/member.json +++ b/erpnext/non_profit/doctype/member/member.json @@ -26,7 +26,7 @@ "razorpay_details_section", "subscription_id", "customer_id", - "subscription_activated", + "subscription_status", "column_break_21", "subscription_start", "subscription_end" @@ -151,12 +151,6 @@ "fieldname": "column_break_21", "fieldtype": "Column Break" }, - { - "default": "0", - "fieldname": "subscription_activated", - "fieldtype": "Check", - "label": "Subscription Activated" - }, { "fieldname": "subscription_start", "fieldtype": "Date", @@ -166,11 +160,17 @@ "fieldname": "subscription_end", "fieldtype": "Date", "label": "Subscription End" + }, + { + "fieldname": "subscription_status", + "fieldtype": "Select", + "label": "Subscription Status", + "options": "\nActive\nHalted" } ], "image_field": "image", "links": [], - "modified": "2020-11-09 12:12:10.174647", + "modified": "2021-07-11 14:27:26.368039", "modified_by": "Administrator", "module": "Non Profit", "name": "Member", diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index 30be585e9a7..67828d6efc8 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -84,7 +84,9 @@ def create_member(user_details): "email_id": user_details.email, "pan_number": user_details.pan or None, "membership_type": user_details.plan_id, - "subscription_id": user_details.subscription_id or None + "customer_id": user_details.customer_id or None, + "subscription_id": user_details.subscription_id or None, + "subscription_status": user_details.subscription_status or "" }) member.insert(ignore_permissions=True) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index e8ae6187b7e..b584116df3c 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -196,11 +196,14 @@ def make_invoice(membership, member, plan, settings): return invoice -def get_member_based_on_subscription(subscription_id, email): - members = frappe.get_all("Member", filters={ - "subscription_id": subscription_id, - "email_id": email - }, order_by="creation desc") +def get_member_based_on_subscription(subscription_id, email=None, customer_id=None): + filters = {"subscription_id": subscription_id} + if email: + filters.update({"email_id": email}) + if customer_id: + filters.update({"customer_id": customer_id}) + + members = frappe.get_all("Member", filters=filters, order_by="creation desc") try: return frappe.get_doc("Member", members[0]["name"]) @@ -209,8 +212,6 @@ def get_member_based_on_subscription(subscription_id, email): def verify_signature(data, endpoint="Membership"): - if frappe.flags.in_test or os.environ.get("CI"): - return True signature = frappe.request.headers.get("X-Razorpay-Signature") settings = frappe.get_doc("Non Profit Settings") @@ -225,16 +226,7 @@ def verify_signature(data, endpoint="Membership"): @frappe.whitelist(allow_guest=True) def trigger_razorpay_subscription(*args, **kwargs): data = frappe.request.get_data(as_text=True) - try: - verify_signature(data) - except Exception as e: - log = frappe.log_error(e, "Membership Webhook Verification Error") - notify_failure(log) - return { "status": "Failed", "reason": e} - - if isinstance(data, six.string_types): - data = json.loads(data) - data = frappe._dict(data) + data = process_request_data(data) subscription = data.payload.get("subscription", {}).get("entity", {}) subscription = frappe._dict(subscription) @@ -281,7 +273,7 @@ def trigger_razorpay_subscription(*args, **kwargs): # Update membership values member.subscription_start = datetime.fromtimestamp(subscription.start_at) member.subscription_end = datetime.fromtimestamp(subscription.end_at) - member.subscription_activated = 1 + member.subscription_status = "Active" member.flags.ignore_mandatory = True member.save() @@ -294,9 +286,67 @@ def trigger_razorpay_subscription(*args, **kwargs): message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), _("Payment ID"), payment.id) log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name)) notify_failure(log) - return { "status": "Failed", "reason": e} + return {"status": "Failed", "reason": e} - return { "status": "Success" } + return {"status": "Success"} + + +@frappe.whitelist(allow_guest=True) +def update_halted_razorpay_subscription(*args, **kwargs): + """ + When all retries have been exhausted, Razorpay moves the subscription to the halted state. + The customer has to manually retry the charge or change the card linked to the subscription, + for the subscription to move back to the active state. + """ + if frappe.request: + data = frappe.request.get_data(as_text=True) + data = process_request_data(data) + elif frappe.flags.in_test: + data = kwargs.get("data") + data = frappe._dict(data) + else: + return + + if not data.event == "subscription.halted": + return + + subscription = data.payload.get("subscription", {}).get("entity", {}) + subscription = frappe._dict(subscription) + + try: + member = get_member_based_on_subscription(subscription.id, customer_id=subscription.customer_id) + if not member: + frappe.throw(_("Member with Razorpay Subscription ID {0} not found").format(subscription.id)) + + member.subscription_status = "Halted" + member.flags.ignore_mandatory = True + member.save() + + if subscription.get("notes"): + member = get_additional_notes(member, subscription) + + except Exception as e: + message = "{0}\n\n{1}".format(e, frappe.get_traceback()) + log = frappe.log_error(message, _("Error updating halted status for member {0}").format(member.name)) + notify_failure(log) + return {"status": "Failed", "reason": e} + + return {"status": "Success"} + + +def process_request_data(data): + try: + verify_signature(data) + except Exception as e: + log = frappe.log_error(e, "Membership Webhook Verification Error") + notify_failure(log) + return {"status": "Failed", "reason": e} + + if isinstance(data, six.string_types): + data = json.loads(data) + data = frappe._dict(data) + + return data def get_company_for_memberships(): @@ -362,4 +412,4 @@ def set_expired_status(): `tabMembership` SET `status` = 'Expired' WHERE `status` not in ('Cancelled') AND `to_date` < %s - """, (nowdate())) \ No newline at end of file + """, (nowdate())) diff --git a/erpnext/non_profit/doctype/membership/test_membership.py b/erpnext/non_profit/doctype/membership/test_membership.py index 31da792e534..5ad2088fc31 100644 --- a/erpnext/non_profit/doctype/membership/test_membership.py +++ b/erpnext/non_profit/doctype/membership/test_membership.py @@ -6,6 +6,7 @@ import unittest import frappe import erpnext from erpnext.non_profit.doctype.member.member import create_member +from erpnext.non_profit.doctype.membership.membership import update_halted_razorpay_subscription from frappe.utils import nowdate, add_months class TestMembership(unittest.TestCase): @@ -13,11 +14,16 @@ class TestMembership(unittest.TestCase): plan = setup_membership() # make test member - self.member_doc = create_member(frappe._dict({ - 'fullname': "_Test_Member", - 'email': "_test_member_erpnext@example.com", - 'plan_id': plan.name - })) + self.member_doc = create_member( + frappe._dict({ + "fullname": "_Test_Member", + "email": "_test_member_erpnext@example.com", + "plan_id": plan.name, + "subscription_id": "sub_DEX6xcJ1HSW4CR", + "customer_id": "cust_C0WlbKhp3aLA7W", + "subscription_status": "Active" + }) + ) self.member_doc.make_customer_and_link() self.member = self.member_doc.name @@ -51,6 +57,20 @@ class TestMembership(unittest.TestCase): "to_date": add_months(nowdate(), 3), }) + def test_halted_memberships(self): + make_membership(self.member, { + "from_date": add_months(nowdate(), 2), + "to_date": add_months(nowdate(), 3) + }) + + self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Active") + payload = get_subscription_payload() + update_halted_razorpay_subscription(data=payload) + self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Halted") + + def tearDown(self): + frappe.db.rollback() + def set_config(key, value): frappe.db.set_value("Non Profit Settings", None, key, value) @@ -115,4 +135,28 @@ def setup_membership(): else: plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm") - return plan \ No newline at end of file + return plan + +def get_subscription_payload(): + return { + "entity": "event", + "account_id": "acc_BFQ7uQEaa7j2z7", + "event": "subscription.halted", + "contains": [ + "subscription" + ], + "payload": { + "subscription": { + "entity": { + "id": "sub_DEX6xcJ1HSW4CR", + "entity": "subscription", + "plan_id": "_rzpy_test_milythm", + "customer_id": "cust_C0WlbKhp3aLA7W", + "status": "halted", + "notes": { + "Important": "Notes for Internal Reference" + }, + } + } + } + } diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.py b/erpnext/non_profit/doctype/membership_type/membership_type.py index 022829bd3a6..c712b99c3b8 100644 --- a/erpnext/non_profit/doctype/membership_type/membership_type.py +++ b/erpnext/non_profit/doctype/membership_type/membership_type.py @@ -15,4 +15,4 @@ class MembershipType(Document): frappe.throw(_("The Linked Item should be a service item")) def get_membership_type(razorpay_id): - return frappe.db.exists("Membership Type", {"razorpay_plan_id": razorpay_id}) \ No newline at end of file + return frappe.db.exists("Membership Type", {"razorpay_plan_id": razorpay_id}) diff --git a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py index a84cc2cdb53..50c93516adc 100644 --- a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py +++ b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py @@ -35,4 +35,4 @@ class NonProfitSettings(Document): def get_plans_for_membership(*args, **kwargs): controller = get_payment_gateway_controller("Razorpay") plans = controller.get_plans() - return [plan.get("item") for plan in plans.get("items")] \ No newline at end of file + return [plan.get("item") for plan in plans.get("items")] diff --git a/erpnext/non_profit/web_form/grant_application/grant_application.js b/erpnext/non_profit/web_form/grant_application/grant_application.js index 7da3f1fb41c..f09e5409192 100644 --- a/erpnext/non_profit/web_form/grant_application/grant_application.js +++ b/erpnext/non_profit/web_form/grant_application/grant_application.js @@ -1,3 +1,3 @@ frappe.ready(function() { // bind events here -}); \ No newline at end of file +}); diff --git a/erpnext/non_profit/web_form/grant_application/grant_application.py b/erpnext/non_profit/web_form/grant_application/grant_application.py index 7666ef6b616..186722a8bf0 100644 --- a/erpnext/non_profit/web_form/grant_application/grant_application.py +++ b/erpnext/non_profit/web_form/grant_application/grant_application.py @@ -4,5 +4,3 @@ def get_context(context): context.no_cache = True context.parents = [dict(label='View All ', route='grant-application', title='View All')] - - diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 29376f00a1c..484d6e1e601 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -291,3 +291,12 @@ erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice erpnext.patches.v13_0.update_job_card_details erpnext.patches.v13_0.update_level_in_bom #1234sswef +erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry +erpnext.patches.v13_0.update_subscription_status_in_memberships +erpnext.patches.v13_0.update_amt_in_work_order_required_items +erpnext.patches.v12_0.show_einvoice_irn_cancelled_field +erpnext.patches.v13_0.delete_orphaned_tables +erpnext.patches.v13_0.update_export_type_for_gst #2021-08-16 +erpnext.patches.v13_0.update_tds_check_field #3 +erpnext.patches.v13_0.update_recipient_email_digest +erpnext.patches.v13_0.shopify_deprecation_warning \ No newline at end of file diff --git a/erpnext/patches/v10_0/migrate_daily_work_summary_settings_to_daily_work_summary_group.py b/erpnext/patches/v10_0/migrate_daily_work_summary_settings_to_daily_work_summary_group.py index 102b6da8757..daa258e8825 100644 --- a/erpnext/patches/v10_0/migrate_daily_work_summary_settings_to_daily_work_summary_group.py +++ b/erpnext/patches/v10_0/migrate_daily_work_summary_settings_to_daily_work_summary_group.py @@ -48,4 +48,4 @@ def get_previous_setting(): return obj def get_setting_companies(): - return frappe.db.sql("select * from `tabDaily Work Summary Settings Company`", as_dict=True) \ No newline at end of file + return frappe.db.sql("select * from `tabDaily Work Summary Settings Company`", as_dict=True) diff --git a/erpnext/patches/v10_0/rename_offer_letter_to_job_offer.py b/erpnext/patches/v10_0/rename_offer_letter_to_job_offer.py index 2e3095153a5..f832936b10a 100644 --- a/erpnext/patches/v10_0/rename_offer_letter_to_job_offer.py +++ b/erpnext/patches/v10_0/rename_offer_letter_to_job_offer.py @@ -7,4 +7,4 @@ def execute(): frappe.rename_doc("DocType", "Offer Letter Term", "Job Offer Term", force=True) frappe.reload_doc("hr", "doctype", "job_offer") frappe.reload_doc("hr", "doctype", "job_offer_term") - frappe.delete_doc("Print Format", "Offer Letter") \ No newline at end of file + frappe.delete_doc("Print Format", "Offer Letter") diff --git a/erpnext/patches/v10_0/rename_price_to_rate_in_pricing_rule.py b/erpnext/patches/v10_0/rename_price_to_rate_in_pricing_rule.py index 48fa22204d1..a9dd3103100 100644 --- a/erpnext/patches/v10_0/rename_price_to_rate_in_pricing_rule.py +++ b/erpnext/patches/v10_0/rename_price_to_rate_in_pricing_rule.py @@ -11,4 +11,4 @@ def execute(): except Exception as e: if e.args[0]!=1054: - raise \ No newline at end of file + raise diff --git a/erpnext/patches/v11_0/add_default_email_template_for_leave.py b/erpnext/patches/v11_0/add_default_email_template_for_leave.py index f722be26b41..0f1e4966231 100644 --- a/erpnext/patches/v11_0/add_default_email_template_for_leave.py +++ b/erpnext/patches/v11_0/add_default_email_template_for_leave.py @@ -27,4 +27,3 @@ def execute(): 'subject': _("Leave Status Notification"), 'owner': frappe.session.user, }).insert(ignore_permissions=True) - diff --git a/erpnext/patches/v11_0/add_expense_claim_default_account.py b/erpnext/patches/v11_0/add_expense_claim_default_account.py index eecf75568a4..a613bd88497 100644 --- a/erpnext/patches/v11_0/add_expense_claim_default_account.py +++ b/erpnext/patches/v11_0/add_expense_claim_default_account.py @@ -8,4 +8,4 @@ def execute(): for company in companies: if company.default_payable_account is not None: - frappe.db.set_value("Company", company.name, "default_expense_claim_payable_account", company.default_payable_account) \ No newline at end of file + frappe.db.set_value("Company", company.name, "default_expense_claim_payable_account", company.default_payable_account) diff --git a/erpnext/patches/v11_0/add_healthcare_service_unit_tree_root.py b/erpnext/patches/v11_0/add_healthcare_service_unit_tree_root.py index d956052f1a6..a45f39d4340 100644 --- a/erpnext/patches/v11_0/add_healthcare_service_unit_tree_root.py +++ b/erpnext/patches/v11_0/add_healthcare_service_unit_tree_root.py @@ -18,4 +18,3 @@ def execute(): 'is_group': 1, 'company': company }).insert(ignore_permissions=True) - diff --git a/erpnext/patches/v11_0/add_index_on_nestedset_doctypes.py b/erpnext/patches/v11_0/add_index_on_nestedset_doctypes.py index 5a30c780f8c..0243dfb38ed 100644 --- a/erpnext/patches/v11_0/add_index_on_nestedset_doctypes.py +++ b/erpnext/patches/v11_0/add_index_on_nestedset_doctypes.py @@ -8,4 +8,4 @@ def execute(): frappe.reload_doc("assets", "doctype", "Location") for dt in ("Account", "Cost Center", "File", "Employee", "Location", "Task", "Customer Group", "Sales Person", "Territory"): frappe.reload_doctype(dt) - frappe.get_doc("DocType", dt).run_module_method("on_doctype_update") \ No newline at end of file + frappe.get_doc("DocType", dt).run_module_method("on_doctype_update") diff --git a/erpnext/patches/v11_0/add_market_segments.py b/erpnext/patches/v11_0/add_market_segments.py index ed47d4293f7..a8841ef3a44 100644 --- a/erpnext/patches/v11_0/add_market_segments.py +++ b/erpnext/patches/v11_0/add_market_segments.py @@ -9,4 +9,4 @@ def execute(): frappe.local.lang = frappe.db.get_default("lang") or 'en' - add_market_segments() \ No newline at end of file + add_market_segments() diff --git a/erpnext/patches/v11_0/add_sales_stages.py b/erpnext/patches/v11_0/add_sales_stages.py index ac2ae1511ae..d06c6889ff7 100644 --- a/erpnext/patches/v11_0/add_sales_stages.py +++ b/erpnext/patches/v11_0/add_sales_stages.py @@ -8,4 +8,4 @@ def execute(): frappe.local.lang = frappe.db.get_default("lang") or 'en' - add_sale_stages() \ No newline at end of file + add_sale_stages() diff --git a/erpnext/patches/v11_0/check_buying_selling_in_currency_exchange.py b/erpnext/patches/v11_0/check_buying_selling_in_currency_exchange.py index 462f830c183..0a1a36007e5 100644 --- a/erpnext/patches/v11_0/check_buying_selling_in_currency_exchange.py +++ b/erpnext/patches/v11_0/check_buying_selling_in_currency_exchange.py @@ -3,4 +3,4 @@ import frappe def execute(): frappe.reload_doc('setup', 'doctype', 'currency_exchange') - frappe.db.sql("""update `tabCurrency Exchange` set for_buying = 1, for_selling = 1""") \ No newline at end of file + frappe.db.sql("""update `tabCurrency Exchange` set for_buying = 1, for_selling = 1""") diff --git a/erpnext/patches/v11_0/create_salary_structure_assignments.py b/erpnext/patches/v11_0/create_salary_structure_assignments.py index a908c16715a..d3ea7a3c1c0 100644 --- a/erpnext/patches/v11_0/create_salary_structure_assignments.py +++ b/erpnext/patches/v11_0/create_salary_structure_assignments.py @@ -69,4 +69,4 @@ def execute(): except DuplicateAssignment: pass - frappe.db.sql("update `tabSalary Structure` set docstatus=1") \ No newline at end of file + frappe.db.sql("update `tabSalary Structure` set docstatus=1") diff --git a/erpnext/patches/v11_0/drop_column_max_days_allowed.py b/erpnext/patches/v11_0/drop_column_max_days_allowed.py index 591c521efbe..029f75a2258 100644 --- a/erpnext/patches/v11_0/drop_column_max_days_allowed.py +++ b/erpnext/patches/v11_0/drop_column_max_days_allowed.py @@ -4,4 +4,4 @@ import frappe def execute(): if frappe.db.exists("DocType", "Leave Type"): if 'max_days_allowed' in frappe.db.get_table_columns("Leave Type"): - frappe.db.sql("alter table `tabLeave Type` drop column max_days_allowed") \ No newline at end of file + frappe.db.sql("alter table `tabLeave Type` drop column max_days_allowed") diff --git a/erpnext/patches/v11_0/ewaybill_fields_gst_india.py b/erpnext/patches/v11_0/ewaybill_fields_gst_india.py index 9925b70a963..4247c788e33 100644 --- a/erpnext/patches/v11_0/ewaybill_fields_gst_india.py +++ b/erpnext/patches/v11_0/ewaybill_fields_gst_india.py @@ -7,4 +7,4 @@ def execute(): if not company: return - make_custom_fields() \ No newline at end of file + make_custom_fields() diff --git a/erpnext/patches/v11_0/hr_ux_cleanups.py b/erpnext/patches/v11_0/hr_ux_cleanups.py index 80476c8a74c..8d187965011 100644 --- a/erpnext/patches/v11_0/hr_ux_cleanups.py +++ b/erpnext/patches/v11_0/hr_ux_cleanups.py @@ -10,4 +10,3 @@ def execute(): for holiday_list in frappe.get_all('Holiday List'): holiday_list = frappe.get_doc('Holiday List', holiday_list.name) holiday_list.db_set('total_holidays', len(holiday_list.holidays), update_modified = False) - diff --git a/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py b/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py index ee709ac2d49..dfcf5ab2886 100644 --- a/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py +++ b/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py @@ -42,4 +42,4 @@ def execute(): 'frequency_of_depreciation': asset_category_doc.frequency_of_depreciation }) - row.db_update() \ No newline at end of file + row.db_update() diff --git a/erpnext/patches/v11_0/make_location_from_warehouse.py b/erpnext/patches/v11_0/make_location_from_warehouse.py index a307e8c3659..8c92b5180d9 100644 --- a/erpnext/patches/v11_0/make_location_from_warehouse.py +++ b/erpnext/patches/v11_0/make_location_from_warehouse.py @@ -28,4 +28,3 @@ def execute(): def get_parent_warehouse_name(warehouse): return frappe.db.get_value('Warehouse', warehouse, 'warehouse_name') - \ No newline at end of file diff --git a/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py b/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py index c7c76355400..6da70b4ce38 100644 --- a/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py +++ b/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py @@ -93,4 +93,4 @@ def execute(): `expense_account`, `income_account`, `buying_cost_center`, `selling_cost_center` ) VALUES {} - '''.format(', '.join(['%s'] * len(to_insert_data))), tuple(to_insert_data)) \ No newline at end of file + '''.format(', '.join(['%s'] * len(to_insert_data))), tuple(to_insert_data)) diff --git a/erpnext/patches/v11_0/move_leave_approvers_from_employee.py b/erpnext/patches/v11_0/move_leave_approvers_from_employee.py index edab34cc58a..ef703d0ea71 100644 --- a/erpnext/patches/v11_0/move_leave_approvers_from_employee.py +++ b/erpnext/patches/v11_0/move_leave_approvers_from_employee.py @@ -31,4 +31,4 @@ def execute(): if not len(department.leave_approvers): department.append("leave_approvers",{ "approver": record.leave_approver - }).db_insert() \ No newline at end of file + }).db_insert() diff --git a/erpnext/patches/v11_0/refactor_autoname_naming.py b/erpnext/patches/v11_0/refactor_autoname_naming.py index b997ba2db22..dd5cb639b1b 100644 --- a/erpnext/patches/v11_0/refactor_autoname_naming.py +++ b/erpnext/patches/v11_0/refactor_autoname_naming.py @@ -117,4 +117,4 @@ def get_series(): def get_series_to_preserve(doctype): series_to_preserve = frappe.db.get_value('DocType', doctype, 'autoname') - return series_to_preserve \ No newline at end of file + return series_to_preserve diff --git a/erpnext/patches/v11_0/refactor_naming_series.py b/erpnext/patches/v11_0/refactor_naming_series.py index b85ab66f148..9f231edea73 100644 --- a/erpnext/patches/v11_0/refactor_naming_series.py +++ b/erpnext/patches/v11_0/refactor_naming_series.py @@ -132,4 +132,4 @@ def get_series_to_preserve(doctype): def get_default_series(doctype): field = frappe.get_meta(doctype).get_field("naming_series") default_series = field.get('default', '') if field else '' - return default_series \ No newline at end of file + return default_series diff --git a/erpnext/patches/v11_0/rename_asset_adjustment_doctype.py b/erpnext/patches/v11_0/rename_asset_adjustment_doctype.py index fad0cf7a45e..923b23048df 100644 --- a/erpnext/patches/v11_0/rename_asset_adjustment_doctype.py +++ b/erpnext/patches/v11_0/rename_asset_adjustment_doctype.py @@ -8,4 +8,4 @@ import frappe def execute(): if frappe.db.table_exists("Asset Adjustment") and not frappe.db.table_exists("Asset Value Adjustment"): frappe.rename_doc('DocType', 'Asset Adjustment', 'Asset Value Adjustment', force=True) - frappe.reload_doc('assets', 'doctype', 'asset_value_adjustment') \ No newline at end of file + frappe.reload_doc('assets', 'doctype', 'asset_value_adjustment') diff --git a/erpnext/patches/v11_0/rename_bom_wo_fields.py b/erpnext/patches/v11_0/rename_bom_wo_fields.py index 882ec84e644..0e6036b0740 100644 --- a/erpnext/patches/v11_0/rename_bom_wo_fields.py +++ b/erpnext/patches/v11_0/rename_bom_wo_fields.py @@ -30,4 +30,4 @@ def execute(): else: frappe.db.sql(""" UPDATE `tab%s` SET transfer_material_against = 'Work Order' - WHERE docstatus < 2""" % (doctype)) \ No newline at end of file + WHERE docstatus < 2""" % (doctype)) diff --git a/erpnext/patches/v11_0/rename_health_insurance.py b/erpnext/patches/v11_0/rename_health_insurance.py index e605071a297..06fc6151675 100644 --- a/erpnext/patches/v11_0/rename_health_insurance.py +++ b/erpnext/patches/v11_0/rename_health_insurance.py @@ -6,4 +6,4 @@ import frappe def execute(): frappe.rename_doc('DocType', 'Health Insurance', 'Employee Health Insurance', force=True) - frappe.reload_doc('hr', 'doctype', 'employee_health_insurance') \ No newline at end of file + frappe.reload_doc('hr', 'doctype', 'employee_health_insurance') diff --git a/erpnext/patches/v11_0/rename_overproduction_percent_field.py b/erpnext/patches/v11_0/rename_overproduction_percent_field.py index 077829f4818..fbf925d955c 100644 --- a/erpnext/patches/v11_0/rename_overproduction_percent_field.py +++ b/erpnext/patches/v11_0/rename_overproduction_percent_field.py @@ -7,4 +7,4 @@ import frappe def execute(): frappe.reload_doc('manufacturing', 'doctype', 'manufacturing_settings') - rename_field('Manufacturing Settings', 'over_production_allowance_percentage', 'overproduction_percentage_for_sales_order') \ No newline at end of file + rename_field('Manufacturing Settings', 'over_production_allowance_percentage', 'overproduction_percentage_for_sales_order') diff --git a/erpnext/patches/v11_0/renamed_from_to_fields_in_project.py b/erpnext/patches/v11_0/renamed_from_to_fields_in_project.py index 4f684400025..d5ca4cc5749 100644 --- a/erpnext/patches/v11_0/renamed_from_to_fields_in_project.py +++ b/erpnext/patches/v11_0/renamed_from_to_fields_in_project.py @@ -10,4 +10,4 @@ def execute(): if frappe.db.has_column('Project', 'from'): rename_field('Project', 'from', 'from_time') - rename_field('Project', 'to', 'to_time') \ No newline at end of file + rename_field('Project', 'to', 'to_time') diff --git a/erpnext/patches/v11_0/set_missing_gst_hsn_code.py b/erpnext/patches/v11_0/set_missing_gst_hsn_code.py index 4353ef80e24..8f8a545c410 100644 --- a/erpnext/patches/v11_0/set_missing_gst_hsn_code.py +++ b/erpnext/patches/v11_0/set_missing_gst_hsn_code.py @@ -41,4 +41,4 @@ def execute(): for t in list(parent): trans_doc = frappe.get_doc(dt, t) hsnwise_tax = get_itemised_tax_breakup_html(trans_doc) - frappe.db.set_value(dt, t, "other_charges_calculation", hsnwise_tax, update_modified=False) \ No newline at end of file + frappe.db.set_value(dt, t, "other_charges_calculation", hsnwise_tax, update_modified=False) diff --git a/erpnext/patches/v11_0/set_salary_component_properties.py b/erpnext/patches/v11_0/set_salary_component_properties.py index 2498888273d..d8ce31f3076 100644 --- a/erpnext/patches/v11_0/set_salary_component_properties.py +++ b/erpnext/patches/v11_0/set_salary_component_properties.py @@ -13,4 +13,4 @@ def execute(): frappe.db.sql("""update `tabSalary Detail` set is_tax_applicable=1 where parentfield='earnings' and statistical_component=0""") frappe.db.sql("""update `tabSalary Detail` set variable_based_on_taxable_salary=1 - where parentfield='deductions' and salary_component in ('TDS', 'Tax Deducted at Source')""") \ No newline at end of file + where parentfield='deductions' and salary_component in ('TDS', 'Tax Deducted at Source')""") diff --git a/erpnext/patches/v11_0/set_user_permissions_for_department.py b/erpnext/patches/v11_0/set_user_permissions_for_department.py index 7bd8577f9c5..2f90f14db3e 100644 --- a/erpnext/patches/v11_0/set_user_permissions_for_department.py +++ b/erpnext/patches/v11_0/set_user_permissions_for_department.py @@ -6,7 +6,7 @@ def execute(): where allow='Department'""", as_dict=1) for d in user_permissions: user_permission = frappe.get_doc("User Permission", d.name) - for new_dept in frappe.db.sql("""select name from tabDepartment + for new_dept in frappe.db.sql("""select name from tabDepartment where ifnull(company, '') != '' and department_name=%s""", d.for_value): try: new_user_permission = frappe.copy_doc(user_permission) @@ -16,4 +16,4 @@ def execute(): pass frappe.reload_doc("hr", "doctype", "department") - frappe.db.sql("update tabDepartment set disabled=1 where ifnull(company, '') = ''") \ No newline at end of file + frappe.db.sql("update tabDepartment set disabled=1 where ifnull(company, '') = ''") diff --git a/erpnext/patches/v11_0/skip_user_permission_check_for_department.py b/erpnext/patches/v11_0/skip_user_permission_check_for_department.py index 0f7fad7e497..4e72917547b 100644 --- a/erpnext/patches/v11_0/skip_user_permission_check_for_department.py +++ b/erpnext/patches/v11_0/skip_user_permission_check_for_department.py @@ -58,4 +58,4 @@ def execute(): if user_permissions_to_delete: frappe.db.sql('DELETE FROM `tabUser Permission` WHERE `name` IN ({})'.format( # nosec ','.join(['%s'] * len(user_permissions_to_delete)) - ), tuple(user_permissions_to_delete)) \ No newline at end of file + ), tuple(user_permissions_to_delete)) diff --git a/erpnext/patches/v11_0/update_account_type_in_party_type.py b/erpnext/patches/v11_0/update_account_type_in_party_type.py index efa04fd2cec..dabaeffc94a 100644 --- a/erpnext/patches/v11_0/update_account_type_in_party_type.py +++ b/erpnext/patches/v11_0/update_account_type_in_party_type.py @@ -10,4 +10,4 @@ def execute(): 'Employee': 'Payable', 'Member': 'Receivable', 'Shareholder': 'Payable', 'Student': 'Receivable'} for party_type, account_type in party_types.items(): - frappe.db.set_value('Party Type', party_type, 'account_type', account_type) \ No newline at end of file + frappe.db.set_value('Party Type', party_type, 'account_type', account_type) diff --git a/erpnext/patches/v11_0/update_allow_transfer_for_manufacture.py b/erpnext/patches/v11_0/update_allow_transfer_for_manufacture.py index 1b58c97ea4d..799e91a3e22 100644 --- a/erpnext/patches/v11_0/update_allow_transfer_for_manufacture.py +++ b/erpnext/patches/v11_0/update_allow_transfer_for_manufacture.py @@ -17,4 +17,4 @@ def execute(): child.include_item_in_manufacturing = 1 where child.item_code = item.name and ifnull(item.is_stock_item, 0) = 1 - """.format(doctype)) \ No newline at end of file + """.format(doctype)) diff --git a/erpnext/patches/v11_0/update_backflush_subcontract_rm_based_on_bom.py b/erpnext/patches/v11_0/update_backflush_subcontract_rm_based_on_bom.py index f2eeadac600..37a616c7021 100644 --- a/erpnext/patches/v11_0/update_backflush_subcontract_rm_based_on_bom.py +++ b/erpnext/patches/v11_0/update_backflush_subcontract_rm_based_on_bom.py @@ -16,4 +16,4 @@ def execute(): where se.purpose = 'Send to Subcontractor' and sed.parent = se.name and pois.rm_item_code = sed.item_code and se.docstatus = 1 - and pois.parenttype = 'Purchase Order'""") \ No newline at end of file + and pois.parenttype = 'Purchase Order'""") diff --git a/erpnext/patches/v11_0/update_brand_in_item_price.py b/erpnext/patches/v11_0/update_brand_in_item_price.py index a8d3fab4812..977d84fefe8 100644 --- a/erpnext/patches/v11_0/update_brand_in_item_price.py +++ b/erpnext/patches/v11_0/update_brand_in_item_price.py @@ -12,4 +12,4 @@ def execute(): `tabItem Price`.brand = `tabItem`.brand where `tabItem Price`.item_code = `tabItem`.name - and `tabItem`.brand is not null and `tabItem`.brand != ''""") \ No newline at end of file + and `tabItem`.brand is not null and `tabItem`.brand != ''""") diff --git a/erpnext/patches/v11_0/update_department_lft_rgt.py b/erpnext/patches/v11_0/update_department_lft_rgt.py index b2f407b18ef..2b382037109 100644 --- a/erpnext/patches/v11_0/update_department_lft_rgt.py +++ b/erpnext/patches/v11_0/update_department_lft_rgt.py @@ -17,4 +17,4 @@ def execute(): frappe.db.sql("""update `tabDepartment` set parent_department = '{0}' where is_group = 0""".format(_('All Departments'))) - rebuild_tree("Department", "parent_department") \ No newline at end of file + rebuild_tree("Department", "parent_department") diff --git a/erpnext/patches/v11_1/delete_bom_browser.py b/erpnext/patches/v11_1/delete_bom_browser.py index 457f5116670..2892674d374 100644 --- a/erpnext/patches/v11_1/delete_bom_browser.py +++ b/erpnext/patches/v11_1/delete_bom_browser.py @@ -5,4 +5,4 @@ from __future__ import unicode_literals import frappe def execute(): - frappe.delete_doc_if_exists('Page', 'bom-browser') \ No newline at end of file + frappe.delete_doc_if_exists('Page', 'bom-browser') diff --git a/erpnext/patches/v11_1/make_job_card_time_logs.py b/erpnext/patches/v11_1/make_job_card_time_logs.py index 6e708df48d8..b706e5c1ffb 100644 --- a/erpnext/patches/v11_1/make_job_card_time_logs.py +++ b/erpnext/patches/v11_1/make_job_card_time_logs.py @@ -26,4 +26,4 @@ def execute(): frappe.reload_doc('manufacturing', 'doctype', 'job_card') frappe.db.sql(""" update `tabJob Card` set total_completed_qty = for_quantity, - total_time_in_mins = time_in_mins where docstatus < 2 """) \ No newline at end of file + total_time_in_mins = time_in_mins where docstatus < 2 """) diff --git a/erpnext/patches/v11_1/move_customer_lead_to_dynamic_column.py b/erpnext/patches/v11_1/move_customer_lead_to_dynamic_column.py index 5b1251c31cf..fc3ec74083a 100644 --- a/erpnext/patches/v11_1/move_customer_lead_to_dynamic_column.py +++ b/erpnext/patches/v11_1/move_customer_lead_to_dynamic_column.py @@ -11,4 +11,4 @@ def execute(): frappe.reload_doctype("Opportunity") frappe.db.sql(""" UPDATE `tabOpportunity` set party_name = lead WHERE opportunity_from = 'Lead' """) - frappe.db.sql(""" UPDATE `tabOpportunity` set party_name = customer WHERE opportunity_from = 'Customer' """) \ No newline at end of file + frappe.db.sql(""" UPDATE `tabOpportunity` set party_name = customer WHERE opportunity_from = 'Customer' """) diff --git a/erpnext/patches/v11_1/rename_depends_on_lwp.py b/erpnext/patches/v11_1/rename_depends_on_lwp.py index a0f2536f7d8..4c4b14fd4e7 100644 --- a/erpnext/patches/v11_1/rename_depends_on_lwp.py +++ b/erpnext/patches/v11_1/rename_depends_on_lwp.py @@ -10,4 +10,4 @@ def execute(): for doctype in ("Salary Component", "Salary Detail"): if "depends_on_lwp" in frappe.db.get_table_columns(doctype): frappe.reload_doc("Payroll", "doctype", scrub(doctype)) - rename_field(doctype, "depends_on_lwp", "depends_on_payment_days") \ No newline at end of file + rename_field(doctype, "depends_on_lwp", "depends_on_payment_days") diff --git a/erpnext/patches/v11_1/renamed_delayed_item_report.py b/erpnext/patches/v11_1/renamed_delayed_item_report.py index 222b9a0b170..8e8725c8af6 100644 --- a/erpnext/patches/v11_1/renamed_delayed_item_report.py +++ b/erpnext/patches/v11_1/renamed_delayed_item_report.py @@ -7,4 +7,4 @@ import frappe def execute(): for report in ["Delayed Order Item Summary", "Delayed Order Summary"]: if frappe.db.exists("Report", report): - frappe.delete_doc("Report", report) \ No newline at end of file + frappe.delete_doc("Report", report) diff --git a/erpnext/patches/v11_1/set_status_for_material_request_type_manufacture.py b/erpnext/patches/v11_1/set_status_for_material_request_type_manufacture.py index d41cff523d5..ec01fbb642e 100644 --- a/erpnext/patches/v11_1/set_status_for_material_request_type_manufacture.py +++ b/erpnext/patches/v11_1/set_status_for_material_request_type_manufacture.py @@ -6,4 +6,4 @@ def execute(): update `tabMaterial Request` set status='Manufactured' where docstatus=1 and material_request_type='Manufacture' and per_ordered=100 and status != 'Stopped' - """) \ No newline at end of file + """) diff --git a/erpnext/patches/v11_1/set_variant_based_on.py b/erpnext/patches/v11_1/set_variant_based_on.py index 019eefd68f4..49a9a177246 100644 --- a/erpnext/patches/v11_1/set_variant_based_on.py +++ b/erpnext/patches/v11_1/set_variant_based_on.py @@ -8,4 +8,4 @@ def execute(): frappe.db.sql("""update tabItem set variant_based_on = 'Item Attribute' where ifnull(variant_based_on, '') = '' and (has_variants=1 or ifnull(variant_of, '') != '') - """) \ No newline at end of file + """) diff --git a/erpnext/patches/v11_1/update_bank_transaction_status.py b/erpnext/patches/v11_1/update_bank_transaction_status.py index 544bc5e6911..354e636c9b0 100644 --- a/erpnext/patches/v11_1/update_bank_transaction_status.py +++ b/erpnext/patches/v11_1/update_bank_transaction_status.py @@ -23,4 +23,4 @@ def execute(): WHERE status = 'Settled' and (deposit = allocated_amount or withdrawal = allocated_amount) and ifnull(allocated_amount, 0) > 0 - """) \ No newline at end of file + """) diff --git a/erpnext/patches/v11_1/update_default_supplier_in_item_defaults.py b/erpnext/patches/v11_1/update_default_supplier_in_item_defaults.py index 347dec1f74d..8c360ad9353 100644 --- a/erpnext/patches/v11_1/update_default_supplier_in_item_defaults.py +++ b/erpnext/patches/v11_1/update_default_supplier_in_item_defaults.py @@ -22,4 +22,4 @@ def execute(): SET `tabItem Default`.default_supplier = `tabItem`.default_supplier WHERE `tabItem Default`.parent = `tabItem`.name and `tabItem Default`.default_supplier is null - and `tabItem`.default_supplier is not null and `tabItem`.default_supplier != '' """) \ No newline at end of file + and `tabItem`.default_supplier is not null and `tabItem`.default_supplier != '' """) diff --git a/erpnext/patches/v11_1/woocommerce_set_creation_user.py b/erpnext/patches/v11_1/woocommerce_set_creation_user.py index 5ccdec6d262..074b904002c 100644 --- a/erpnext/patches/v11_1/woocommerce_set_creation_user.py +++ b/erpnext/patches/v11_1/woocommerce_set_creation_user.py @@ -8,4 +8,4 @@ def execute(): if cint(doc.enable_sync): doc.creation_user = doc.modified_by - doc.save(ignore_permissions=True) \ No newline at end of file + doc.save(ignore_permissions=True) diff --git a/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py index b6bd5fa311c..c2ed6c288fe 100644 --- a/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py +++ b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py @@ -13,4 +13,4 @@ def execute(): where a.gstin = %s and dl.parent = a.name and dl.link_doctype = 'Company' """, (creds.get('gstin'))) if company_name and len(company_name) > 0: - frappe.db.set_value('E Invoice User', creds.get('name'), 'company', company_name[0][0]) \ No newline at end of file + frappe.db.set_value('E Invoice User', creds.get('name'), 'company', company_name[0][0]) diff --git a/erpnext/patches/v12_0/add_default_buying_selling_terms_in_company.py b/erpnext/patches/v12_0/add_default_buying_selling_terms_in_company.py index 484f81a7aca..855d21dd992 100644 --- a/erpnext/patches/v12_0/add_default_buying_selling_terms_in_company.py +++ b/erpnext/patches/v12_0/add_default_buying_selling_terms_in_company.py @@ -14,6 +14,6 @@ def execute(): for company in frappe.get_all("Company", ["name", "default_selling_terms", "default_buying_terms"]): if company.default_selling_terms and not company.default_buying_terms: frappe.db.set_value("Company", company.name, "default_buying_terms", company.default_selling_terms) - + frappe.reload_doc("setup", "doctype", "terms_and_conditions") frappe.db.sql("update `tabTerms and Conditions` set selling=1, buying=1, hr=1") diff --git a/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py b/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py index 4d649dd0f0c..6fe578dbd95 100644 --- a/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py +++ b/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py @@ -15,4 +15,4 @@ def execute(): ] } - create_custom_fields(custom_fields, update=True) \ No newline at end of file + create_custom_fields(custom_fields, update=True) diff --git a/erpnext/patches/v12_0/add_einvoice_status_field.py b/erpnext/patches/v12_0/add_einvoice_status_field.py index 387e88588d9..2dfd30714c8 100644 --- a/erpnext/patches/v12_0/add_einvoice_status_field.py +++ b/erpnext/patches/v12_0/add_einvoice_status_field.py @@ -13,13 +13,13 @@ def execute(): 'Sales Invoice': [ dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type', print_hide=1, hidden=1), - + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section', no_copy=1, print_hide=1), - + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), - dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', + dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', no_copy=1, print_hide=1), dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date', @@ -66,4 +66,4 @@ def execute(): if signed_einvoice: signed_einvoice = json.loads(signed_einvoice) frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_no', signed_einvoice.get('AckNo'), update_modified=False) - frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_date', signed_einvoice.get('AckDt'), update_modified=False) \ No newline at end of file + frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_date', signed_einvoice.get('AckDt'), update_modified=False) diff --git a/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py index bf8f566d32a..c1c11e26006 100644 --- a/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py +++ b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py @@ -15,4 +15,4 @@ def execute(): dict(role='Accounts User'), dict(role='Accounts Manager') ] - )).insert() \ No newline at end of file + )).insert() diff --git a/erpnext/patches/v12_0/add_eway_bill_in_delivery_note.py b/erpnext/patches/v12_0/add_eway_bill_in_delivery_note.py index bb4b0380f88..cf1ed3676bf 100644 --- a/erpnext/patches/v12_0/add_eway_bill_in_delivery_note.py +++ b/erpnext/patches/v12_0/add_eway_bill_in_delivery_note.py @@ -16,4 +16,4 @@ def execute(): 'insert_after': 'customer_name_in_arabic', 'translatable': 0, 'owner': 'Administrator' - }) \ No newline at end of file + }) diff --git a/erpnext/patches/v12_0/add_ewaybill_validity_field.py b/erpnext/patches/v12_0/add_ewaybill_validity_field.py index 87d98f1a563..f29b71437e8 100644 --- a/erpnext/patches/v12_0/add_ewaybill_validity_field.py +++ b/erpnext/patches/v12_0/add_ewaybill_validity_field.py @@ -13,4 +13,4 @@ def execute(): depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill') ] } - create_custom_fields(custom_fields, update=True) \ No newline at end of file + create_custom_fields(custom_fields, update=True) diff --git a/erpnext/patches/v12_0/add_export_type_field_in_party_master.py b/erpnext/patches/v12_0/add_export_type_field_in_party_master.py index 5bb6e3fb339..a0b1f87d61b 100644 --- a/erpnext/patches/v12_0/add_export_type_field_in_party_master.py +++ b/erpnext/patches/v12_0/add_export_type_field_in_party_master.py @@ -38,5 +38,3 @@ def execute(): WHERE fieldname = 'is_inter_state' AND dt IN ('Sales Taxes and Charges Template', 'Purchase Taxes and Charges Template') """) - - diff --git a/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py b/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py index 1208222504c..c90819238c8 100644 --- a/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py +++ b/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py @@ -16,4 +16,4 @@ def execute(): ] } - create_custom_fields(custom_fields, update=True) \ No newline at end of file + create_custom_fields(custom_fields, update=True) diff --git a/erpnext/patches/v12_0/add_item_name_in_work_orders.py b/erpnext/patches/v12_0/add_item_name_in_work_orders.py index 485dd314a16..d765b93d218 100644 --- a/erpnext/patches/v12_0/add_item_name_in_work_orders.py +++ b/erpnext/patches/v12_0/add_item_name_in_work_orders.py @@ -11,4 +11,4 @@ def execute(): SET wo.item_name = item.item_name """) - frappe.db.commit() \ No newline at end of file + frappe.db.commit() diff --git a/erpnext/patches/v12_0/add_permission_in_lower_deduction.py b/erpnext/patches/v12_0/add_permission_in_lower_deduction.py index af9bf74f30e..2e42368b152 100644 --- a/erpnext/patches/v12_0/add_permission_in_lower_deduction.py +++ b/erpnext/patches/v12_0/add_permission_in_lower_deduction.py @@ -10,4 +10,4 @@ def execute(): add_permission('Lower Deduction Certificate', 'Accounts Manager', 0) update_permission_property('Lower Deduction Certificate', 'Accounts Manager', 0, 'write', 1) - update_permission_property('Lower Deduction Certificate', 'Accounts Manager', 0, 'create', 1) \ No newline at end of file + update_permission_property('Lower Deduction Certificate', 'Accounts Manager', 0, 'create', 1) diff --git a/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py b/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py index 657decfed23..f171542df16 100644 --- a/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py +++ b/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py @@ -39,4 +39,4 @@ def execute(): create_custom_field(doctype, df) frappe.clear_cache(doctype=doctype) - count += 1 \ No newline at end of file + count += 1 diff --git a/erpnext/patches/v12_0/create_default_energy_point_rules.py b/erpnext/patches/v12_0/create_default_energy_point_rules.py index 88233b4cf7f..93d2576bb6d 100644 --- a/erpnext/patches/v12_0/create_default_energy_point_rules.py +++ b/erpnext/patches/v12_0/create_default_energy_point_rules.py @@ -3,4 +3,4 @@ from erpnext.setup.install import create_default_energy_point_rules def execute(): frappe.reload_doc('social', 'doctype', 'energy_point_rule') - create_default_energy_point_rules() \ No newline at end of file + create_default_energy_point_rules() diff --git a/erpnext/patches/v12_0/create_irs_1099_field_united_states.py b/erpnext/patches/v12_0/create_irs_1099_field_united_states.py index 7feaffdf408..23a8f24d780 100644 --- a/erpnext/patches/v12_0/create_irs_1099_field_united_states.py +++ b/erpnext/patches/v12_0/create_irs_1099_field_united_states.py @@ -13,4 +13,4 @@ def execute(): if not company: return - make_custom_fields() \ No newline at end of file + make_custom_fields() diff --git a/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py b/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py index 0078a53cd69..a6230f42771 100644 --- a/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py +++ b/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py @@ -112,4 +112,4 @@ def execute(): 'itc_central_tax': values.get('itc_central_tax'), 'itc_state_tax': values['itc_state_tax'], 'itc_cess_amount': values['itc_cess_amount'], - }) \ No newline at end of file + }) diff --git a/erpnext/patches/v12_0/create_taxable_value_field.py b/erpnext/patches/v12_0/create_taxable_value_field.py index a0c9fcf4cbe..b9ee81df50e 100644 --- a/erpnext/patches/v12_0/create_taxable_value_field.py +++ b/erpnext/patches/v12_0/create_taxable_value_field.py @@ -15,4 +15,4 @@ def execute(): ] } - create_custom_fields(custom_fields, update=True) \ No newline at end of file + create_custom_fields(custom_fields, update=True) diff --git a/erpnext/patches/v12_0/delete_priority_property_setter.py b/erpnext/patches/v12_0/delete_priority_property_setter.py index 59272675430..163855729df 100644 --- a/erpnext/patches/v12_0/delete_priority_property_setter.py +++ b/erpnext/patches/v12_0/delete_priority_property_setter.py @@ -6,4 +6,4 @@ def execute(): WHERE `tabProperty Setter`.doc_type='Issue' AND `tabProperty Setter`.field_name='priority' AND `tabProperty Setter`.property='options' - """) \ No newline at end of file + """) diff --git a/erpnext/patches/v12_0/fix_quotation_expired_status.py b/erpnext/patches/v12_0/fix_quotation_expired_status.py index c8708d80134..ac7e82d2d0d 100644 --- a/erpnext/patches/v12_0/fix_quotation_expired_status.py +++ b/erpnext/patches/v12_0/fix_quotation_expired_status.py @@ -6,23 +6,23 @@ def execute(): # filter out submitted expired quotations which has sales order created cond = "qo.docstatus = 1 and qo.status = 'Expired'" invalid_so_against_quo = """ - SELECT + SELECT so.name FROM `tabSales Order` so, `tabSales Order Item` so_item - WHERE + WHERE so_item.docstatus = 1 and so.docstatus = 1 and so_item.parent = so.name and so_item.prevdoc_docname = qo.name and qo.valid_till < so.transaction_date""" # check if SO was created after quotation expired - + frappe.db.sql( """UPDATE `tabQuotation` qo SET qo.status = 'Expired' WHERE {cond} and exists({invalid_so_against_quo})""" .format(cond=cond, invalid_so_against_quo=invalid_so_against_quo) ) - + valid_so_against_quo = """ - SELECT + SELECT so.name FROM `tabSales Order` so, `tabSales Order Item` so_item - WHERE + WHERE so_item.docstatus = 1 and so.docstatus = 1 and so_item.parent = so.name and so_item.prevdoc_docname = qo.name diff --git a/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py b/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py index 548c1a47175..97badf355d9 100644 --- a/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py +++ b/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py @@ -19,4 +19,4 @@ def execute(): frappe.delete_doc("Report", "Sales Partner-wise Transaction Summary") frappe.delete_doc("Report", "Sales Person Target Variance Item Group-Wise") - frappe.delete_doc("Report", "Territory Target Variance Item Group-Wise") \ No newline at end of file + frappe.delete_doc("Report", "Territory Target Variance Item Group-Wise") diff --git a/erpnext/patches/v12_0/recalculate_requested_qty_in_bin.py b/erpnext/patches/v12_0/recalculate_requested_qty_in_bin.py index 8267df95e11..46794bebe70 100644 --- a/erpnext/patches/v12_0/recalculate_requested_qty_in_bin.py +++ b/erpnext/patches/v12_0/recalculate_requested_qty_in_bin.py @@ -10,4 +10,4 @@ def execute(): for entry in bin_details: update_bin_qty(entry.get("item_code"), entry.get("warehouse"), { "indented_qty": get_indented_qty(entry.get("item_code"), entry.get("warehouse")) - }) \ No newline at end of file + }) diff --git a/erpnext/patches/v12_0/remove_bank_remittance_custom_fields.py b/erpnext/patches/v12_0/remove_bank_remittance_custom_fields.py index d1446b3227d..be884f94d15 100644 --- a/erpnext/patches/v12_0/remove_bank_remittance_custom_fields.py +++ b/erpnext/patches/v12_0/remove_bank_remittance_custom_fields.py @@ -11,4 +11,4 @@ def execute(): if frappe.db.exists("Custom Field", "Company-bank_remittance_section"): deprecated_fields = ['bank_remittance_section', 'client_code', 'remittance_column_break', 'product_code'] for i in range(len(deprecated_fields)): - frappe.delete_doc("Custom Field", 'Company-'+deprecated_fields[i]) \ No newline at end of file + frappe.delete_doc("Custom Field", 'Company-'+deprecated_fields[i]) diff --git a/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py b/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py index 7859606e5cb..4fcffb702a4 100644 --- a/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py +++ b/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py @@ -25,4 +25,4 @@ def delete_denied_leaves_from_leave_ledger_entry(leave_application_list): WHERE transaction_type = 'Leave Application' AND transaction_name in (%s) ''' % (', '.join(['%s'] * len(leave_application_list))), #nosec - tuple(leave_application_list)) \ No newline at end of file + tuple(leave_application_list)) diff --git a/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py b/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py index 24286dcebf9..6b1b601db19 100644 --- a/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py @@ -43,4 +43,4 @@ def delete_duplicate_ledger_entries(duplicate_records_list): AND is_carry_forward = %s AND from_date = %s AND to_date = %s - ''', tuple(d)) \ No newline at end of file + ''', tuple(d)) diff --git a/erpnext/patches/v12_0/rename_account_type_doctype.py b/erpnext/patches/v12_0/rename_account_type_doctype.py index ffb4e937b18..9a08ad45213 100644 --- a/erpnext/patches/v12_0/rename_account_type_doctype.py +++ b/erpnext/patches/v12_0/rename_account_type_doctype.py @@ -4,4 +4,4 @@ import frappe def execute(): frappe.rename_doc('DocType', 'Account Type', 'Bank Account Type', force=True) frappe.rename_doc('DocType', 'Account Subtype', 'Bank Account Subtype', force=True) - frappe.reload_doc('accounts', 'doctype', 'bank_account') \ No newline at end of file + frappe.reload_doc('accounts', 'doctype', 'bank_account') diff --git a/erpnext/patches/v12_0/rename_bank_account_field_in_journal_entry_account.py b/erpnext/patches/v12_0/rename_bank_account_field_in_journal_entry_account.py index 4230cb88f4d..7489ea30a09 100644 --- a/erpnext/patches/v12_0/rename_bank_account_field_in_journal_entry_account.py +++ b/erpnext/patches/v12_0/rename_bank_account_field_in_journal_entry_account.py @@ -14,4 +14,4 @@ def execute(): def update_journal_entry_account_fieldname(): ''' maps data from old field to the new field ''' if frappe.db.has_column('Journal Entry Account', 'bank_account_no'): - rename_field("Journal Entry Account", "bank_account_no", "bank_account") \ No newline at end of file + rename_field("Journal Entry Account", "bank_account_no", "bank_account") diff --git a/erpnext/patches/v12_0/rename_lost_reason_detail.py b/erpnext/patches/v12_0/rename_lost_reason_detail.py index d0dc356bd0e..c71b91c9256 100644 --- a/erpnext/patches/v12_0/rename_lost_reason_detail.py +++ b/erpnext/patches/v12_0/rename_lost_reason_detail.py @@ -15,4 +15,4 @@ def execute(): SELECT o.`name`, o.`creation`, o.`modified`, o.`modified_by`, o.`owner`, o.`docstatus`, o.`parent`, o.`parentfield`, o.`parenttype`, o.`idx`, o.`_comments`, o.`_assign`, o.`_user_tags`, o.`_liked_by`, o.`lost_reason` FROM `tabOpportunity Lost Reason` o LEFT JOIN `tabQuotation Lost Reason` q ON q.name = o.name WHERE q.name IS NULL""") - frappe.delete_doc("DocType", "Lost Reason Detail") \ No newline at end of file + frappe.delete_doc("DocType", "Lost Reason Detail") diff --git a/erpnext/patches/v12_0/rename_pos_closing_doctype.py b/erpnext/patches/v12_0/rename_pos_closing_doctype.py index 0577f81234c..9d8626b8527 100644 --- a/erpnext/patches/v12_0/rename_pos_closing_doctype.py +++ b/erpnext/patches/v12_0/rename_pos_closing_doctype.py @@ -7,10 +7,10 @@ def execute(): if frappe.db.table_exists("POS Closing Voucher"): if not frappe.db.exists("DocType", "POS Closing Entry"): frappe.rename_doc('DocType', 'POS Closing Voucher', 'POS Closing Entry', force=True) - + if not frappe.db.exists('DocType', 'POS Closing Entry Taxes'): frappe.rename_doc('DocType', 'POS Closing Voucher Taxes', 'POS Closing Entry Taxes', force=True) - + if not frappe.db.exists('DocType', 'POS Closing Voucher Details'): frappe.rename_doc('DocType', 'POS Closing Voucher Details', 'POS Closing Entry Detail', force=True) @@ -22,4 +22,4 @@ def execute(): frappe.delete_doc("DocType", "POS Closing Voucher") frappe.delete_doc("DocType", "POS Closing Voucher Taxes") frappe.delete_doc("DocType", "POS Closing Voucher Details") - frappe.delete_doc("DocType", "POS Closing Voucher Invoices") \ No newline at end of file + frappe.delete_doc("DocType", "POS Closing Voucher Invoices") diff --git a/erpnext/patches/v12_0/rename_tolerance_fields.py b/erpnext/patches/v12_0/rename_tolerance_fields.py index aa2fff4ca72..20b096331ed 100644 --- a/erpnext/patches/v12_0/rename_tolerance_fields.py +++ b/erpnext/patches/v12_0/rename_tolerance_fields.py @@ -12,4 +12,4 @@ def execute(): qty_allowance = frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance") frappe.db.set_value("Accounts Settings", None, "over_delivery_receipt_allowance", qty_allowance) - frappe.db.sql("update tabItem set over_billing_allowance=over_delivery_receipt_allowance") \ No newline at end of file + frappe.db.sql("update tabItem set over_billing_allowance=over_delivery_receipt_allowance") diff --git a/erpnext/patches/v12_0/replace_accounting_with_accounts_in_home_settings.py b/erpnext/patches/v12_0/replace_accounting_with_accounts_in_home_settings.py index 09fc4c1b04e..f88a22f6c9d 100644 --- a/erpnext/patches/v12_0/replace_accounting_with_accounts_in_home_settings.py +++ b/erpnext/patches/v12_0/replace_accounting_with_accounts_in_home_settings.py @@ -2,4 +2,4 @@ import frappe def execute(): frappe.db.sql("""UPDATE `tabUser` SET `home_settings` = REPLACE(`home_settings`, 'Accounting', 'Accounts')""") - frappe.cache().delete_key('home_settings') \ No newline at end of file + frappe.cache().delete_key('home_settings') diff --git a/erpnext/patches/v12_0/repost_stock_ledger_entries_for_target_warehouse.py b/erpnext/patches/v12_0/repost_stock_ledger_entries_for_target_warehouse.py index 13e935b2d39..c52f380d8c2 100644 --- a/erpnext/patches/v12_0/repost_stock_ledger_entries_for_target_warehouse.py +++ b/erpnext/patches/v12_0/repost_stock_ledger_entries_for_target_warehouse.py @@ -66,6 +66,3 @@ def execute(): frappe.db.sql(""" UPDATE `tabPacked Item` set target_warehouse = null WHERE creation > '2020-04-16' and docstatus < 2 and parenttype = 'Sales Order' """) - - - diff --git a/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py b/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py index 5ee75be4990..b5d7e3dcb9e 100644 --- a/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py +++ b/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py @@ -4,4 +4,4 @@ import frappe def execute(): frappe.reload_doc("accounts", "doctype", "accounts_settings") - frappe.db.set_value("Accounts Settings", None, "automatically_process_deferred_accounting_entry", 1) \ No newline at end of file + frappe.db.set_value("Accounts Settings", None, "automatically_process_deferred_accounting_entry", 1) diff --git a/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py b/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py index 8ba0d79a831..4415cfeaba9 100644 --- a/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py +++ b/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py @@ -5,4 +5,4 @@ def execute(): UPDATE `tabExpense Claim Detail` child, `tabExpense Claim` par SET child.cost_center = par.cost_center WHERE child.parent = par.name - """) \ No newline at end of file + """) diff --git a/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py b/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py index 4d4fc7c4629..13110dfe03f 100644 --- a/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py +++ b/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py @@ -10,8 +10,8 @@ def execute(): if frappe.db.exists("DocType", "Asset Settings"): frappe.reload_doctype("Asset Category") cwip_value = frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting") - + frappe.db.sql("""UPDATE `tabAsset Category` SET enable_cwip_accounting = %s""", cint(cwip_value)) frappe.db.sql("""DELETE FROM `tabSingles` where doctype = 'Asset Settings'""") - frappe.delete_doc_if_exists("DocType", "Asset Settings") \ No newline at end of file + frappe.delete_doc_if_exists("DocType", "Asset Settings") diff --git a/erpnext/patches/v12_0/set_default_homepage_type.py b/erpnext/patches/v12_0/set_default_homepage_type.py index 241e4b9b5e1..a290e31cf24 100644 --- a/erpnext/patches/v12_0/set_default_homepage_type.py +++ b/erpnext/patches/v12_0/set_default_homepage_type.py @@ -1,4 +1,4 @@ import frappe def execute(): - frappe.db.set_value('Homepage', 'Homepage', 'hero_section_based_on', 'Default') \ No newline at end of file + frappe.db.set_value('Homepage', 'Homepage', 'hero_section_based_on', 'Default') diff --git a/erpnext/patches/v12_0/set_default_payroll_based_on.py b/erpnext/patches/v12_0/set_default_payroll_based_on.py index 04b54a6cf61..038bd6d21ae 100644 --- a/erpnext/patches/v12_0/set_default_payroll_based_on.py +++ b/erpnext/patches/v12_0/set_default_payroll_based_on.py @@ -3,4 +3,4 @@ import frappe def execute(): frappe.reload_doc("hr", "doctype", "hr_settings") - frappe.db.set_value("HR Settings", None, "payroll_based_on", "Leave") \ No newline at end of file + frappe.db.set_value("HR Settings", None, "payroll_based_on", "Leave") diff --git a/erpnext/patches/v12_0/set_default_shopify_app_type.py b/erpnext/patches/v12_0/set_default_shopify_app_type.py index d040ea7f71c..65958a25afd 100644 --- a/erpnext/patches/v12_0/set_default_shopify_app_type.py +++ b/erpnext/patches/v12_0/set_default_shopify_app_type.py @@ -3,4 +3,4 @@ import frappe def execute(): frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_settings') - frappe.db.set_value('Shopify Settings', None, 'app_type', 'Private') \ No newline at end of file + frappe.db.set_value('Shopify Settings', None, 'app_type', 'Private') diff --git a/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py b/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py index a996a69b3d9..a27c7b24a8c 100644 --- a/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py +++ b/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py @@ -30,4 +30,4 @@ def execute(): s.docstatus = 1 AND s.company = %s AND t.parent = s.name - """, (account, company)) \ No newline at end of file + """, (account, company)) diff --git a/erpnext/patches/v12_0/set_gst_category.py b/erpnext/patches/v12_0/set_gst_category.py index 55bbdee7edf..cc093953bf4 100644 --- a/erpnext/patches/v12_0/set_gst_category.py +++ b/erpnext/patches/v12_0/set_gst_category.py @@ -48,5 +48,3 @@ def execute(): frappe.db.sql(""" UPDATE `tab{doctype}` t1, `tabAddress` t2, `tabDynamic Link` t3 SET t1.gst_category = "Overseas" where t3.link_name = t1.name and t3.parent = t2.name and t2.country != 'India' """.format(doctype=doctype)) #nosec - - diff --git a/erpnext/patches/v12_0/set_italian_import_supplier_invoice_permissions.py b/erpnext/patches/v12_0/set_italian_import_supplier_invoice_permissions.py index a6011c4dace..8fdc73b8ff1 100644 --- a/erpnext/patches/v12_0/set_italian_import_supplier_invoice_permissions.py +++ b/erpnext/patches/v12_0/set_italian_import_supplier_invoice_permissions.py @@ -9,4 +9,4 @@ def execute(): countries = frappe.get_all("Company", fields="country") countries = [country["country"] for country in countries] if "Italy" in countries: - add_permissions() \ No newline at end of file + add_permissions() diff --git a/erpnext/patches/v12_0/set_multi_uom_in_rfq.py b/erpnext/patches/v12_0/set_multi_uom_in_rfq.py index 70ca6b222e9..a5c8f7524a7 100644 --- a/erpnext/patches/v12_0/set_multi_uom_in_rfq.py +++ b/erpnext/patches/v12_0/set_multi_uom_in_rfq.py @@ -13,4 +13,4 @@ def execute(): SET stock_uom = uom, conversion_factor = 1, - stock_qty = qty""") \ No newline at end of file + stock_qty = qty""") diff --git a/erpnext/patches/v12_0/set_payment_entry_status.py b/erpnext/patches/v12_0/set_payment_entry_status.py index fafbec6a9a7..84645a38639 100644 --- a/erpnext/patches/v12_0/set_payment_entry_status.py +++ b/erpnext/patches/v12_0/set_payment_entry_status.py @@ -6,4 +6,4 @@ def execute(): WHEN docstatus = 1 THEN 'Submitted' WHEN docstatus = 2 THEN 'Cancelled' ELSE 'Draft' - END;""") \ No newline at end of file + END;""") diff --git a/erpnext/patches/v12_0/set_priority_for_support.py b/erpnext/patches/v12_0/set_priority_for_support.py index a5490ef20d5..66696bee541 100644 --- a/erpnext/patches/v12_0/set_priority_for_support.py +++ b/erpnext/patches/v12_0/set_priority_for_support.py @@ -81,4 +81,4 @@ def set_priorities_service_level_agreement(): doc.flags.ignore_validate = True doc.save(ignore_permissions=True) except frappe.db.TableMissingError: - frappe.reload_doc("support", "doctype", "service_level_agreement") \ No newline at end of file + frappe.reload_doc("support", "doctype", "service_level_agreement") diff --git a/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py b/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py index 07026732fd4..6c11cb415f9 100644 --- a/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py +++ b/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py @@ -11,4 +11,4 @@ def execute(): filters={'sales_order': ('!=', ''), 'sales_order_item': ('!=', '')}): # update produced qty in sales order - update_produced_qty_in_so_item(d.sales_order, d.sales_order_item) \ No newline at end of file + update_produced_qty_in_so_item(d.sales_order, d.sales_order_item) diff --git a/erpnext/patches/v12_0/set_production_capacity_in_workstation.py b/erpnext/patches/v12_0/set_production_capacity_in_workstation.py index bae1e28deb9..babaebeaefc 100644 --- a/erpnext/patches/v12_0/set_production_capacity_in_workstation.py +++ b/erpnext/patches/v12_0/set_production_capacity_in_workstation.py @@ -5,4 +5,4 @@ def execute(): frappe.reload_doc("manufacturing", "doctype", "workstation") frappe.db.sql(""" UPDATE `tabWorkstation` - SET production_capacity = 1 """) \ No newline at end of file + SET production_capacity = 1 """) diff --git a/erpnext/patches/v12_0/set_quotation_status.py b/erpnext/patches/v12_0/set_quotation_status.py index 64a9080a8fe..87643a23545 100644 --- a/erpnext/patches/v12_0/set_quotation_status.py +++ b/erpnext/patches/v12_0/set_quotation_status.py @@ -4,4 +4,4 @@ import frappe def execute(): frappe.db.sql(""" UPDATE `tabQuotation` set status = 'Open' - where docstatus = 1 and status = 'Submitted' """) \ No newline at end of file + where docstatus = 1 and status = 'Submitted' """) diff --git a/erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py b/erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py index 63ca540a8e2..1cc37caba42 100644 --- a/erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py +++ b/erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py @@ -8,4 +8,4 @@ import frappe def execute(): frappe.reload_doc("stock", "doctype", "pick_list") frappe.db.sql("""UPDATE `tabPick List` set purpose = 'Delivery' - WHERE docstatus = 1 and purpose = 'Delivery against Sales Order' """) \ No newline at end of file + WHERE docstatus = 1 and purpose = 'Delivery against Sales Order' """) diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py index 2474bc3b82c..82b14fc9d60 100644 --- a/erpnext/patches/v12_0/setup_einvoice_fields.py +++ b/erpnext/patches/v12_0/setup_einvoice_fields.py @@ -14,9 +14,9 @@ def execute(): 'Sales Invoice': [ dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1, depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), - + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1), - + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, diff --git a/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py b/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py new file mode 100644 index 00000000000..2319c17b34c --- /dev/null +++ b/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py @@ -0,0 +1,12 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + irn_cancelled_field = frappe.db.exists('Custom Field', {'dt': 'Sales Invoice', 'fieldname': 'irn_cancelled'}) + if irn_cancelled_field: + frappe.db.set_value('Custom Field', irn_cancelled_field, 'depends_on', 'eval: doc.irn') + frappe.db.set_value('Custom Field', irn_cancelled_field, 'read_only', 0) diff --git a/erpnext/patches/v12_0/stock_entry_enhancements.py b/erpnext/patches/v12_0/stock_entry_enhancements.py index 847d92894be..17fdcd9395a 100644 --- a/erpnext/patches/v12_0/stock_entry_enhancements.py +++ b/erpnext/patches/v12_0/stock_entry_enhancements.py @@ -49,4 +49,4 @@ def add_gst_hsn_code_field(): `tabStock Entry Detail`.gst_hsn_code = `tabItem`.gst_hsn_code Where `tabItem`.name = `tabStock Entry Detail`.item_code and `tabItem`.gst_hsn_code is not null - """) \ No newline at end of file + """) diff --git a/erpnext/patches/v12_0/unhide_cost_center_field.py b/erpnext/patches/v12_0/unhide_cost_center_field.py index 6005ab70726..3474a34af4b 100644 --- a/erpnext/patches/v12_0/unhide_cost_center_field.py +++ b/erpnext/patches/v12_0/unhide_cost_center_field.py @@ -10,4 +10,4 @@ def execute(): WHERE doc_type in ('Sales Invoice', 'Purchase Invoice', 'Payment Entry') AND field_name = 'cost_center' AND property = 'hidden' - """) \ No newline at end of file + """) diff --git a/erpnext/patches/v12_0/update_appointment_reminder_scheduler_entry.py b/erpnext/patches/v12_0/update_appointment_reminder_scheduler_entry.py index 91931eeb3bc..f4516649610 100644 --- a/erpnext/patches/v12_0/update_appointment_reminder_scheduler_entry.py +++ b/erpnext/patches/v12_0/update_appointment_reminder_scheduler_entry.py @@ -4,4 +4,4 @@ def execute(): job = frappe.db.exists('Scheduled Job Type', 'patient_appointment.send_appointment_reminder') if job: method = 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder' - frappe.db.set_value('Scheduled Job Type', job, 'method', method) \ No newline at end of file + frappe.db.set_value('Scheduled Job Type', job, 'method', method) diff --git a/erpnext/patches/v12_0/update_bom_in_so_mr.py b/erpnext/patches/v12_0/update_bom_in_so_mr.py index 309ae4c2ab7..8a871718133 100644 --- a/erpnext/patches/v12_0/update_bom_in_so_mr.py +++ b/erpnext/patches/v12_0/update_bom_in_so_mr.py @@ -16,4 +16,4 @@ def execute(): WHERE child_doc.item_code = item.name and child_doc.docstatus < 2 and item.default_bom is not null and item.default_bom != '' {cond} - """.format(doc = doctype, cond = condition)) \ No newline at end of file + """.format(doc = doctype, cond = condition)) diff --git a/erpnext/patches/v12_0/update_end_date_and_status_in_email_campaign.py b/erpnext/patches/v12_0/update_end_date_and_status_in_email_campaign.py index db71a735def..c45f6221f93 100644 --- a/erpnext/patches/v12_0/update_end_date_and_status_in_email_campaign.py +++ b/erpnext/patches/v12_0/update_end_date_and_status_in_email_campaign.py @@ -21,4 +21,4 @@ def execute(): elif end_date >= today_date: doc.db_set("status", "In Progress") elif end_date < today_date: - doc.db_set("status", "Completed") \ No newline at end of file + doc.db_set("status", "Completed") diff --git a/erpnext/patches/v12_0/update_ewaybill_field_position.py b/erpnext/patches/v12_0/update_ewaybill_field_position.py index c0230c43954..9e5f599d2c8 100644 --- a/erpnext/patches/v12_0/update_ewaybill_field_position.py +++ b/erpnext/patches/v12_0/update_ewaybill_field_position.py @@ -25,4 +25,4 @@ def execute(): 'translatable': 0 }) - ewaybill_field.save() \ No newline at end of file + ewaybill_field.save() diff --git a/erpnext/patches/v12_0/update_gst_category.py b/erpnext/patches/v12_0/update_gst_category.py index 963edad150e..1a54216b885 100644 --- a/erpnext/patches/v12_0/update_gst_category.py +++ b/erpnext/patches/v12_0/update_gst_category.py @@ -16,4 +16,4 @@ def execute(): frappe.db.sql(""" UPDATE `tabPurchase Invoice` set gst_category = 'Unregistered' where gst_category = 'Registered Regular' and ifnull(supplier_gstin, '')='' - """) \ No newline at end of file + """) diff --git a/erpnext/patches/v12_0/update_healthcare_refactored_changes.py b/erpnext/patches/v12_0/update_healthcare_refactored_changes.py index d06c5713d23..d0b04433979 100644 --- a/erpnext/patches/v12_0/update_healthcare_refactored_changes.py +++ b/erpnext/patches/v12_0/update_healthcare_refactored_changes.py @@ -134,4 +134,4 @@ def execute(): status = (CASE WHEN visited >= max_visits THEN 'Completed' ELSE 'Pending' END) - """) \ No newline at end of file + """) diff --git a/erpnext/patches/v12_0/update_is_cancelled_field.py b/erpnext/patches/v12_0/update_is_cancelled_field.py index 0b2e82750b2..4bbec44aa42 100644 --- a/erpnext/patches/v12_0/update_is_cancelled_field.py +++ b/erpnext/patches/v12_0/update_is_cancelled_field.py @@ -12,4 +12,4 @@ def execute(): frappe.reload_doc("stock", "doctype", "stock_ledger_entry") frappe.reload_doc("stock", "doctype", "serial_no") except: - pass \ No newline at end of file + pass diff --git a/erpnext/patches/v12_0/update_item_tax_template_company.py b/erpnext/patches/v12_0/update_item_tax_template_company.py index f7496999b33..e15894df890 100644 --- a/erpnext/patches/v12_0/update_item_tax_template_company.py +++ b/erpnext/patches/v12_0/update_item_tax_template_company.py @@ -10,4 +10,4 @@ def execute(): for tax in doc.taxes: doc.company = frappe.get_value('Account', tax.tax_type, 'company') break - doc.save() \ No newline at end of file + doc.save() diff --git a/erpnext/patches/v12_0/update_owner_fields_in_acc_dimension_custom_fields.py b/erpnext/patches/v12_0/update_owner_fields_in_acc_dimension_custom_fields.py index e4dcecd9bdb..6ebaf48e0e8 100644 --- a/erpnext/patches/v12_0/update_owner_fields_in_acc_dimension_custom_fields.py +++ b/erpnext/patches/v12_0/update_owner_fields_in_acc_dimension_custom_fields.py @@ -14,4 +14,4 @@ def execute(): SET owner = 'Administrator' WHERE fieldname = %s AND dt IN (%s)""" % #nosec - ('%s', ', '.join(['%s']* len(doclist))), tuple([dimension.fieldname] + doclist)) \ No newline at end of file + ('%s', ', '.join(['%s']* len(doclist))), tuple([dimension.fieldname] + doclist)) diff --git a/erpnext/patches/v12_0/update_price_list_currency_in_bom.py b/erpnext/patches/v12_0/update_price_list_currency_in_bom.py index f5e7b947c23..09f07074299 100644 --- a/erpnext/patches/v12_0/update_price_list_currency_in_bom.py +++ b/erpnext/patches/v12_0/update_price_list_currency_in_bom.py @@ -28,4 +28,4 @@ def execute(): plc_conversion_rate = get_exchange_rate(d.currency, d.company_currency, getdate(d.creation), "for_buying") - frappe.db.set_value("BOM", d.name, "plc_conversion_rate", plc_conversion_rate) \ No newline at end of file + frappe.db.set_value("BOM", d.name, "plc_conversion_rate", plc_conversion_rate) diff --git a/erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py b/erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py index 7450e9cd8c0..8dbfa1866d3 100644 --- a/erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py +++ b/erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py @@ -19,4 +19,4 @@ def execute(): gst_state = 'Dadra and Nagar Haveli and Daman and Diu', gst_state_number = 26 WHERE gst_state = 'Daman and Diu' - """) \ No newline at end of file + """) diff --git a/erpnext/patches/v12_0/update_uom_conversion_factor.py b/erpnext/patches/v12_0/update_uom_conversion_factor.py index b5a20aa6fd9..24914fd13bc 100644 --- a/erpnext/patches/v12_0/update_uom_conversion_factor.py +++ b/erpnext/patches/v12_0/update_uom_conversion_factor.py @@ -8,4 +8,4 @@ def execute(): frappe.reload_doc("setup", "doctype", "UOM") frappe.reload_doc("stock", "doctype", "UOM Category") - add_uom_data() \ No newline at end of file + add_uom_data() diff --git a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py new file mode 100644 index 00000000000..0d8109c41ad --- /dev/null +++ b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py @@ -0,0 +1,112 @@ +# Copyright (c) 2020, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from frappe.utils import cstr, flt, cint +from erpnext.stock.stock_ledger import make_sl_entries +from erpnext.controllers.stock_controller import create_repost_item_valuation_entry + +def execute(): + if not frappe.db.has_column('Work Order', 'has_batch_no'): + return + + frappe.reload_doc('manufacturing', 'doctype', 'manufacturing_settings') + if cint(frappe.db.get_single_value('Manufacturing Settings', 'make_serial_no_batch_from_work_order')): + return + + frappe.reload_doc('manufacturing', 'doctype', 'work_order') + filters = { + 'docstatus': 1, + 'produced_qty': ('>', 0), + 'creation': ('>=', '2021-06-29 00:00:00'), + 'has_batch_no': 1 + } + + fields = ['name', 'production_item'] + + work_orders = [d.name for d in frappe.get_all('Work Order', filters = filters, fields=fields)] + + if not work_orders: + return + + repost_stock_entries = [] + + stock_entries = frappe.db.sql_list(''' + SELECT + se.name + FROM + `tabStock Entry` se + WHERE + se.purpose = 'Manufacture' and se.docstatus < 2 and se.work_order in %s + and not exists( + select name from `tabStock Entry Detail` sed where sed.parent = se.name and sed.is_finished_item = 1 + ) + ORDER BY + se.posting_date, se.posting_time + ''', (work_orders,)) + + if stock_entries: + print('Length of stock entries', len(stock_entries)) + + for stock_entry in stock_entries: + doc = frappe.get_doc('Stock Entry', stock_entry) + doc.set_work_order_details() + doc.load_items_from_bom() + doc.calculate_rate_and_amount() + set_expense_account(doc) + doc.make_batches('t_warehouse') + + if doc.docstatus == 0: + doc.save() + else: + repost_stock_entry(doc) + repost_stock_entries.append(doc) + + for repost_doc in repost_stock_entries: + repost_future_sle_and_gle(repost_doc) + +def set_expense_account(doc): + for row in doc.items: + if row.is_finished_item and not row.expense_account: + row.expense_account = frappe.get_cached_value('Company', doc.company, 'stock_adjustment_account') + +def repost_stock_entry(doc): + doc.db_update() + for child_row in doc.items: + if child_row.is_finished_item: + child_row.db_update() + + sl_entries = [] + finished_item_row = doc.get_finished_item_row() + get_sle_for_target_warehouse(doc, sl_entries, finished_item_row) + + if sl_entries: + try: + make_sl_entries(sl_entries, True) + except Exception: + print(f'SLE entries not posted for the stock entry {doc.name}') + traceback = frappe.get_traceback() + frappe.log_error(traceback) + +def get_sle_for_target_warehouse(doc, sl_entries, finished_item_row): + for d in doc.get('items'): + if cstr(d.t_warehouse) and finished_item_row and d.name == finished_item_row.name: + sle = doc.get_sl_entries(d, { + "warehouse": cstr(d.t_warehouse), + "actual_qty": flt(d.transfer_qty), + "incoming_rate": flt(d.valuation_rate) + }) + + sle.recalculate_rate = 1 + sl_entries.append(sle) + +def repost_future_sle_and_gle(doc): + args = frappe._dict({ + "posting_date": doc.posting_date, + "posting_time": doc.posting_time, + "voucher_type": doc.doctype, + "voucher_no": doc.name, + "company": doc.company + }) + + create_repost_item_valuation_entry(args) diff --git a/erpnext/patches/v13_0/add_naming_series_to_old_projects.py b/erpnext/patches/v13_0/add_naming_series_to_old_projects.py index 5ed9040f1ed..a7b66f0d2bb 100644 --- a/erpnext/patches/v13_0/add_naming_series_to_old_projects.py +++ b/erpnext/patches/v13_0/add_naming_series_to_old_projects.py @@ -10,4 +10,3 @@ def execute(): naming_series = 'PROJ-.####' WHERE naming_series is NULL""") - diff --git a/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py b/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py index be85cfdeeff..7de9fa1e23e 100644 --- a/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py +++ b/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py @@ -5,4 +5,4 @@ def execute(): frappe.reload_doctype("Buying Settings") buying_settings = frappe.get_single("Buying Settings") buying_settings.bill_for_rejected_quantity_in_purchase_invoice = 0 - buying_settings.save() \ No newline at end of file + buying_settings.save() diff --git a/erpnext/patches/v13_0/change_default_pos_print_format.py b/erpnext/patches/v13_0/change_default_pos_print_format.py index 605a29e4778..1e4f383dda6 100644 --- a/erpnext/patches/v13_0/change_default_pos_print_format.py +++ b/erpnext/patches/v13_0/change_default_pos_print_format.py @@ -5,4 +5,4 @@ def execute(): frappe.db.sql( """UPDATE `tabPOS Profile` profile SET profile.`print_format` = 'POS Invoice' - WHERE profile.`print_format` = 'Point of Sale'""") \ No newline at end of file + WHERE profile.`print_format` = 'Point of Sale'""") diff --git a/erpnext/patches/v13_0/check_is_income_tax_component.py b/erpnext/patches/v13_0/check_is_income_tax_component.py index c92d52dcec0..ebae3ad7157 100644 --- a/erpnext/patches/v13_0/check_is_income_tax_component.py +++ b/erpnext/patches/v13_0/check_is_income_tax_component.py @@ -43,4 +43,4 @@ def execute(): if frappe.db.exists("Salary Component", "Provident Fund"): frappe.db.set_value("Salary Component", "Provident Fund", "component_type", "Provident Fund") if frappe.db.exists("Salary Component", "Professional Tax"): - frappe.db.set_value("Salary Component", "Professional Tax", "component_type", "Professional Tax") \ No newline at end of file + frappe.db.set_value("Salary Component", "Professional Tax", "component_type", "Professional Tax") diff --git a/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py index 289b6a761e3..341955aa35f 100644 --- a/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py +++ b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py @@ -20,4 +20,4 @@ def execute(): "doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter - }).insert(ignore_permissions=True) \ No newline at end of file + }).insert(ignore_permissions=True) diff --git a/erpnext/patches/v13_0/create_healthcare_custom_fields_in_stock_entry_detail.py b/erpnext/patches/v13_0/create_healthcare_custom_fields_in_stock_entry_detail.py index 585e5406265..08d4876c0d1 100644 --- a/erpnext/patches/v13_0/create_healthcare_custom_fields_in_stock_entry_detail.py +++ b/erpnext/patches/v13_0/create_healthcare_custom_fields_in_stock_entry_detail.py @@ -7,4 +7,4 @@ def execute(): return if data['custom_fields']: - create_custom_fields(data['custom_fields']) \ No newline at end of file + create_custom_fields(data['custom_fields']) 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 index 90dc0e2e18b..9a354537f7d 100644 --- 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 @@ -74,6 +74,3 @@ def create_assignment(employee, leave_policy, leave_period=None, allocation_exis def get_employee_with_grade(grade): return frappe.get_list("Employee", filters = {"grade": grade}) - - - diff --git a/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py b/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py index 59b2e49b26e..6ad3402ba02 100644 --- a/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py +++ b/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py @@ -15,4 +15,4 @@ def execute(): frappe.reload_doc('accounts', 'doctype', 'pos_invoice') frappe.reload_doc('accounts', 'doctype', 'pos_invoice_item') - make_custom_fields() \ No newline at end of file + make_custom_fields() diff --git a/erpnext/patches/v13_0/delete_old_purchase_reports.py b/erpnext/patches/v13_0/delete_old_purchase_reports.py index 8bdc07ee5b8..c17aad06c7f 100644 --- a/erpnext/patches/v13_0/delete_old_purchase_reports.py +++ b/erpnext/patches/v13_0/delete_old_purchase_reports.py @@ -20,4 +20,4 @@ def delete_auto_email_reports(report): """ Check for one or multiple Auto Email Reports and delete """ auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"]) for auto_email_report in auto_email_reports: - frappe.delete_doc("Auto Email Report", auto_email_report[0]) \ No newline at end of file + frappe.delete_doc("Auto Email Report", auto_email_report[0]) diff --git a/erpnext/patches/v13_0/delete_old_sales_reports.py b/erpnext/patches/v13_0/delete_old_sales_reports.py index 0f44865808a..671c012c8a0 100644 --- a/erpnext/patches/v13_0/delete_old_sales_reports.py +++ b/erpnext/patches/v13_0/delete_old_sales_reports.py @@ -18,4 +18,4 @@ def delete_auto_email_reports(report): """ Check for one or multiple Auto Email Reports and delete """ auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"]) for auto_email_report in auto_email_reports: - frappe.delete_doc("Auto Email Report", auto_email_report[0]) \ No newline at end of file + frappe.delete_doc("Auto Email Report", auto_email_report[0]) diff --git a/erpnext/patches/v13_0/delete_orphaned_tables.py b/erpnext/patches/v13_0/delete_orphaned_tables.py new file mode 100644 index 00000000000..50a4a0efcbe --- /dev/null +++ b/erpnext/patches/v13_0/delete_orphaned_tables.py @@ -0,0 +1,69 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe +from frappe.utils import getdate + +def execute(): + frappe.reload_doc('setup', 'doctype', 'transaction_deletion_record') + + if has_deleted_company_transactions(): + child_doctypes = get_child_doctypes_whose_parent_doctypes_were_affected() + + for doctype in child_doctypes: + docs = frappe.get_all(doctype, fields=['name', 'parent', 'parenttype', 'creation']) + + for doc in docs: + if not frappe.db.exists(doc['parenttype'], doc['parent']): + frappe.db.delete(doctype, {'name': doc['name']}) + + elif check_for_new_doc_with_same_name_as_deleted_parent(doc): + frappe.db.delete(doctype, {'name': doc['name']}) + +def has_deleted_company_transactions(): + return frappe.get_all('Transaction Deletion Record') + +def get_child_doctypes_whose_parent_doctypes_were_affected(): + parent_doctypes = get_affected_doctypes() + child_doctypes = frappe.get_all( + 'DocField', + filters={ + 'fieldtype': 'Table', + 'parent':['in', parent_doctypes] + }, pluck='options') + + return child_doctypes + +def get_affected_doctypes(): + affected_doctypes = [] + tdr_docs = frappe.get_all('Transaction Deletion Record', pluck="name") + + for tdr in tdr_docs: + tdr_doc = frappe.get_doc("Transaction Deletion Record", tdr) + + for doctype in tdr_doc.doctypes: + if is_not_child_table(doctype.doctype_name): + affected_doctypes.append(doctype.doctype_name) + + affected_doctypes = remove_duplicate_items(affected_doctypes) + return affected_doctypes + +def is_not_child_table(doctype): + return not bool(frappe.get_value('DocType', doctype, 'istable')) + +def remove_duplicate_items(affected_doctypes): + return list(set(affected_doctypes)) + +def check_for_new_doc_with_same_name_as_deleted_parent(doc): + """ + Compares creation times of parent and child docs. + Since Transaction Deletion Record resets the naming series after deletion, + it allows the creation of new docs with the same names as the deleted ones. + """ + + parent_creation_time = frappe.db.get_value(doc['parenttype'], doc['parent'], 'creation') + child_creation_time = doc['creation'] + + return getdate(parent_creation_time) > getdate(child_creation_time) diff --git a/erpnext/patches/v13_0/delete_report_requested_items_to_order.py b/erpnext/patches/v13_0/delete_report_requested_items_to_order.py index 94a9fa85a8e..8d6340d44ef 100644 --- a/erpnext/patches/v13_0/delete_report_requested_items_to_order.py +++ b/erpnext/patches/v13_0/delete_report_requested_items_to_order.py @@ -9,4 +9,4 @@ def execute(): frappe.db.sql(""" DELETE FROM `tabReport` WHERE name = 'Requested Items to Order' - """) \ No newline at end of file + """) diff --git a/erpnext/patches/v13_0/drop_razorpay_payload_column.py b/erpnext/patches/v13_0/drop_razorpay_payload_column.py index 8980fd00392..76b8041cd94 100644 --- a/erpnext/patches/v13_0/drop_razorpay_payload_column.py +++ b/erpnext/patches/v13_0/drop_razorpay_payload_column.py @@ -4,4 +4,4 @@ import frappe def execute(): if frappe.db.exists("DocType", "Membership"): if 'webhook_payload' in frappe.db.get_table_columns("Membership"): - frappe.db.sql("alter table `tabMembership` drop column webhook_payload") \ No newline at end of file + frappe.db.sql("alter table `tabMembership` drop column webhook_payload") diff --git a/erpnext/patches/v13_0/fix_non_unique_represents_company.py b/erpnext/patches/v13_0/fix_non_unique_represents_company.py index 61dc824dd4c..f20c73ae102 100644 --- a/erpnext/patches/v13_0/fix_non_unique_represents_company.py +++ b/erpnext/patches/v13_0/fix_non_unique_represents_company.py @@ -5,4 +5,4 @@ def execute(): update tabCustomer set represents_company = NULL where represents_company = '' - """) \ No newline at end of file + """) diff --git a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py index 11e1e9b3b94..dca43b4193d 100644 --- a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py +++ b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py @@ -12,7 +12,7 @@ def execute(): German companies used to use a dedicated payable/receivable account for every party to mimick party accounts in the external accounting software "DATEV". This is no longer necessary. The reference ID for DATEV will be - stored in a new custom field "debtor_creditor_number". + stored in a new custom field "debtor_creditor_number". """ company_list = frappe.get_all('Company', filters={'country': 'Germany'}) diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index 021bb72cae6..c4ad1b7ff4f 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -67,4 +67,4 @@ def execute(): def get_creation_time(): return frappe.db.sql(''' SELECT create_time FROM - INFORMATION_SCHEMA.TABLES where TABLE_NAME = "tabRepost Item Valuation" ''', as_list=1)[0][0] \ No newline at end of file + INFORMATION_SCHEMA.TABLES where TABLE_NAME = "tabRepost Item Valuation" ''', as_list=1)[0][0] diff --git a/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py b/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py index ee7734053c1..d2228c3bf31 100644 --- a/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py +++ b/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py @@ -9,7 +9,7 @@ def execute(): '''`sales_invoice` field from loyalty point entry is splitted into `invoice_type` & `invoice` fields''' frappe.reload_doc("Accounts", "doctype", "loyalty_point_entry") - + if not frappe.db.has_column('Loyalty Point Entry', 'sales_invoice'): return @@ -17,4 +17,4 @@ def execute(): """UPDATE `tabLoyalty Point Entry` lpe SET lpe.`invoice_type` = 'Sales Invoice', lpe.`invoice` = lpe.`sales_invoice` WHERE lpe.`sales_invoice` IS NOT NULL - AND (lpe.`invoice` IS NULL OR lpe.`invoice` = '')""") \ No newline at end of file + AND (lpe.`invoice` IS NULL OR lpe.`invoice` = '')""") diff --git a/erpnext/patches/v13_0/make_non_standard_user_type.py b/erpnext/patches/v13_0/make_non_standard_user_type.py index a9d7883d40a..73361f00262 100644 --- a/erpnext/patches/v13_0/make_non_standard_user_type.py +++ b/erpnext/patches/v13_0/make_non_standard_user_type.py @@ -21,4 +21,4 @@ def execute(): frappe.flags.ignore_select_perm = True frappe.flags.update_select_perm_after_migrate = True - add_non_standard_user_types() \ No newline at end of file + add_non_standard_user_types() diff --git a/erpnext/patches/v13_0/move_branch_code_to_bank_account.py b/erpnext/patches/v13_0/move_branch_code_to_bank_account.py index 833ae2a48fb..24d9196d29f 100644 --- a/erpnext/patches/v13_0/move_branch_code_to_bank_account.py +++ b/erpnext/patches/v13_0/move_branch_code_to_bank_account.py @@ -14,4 +14,4 @@ def execute(): frappe.db.sql("""UPDATE `tabBank` b, `tabBank Account` ba SET ba.branch_code = b.branch_code WHERE ba.bank = b.name AND - ifnull(b.branch_code, '') != '' AND ifnull(ba.branch_code, '') = ''""") \ No newline at end of file + ifnull(b.branch_code, '') != '' AND ifnull(ba.branch_code, '') = ''""") diff --git a/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py b/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py index fde8f864703..15aeb76e53f 100644 --- a/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py +++ b/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py @@ -51,4 +51,3 @@ def execute(): and parent = %s and salary_component = %s """, (salary["name"], comp_type, salary["salary_slip"], salary["salary_component"])) - diff --git a/erpnext/patches/v13_0/rename_issue_doctype_fields.py b/erpnext/patches/v13_0/rename_issue_doctype_fields.py index fa1dfed6435..41c51c36dcb 100644 --- a/erpnext/patches/v13_0/rename_issue_doctype_fields.py +++ b/erpnext/patches/v13_0/rename_issue_doctype_fields.py @@ -37,7 +37,7 @@ def execute(): if frappe.db.exists('DocType', 'Opportunity'): opportunities = frappe.db.get_all('Opportunity', fields=['name', 'mins_to_first_response'], order_by='creation desc') - frappe.reload_doc('crm', 'doctype', 'opportunity') + frappe.reload_doctype('Opportunity', force=True) rename_field('Opportunity', 'mins_to_first_response', 'first_response_time') # change fieldtype to duration diff --git a/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py index 48325fc2d43..4ef04ad9b1b 100644 --- a/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py +++ b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py @@ -17,4 +17,4 @@ def rename_status(): status = 'On Hold' WHERE status = 'Hold' - """) \ No newline at end of file + """) diff --git a/erpnext/patches/v13_0/rename_membership_settings_to_non_profit_settings.py b/erpnext/patches/v13_0/rename_membership_settings_to_non_profit_settings.py index 3fa09a7baaa..f60567b6b21 100644 --- a/erpnext/patches/v13_0/rename_membership_settings_to_non_profit_settings.py +++ b/erpnext/patches/v13_0/rename_membership_settings_to_non_profit_settings.py @@ -19,4 +19,4 @@ def execute(): } for old_name, new_name in rename_fields_map.items(): - rename_field("Non Profit Settings", old_name, new_name) \ No newline at end of file + rename_field("Non Profit Settings", old_name, new_name) diff --git a/erpnext/patches/v13_0/replace_pos_page_with_point_of_sale_page.py b/erpnext/patches/v13_0/replace_pos_page_with_point_of_sale_page.py index 390e217cada..d8bcd7f0775 100644 --- a/erpnext/patches/v13_0/replace_pos_page_with_point_of_sale_page.py +++ b/erpnext/patches/v13_0/replace_pos_page_with_point_of_sale_page.py @@ -3,4 +3,4 @@ import frappe def execute(): if frappe.db.exists("Page", "point-of-sale"): - frappe.rename_doc("Page", "pos", "point-of-sale", 1, 1) \ No newline at end of file + frappe.rename_doc("Page", "pos", "point-of-sale", 1, 1) diff --git a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py index 7cb264830ab..bc1fc98e4da 100644 --- a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py +++ b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py @@ -23,5 +23,5 @@ def execute(): pos_payment_method.parentfield = payment_mode.parentfield pos_payment_method.parenttype = payment_mode.parenttype pos_payment_method.db_insert() - + frappe.db.sql("""delete from `tabSales Invoice Payment` where parent=%s""", pos_profile.name) diff --git a/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py b/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py index 66857c4e659..13ec41ec55e 100644 --- a/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py +++ b/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py @@ -4,4 +4,4 @@ def execute(): frappe.reload_doc('HR', 'doctype', 'Leave Allocation') frappe.reload_doc('HR', 'doctype', 'Leave Ledger Entry') frappe.db.sql("""update `tabLeave Ledger Entry` as lle set company = (select company from `tabEmployee` where employee = lle.employee)""") - frappe.db.sql("""update `tabLeave Allocation` as la set company = (select company from `tabEmployee` where employee = la.employee)""") \ No newline at end of file + frappe.db.sql("""update `tabLeave Allocation` as la set company = (select company from `tabEmployee` where employee = la.employee)""") diff --git a/erpnext/patches/v13_0/set_payment_channel_in_payment_gateway_account.py b/erpnext/patches/v13_0/set_payment_channel_in_payment_gateway_account.py index edca2383930..7f75946af9f 100644 --- a/erpnext/patches/v13_0/set_payment_channel_in_payment_gateway_account.py +++ b/erpnext/patches/v13_0/set_payment_channel_in_payment_gateway_account.py @@ -14,4 +14,4 @@ def set_payment_channel_as_email(): frappe.db.sql(""" UPDATE `tabPayment Gateway Account` SET `payment_channel` = "Email" - """) \ No newline at end of file + """) diff --git a/erpnext/patches/v13_0/set_pos_closing_as_failed.py b/erpnext/patches/v13_0/set_pos_closing_as_failed.py index 1c576db1c7e..7968e74f50f 100644 --- a/erpnext/patches/v13_0/set_pos_closing_as_failed.py +++ b/erpnext/patches/v13_0/set_pos_closing_as_failed.py @@ -4,4 +4,4 @@ import frappe def execute(): frappe.reload_doc('accounts', 'doctype', 'pos_closing_entry') - frappe.db.sql("update `tabPOS Closing Entry` set `status` = 'Failed' where `status` = 'Queued'") \ No newline at end of file + frappe.db.sql("update `tabPOS Closing Entry` set `status` = 'Failed' where `status` = 'Queued'") diff --git a/erpnext/patches/v13_0/set_training_event_attendance.py b/erpnext/patches/v13_0/set_training_event_attendance.py index 18cad8d86c0..3db183fb2ab 100644 --- a/erpnext/patches/v13_0/set_training_event_attendance.py +++ b/erpnext/patches/v13_0/set_training_event_attendance.py @@ -6,4 +6,4 @@ def execute(): frappe.reload_doc('hr', 'doctype', 'training_event_employee') frappe.db.sql("update `tabTraining Event Employee` set `attendance` = 'Present'") - frappe.db.sql("update `tabTraining Event Employee` set `is_mandatory` = 1 where `attendance` = 'Mandatory'") \ No newline at end of file + frappe.db.sql("update `tabTraining Event Employee` set `is_mandatory` = 1 where `attendance` = 'Mandatory'") diff --git a/erpnext/patches/v13_0/set_youtube_video_id.py b/erpnext/patches/v13_0/set_youtube_video_id.py index c3b49eb4fe5..f6104d1579f 100644 --- a/erpnext/patches/v13_0/set_youtube_video_id.py +++ b/erpnext/patches/v13_0/set_youtube_video_id.py @@ -7,4 +7,4 @@ def execute(): for video in frappe.get_all("Video", fields=["name", "url", "youtube_video_id"]): if video.url and not video.youtube_video_id: - frappe.db.set_value("Video", video.name, "youtube_video_id", get_id_from_url(video.url)) \ No newline at end of file + frappe.db.set_value("Video", video.name, "youtube_video_id", get_id_from_url(video.url)) diff --git a/erpnext/patches/v13_0/setting_custom_roles_for_some_regional_reports.py b/erpnext/patches/v13_0/setting_custom_roles_for_some_regional_reports.py index ecc7822e1d7..c8c160fae71 100644 --- a/erpnext/patches/v13_0/setting_custom_roles_for_some_regional_reports.py +++ b/erpnext/patches/v13_0/setting_custom_roles_for_some_regional_reports.py @@ -7,4 +7,4 @@ def execute(): if not company: return - add_custom_roles_for_reports() \ No newline at end of file + add_custom_roles_for_reports() diff --git a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py index d927524a3c0..83581dd4144 100644 --- a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py +++ b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py @@ -13,4 +13,4 @@ def execute(): frappe.reload_doc("healthcare", "doctype", "Patient History Standard Document Type") frappe.reload_doc("healthcare", "doctype", "Patient History Custom Document Type") - setup_patient_history_settings() \ No newline at end of file + setup_patient_history_settings() diff --git a/erpnext/patches/v13_0/shopify_deprecation_warning.py b/erpnext/patches/v13_0/shopify_deprecation_warning.py new file mode 100644 index 00000000000..8b0f1935cfb --- /dev/null +++ b/erpnext/patches/v13_0/shopify_deprecation_warning.py @@ -0,0 +1,15 @@ +import click +import frappe + + +def execute(): + + frappe.reload_doc("erpnext_integrations", "doctype", "shopify_settings") + if not frappe.db.get_single_value("Shopify Settings", "enable_shopify"): + return + + click.secho( + "Shopify Integration is moved to a separate app and will be removed from ERPNext in version-14.\n" + "Please install the app to continue using the integration: https://github.com/frappe/ecommerce_integrations", + fg="yellow", + ) diff --git a/erpnext/patches/v13_0/stock_entry_enhancements.py b/erpnext/patches/v13_0/stock_entry_enhancements.py index 0bdcc9c0e88..7b93ce35768 100644 --- a/erpnext/patches/v13_0/stock_entry_enhancements.py +++ b/erpnext/patches/v13_0/stock_entry_enhancements.py @@ -8,18 +8,18 @@ def execute(): frappe.reload_doc("stock", "doctype", "stock_entry") if frappe.db.has_column("Stock Entry", "add_to_transit"): frappe.db.sql(""" - UPDATE `tabStock Entry` SET + UPDATE `tabStock Entry` SET stock_entry_type = 'Material Transfer', purpose = 'Material Transfer', add_to_transit = 1 WHERE stock_entry_type = 'Send to Warehouse' """) - frappe.db.sql("""UPDATE `tabStock Entry` SET + frappe.db.sql("""UPDATE `tabStock Entry` SET stock_entry_type = 'Material Transfer', purpose = 'Material Transfer' WHERE stock_entry_type = 'Receive at Warehouse' """) - + frappe.reload_doc("stock", "doctype", "warehouse_type") if not frappe.db.exists('Warehouse Type', 'Transit'): doc = frappe.new_doc('Warehouse Type') @@ -28,4 +28,4 @@ def execute(): frappe.reload_doc("stock", "doctype", "stock_entry_type") frappe.delete_doc_if_exists("Stock Entry Type", "Send to Warehouse") - frappe.delete_doc_if_exists("Stock Entry Type", "Receive at Warehouse") \ No newline at end of file + frappe.delete_doc_if_exists("Stock Entry Type", "Receive at Warehouse") diff --git a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py index adfa20e368a..50f233deef4 100644 --- a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py +++ b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py @@ -38,4 +38,4 @@ def execute(): jc.production_item = wo.production_item, jc.item_name = wo.item_name WHERE jc.work_order = wo.name and IFNULL(jc.production_item, "") = "" - """) \ No newline at end of file + """) diff --git a/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py b/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py new file mode 100644 index 00000000000..dc9ed18eade --- /dev/null +++ b/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py @@ -0,0 +1,9 @@ +import frappe + +def execute(): + """ Correct amount in child table of required items table.""" + + frappe.reload_doc("manufacturing", "doctype", "work_order") + frappe.reload_doc("manufacturing", "doctype", "work_order_item") + + frappe.db.sql("""UPDATE `tabWork Order Item` SET amount = rate * required_qty""") diff --git a/erpnext/patches/v13_0/update_deferred_settings.py b/erpnext/patches/v13_0/update_deferred_settings.py index a7d82077b76..bcc09527a29 100644 --- a/erpnext/patches/v13_0/update_deferred_settings.py +++ b/erpnext/patches/v13_0/update_deferred_settings.py @@ -8,4 +8,4 @@ def execute(): accounts_settings.book_deferred_entries_based_on = 'Days' accounts_settings.book_deferred_entries_via_journal_entry = 0 accounts_settings.submit_journal_entries = 0 - accounts_settings.save() \ No newline at end of file + accounts_settings.save() diff --git a/erpnext/patches/v13_0/update_export_type_for_gst.py b/erpnext/patches/v13_0/update_export_type_for_gst.py new file mode 100644 index 00000000000..ef70b55d94c --- /dev/null +++ b/erpnext/patches/v13_0/update_export_type_for_gst.py @@ -0,0 +1,32 @@ +import frappe + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + # Update custom fields + fieldname = frappe.db.get_value('Custom Field', {'dt': 'Customer', 'fieldname': 'export_type'}) + if fieldname: + frappe.db.set_value('Custom Field', fieldname, + { + 'default': '', + 'mandatory_depends_on': 'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)' + }) + + fieldname = frappe.db.get_value('Custom Field', {'dt': 'Supplier', 'fieldname': 'export_type'}) + if fieldname: + frappe.db.set_value('Custom Field', fieldname, + { + 'default': '', + 'mandatory_depends_on': 'eval:in_list(["SEZ", "Overseas"], doc.gst_category)' + }) + + # Update Customer/Supplier Masters + frappe.db.sql(""" + UPDATE `tabCustomer` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas', 'Deemed Export') + """) + + frappe.db.sql(""" + UPDATE `tabSupplier` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas') + """) diff --git a/erpnext/patches/v13_0/update_job_card_details.py b/erpnext/patches/v13_0/update_job_card_details.py index d4e65c6f2f2..733b3a960cf 100644 --- a/erpnext/patches/v13_0/update_job_card_details.py +++ b/erpnext/patches/v13_0/update_job_card_details.py @@ -13,4 +13,4 @@ def execute(): SET jc.hour_rate = wo.hour_rate WHERE jc.operation_id = wo.name and jc.docstatus < 2 and wo.hour_rate > 0 - """) \ No newline at end of file + """) diff --git a/erpnext/patches/v13_0/update_project_template_tasks.py b/erpnext/patches/v13_0/update_project_template_tasks.py index 8cc27d217fe..b41b74205c7 100644 --- a/erpnext/patches/v13_0/update_project_template_tasks.py +++ b/erpnext/patches/v13_0/update_project_template_tasks.py @@ -44,4 +44,4 @@ def execute(): "task": tsk.name, "subject": tsk.subject }) - template.save() \ No newline at end of file + template.save() diff --git a/erpnext/patches/v13_0/update_reason_for_resignation_in_employee.py b/erpnext/patches/v13_0/update_reason_for_resignation_in_employee.py index 792118fbee2..ccdc334f306 100644 --- a/erpnext/patches/v13_0/update_reason_for_resignation_in_employee.py +++ b/erpnext/patches/v13_0/update_reason_for_resignation_in_employee.py @@ -12,4 +12,3 @@ def execute(): SET reason_for_leaving = reason_for_resignation WHERE status = 'Left' and reason_for_leaving is null and reason_for_resignation is not null """) - diff --git a/erpnext/patches/v13_0/update_recipient_email_digest.py b/erpnext/patches/v13_0/update_recipient_email_digest.py new file mode 100644 index 00000000000..ed90e126670 --- /dev/null +++ b/erpnext/patches/v13_0/update_recipient_email_digest.py @@ -0,0 +1,22 @@ +# Copyright (c) 2020, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc("setup", "doctype", "Email Digest") + frappe.reload_doc("setup", "doctype", "Email Digest Recipient") + email_digests = frappe.db.get_list('Email Digest', fields=['name', 'recipient_list']) + for email_digest in email_digests: + if email_digest.recipient_list: + for recipient in email_digest.recipient_list.split("\n"): + if frappe.db.exists('User', recipient): + doc = frappe.get_doc({ + 'doctype': 'Email Digest Recipient', + 'parenttype': 'Email Digest', + 'parentfield': 'recipients', + 'parent': email_digest.name, + 'recipient': recipient + }) + doc.insert() diff --git a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py index 7f42cd92e3c..e642547ef82 100644 --- a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py +++ b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py @@ -1,8 +1,7 @@ -# Copyright (c) 2019, Frappe and Contributors +# Copyright (c) 2021, Frappe and Contributors # License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals import frappe +from erpnext.controllers.status_updater import OverAllowanceError def execute(): frappe.reload_doc('stock', 'doctype', 'purchase_receipt') @@ -14,9 +13,15 @@ def execute(): for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1}): # Update original receipt/delivery document from return return_doc = frappe.get_cached_doc(doctype, return_doc.name) - return_doc.update_prevdoc_status() + try: + return_doc.update_prevdoc_status() + except OverAllowanceError: + frappe.db.rollback() + continue + return_against = frappe.get_doc(doctype, return_doc.return_against) return_against.update_billing_status() + frappe.db.commit() # Set received qty in stock uom in PR, as returned qty is checked against it frappe.db.sql(""" update `tabPurchase Receipt Item` @@ -24,4 +29,4 @@ def execute(): where docstatus = 1 """) for doctype in ('Purchase Receipt', 'Delivery Note'): - update_from_return_docs(doctype) \ No newline at end of file + update_from_return_docs(doctype) diff --git a/erpnext/patches/v13_0/update_subscription.py b/erpnext/patches/v13_0/update_subscription.py index 871ebf17c4e..d25e9c805b7 100644 --- a/erpnext/patches/v13_0/update_subscription.py +++ b/erpnext/patches/v13_0/update_subscription.py @@ -38,4 +38,4 @@ def execute(): UPDATE `tabSubscription Plan` SET price_determination = %s WHERE price_determination = %s - """, (value, key)) \ No newline at end of file + """, (value, key)) diff --git a/erpnext/patches/v13_0/update_subscription_status_in_memberships.py b/erpnext/patches/v13_0/update_subscription_status_in_memberships.py new file mode 100644 index 00000000000..d9c3e453d47 --- /dev/null +++ b/erpnext/patches/v13_0/update_subscription_status_in_memberships.py @@ -0,0 +1,9 @@ +import frappe + +def execute(): + if frappe.db.exists('DocType', 'Member'): + frappe.reload_doc('Non Profit', 'doctype', 'Member') + + if frappe.db.has_column('Member', 'subscription_activated'): + frappe.db.sql('UPDATE `tabMember` SET subscription_status = "Active" WHERE subscription_activated = 1') + frappe.db.sql_ddl('ALTER table `tabMember` DROP COLUMN subscription_activated') diff --git a/erpnext/patches/v13_0/update_tds_check_field.py b/erpnext/patches/v13_0/update_tds_check_field.py new file mode 100644 index 00000000000..341b0e8e2e2 --- /dev/null +++ b/erpnext/patches/v13_0/update_tds_check_field.py @@ -0,0 +1,9 @@ +import frappe + +def execute(): + if frappe.db.has_table("Tax Withholding Category") \ + and frappe.db.has_column("Tax Withholding Category", "round_off_tax_amount"): + frappe.db.sql(""" + UPDATE `tabTax Withholding Category` set round_off_tax_amount = 0 + WHERE round_off_tax_amount IS NULL + """) diff --git a/erpnext/patches/v13_0/update_timesheet_changes.py b/erpnext/patches/v13_0/update_timesheet_changes.py index 93b7f8e59a4..a36c84ea6e2 100644 --- a/erpnext/patches/v13_0/update_timesheet_changes.py +++ b/erpnext/patches/v13_0/update_timesheet_changes.py @@ -22,4 +22,4 @@ def execute(): exchange_rate = 1.0, base_total_billable_amount = total_billable_amount, base_total_billed_amount = total_billed_amount, - base_total_costing_amount = total_costing_amount""".format(base_currency)) \ No newline at end of file + base_total_costing_amount = total_costing_amount""".format(base_currency)) diff --git a/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py b/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py index 340bf4947b6..7d344f9cd7e 100644 --- a/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py +++ b/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py @@ -96,8 +96,8 @@ def execute(): # update currency in following doctypes based on company currency doctypes_for_currency = ['Employee Advance', 'Leave Encashment', 'Employee Benefit Application', - 'Employee Benefit Claim', 'Employee Incentive', 'Additional Salary', - 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission', + 'Employee Benefit Claim', 'Employee Incentive', 'Additional Salary', + 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission', 'Income Tax Slab', 'Retention Bonus', 'Salary Structure'] for dt in doctypes_for_currency: diff --git a/erpnext/patches/v8_1/removed_roles_from_gst_report_non_indian_account.py b/erpnext/patches/v8_1/removed_roles_from_gst_report_non_indian_account.py index ccb2e0ec74a..55f5f8201fb 100644 --- a/erpnext/patches/v8_1/removed_roles_from_gst_report_non_indian_account.py +++ b/erpnext/patches/v8_1/removed_roles_from_gst_report_non_indian_account.py @@ -15,4 +15,4 @@ def execute(): where parenttype = 'Report' and parent in('GST Sales Register', 'GST Purchase Register', 'GST Itemised Sales Register', - 'GST Itemised Purchase Register', 'Eway Bill')""") \ No newline at end of file + 'GST Itemised Purchase Register', 'Eway Bill')""") diff --git a/erpnext/patches/v8_1/setup_gst_india.py b/erpnext/patches/v8_1/setup_gst_india.py index e8b017d8644..c214990693c 100644 --- a/erpnext/patches/v8_1/setup_gst_india.py +++ b/erpnext/patches/v8_1/setup_gst_india.py @@ -50,4 +50,4 @@ ERPNext Team. try: sendmail_to_system_managers("[Important] ERPNext GST updates", message) except Exception as e: - pass \ No newline at end of file + pass diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index ebeddf97f9e..381f399e9fa 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -7,6 +7,7 @@ import frappe from frappe.model.document import Document from frappe import _, bold from frappe.utils import getdate, date_diff, comma_and, formatdate +from erpnext.hr.utils import validate_active_employee class AdditionalSalary(Document): def on_submit(self): @@ -19,6 +20,7 @@ class AdditionalSalary(Document): self.update_employee_referral(cancel=True) def validate(self): + validate_active_employee(self.employee) self.validate_dates() self.validate_salary_structure() self.validate_recurring_additional_salary_overlap() diff --git a/erpnext/payroll/doctype/additional_salary/test_additional_salary.py b/erpnext/payroll/doctype/additional_salary/test_additional_salary.py index 4d47f25fcf3..2a9c56179e7 100644 --- a/erpnext/payroll/doctype/additional_salary/test_additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/test_additional_salary.py @@ -27,7 +27,7 @@ class TestAdditionalSalary(unittest.TestCase): frappe.db.set_value("Employee", emp_id, "relieving_date", add_days(nowdate(), 1800)) salary_structure = make_salary_structure("Test Salary Structure Additional Salary", "Monthly", employee=emp_id) add_sal = get_additional_salary(emp_id) - + ss = make_employee_salary_slip("test_additional@salary.com", "Monthly", salary_structure=salary_structure.name) for earning in ss.earnings: if earning.salary_component == "Recurring Salary Component": 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 27df30a459c..c7fbb06b100 100644 --- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py +++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py @@ -9,10 +9,11 @@ from frappe.utils import date_diff, getdate, rounded, add_days, cstr, cint, flt from frappe.model.document import Document from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period_days, get_period_factor from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure -from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount +from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount, validate_active_employee class EmployeeBenefitApplication(Document): def validate(self): + 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) @@ -252,4 +253,4 @@ def get_earning_components_max_benefits(employee, date, earning_component): order by name """, salary_structure, earning_component) - return amount if amount else 0 \ No newline at end of file + return amount if amount else 0 diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py index d9937a7bb97..c6713f3aa46 100644 --- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py +++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py @@ -8,12 +8,13 @@ from frappe import _ from frappe.utils import flt from frappe.model.document import Document from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_max_benefits -from erpnext.hr.utils import get_previous_claimed_amount +from erpnext.hr.utils import get_previous_claimed_amount, validate_active_employee from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure class EmployeeBenefitClaim(Document): def validate(self): + validate_active_employee(self.employee) max_benefits = get_max_benefits(self.employee, self.claim_date) if not max_benefits or max_benefits <= 0: frappe.throw(_("Employee {0} has no maximum benefit amount").format(self.employee)) diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py index ead3db126f7..6b918ba76d1 100644 --- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py +++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py @@ -6,9 +6,11 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document +from erpnext.hr.utils import validate_active_employee class EmployeeIncentive(Document): def validate(self): + validate_active_employee(self.employee) self.validate_salary_structure() def validate_salary_structure(self): diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py index fb71a2877a1..e11d60a4649 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py @@ -8,11 +8,12 @@ from frappe.model.document import Document from frappe import _ from frappe.utils import flt from frappe.model.mapper import get_mapped_doc -from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \ +from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \ calculate_annual_eligible_hra_exemption, validate_duplicate_exemption_for_payroll_period class EmployeeTaxExemptionDeclaration(Document): def validate(self): + validate_active_employee(self.employee) validate_tax_declaration(self.declarations) validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee) self.set_total_declared_amount() diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py index 5bc33a65f2c..8131ae0fa85 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py @@ -7,11 +7,12 @@ import frappe from frappe.model.document import Document from frappe import _ from frappe.utils import flt -from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \ +from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \ calculate_hra_exemption_for_period, validate_duplicate_exemption_for_payroll_period class EmployeeTaxExemptionProofSubmission(Document): def validate(self): + validate_active_employee(self.employee) validate_tax_declaration(self.tax_exemption_proofs) self.set_total_actual_amount() self.set_total_exemption_amount() diff --git a/erpnext/payroll/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py b/erpnext/payroll/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py index a8dd7e4d6dd..d3f24c93780 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py @@ -13,4 +13,4 @@ class EmployeeTaxExemptionSubCategory(Document): category_max_amount = frappe.db.get_value("Employee Tax Exemption Category", self.exemption_category, "max_amount") if flt(self.max_amount) > flt(category_max_amount): frappe.throw(_("Max Exemption Amount cannot be greater than maximum exemption amount {0} of Tax Exemption Category {1}") - .format(category_max_amount, self.exemption_category)) \ No newline at end of file + .format(category_max_amount, self.exemption_category)) diff --git a/erpnext/payroll/doctype/gratuity/gratuity.js b/erpnext/payroll/doctype/gratuity/gratuity.js index 565d2c49f94..377f3c64916 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.js +++ b/erpnext/payroll/doctype/gratuity/gratuity.js @@ -69,4 +69,4 @@ frappe.ui.form.on('Gratuity', { } } -}); \ No newline at end of file +}); diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index 1acd6e342fd..8cb804db6fa 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -246,4 +246,3 @@ def get_last_salary_slip(employee): "employee": employee, 'docstatus': 1 }, order_by = "start_date desc")[0].name - diff --git a/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py index 5b2489f22cd..483e346a32d 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py +++ b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py @@ -17,4 +17,4 @@ def get_data(): 'items': ['Additional Salary'] } ] - } \ No newline at end of file + } diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js index ee6c5df7371..014a121c96a 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js @@ -37,4 +37,4 @@ frappe.ui.form.on('Gratuity Rule Slab', { frappe.throw(__("To(Year) year can not be less than From(year) ")); } } -}); \ No newline at end of file +}); diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py index 0d70163495a..0f27315cfbf 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py @@ -10,4 +10,4 @@ def get_data(): 'items': ['Gratuity'] } ] - } \ No newline at end of file + } diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry_dashboard.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry_dashboard.py index 7af507d119c..0346a7cc594 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry_dashboard.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry_dashboard.py @@ -13,4 +13,4 @@ def get_data(): 'items': ['Salary Slip', 'Journal Entry'] } ] - } \ No newline at end of file + } diff --git a/erpnext/payroll/doctype/payroll_period/payroll_period_dashboard.py b/erpnext/payroll/doctype/payroll_period/payroll_period_dashboard.py index 4e9c7c9e7cc..e33299559cc 100644 --- a/erpnext/payroll/doctype/payroll_period/payroll_period_dashboard.py +++ b/erpnext/payroll/doctype/payroll_period/payroll_period_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Employee Tax Exemption Proof Submission', 'Employee Tax Exemption Declaration'] }, ], - } \ No newline at end of file + } diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py index 049ea265cce..055bea74108 100644 --- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py +++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py @@ -7,11 +7,10 @@ import frappe from frappe.model.document import Document from frappe import _ from frappe.utils import getdate - +from erpnext.hr.utils import validate_active_employee class RetentionBonus(Document): def validate(self): - if frappe.get_value('Employee', self.employee, 'status') != 'Active': - frappe.throw(_('Cannot create Retention Bonus for Left or Inactive Employees')) + validate_active_employee(self.employee) if getdate(self.bonus_payment_date) < getdate(): frappe.throw(_('Bonus Payment Date cannot be a past date')) diff --git a/erpnext/payroll/doctype/salary_component/salary_component.js b/erpnext/payroll/doctype/salary_component/salary_component.js index dbf75140ac1..e9e6f81862c 100644 --- a/erpnext/payroll/doctype/salary_component/salary_component.js +++ b/erpnext/payroll/doctype/salary_component/salary_component.js @@ -4,11 +4,18 @@ frappe.ui.form.on('Salary Component', { setup: function(frm) { frm.set_query("account", "accounts", function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; + let d = frappe.get_doc(cdt, cdn); + + let root_type = "Liability"; + if (frm.doc.type == "Deduction") { + root_type = "Expense"; + } + return { filters: { "is_group": 0, - "company": d.company + "company": d.company, + "root_type": root_type } }; }); diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index bead880ef70..a25e94d122b 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -19,6 +19,7 @@ from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_appli from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry from erpnext.accounts.utils import get_fiscal_year +from erpnext.hr.utils import validate_active_employee from six import iteritems class SalarySlip(TransactionBase): @@ -39,6 +40,7 @@ class SalarySlip(TransactionBase): def validate(self): self.status = self.get_status() + validate_active_employee(self.employee) self.validate_dates() self.check_existing() if not self.salary_slip_based_on_timesheet: @@ -644,10 +646,13 @@ class SalarySlip(TransactionBase): continue if ( - (not d.additional_salary - and (not additional_salary or additional_salary.overwrite)) - or (additional_salary - and additional_salary.name == d.additional_salary) + ( + not d.additional_salary + and (not additional_salary or additional_salary.overwrite) + ) or ( + additional_salary + and additional_salary.name == d.additional_salary + ) ): component_row = d break @@ -678,8 +683,13 @@ class SalarySlip(TransactionBase): component_row.set('abbr', abbr) if additional_salary: - component_row.default_amount = 0 - component_row.additional_amount = amount + if additional_salary.overwrite: + component_row.additional_amount = flt(flt(amount) - flt(component_row.get("default_amount", 0)), + component_row.precision("additional_amount")) + else: + component_row.default_amount = 0 + component_row.additional_amount = amount + component_row.additional_salary = additional_salary.name component_row.deduct_full_tax_on_selected_payroll_date = \ additional_salary.deduct_full_tax_on_selected_payroll_date diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.js b/erpnext/payroll/doctype/salary_slip/test_salary_slip.js index 06a1c7d72df..a47eba1887d 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.js +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.js @@ -52,4 +52,4 @@ QUnit.test("test salary slip", function(assert) { () => frappe.click_button('Yes'), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/payroll/doctype/salary_structure/condition_and_formula_help.html b/erpnext/payroll/doctype/salary_structure/condition_and_formula_help.html index d07a1ab551a..0f6cc378513 100644 --- a/erpnext/payroll/doctype/salary_structure/condition_and_formula_help.html +++ b/erpnext/payroll/doctype/salary_structure/condition_and_formula_help.html @@ -44,4 +44,4 @@
Condition: annual_taxable_earning > 20000000
Formula: annual_taxable_earning * 0.10 
- \ No newline at end of file + diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.py b/erpnext/payroll/doctype/salary_structure/salary_structure.py index 58c445f8a96..6dfb3a57d5d 100644 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.py @@ -206,4 +206,3 @@ def get_employees(salary_structure): salary_structure, salary_structure)) return list(set([d.employee for d in employees])) - diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure_dashboard.py b/erpnext/payroll/doctype/salary_structure/salary_structure_dashboard.py index 547f2b81be7..0159e3530fb 100644 --- a/erpnext/payroll/doctype/salary_structure/salary_structure_dashboard.py +++ b/erpnext/payroll/doctype/salary_structure/salary_structure_dashboard.py @@ -15,4 +15,4 @@ def get_data(): 'items': ['Employee Grade'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py index a0c3013061d..5fb3ce2a98e 100644 --- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py +++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py @@ -36,7 +36,7 @@ class SalaryStructureAssignment(Document): def validate_income_tax_slab(self): if not self.income_tax_slab: return - + income_tax_slab_currency = frappe.db.get_value('Income Tax Slab', self.income_tax_slab, 'currency') if self.currency != income_tax_slab_currency: frappe.throw(_("Currency of selected Income Tax Slab should be {0} instead of {1}").format(self.currency, income_tax_slab_currency)) @@ -69,4 +69,4 @@ def get_employee_currency(employee): employee_currency = frappe.db.get_value('Salary Structure Assignment', {'employee': employee}, 'currency') if not employee_currency: frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(employee)) - return employee_currency \ No newline at end of file + return employee_currency diff --git a/erpnext/payroll/notification/as b/erpnext/payroll/notification/as index 7a395572613..05c2c1bec27 100644 --- a/erpnext/payroll/notification/as +++ b/erpnext/payroll/notification/as @@ -1 +1 @@ -update from `tabNotification` set module='Payroll' where name = "Retention Bonus" \ No newline at end of file +update from `tabNotification` set module='Payroll' where name = "Retention Bonus" diff --git a/erpnext/payroll/print_format/salary_slip_based_on_timesheet/salary_slip_based_on_timesheet.json b/erpnext/payroll/print_format/salary_slip_based_on_timesheet/salary_slip_based_on_timesheet.json index ceaf4a6ac71..d5fee6b5b83 100644 --- a/erpnext/payroll/print_format/salary_slip_based_on_timesheet/salary_slip_based_on_timesheet.json +++ b/erpnext/payroll/print_format/salary_slip_based_on_timesheet/salary_slip_based_on_timesheet.json @@ -13,6 +13,6 @@ "name": "Salary Slip based on Timesheet", "owner": "Administrator", "print_format_builder": 1, - "print_format_type": "Server", + "print_format_type": "Jinja", "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/payroll/print_format/salary_slip_standard/salary_slip_standard.json b/erpnext/payroll/print_format/salary_slip_standard/salary_slip_standard.json index b01239fe02d..98a4435a50e 100644 --- a/erpnext/payroll/print_format/salary_slip_standard/salary_slip_standard.json +++ b/erpnext/payroll/print_format/salary_slip_standard/salary_slip_standard.json @@ -16,7 +16,7 @@ "name": "Salary Slip Standard", "owner": "Administrator", "print_format_builder": 1, - "print_format_type": "Server", + "print_format_type": "Jinja", "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/payroll/report/bank_remittance/bank_remittance.js b/erpnext/payroll/report/bank_remittance/bank_remittance.js index 6482ed34516..8b75b4facea 100644 --- a/erpnext/payroll/report/bank_remittance/bank_remittance.js +++ b/erpnext/payroll/report/bank_remittance/bank_remittance.js @@ -25,4 +25,3 @@ frappe.query_reports["Bank Remittance"] = { ] } - diff --git a/erpnext/payroll/report/bank_remittance/bank_remittance.py b/erpnext/payroll/report/bank_remittance/bank_remittance.py index 500543ceb02..05a5366a5c1 100644 --- a/erpnext/payroll/report/bank_remittance/bank_remittance.py +++ b/erpnext/payroll/report/bank_remittance/bank_remittance.py @@ -95,6 +95,7 @@ def execute(filters=None): "amount": salary.net_pay, } data.append(row) + return columns, data def get_bank_accounts(): @@ -116,7 +117,7 @@ def get_payroll_entries(accounts, filters): entries = get_all("Payroll Entry", payroll_filter, ["name", "payment_account"]) payment_accounts = [d.payment_account for d in entries] - set_company_account(payment_accounts, entries) + entries = set_company_account(payment_accounts, entries) return entries def get_salary_slips(payroll_entries): diff --git a/erpnext/payroll/report/income_tax_deductions/income_tax_deductions.js b/erpnext/payroll/report/income_tax_deductions/income_tax_deductions.js index 4bbb7f6a1be..6ecf2b1960c 100644 --- a/erpnext/payroll/report/income_tax_deductions/income_tax_deductions.js +++ b/erpnext/payroll/report/income_tax_deductions/income_tax_deductions.js @@ -4,4 +4,4 @@ frappe.require("assets/erpnext/js/salary_slip_deductions_report_filters.js", function() { frappe.query_reports["Income Tax Deductions"] = erpnext.salary_slip_deductions_report_filters; -}); \ No newline at end of file +}); diff --git a/erpnext/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.js b/erpnext/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.js index 166d982c9c6..9b829541692 100644 --- a/erpnext/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.js +++ b/erpnext/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.js @@ -4,4 +4,4 @@ frappe.require("assets/erpnext/js/salary_slip_deductions_report_filters.js", function() { frappe.query_reports["Salary Payments Based On Payment Mode"] = erpnext.salary_slip_deductions_report_filters; -}); \ No newline at end of file +}); diff --git a/erpnext/portal/doctype/homepage/homepage.py b/erpnext/portal/doctype/homepage/homepage.py index 4e4d4774abf..54ea7c62df4 100644 --- a/erpnext/portal/doctype/homepage/homepage.py +++ b/erpnext/portal/doctype/homepage/homepage.py @@ -23,4 +23,3 @@ class Homepage(Document): doc.save() self.append('products', dict(item_code=d.name, item_name=d.item_name, description=d.description, image=d.image)) - diff --git a/erpnext/portal/product_configurator/test_product_configurator.py b/erpnext/portal/product_configurator/test_product_configurator.py index daaba671736..28e064218b1 100644 --- a/erpnext/portal/product_configurator/test_product_configurator.py +++ b/erpnext/portal/product_configurator/test_product_configurator.py @@ -141,4 +141,4 @@ class TestProductConfigurator(unittest.TestCase): # teardown doc.delete() - item_group_doc.delete() \ No newline at end of file + item_group_doc.delete() diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index d77eb2c3966..d60b1a2b05e 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -2,6 +2,7 @@ import frappe from frappe.utils import cint from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager from erpnext.shopping_cart.product_info import get_product_info_for_website +from erpnext.setup.doctype.item_group.item_group import get_child_groups def get_field_filter_data(): product_settings = get_product_settings() @@ -89,6 +90,7 @@ def get_products_for_website(field_filters=None, attribute_filters=None, search= def get_products_html_for_website(field_filters=None, attribute_filters=None): field_filters = frappe.parse_json(field_filters) attribute_filters = frappe.parse_json(attribute_filters) + set_item_group_filters(field_filters) items = get_products_for_website(field_filters, attribute_filters) html = ''.join(get_html_for_items(items)) @@ -98,6 +100,10 @@ def get_products_html_for_website(field_filters=None, attribute_filters=None): return html +def set_item_group_filters(field_filters): + if field_filters is not None and 'item_group' in field_filters: + field_filters['item_group'] = [ig[0] for ig in get_child_groups(field_filters['item_group'])] + def get_item_codes_by_attributes(attribute_filters, template_item_code=None): items = [] diff --git a/erpnext/projects/doctype/activity_cost/activity_cost.js b/erpnext/projects/doctype/activity_cost/activity_cost.js index ba10153e5cd..2d22caad8e2 100644 --- a/erpnext/projects/doctype/activity_cost/activity_cost.js +++ b/erpnext/projects/doctype/activity_cost/activity_cost.js @@ -1 +1 @@ -cur_frm.add_fetch('employee', 'employee_name', 'employee_name'); \ No newline at end of file +cur_frm.add_fetch('employee', 'employee_name', 'employee_name'); diff --git a/erpnext/projects/doctype/activity_cost/activity_cost.py b/erpnext/projects/doctype/activity_cost/activity_cost.py index 862a70717ab..99226ea581c 100644 --- a/erpnext/projects/doctype/activity_cost/activity_cost.py +++ b/erpnext/projects/doctype/activity_cost/activity_cost.py @@ -13,7 +13,7 @@ class ActivityCost(Document): def validate(self): self.set_title() self.check_unique() - + def set_title(self): if self.employee: if not self.employee_name: diff --git a/erpnext/projects/doctype/activity_cost/test_activity_cost.py b/erpnext/projects/doctype/activity_cost/test_activity_cost.py index 67d76eb1eee..5f35f299b36 100644 --- a/erpnext/projects/doctype/activity_cost/test_activity_cost.py +++ b/erpnext/projects/doctype/activity_cost/test_activity_cost.py @@ -22,4 +22,4 @@ class TestActivityCost(unittest.TestCase): activity_cost1.insert() activity_cost2 = frappe.copy_doc(activity_cost1) self.assertRaises(DuplicationError, activity_cost2.insert ) - frappe.db.sql("delete from `tabActivity Cost`") \ No newline at end of file + frappe.db.sql("delete from `tabActivity Cost`") diff --git a/erpnext/projects/doctype/activity_type/activity_type.py b/erpnext/projects/doctype/activity_type/activity_type.py index 8b610c29561..50e18ef4de9 100644 --- a/erpnext/projects/doctype/activity_type/activity_type.py +++ b/erpnext/projects/doctype/activity_type/activity_type.py @@ -5,4 +5,4 @@ from __future__ import unicode_literals from frappe.model.document import Document class ActivityType(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/projects/doctype/activity_type/test_activity_type.py b/erpnext/projects/doctype/activity_type/test_activity_type.py index 3ea28dfbe2c..dcb01018de0 100644 --- a/erpnext/projects/doctype/activity_type/test_activity_type.py +++ b/erpnext/projects/doctype/activity_type/test_activity_type.py @@ -4,4 +4,4 @@ from __future__ import unicode_literals import frappe -test_records = frappe.get_test_records('Activity Type') \ No newline at end of file +test_records = frappe.get_test_records('Activity Type') diff --git a/erpnext/projects/doctype/project/project_dashboard.html b/erpnext/projects/doctype/project/project_dashboard.html index f5bfbb7ca1f..1f299e30833 100644 --- a/erpnext/projects/doctype/project/project_dashboard.html +++ b/erpnext/projects/doctype/project/project_dashboard.html @@ -23,4 +23,4 @@ -{% endfor %} \ No newline at end of file +{% endfor %} diff --git a/erpnext/projects/doctype/project_template/project_template.py b/erpnext/projects/doctype/project_template/project_template.py index aace40240c4..2426fd2af89 100644 --- a/erpnext/projects/doctype/project_template/project_template.py +++ b/erpnext/projects/doctype/project_template/project_template.py @@ -22,7 +22,7 @@ class ProjectTemplate(Document): task_details_format = get_link_to_form("Task",task_details.name) dependency_task_format = get_link_to_form("Task", dependency_task.task) frappe.throw(_("Task {0} depends on Task {1}. Please add Task {1} to the Tasks list.").format(frappe.bold(task_details_format), frappe.bold(dependency_task_format))) - + def check_dependent_task_presence(self, task): for task_details in self.tasks: if task_details.task == task: diff --git a/erpnext/projects/doctype/project_template/test_project_template.py b/erpnext/projects/doctype/project_template/test_project_template.py index 95663cdcbbb..d546fd09a30 100644 --- a/erpnext/projects/doctype/project_template/test_project_template.py +++ b/erpnext/projects/doctype/project_template/test_project_template.py @@ -26,4 +26,4 @@ def make_project_template(project_template_name, project_tasks=[]): }) doc.insert() - return frappe.get_doc('Project Template', project_template_name) \ No newline at end of file + return frappe.get_doc('Project Template', project_template_name) diff --git a/erpnext/projects/doctype/project_type/project_type.js b/erpnext/projects/doctype/project_type/project_type.js index a1f941fe148..e3dda5eccc5 100644 --- a/erpnext/projects/doctype/project_type/project_type.js +++ b/erpnext/projects/doctype/project_type/project_type.js @@ -3,4 +3,4 @@ frappe.ui.form.on('Project Type', { -}); \ No newline at end of file +}); diff --git a/erpnext/projects/doctype/project_type/project_type.py b/erpnext/projects/doctype/project_type/project_type.py index f46876eda23..36137ca0186 100644 --- a/erpnext/projects/doctype/project_type/project_type.py +++ b/erpnext/projects/doctype/project_type/project_type.py @@ -10,4 +10,4 @@ from frappe import _ class ProjectType(Document): def on_trash(self): if self.name == "External": - frappe.throw(_("You cannot delete Project Type 'External'")) \ No newline at end of file + frappe.throw(_("You cannot delete Project Type 'External'")) diff --git a/erpnext/projects/doctype/project_update/project_update.py b/erpnext/projects/doctype/project_update/project_update.py index faa4bf1f9b6..2e1ec746ed6 100644 --- a/erpnext/projects/doctype/project_update/project_update.py +++ b/erpnext/projects/doctype/project_update/project_update.py @@ -39,4 +39,4 @@ def email_sending(project_name,frequency,date_start,date_end,progress,number_of_ for emails in email: frappe.sendmail(recipients=emails,subject=frappe._(project_name + ' ' + 'Summary'),message = msg) else: - pass \ No newline at end of file + pass diff --git a/erpnext/projects/doctype/project_update/test_project_update.py b/erpnext/projects/doctype/project_update/test_project_update.py index d5d09194446..2edd2f85a31 100644 --- a/erpnext/projects/doctype/project_update/test_project_update.py +++ b/erpnext/projects/doctype/project_update/test_project_update.py @@ -10,4 +10,4 @@ class TestProjectUpdate(unittest.TestCase): pass test_records = frappe.get_test_records('Project Update') -test_ignore = ["Sales Order"] \ No newline at end of file +test_ignore = ["Sales Order"] diff --git a/erpnext/projects/doctype/task/task_tree.js b/erpnext/projects/doctype/task/task_tree.js index d1d872f28a4..9ebfcdd180c 100644 --- a/erpnext/projects/doctype/task/task_tree.js +++ b/erpnext/projects/doctype/task/task_tree.js @@ -81,4 +81,4 @@ frappe.treeview_settings['Task'] = { } ], extend_toolbar: true -}; \ No newline at end of file +}; diff --git a/erpnext/projects/doctype/timesheet/timesheet.css b/erpnext/projects/doctype/timesheet/timesheet.css index 3a38415e6c6..1e055629ba8 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.css +++ b/erpnext/projects/doctype/timesheet/timesheet.css @@ -20,4 +20,4 @@ .playpause { border-right: 1px dashed #fff; border-bottom: 1px dashed #fff; -} \ No newline at end of file +} diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 84c7b8118b8..1655b76b988 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -399,4 +399,4 @@ function set_project_in_timelog(frm) { frappe.model.set_value(item.doctype, item.name, "project", frm.doc.parent_project); }); } -} \ No newline at end of file +} diff --git a/erpnext/projects/doctype/timesheet/timesheet.json b/erpnext/projects/doctype/timesheet/timesheet.json index 75f7478ed18..be6771e56f9 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.json +++ b/erpnext/projects/doctype/timesheet/timesheet.json @@ -310,6 +310,7 @@ "read_only": 1 }, { + "default": "1", "fieldname": "exchange_rate", "fieldtype": "Float", "label": "Exchange Rate" @@ -319,7 +320,7 @@ "idx": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-18 16:10:08.249619", + "modified": "2021-06-09 12:08:53.930200", "modified_by": "Administrator", "module": "Projects", "name": "Timesheet", diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index c8bd80fca0a..5f569d6bcd4 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -15,12 +15,15 @@ from erpnext.manufacturing.doctype.workstation.workstation import (check_if_with WorkstationHolidayError) from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations from erpnext.setup.utils import get_exchange_rate +from erpnext.hr.utils import validate_active_employee class OverlapError(frappe.ValidationError): pass class OverWorkLoggedError(frappe.ValidationError): pass class Timesheet(Document): def validate(self): + if self.employee: + validate_active_employee(self.employee) self.set_employee_name() self.set_status() self.validate_dates() @@ -224,7 +227,8 @@ def get_projectwise_timesheet_data(project=None, parent=None, from_time=None, to return frappe.db.sql("""SELECT tsd.name as name, tsd.parent as parent, tsd.billing_hours as billing_hours, tsd.billing_amount as billing_amount, tsd.activity_type as activity_type, - tsd.description as description, ts.currency as currency + tsd.description as description, ts.currency as currency, + tsd.project_name as project_name FROM `tabTimesheet Detail` tsd INNER JOIN `tabTimesheet` ts ON ts.name = tsd.parent WHERE tsd.parenttype = 'Timesheet' @@ -232,6 +236,19 @@ def get_projectwise_timesheet_data(project=None, parent=None, from_time=None, to and tsd.is_billable = 1 and tsd.sales_invoice is null""".format(condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time}, as_dict=1) +@frappe.whitelist() +def get_timesheet_detail_rate(timelog, currency): + timelog_detail = frappe.db.sql("""SELECT tsd.billing_amount as billing_amount, + ts.currency as currency FROM `tabTimesheet Detail` tsd + INNER JOIN `tabTimesheet` ts ON ts.name=tsd.parent + WHERE tsd.name = '{0}'""".format(timelog), as_dict = 1)[0] + + if timelog_detail.currency: + exchange_rate = get_exchange_rate(timelog_detail.currency, currency) + + return timelog_detail.billing_amount * exchange_rate + return timelog_detail.billing_amount + @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_timesheet(doctype, txt, searchfield, start, page_len, filters): diff --git a/erpnext/projects/doctype/timesheet/timesheet_calendar.js b/erpnext/projects/doctype/timesheet/timesheet_calendar.js index 14f016a7653..80967ede1ce 100644 --- a/erpnext/projects/doctype/timesheet/timesheet_calendar.js +++ b/erpnext/projects/doctype/timesheet/timesheet_calendar.js @@ -9,8 +9,8 @@ frappe.views.calendar["Timesheet"] = { "title": "title" }, style_map: { - "0": "info", - "1": "standard", + "0": "info", + "1": "standard", "2": "danger" }, gantt: true, diff --git a/erpnext/projects/doctype/timesheet/timesheet_dashboard.py b/erpnext/projects/doctype/timesheet/timesheet_dashboard.py index acff97a2269..088d98c4d5f 100644 --- a/erpnext/projects/doctype/timesheet/timesheet_dashboard.py +++ b/erpnext/projects/doctype/timesheet/timesheet_dashboard.py @@ -10,4 +10,4 @@ def get_data(): 'items': ['Sales Invoice', 'Salary Slip'] } ] - } \ No newline at end of file + } diff --git a/erpnext/projects/doctype/timesheet/timesheet_list.js b/erpnext/projects/doctype/timesheet/timesheet_list.js index 1b200f855db..b59fdc96fe8 100644 --- a/erpnext/projects/doctype/timesheet/timesheet_list.js +++ b/erpnext/projects/doctype/timesheet/timesheet_list.js @@ -4,13 +4,13 @@ frappe.listview_settings['Timesheet'] = { if (doc.status== "Billed") { return [__("Billed"), "green", "status,=," + "Billed"] } - + if (doc.status== "Payslip") { return [__("Payslip"), "green", "status,=," + "Payslip"] } - + if (doc.status== "Completed") { return [__("Completed"), "green", "status,=," + "Completed"] } } -}; \ No newline at end of file +}; diff --git a/erpnext/projects/report/billing_summary.py b/erpnext/projects/report/billing_summary.py index 5efde41b5b3..a22ed7b8338 100644 --- a/erpnext/projects/report/billing_summary.py +++ b/erpnext/projects/report/billing_summary.py @@ -144,4 +144,4 @@ def get_billable_and_total_duration(activity, start_time, end_time): if activity_duration != activity.billing_hours: billing_duration = activity_duration * activity.billing_hours / activity.hours - return flt(activity_duration, precision), flt(billing_duration, precision) \ No newline at end of file + return flt(activity_duration, precision), flt(billing_duration, precision) diff --git a/erpnext/projects/report/daily_timesheet_summary/daily_timesheet_summary.py b/erpnext/projects/report/daily_timesheet_summary/daily_timesheet_summary.py index 682fb2e09dc..3dcae5b1b53 100644 --- a/erpnext/projects/report/daily_timesheet_summary/daily_timesheet_summary.py +++ b/erpnext/projects/report/daily_timesheet_summary/daily_timesheet_summary.py @@ -20,8 +20,8 @@ def execute(filters=None): return columns, data def get_column(): - return [_("Timesheet") + ":Link/Timesheet:120", _("Employee") + "::150", _("Employee Name") + "::150", - _("From Datetime") + "::140", _("To Datetime") + "::140", _("Hours") + "::70", + return [_("Timesheet") + ":Link/Timesheet:120", _("Employee") + "::150", _("Employee Name") + "::150", + _("From Datetime") + "::140", _("To Datetime") + "::140", _("Hours") + "::70", _("Activity Type") + "::120", _("Task") + ":Link/Task:150", _("Project") + ":Link/Project:120", _("Status") + "::70"] @@ -45,4 +45,4 @@ def get_conditions(filters): if match_conditions: conditions += " and %s" % match_conditions - return conditions \ No newline at end of file + return conditions diff --git a/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py b/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py index dbeedb4be92..78291b2d781 100644 --- a/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py +++ b/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py @@ -10,7 +10,7 @@ class TestDelayedTasksSummary(unittest.TestCase): def setUp(self): task1 = create_task("_Test Task 98", add_days(nowdate(), -10), nowdate()) create_task("_Test Task 99", add_days(nowdate(), -10), add_days(nowdate(), -1)) - + task1.status = "Completed" task1.completed_on = add_days(nowdate(), -1) task1.save() @@ -38,7 +38,7 @@ class TestDelayedTasksSummary(unittest.TestCase): ] report = execute(filters) data = list(filter(lambda x: x.subject == "_Test Task 99", report[1]))[0] - + for key in ["subject", "status", "priority", "delay"]: self.assertEqual(expected_data[0].get(key), data.get(key)) @@ -51,4 +51,4 @@ class TestDelayedTasksSummary(unittest.TestCase): def tearDown(self): for task in ["_Test Task 98", "_Test Task 99"]: - frappe.get_doc("Task", {"subject": task}).delete() \ No newline at end of file + frappe.get_doc("Task", {"subject": task}).delete() diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py index cd5ad7803a5..17c92c234d5 100644 --- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py +++ b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py @@ -11,4 +11,4 @@ def execute(filters=None): columns = get_columns() data = get_data(filters) - return columns, data \ No newline at end of file + return columns, data diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py index 0e5a59756e3..969fc556e8d 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py @@ -195,4 +195,4 @@ class TestEmployeeUtilization(unittest.TestCase): 'per_util': 27.78, 'per_util_billed_only': 27.78 } - ] \ No newline at end of file + ] diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.py b/erpnext/projects/report/project_billing_summary/project_billing_summary.py index cd5ad7803a5..17c92c234d5 100644 --- a/erpnext/projects/report/project_billing_summary/project_billing_summary.py +++ b/erpnext/projects/report/project_billing_summary/project_billing_summary.py @@ -11,4 +11,4 @@ def execute(filters=None): columns = get_columns() data = get_data(filters) - return columns, data \ No newline at end of file + return columns, data diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 9139d84facc..0a52f7bf904 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -208,4 +208,4 @@ def get_columns(): "options": "Currency", "width": 80 } - ] \ No newline at end of file + ] diff --git a/erpnext/projects/web_form/tasks/tasks.js b/erpnext/projects/web_form/tasks/tasks.js index 699703c5792..ffc5e984253 100644 --- a/erpnext/projects/web_form/tasks/tasks.js +++ b/erpnext/projects/web_form/tasks/tasks.js @@ -1,3 +1,3 @@ frappe.ready(function() { // bind events here -}) \ No newline at end of file +}) diff --git a/erpnext/projects/web_form/tasks/tasks.py b/erpnext/projects/web_form/tasks/tasks.py index e97f36d04b4..e5a94048be1 100644 --- a/erpnext/projects/web_form/tasks/tasks.py +++ b/erpnext/projects/web_form/tasks/tasks.py @@ -6,7 +6,7 @@ def get_context(context): if frappe.form_dict.project: context.parents = [{'title': frappe.form_dict.project, 'route': '/projects?project='+ frappe.form_dict.project}] context.success_url = "/projects?project=" + frappe.form_dict.project - + elif context.doc and context.doc.get('project'): context.parents = [{'title': context.doc.project, 'route': '/projects?project='+ context.doc.project}] context.success_url = "/projects?project=" + context.doc.project diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 7a3cb838a99..3c60e3ee500 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -3,7 +3,8 @@ "public/less/erpnext.less", "public/less/hub.less", "public/scss/call_popup.scss", - "public/scss/point-of-sale.scss" + "public/scss/point-of-sale.scss", + "public/scss/hierarchy_chart.scss" ], "css/marketplace.css": [ "public/less/hub.less" @@ -43,7 +44,8 @@ "public/js/call_popup/call_popup.js", "public/js/utils/dimension_tree_filter.js", "public/js/telephony.js", - "public/js/templates/call_link.html" + "public/js/templates/call_link.html", + "public/js/templates/node_card.html" ], "js/item-dashboard.min.js": [ "stock/dashboard/item_dashboard.html", @@ -66,5 +68,9 @@ "public/js/bank_reconciliation_tool/data_table_manager.js", "public/js/bank_reconciliation_tool/number_card.js", "public/js/bank_reconciliation_tool/dialog_manager.js" + ], + "js/hierarchy-chart.min.js": [ + "public/js/hierarchy_chart/hierarchy_chart_desktop.js", + "public/js/hierarchy_chart/hierarchy_chart_mobile.js" ] } diff --git a/erpnext/public/css/leaflet/leaflet.css b/erpnext/public/css/leaflet/leaflet.css index 979a8bd712b..d89fef3a534 100755 --- a/erpnext/public/css/leaflet/leaflet.css +++ b/erpnext/public/css/leaflet/leaflet.css @@ -608,4 +608,4 @@ .leaflet-div-icon { background: #fff; border: 1px solid #666; -} \ No newline at end of file +} diff --git a/erpnext/public/css/leaflet/leaflet.draw.css b/erpnext/public/css/leaflet/leaflet.draw.css index 6fb7db0e64a..56f4a3542ba 100755 --- a/erpnext/public/css/leaflet/leaflet.draw.css +++ b/erpnext/public/css/leaflet/leaflet.draw.css @@ -313,4 +313,4 @@ .leaflet-oldie .leaflet-draw-actions-top.leaflet-draw-actions-bottom a { height: 27px; line-height: 27px; -} \ No newline at end of file +} diff --git a/erpnext/public/images/erpnext-favicon.svg b/erpnext/public/images/erpnext-favicon.svg index a3ac3bb2ce2..6bc6b2c2db1 100644 --- a/erpnext/public/images/erpnext-favicon.svg +++ b/erpnext/public/images/erpnext-favicon.svg @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/erpnext/public/images/erpnext-logo.svg b/erpnext/public/images/erpnext-logo.svg index a3ac3bb2ce2..6bc6b2c2db1 100644 --- a/erpnext/public/images/erpnext-logo.svg +++ b/erpnext/public/images/erpnext-logo.svg @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/erpnext/public/images/pos.svg b/erpnext/public/images/pos.svg index 3d12d9cb86e..90714e9491d 100644 --- a/erpnext/public/images/pos.svg +++ b/erpnext/public/images/pos.svg @@ -1,4 +1,4 @@ - \ No newline at end of file + diff --git a/erpnext/public/js/contact.js b/erpnext/public/js/contact.js new file mode 100644 index 00000000000..41a0e8a9f99 --- /dev/null +++ b/erpnext/public/js/contact.js @@ -0,0 +1,16 @@ + + +frappe.ui.form.on("Contact", { + refresh(frm) { + frm.set_query('link_doctype', "links", function() { + return { + query: "frappe.contacts.address_and_contact.filter_dynamic_link_doctypes", + filters: { + fieldtype: ["in", ["HTML", "Text Editor"]], + fieldname: ["in", ["contact_html", "company_description"]], + } + }; + }); + frm.refresh_field("links"); + } +}); diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index 7b997a11530..84c717676c7 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -31,6 +31,14 @@ frappe.ui.form.on(cur_frm.doctype, { } } }); + frm.set_query("cost_center", "taxes", function(doc) { + return { + filters: { + "company": doc.company, + "is_group": 0 + } + }; + }); } }, validate: function(frm) { diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 5c9f5d7da43..9c4851ebb0e 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -508,4 +508,4 @@ erpnext.buying.get_items_from_product_bundle = function(frm) { }); dialog.show(); -} \ No newline at end of file +} diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 1de9ec1a7df..90cb5559394 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -47,7 +47,10 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ if (in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) && this.frm.doc.is_pos && this.frm.doc.is_return) { - this.update_paid_amount_for_return(); + if (this.frm.doc.doctype == "Sales Invoice") { + this.set_total_amount_to_default_mop(); + } + this.calculate_paid_amount(); } // Sales person's commission @@ -65,7 +68,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ this.frm.refresh_fields(); }, - calculate_discount_amount: function(){ + calculate_discount_amount: function() { if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) { this.set_discount_amount(); this.apply_discount_amount(); @@ -73,18 +76,15 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ }, _calculate_taxes_and_totals: function() { - frappe.run_serially([ - () => this.validate_conversion_rate(), - () => this.calculate_item_values(), - () => this.update_item_tax_map(), - () => this.initialize_taxes(), - () => this.determine_exclusive_rate(), - () => this.calculate_net_total(), - () => this.calculate_taxes(), - () => this.manipulate_grand_total_for_inclusive_tax(), - () => this.calculate_totals(), - () => this._cleanup() - ]); + this.validate_conversion_rate(); + this.calculate_item_values(); + this.initialize_taxes(); + this.determine_exclusive_rate(); + this.calculate_net_total(); + this.calculate_taxes(); + this.manipulate_grand_total_for_inclusive_tax(); + this.calculate_totals(); + this._cleanup(); }, validate_conversion_rate: function() { @@ -105,7 +105,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ }, calculate_item_values: function() { - var me = this; + let me = this; if (!this.discount_amount_applied) { $.each(this.frm.doc["items"] || [], function(i, item) { frappe.model.round_floats_in(item); @@ -266,46 +266,6 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); }, - update_item_tax_map: function() { - let me = this; - let item_codes = []; - let item_rates = {}; - let item_tax_templates = {}; - - $.each(this.frm.doc.items || [], function(i, item) { - if (item.item_code) { - // Use combination of name and item code in case same item is added multiple times - item_codes.push([item.item_code, item.name]); - item_rates[item.name] = item.net_rate; - item_tax_templates[item.name] = item.item_tax_template; - } - }); - - if (item_codes.length) { - return this.frm.call({ - method: "erpnext.stock.get_item_details.get_item_tax_info", - args: { - company: me.frm.doc.company, - tax_category: cstr(me.frm.doc.tax_category), - item_codes: item_codes, - item_rates: item_rates, - item_tax_templates: item_tax_templates - }, - callback: function(r) { - if (!r.exc) { - $.each(me.frm.doc.items || [], function(i, item) { - if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) { - item.item_tax_template = r.message[item.name].item_tax_template; - item.item_tax_rate = r.message[item.name].item_tax_rate; - me.add_taxes_from_item_tax_template(item.item_tax_rate); - } - }); - } - } - }); - } - }, - add_taxes_from_item_tax_template: function(item_tax_map) { let me = this; @@ -630,8 +590,6 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail); }); } - - this.frm.refresh_fields(); }, set_discount_amount: function() { @@ -775,7 +733,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ } }, - update_paid_amount_for_return: function() { + set_total_amount_to_default_mop: function() { var grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total; if(this.frm.doc.party_account_currency == this.frm.doc.currency) { @@ -788,17 +746,12 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ precision("base_grand_total") ); } - this.frm.doc.payments.find(pay => { if (pay.default) { pay.amount = total_amount_to_pay; - } else { - pay.amount = 0.0 } }); this.frm.refresh_fields(); - - this.calculate_paid_amount(); }, set_default_payment: function(total_amount_to_pay, update_paid_amount) { diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index b3af3d67eaa..1d110bffcc0 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -342,30 +342,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.set_dynamic_labels(); this.setup_sms(); this.setup_quality_inspection(); - let scan_barcode_field = this.frm.get_field('scan_barcode'); - if (scan_barcode_field && scan_barcode_field.get_value()) { - scan_barcode_field.set_value(""); - scan_barcode_field.set_new_description(""); - - if (frappe.is_mobile()) { - if (scan_barcode_field.$input_wrapper.find('.input-group').length) return; - - let $input_group = $('
'); - scan_barcode_field.$input_wrapper.find('.control-input').append($input_group); - $input_group.append(scan_barcode_field.$input); - $(` - - `) - .on('click', '.btn', () => { - frappe.barcode.scan_barcode().then(barcode => { - scan_barcode_field.set_value(barcode); - }); - }) - .appendTo($input_group); - } - } }, scan_barcode: function() { @@ -732,7 +708,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.frm.trigger("item_code", cdt, cdn); } else { - // Replacing all occurences of comma with carriage return + // Replace all occurences of comma with line feed item.serial_no = item.serial_no.replace(/,/g, '\n'); item.conversion_factor = item.conversion_factor || 1; refresh_field("serial_no", item.name, item.parentfield); @@ -826,9 +802,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ frappe.run_serially([ () => me.frm.script_manager.trigger("currency"), + () => me.update_item_tax_map(), () => me.apply_default_taxes(), - () => me.apply_pricing_rule(), - () => me.calculate_taxes_and_totals() + () => me.apply_pricing_rule() ]); } } @@ -1787,6 +1763,46 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ ]); }, + update_item_tax_map: function() { + let me = this; + let item_codes = []; + let item_rates = {}; + let item_tax_templates = {}; + + $.each(this.frm.doc.items || [], function(i, item) { + if (item.item_code) { + // Use combination of name and item code in case same item is added multiple times + item_codes.push([item.item_code, item.name]); + item_rates[item.name] = item.net_rate; + item_tax_templates[item.name] = item.item_tax_template; + } + }); + + if (item_codes.length) { + return this.frm.call({ + method: "erpnext.stock.get_item_details.get_item_tax_info", + args: { + company: me.frm.doc.company, + tax_category: cstr(me.frm.doc.tax_category), + item_codes: item_codes, + item_rates: item_rates, + item_tax_templates: item_tax_templates + }, + callback: function(r) { + if (!r.exc) { + $.each(me.frm.doc.items || [], function(i, item) { + if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) { + item.item_tax_template = r.message[item.name].item_tax_template; + item.item_tax_rate = r.message[item.name].item_tax_rate; + me.add_taxes_from_item_tax_template(item.item_tax_rate); + } + }); + } + } + }); + } + }, + item_tax_template: function(doc, cdt, cdn) { var me = this; if(me.frm.updating_party_details) return; diff --git a/erpnext/public/js/education/assessment_result_tool.html b/erpnext/public/js/education/assessment_result_tool.html index b591010ec86..f7d1ab39fcf 100644 --- a/erpnext/public/js/education/assessment_result_tool.html +++ b/erpnext/public/js/education/assessment_result_tool.html @@ -69,4 +69,4 @@ {% endfor %} - \ No newline at end of file + diff --git a/erpnext/public/js/education/student_button.html b/erpnext/public/js/education/student_button.html index 3cf259216a7..b64c73a43c7 100644 --- a/erpnext/public/js/education/student_button.html +++ b/erpnext/public/js/education/student_button.html @@ -1,12 +1,12 @@
-
\ No newline at end of file +
diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index b2f7afe53f3..0d79b10c041 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -176,5 +176,3 @@ function get_filters() { return filters; } - - diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index aa9bba17c77..d0c935f4887 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -54,7 +54,7 @@ frappe.help.help_links["permission-manager"] = [ frappe.help.help_links["Form/System Settings"] = [ { - label: "Naming Series", + label: "System Settings", url: docsUrl + "user/manual/en/setting-up/settings/system-settings", }, ]; @@ -206,7 +206,7 @@ frappe.help.help_links["Form/PayPal Settings"] = [ label: "PayPal Settings", url: docsUrl + - "user/manual/en/setting-up/integrations/paypal-integration", + "user/manual/en/erpnext_integration/paypal-integration", }, ]; @@ -215,14 +215,14 @@ frappe.help.help_links["Form/Razorpay Settings"] = [ label: "Razorpay Settings", url: docsUrl + - "user/manual/en/setting-up/integrations/razorpay-integration", + "user/manual/en/erpnext_integration/razorpay-integration", }, ]; frappe.help.help_links["Form/Dropbox Settings"] = [ { label: "Dropbox Settings", - url: docsUrl + "user/manual/en/setting-up/integrations/dropbox-backup", + url: docsUrl + "user/manual/en/erpnext_integration/dropbox-backup", }, ]; @@ -230,7 +230,7 @@ frappe.help.help_links["Form/LDAP Settings"] = [ { label: "LDAP Settings", url: - docsUrl + "user/manual/en/setting-up/integrations/ldap-integration", + docsUrl + "user/manual/en/erpnext_integration/ldap-integration", }, ]; @@ -239,7 +239,7 @@ frappe.help.help_links["Form/Stripe Settings"] = [ label: "Stripe Settings", url: docsUrl + - "user/manual/en/setting-up/integrations/stripe-integration", + "user/manual/en/erpnext_integration/stripe-integration", }, ]; @@ -991,7 +991,7 @@ frappe.help.help_links["Form/BOM"] = [ label: "Nested BOM Structure", url: docsUrl + - "user/manual/en/manufacturing/articles/nested-bom-structure", + "user/manual/en/manufacturing/articles/managing-multi-level-bom", }, ]; diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js new file mode 100644 index 00000000000..23ec2fdb849 --- /dev/null +++ b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js @@ -0,0 +1,600 @@ +import html2canvas from 'html2canvas'; +erpnext.HierarchyChart = class { + /* Options: + - doctype + - wrapper: wrapper for the hierarchy view + - method: + - to get the data for each node + - this method should return id, name, title, image, and connections for each node + */ + constructor(doctype, wrapper, method) { + this.page = wrapper.page; + this.method = method; + this.doctype = doctype; + + this.setup_page_style(); + this.page.main.addClass('frappe-card'); + + this.nodes = {}; + this.setup_node_class(); + } + + setup_page_style() { + this.page.main.css({ + 'min-height': '300px', + 'max-height': '600px', + 'overflow': 'auto', + 'position': 'relative' + }); + } + + setup_node_class() { + let me = this; + this.Node = class { + constructor({ + id, parent, parent_id, image, name, title, expandable, connections, is_root // eslint-disable-line + }) { + // to setup values passed via constructor + $.extend(this, arguments[0]); + + this.expanded = 0; + + me.nodes[this.id] = this; + me.make_node_element(this); + + if (!me.all_nodes_expanded) { + me.setup_node_click_action(this); + } + + me.setup_edit_node_action(this); + } + }; + } + + make_node_element(node) { + let node_card = frappe.render_template('node_card', { + id: node.id, + name: node.name, + title: node.title, + image: node.image, + parent: node.parent_id, + connections: node.connections, + is_mobile: false + }); + + node.parent.append(node_card); + node.$link = $(`#${node.id}`); + } + + show() { + frappe.breadcrumbs.add('HR'); + + this.setup_actions(); + if ($(`[data-fieldname="company"]`).length) return; + let me = this; + + let company = this.page.add_field({ + fieldtype: 'Link', + options: 'Company', + fieldname: 'company', + placeholder: __('Select Company'), + default: frappe.defaults.get_default('company'), + only_select: true, + reqd: 1, + change: () => { + me.company = undefined; + + if (company.get_value() && me.company != company.get_value()) { + me.company = company.get_value(); + + // svg for connectors + me.make_svg_markers(); + me.setup_hierarchy(); + me.render_root_nodes(); + me.all_nodes_expanded = false; + } + } + }); + + company.refresh(); + $(`[data-fieldname="company"]`).trigger('change'); + $(`[data-fieldname="company"] .link-field`).css('z-index', 2); + } + + setup_actions() { + let me = this; + this.page.clear_inner_toolbar(); + this.page.add_inner_button(__('Export'), function() { + me.export_chart(); + }); + + this.page.add_inner_button(__('Expand All'), function() { + me.load_children(me.root_node, true); + me.all_nodes_expanded = true; + + me.page.remove_inner_button(__('Expand All')); + me.page.add_inner_button(__('Collapse All'), function() { + me.setup_hierarchy(); + me.render_root_nodes(); + me.all_nodes_expanded = false; + + me.page.remove_inner_button(__('Collapse All')); + me.setup_actions(); + }); + }); + } + + export_chart() { + frappe.dom.freeze(__('Exporting...')); + this.page.main.css({ + 'min-height': '', + 'max-height': '', + 'overflow': 'visible', + 'position': 'fixed', + 'left': '0', + 'top': '0' + }); + + $('.node-card').addClass('exported'); + + html2canvas(document.querySelector('#hierarchy-chart-wrapper'), { + scrollY: -window.scrollY, + scrollX: 0 + }).then(function(canvas) { + // Export the canvas to its data URI representation + let dataURL = canvas.toDataURL('image/png'); + + // download the image + let a = document.createElement('a'); + a.href = dataURL; + a.download = 'hierarchy_chart'; + a.click(); + }).finally(() => { + frappe.dom.unfreeze(); + }); + + this.setup_page_style(); + $('.node-card').removeClass('exported'); + } + + setup_hierarchy() { + if (this.$hierarchy) + this.$hierarchy.remove(); + + $(`#connectors`).empty(); + + // setup hierarchy + this.$hierarchy = $( + `
    +
  • +
      +
    • +
    `); + + this.page.main + .find('#hierarchy-chart-wrapper') + .append(this.$hierarchy); + + this.nodes = {}; + this.all_nodes_expanded = false; + } + + make_svg_markers() { + $('#hierarchy-chart-wrapper').remove(); + + this.page.main.append(` +
    + + + + + + + + + + + + + + + + + + + +
    `); + } + + render_root_nodes(expanded_view=false) { + let me = this; + + return frappe.call({ + method: me.method, + args: { + company: me.company + } + }).then(r => { + if (r.message.length) { + let expand_node = undefined; + let node = undefined; + + $.each(r.message, (i, data) => { + node = new me.Node({ + id: data.id, + parent: $('
  • ').appendTo(me.$hierarchy.find('.node-children')), + parent_id: undefined, + image: data.image, + name: data.name, + title: data.title, + expandable: true, + connections: data.connections, + is_root: true + }); + + if (!expand_node && data.connections) + expand_node = node; + }); + + me.root_node = expand_node; + if (!expanded_view) { + me.expand_node(expand_node); + } + } + }); + } + + expand_node(node) { + const is_sibling = this.selected_node && this.selected_node.parent_id === node.parent_id; + this.set_selected_node(node); + this.show_active_path(node); + this.collapse_previous_level_nodes(node); + + // since the previous node collapses, all connections to that node need to be rebuilt + // if a sibling node is clicked, connections don't need to be rebuilt + if (!is_sibling) { + // rebuild outgoing connections + this.refresh_connectors(node.parent_id); + + // rebuild incoming connections + let grandparent = $(`#${node.parent_id}`).attr('data-parent'); + this.refresh_connectors(grandparent); + } + + if (node.expandable && !node.expanded) { + return this.load_children(node); + } + } + + collapse_node() { + if (this.selected_node.expandable) { + this.selected_node.$children.hide(); + $(`path[data-parent="${this.selected_node.id}"]`).hide(); + this.selected_node.expanded = false; + } + } + + show_active_path(node) { + // mark node parent on active path + $(`#${node.parent_id}`).addClass('active-path'); + } + + load_children(node, deep=false) { + if (!deep) { + frappe.run_serially([ + () => this.get_child_nodes(node.id), + (child_nodes) => this.render_child_nodes(node, child_nodes) + ]); + } else { + frappe.run_serially([ + () => frappe.dom.freeze(), + () => this.setup_hierarchy(), + () => this.render_root_nodes(true), + () => this.get_all_nodes(node.id, node.name), + (data_list) => this.render_children_of_all_nodes(data_list), + () => frappe.dom.unfreeze() + ]); + } + } + + get_child_nodes(node_id) { + return new Promise(resolve => { + frappe.call({ + method: this.method, + args: { + parent: node_id, + company: this.company + } + }).then(r => resolve(r.message)); + }); + } + + render_child_nodes(node, child_nodes) { + const last_level = this.$hierarchy.find('.level:last').index(); + const current_level = $(`#${node.id}`).parent().parent().parent().index(); + + if (last_level === current_level) { + this.$hierarchy.append(` +
  • + `); + } + + if (!node.$children) { + node.$children = $('
      ') + .hide() + .appendTo(this.$hierarchy.find('.level:last')); + + node.$children.empty(); + + if (child_nodes) { + $.each(child_nodes, (_i, data) => { + this.add_node(node, data); + setTimeout(() => { + this.add_connector(node.id, data.id); + }, 250); + }); + } + } + + node.$children.show(); + $(`path[data-parent="${node.id}"]`).show(); + node.expanded = true; + } + + get_all_nodes(node_id, node_name) { + return new Promise(resolve => { + frappe.call({ + method: 'erpnext.utilities.hierarchy_chart.get_all_nodes', + args: { + method: this.method, + company: this.company, + parent: node_id, + parent_name: node_name + }, + callback: (r) => { + resolve(r.message); + } + }); + }); + } + + render_children_of_all_nodes(data_list) { + let entry = undefined; + let node = undefined; + + while (data_list.length) { + // to avoid overlapping connectors + entry = data_list.shift(); + node = this.nodes[entry.parent]; + if (node) { + this.render_child_nodes_for_expanded_view(node, entry.data); + } else if (data_list.length) { + data_list.push(entry); + } + } + } + + render_child_nodes_for_expanded_view(node, child_nodes) { + node.$children = $('
        '); + + const last_level = this.$hierarchy.find('.level:last').index(); + const node_level = $(`#${node.id}`).parent().parent().parent().index(); + + if (last_level === node_level) { + this.$hierarchy.append(` +
      • + `); + node.$children.appendTo(this.$hierarchy.find('.level:last')); + } else { + node.$children.appendTo(this.$hierarchy.find('.level:eq(' + (node_level + 1) + ')')); + } + + node.$children.hide().empty(); + + if (child_nodes) { + $.each(child_nodes, (_i, data) => { + this.add_node(node, data); + setTimeout(() => { + this.add_connector(node.id, data.id); + }, 250); + }); + } + + node.$children.show(); + $(`path[data-parent="${node.id}"]`).show(); + node.expanded = true; + } + + add_node(node, data) { + return new this.Node({ + id: data.id, + parent: $('
      • ').appendTo(node.$children), + parent_id: node.id, + image: data.image, + name: data.name, + title: data.title, + expandable: data.expandable, + connections: data.connections, + children: undefined + }); + } + + add_connector(parent_id, child_id) { + // using pure javascript for better performance + const parent_node = document.querySelector(`#${parent_id}`); + const child_node = document.querySelector(`#${child_id}`); + + let path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + + // we need to connect right side of the parent to the left side of the child node + const pos_parent_right = { + x: parent_node.offsetLeft + parent_node.offsetWidth, + y: parent_node.offsetTop + parent_node.offsetHeight / 2 + }; + const pos_child_left = { + x: child_node.offsetLeft - 5, + y: child_node.offsetTop + child_node.offsetHeight / 2 + }; + + const connector = this.get_connector(pos_parent_right, pos_child_left); + + path.setAttribute('d', connector); + this.set_path_attributes(path, parent_id, child_id); + + document.getElementById('connectors').appendChild(path); + } + + get_connector(pos_parent_right, pos_child_left) { + if (pos_parent_right.y === pos_child_left.y) { + // don't add arcs if it's a straight line + return "M" + + (pos_parent_right.x) + "," + (pos_parent_right.y) + " " + + "L"+ + (pos_child_left.x) + "," + (pos_child_left.y); + } else { + let arc_1 = ""; + let arc_2 = ""; + let offset = 0; + + if (pos_parent_right.y > pos_child_left.y) { + // if child is above parent on Y axis 1st arc is anticlocwise + // second arc is clockwise + arc_1 = "a10,10 1 0 0 10,-10 "; + arc_2 = "a10,10 0 0 1 10,-10 "; + offset = 10; + } else { + // if child is below parent on Y axis 1st arc is clockwise + // second arc is anticlockwise + arc_1 = "a10,10 0 0 1 10,10 "; + arc_2 = "a10,10 1 0 0 10,10 "; + offset = -10; + } + + return "M" + (pos_parent_right.x) + "," + (pos_parent_right.y) + " " + + "L" + + (pos_parent_right.x + 40) + "," + (pos_parent_right.y) + " " + + arc_1 + + "L" + + (pos_parent_right.x + 50) + "," + (pos_child_left.y + offset) + " " + + arc_2 + + "L"+ + (pos_child_left.x) + "," + (pos_child_left.y); + } + } + + set_path_attributes(path, parent_id, child_id) { + path.setAttribute("data-parent", parent_id); + path.setAttribute("data-child", child_id); + const parent = $(`#${parent_id}`); + + if (parent.hasClass('active')) { + path.setAttribute("class", "active-connector"); + path.setAttribute("marker-start", "url(#arrowstart-active)"); + path.setAttribute("marker-end", "url(#arrowhead-active)"); + } else { + path.setAttribute("class", "collapsed-connector"); + path.setAttribute("marker-start", "url(#arrowstart-collapsed)"); + path.setAttribute("marker-end", "url(#arrowhead-collapsed)"); + } + } + + set_selected_node(node) { + // remove active class from the current node + if (this.selected_node) + this.selected_node.$link.removeClass('active'); + + // add active class to the newly selected node + this.selected_node = node; + node.$link.addClass('active'); + } + + collapse_previous_level_nodes(node) { + let node_parent = $(`#${node.parent_id}`); + let previous_level_nodes = node_parent.parent().parent().children('li'); + let node_card = undefined; + + previous_level_nodes.each(function() { + node_card = $(this).find('.node-card'); + + if (!node_card.hasClass('active-path')) { + node_card.addClass('collapsed'); + } + }); + } + + refresh_connectors(node_parent) { + if (!node_parent) return; + + $(`path[data-parent="${node_parent}"]`).remove(); + + frappe.run_serially([ + () => this.get_child_nodes(node_parent), + (child_nodes) => { + if (child_nodes) { + $.each(child_nodes, (_i, data) => { + this.add_connector(node_parent, data.id); + }); + } + } + ]); + } + + setup_node_click_action(node) { + let me = this; + let node_element = $(`#${node.id}`); + + node_element.click(function() { + const is_sibling = me.selected_node.parent_id === node.parent_id; + + if (is_sibling) { + me.collapse_node(); + } else if (node_element.is(':visible') + && (node_element.hasClass('collapsed') || node_element.hasClass('active-path'))) { + me.remove_levels_after_node(node); + me.remove_orphaned_connectors(); + } + + me.expand_node(node); + }); + } + + setup_edit_node_action(node) { + let node_element = $(`#${node.id}`); + let me = this; + + node_element.find('.btn-edit-node').click(function() { + frappe.set_route('Form', me.doctype, node.id); + }); + } + + remove_levels_after_node(node) { + let level = $(`#${node.id}`).parent().parent().parent().index(); + + level = $('.hierarchy > li:eq('+ level + ')'); + level.nextAll('li').remove(); + + let nodes = level.find('.node-card'); + let node_object = undefined; + + $.each(nodes, (_i, element) => { + node_object = this.nodes[element.id]; + node_object.expanded = 0; + node_object.$children = undefined; + }); + + nodes.removeClass('collapsed active-path'); + } + + remove_orphaned_connectors() { + let paths = $('#connectors > path'); + $.each(paths, (_i, path) => { + const parent = $(path).data('parent'); + const child = $(path).data('child'); + + if ($(`#${parent}`).length && $(`#${child}`).length) + return; + + $(path).remove(); + }); + } +}; diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js new file mode 100644 index 00000000000..b1b78c05174 --- /dev/null +++ b/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js @@ -0,0 +1,551 @@ +erpnext.HierarchyChartMobile = class { + /* Options: + - doctype + - wrapper: wrapper for the hierarchy view + - method: + - to get the data for each node + - this method should return id, name, title, image, and connections for each node + */ + constructor(doctype, wrapper, method) { + this.page = wrapper.page; + this.method = method; + this.doctype = doctype; + + this.page.main.css({ + 'min-height': '300px', + 'max-height': '600px', + 'overflow': 'auto', + 'position': 'relative' + }); + this.page.main.addClass('frappe-card'); + + this.nodes = {}; + this.setup_node_class(); + } + + setup_node_class() { + let me = this; + this.Node = class { + constructor({ + id, parent, parent_id, image, name, title, expandable, connections, is_root // eslint-disable-line + }) { + // to setup values passed via constructor + $.extend(this, arguments[0]); + + this.expanded = 0; + + me.nodes[this.id] = this; + me.make_node_element(this); + me.setup_node_click_action(this); + me.setup_edit_node_action(this); + } + }; + } + + make_node_element(node) { + let node_card = frappe.render_template('node_card', { + id: node.id, + name: node.name, + title: node.title, + image: node.image, + parent: node.parent_id, + connections: node.connections, + is_mobile: true + }); + + node.parent.append(node_card); + node.$link = $(`#${node.id}`); + node.$link.addClass('mobile-node'); + } + + show() { + frappe.breadcrumbs.add('HR'); + + let me = this; + if ($(`[data-fieldname="company"]`).length) return; + + let company = this.page.add_field({ + fieldtype: 'Link', + options: 'Company', + fieldname: 'company', + placeholder: __('Select Company'), + default: frappe.defaults.get_default('company'), + only_select: true, + reqd: 1, + change: () => { + me.company = undefined; + + if (company.get_value() && me.company != company.get_value()) { + me.company = company.get_value(); + + // svg for connectors + me.make_svg_markers(); + + if (me.$sibling_group) + me.$sibling_group.remove(); + + // setup sibling group wrapper + me.$sibling_group = $(`
        `); + me.page.main.append(me.$sibling_group); + + me.setup_hierarchy(); + me.render_root_nodes(); + } + } + }); + + company.refresh(); + $(`[data-fieldname="company"]`).trigger('change'); + } + + make_svg_markers() { + $('#arrows').remove(); + + this.page.main.prepend(` + + + + + + + + + + + + + + + + + + + `); + } + + setup_hierarchy() { + $(`#connectors`).empty(); + if (this.$hierarchy) + this.$hierarchy.remove(); + + if (this.$sibling_group) + this.$sibling_group.empty(); + + this.$hierarchy = $( + `
          +
        • +
        `); + + this.page.main.append(this.$hierarchy); + } + + render_root_nodes() { + let me = this; + + frappe.call({ + method: me.method, + args: { + company: me.company + }, + }).then(r => { + if (r.message.length) { + let root_level = me.$hierarchy.find('.root-level'); + root_level.empty(); + + $.each(r.message, (_i, data) => { + return new me.Node({ + id: data.id, + parent: root_level, + parent_id: undefined, + image: data.image, + name: data.name, + title: data.title, + expandable: true, + connections: data.connections, + is_root: true + }); + }); + } + }); + } + + expand_node(node) { + const is_same_node = (this.selected_node && this.selected_node.id === node.id); + this.set_selected_node(node); + this.show_active_path(node); + + if (this.$sibling_group) { + const sibling_parent = this.$sibling_group.find('.node-group').attr('data-parent'); + if (node.parent_id !== undefined && node.parent_id != sibling_parent) + this.$sibling_group.empty(); + } + + if (!is_same_node) { + // since the previous/parent node collapses, all connections to that node need to be rebuilt + // rebuild outgoing connections of parent + this.refresh_connectors(node.parent_id, node.id); + + // rebuild incoming connections of parent + let grandparent = $(`#${node.parent_id}`).attr('data-parent'); + this.refresh_connectors(grandparent, node.parent_id); + } + + if (node.expandable && !node.expanded) { + return this.load_children(node); + } + } + + collapse_node() { + let node = this.selected_node; + if (node.expandable && node.$children) { + node.$children.hide(); + node.expanded = 0; + + // add a collapsed level to show the collapsed parent + // and a button beside it to move to that level + let node_parent = node.$link.parent(); + node_parent.prepend( + `
        ` + ); + + node_parent + .find('.collapsed-level') + .append(node.$link); + + frappe.run_serially([ + () => this.get_child_nodes(node.parent_id, node.id), + (child_nodes) => this.get_node_group(child_nodes, node.parent_id), + (node_group) => node_parent.find('.collapsed-level').append(node_group), + () => this.setup_node_group_action() + ]); + } + } + + show_active_path(node) { + // mark node parent on active path + $(`#${node.parent_id}`).addClass('active-path'); + } + + load_children(node) { + frappe.run_serially([ + () => this.get_child_nodes(node.id), + (child_nodes) => this.render_child_nodes(node, child_nodes) + ]); + } + + get_child_nodes(node_id, exclude_node=null) { + let me = this; + return new Promise(resolve => { + frappe.call({ + method: this.method, + args: { + parent: node_id, + company: me.company, + exclude_node: exclude_node + } + }).then(r => resolve(r.message)); + }); + } + + render_child_nodes(node, child_nodes) { + if (!node.$children) { + node.$children = $('
          ') + .hide() + .appendTo(node.$link.parent()); + + node.$children.empty(); + + if (child_nodes) { + $.each(child_nodes, (_i, data) => { + this.add_node(node, data); + $(`#${data.id}`).addClass('active-child'); + + setTimeout(() => { + this.add_connector(node.id, data.id); + }, 250); + }); + } + } + + node.$children.show(); + node.expanded = 1; + } + + add_node(node, data) { + var $li = $('
        • '); + + return new this.Node({ + id: data.id, + parent: $li.appendTo(node.$children), + parent_id: node.id, + image: data.image, + name: data.name, + title: data.title, + expandable: data.expandable, + connections: data.connections, + children: undefined + }); + } + + add_connector(parent_id, child_id) { + const parent_node = document.querySelector(`#${parent_id}`); + const child_node = document.querySelector(`#${child_id}`); + + const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + + let connector = undefined; + + if ($(`#${parent_id}`).hasClass('active')) { + connector = this.get_connector_for_active_node(parent_node, child_node); + } else if ($(`#${parent_id}`).hasClass('active-path')) { + connector = this.get_connector_for_collapsed_node(parent_node, child_node); + } + + path.setAttribute('d', connector); + this.set_path_attributes(path, parent_id, child_id); + + document.getElementById('connectors').appendChild(path); + } + + get_connector_for_active_node(parent_node, child_node) { + // we need to connect the bottom left of the parent to the left side of the child node + let pos_parent_bottom = { + x: parent_node.offsetLeft + 20, + y: parent_node.offsetTop + parent_node.offsetHeight + }; + let pos_child_left = { + x: child_node.offsetLeft - 5, + y: child_node.offsetTop + child_node.offsetHeight / 2 + }; + + let connector = + "M" + + (pos_parent_bottom.x) + "," + (pos_parent_bottom.y) + " " + + "L" + + (pos_parent_bottom.x) + "," + (pos_child_left.y - 10) + " " + + "a10,10 1 0 0 10,10 " + + "L" + + (pos_child_left.x) + "," + (pos_child_left.y); + + return connector; + } + + get_connector_for_collapsed_node(parent_node, child_node) { + // we need to connect the bottom left of the parent to the top left of the child node + let pos_parent_bottom = { + x: parent_node.offsetLeft + 20, + y: parent_node.offsetTop + parent_node.offsetHeight + }; + let pos_child_top = { + x: child_node.offsetLeft + 20, + y: child_node.offsetTop + }; + + let connector = + "M" + + (pos_parent_bottom.x) + "," + (pos_parent_bottom.y) + " " + + "L" + + (pos_child_top.x) + "," + (pos_child_top.y); + + return connector; + } + + set_path_attributes(path, parent_id, child_id) { + path.setAttribute("data-parent", parent_id); + path.setAttribute("data-child", child_id); + const parent = $(`#${parent_id}`); + + if (parent.hasClass('active')) { + path.setAttribute("class", "active-connector"); + path.setAttribute("marker-start", "url(#arrowstart-active)"); + path.setAttribute("marker-end", "url(#arrowhead-active)"); + } else if (parent.hasClass('active-path')) { + path.setAttribute("class", "collapsed-connector"); + } + } + + set_selected_node(node) { + // remove .active class from the current node + if (this.selected_node) + this.selected_node.$link.removeClass('active'); + + // add active class to the newly selected node + this.selected_node = node; + node.$link.addClass('active'); + } + + setup_node_click_action(node) { + let me = this; + let node_element = $(`#${node.id}`); + + node_element.click(function() { + let el = undefined; + + if (node.is_root) { + el = $(this).detach(); + me.$hierarchy.empty(); + $(`#connectors`).empty(); + me.add_node_to_hierarchy(el, node); + } else if (node_element.is(':visible') && node_element.hasClass('active-path')) { + me.remove_levels_after_node(node); + me.remove_orphaned_connectors(); + } else { + el = $(this).detach(); + me.add_node_to_hierarchy(el, node); + me.collapse_node(); + } + + me.expand_node(node); + }); + } + + setup_edit_node_action(node) { + let node_element = $(`#${node.id}`); + let me = this; + + node_element.find('.btn-edit-node').click(function() { + frappe.set_route('Form', me.doctype, node.id); + }); + } + + setup_node_group_action() { + let me = this; + + $('.node-group').on('click', function() { + let parent = $(this).attr('data-parent'); + if (parent === 'undefined') { + me.setup_hierarchy(); + me.render_root_nodes(); + } else { + me.expand_sibling_group_node(parent); + } + }); + } + + add_node_to_hierarchy(node_element, node) { + this.$hierarchy.append(`
        • `); + node_element.removeClass('active-child active-path'); + this.$hierarchy.find('.level:last').append(node_element); + + let node_object = this.nodes[node.id]; + node_object.expanded = 0; + node_object.$children = undefined; + this.nodes[node.id] = node_object; + } + + get_node_group(nodes, parent, collapsed=true) { + let limit = 2; + const display_nodes = nodes.slice(0, limit); + const extra_nodes = nodes.slice(limit); + + let html = display_nodes.map(node => + this.get_avatar(node) + ).join(''); + + if (extra_nodes.length === 1) { + let node = extra_nodes[0]; + html += this.get_avatar(node); + } else if (extra_nodes.length > 1) { + html = ` + ${html} + +
          + +${extra_nodes.length} +
          +
          + `; + } + + if (html) { + const $node_group = + $(`
          +
          + ${html} +
          +
          `); + + if (collapsed) + $node_group.addClass('collapsed'); + + return $node_group; + } + + return null; + } + + get_avatar(node) { + return ` + + `; + } + + expand_sibling_group_node(parent) { + let node_object = this.nodes[parent]; + let node = node_object.$link; + + node.removeClass('active-child active-path'); + node_object.expanded = 0; + node_object.$children = undefined; + this.nodes[node.id] = node_object; + + // show parent's siblings and expand parent node + frappe.run_serially([ + () => this.get_child_nodes(node_object.parent_id, node_object.id), + (child_nodes) => this.get_node_group(child_nodes, node_object.parent_id, false), + (node_group) => { + if (node_group) + this.$sibling_group.empty().append(node_group); + }, + () => this.setup_node_group_action(), + () => this.reattach_and_expand_node(node, node_object) + ]); + } + + reattach_and_expand_node(node, node_object) { + var el = node.detach(); + + this.$hierarchy.empty().append(` +
        • + `); + this.$hierarchy.find('.level').append(el); + $(`#connectors`).empty(); + this.expand_node(node_object); + } + + remove_levels_after_node(node) { + let level = $(`#${node.id}`).parent().parent().index(); + + level = $('.hierarchy-mobile > li:eq('+ level + ')'); + level.nextAll('li').remove(); + + let node_object = this.nodes[node.id]; + let current_node = level.find(`#${node.id}`).detach(); + current_node.removeClass('active-child active-path'); + + node_object.expanded = 0; + node_object.$children = undefined; + + level.empty().append(current_node); + } + + remove_orphaned_connectors() { + let paths = $('#connectors > path'); + $.each(paths, (_i, path) => { + const parent = $(path).data('parent'); + const child = $(path).data('child'); + + if ($(`#${parent}`).length && $(`#${child}`).length) + return; + + $(path).remove(); + }); + } + + refresh_connectors(node_parent, node_id) { + if (!node_parent) return; + + $(`path[data-parent="${node_parent}"]`).remove(); + this.add_connector(node_parent, node_id); + } +}; diff --git a/erpnext/public/js/hub/components/ReviewArea.vue b/erpnext/public/js/hub/components/ReviewArea.vue index 5e4e439f3d0..aa83bb0e465 100644 --- a/erpnext/public/js/hub/components/ReviewArea.vue +++ b/erpnext/public/js/hub/components/ReviewArea.vue @@ -137,4 +137,4 @@ export default { } } } - \ No newline at end of file + diff --git a/erpnext/public/js/hub/components/ReviewTimelineItem.vue b/erpnext/public/js/hub/components/ReviewTimelineItem.vue index f0fe001973c..d0e83f3b1cd 100644 --- a/erpnext/public/js/hub/components/ReviewTimelineItem.vue +++ b/erpnext/public/js/hub/components/ReviewTimelineItem.vue @@ -51,4 +51,3 @@ export default { } } - diff --git a/erpnext/public/js/hub/pages/FeaturedItems.vue b/erpnext/public/js/hub/pages/FeaturedItems.vue index 63ae7e99bbd..8380b2b2c0b 100644 --- a/erpnext/public/js/hub/pages/FeaturedItems.vue +++ b/erpnext/public/js/hub/pages/FeaturedItems.vue @@ -69,7 +69,7 @@ export default { const item_name = this.items.filter(item => item.hub_item_name === hub_item_name); - alert_message = __('{0} removed. {1}', [item_name, + alert_message = __('{0} removed. {1}', [item_name, `${__('Undo')}`]); alert = frappe.show_alert(alert_message, grace_period / 1000, { diff --git a/erpnext/public/js/hub/pages/Publish.vue b/erpnext/public/js/hub/pages/Publish.vue index 96fa0aae4e5..ecba4b1e5a8 100644 --- a/erpnext/public/js/hub/pages/Publish.vue +++ b/erpnext/public/js/hub/pages/Publish.vue @@ -78,7 +78,7 @@ export default { empty_state_message: __('No Items selected yet. Browse and click on items below to publish.'), valid_items_instruction: __('Only items with an image and description can be published. Please update them if an item in your inventory does not appear.'), last_sync_message: (hub.settings.last_sync_datetime) - ? __('Last sync was {0}.', [`${comment_when(hub.settings.last_sync_datetime)}`]) + + ? __('Last sync was {0}.', [`${comment_when(hub.settings.last_sync_datetime)}`]) + ` ${__('See your Published Items.')}` : '' }; diff --git a/erpnext/public/js/hub/pages/Seller.vue b/erpnext/public/js/hub/pages/Seller.vue index c0903c64c37..3c9b800f4a0 100644 --- a/erpnext/public/js/hub/pages/Seller.vue +++ b/erpnext/public/js/hub/pages/Seller.vue @@ -24,7 +24,7 @@
          - {{ item_container_heading }} + {{ item_container_heading }} Customize your Featured Items @@ -160,7 +160,7 @@ export default { ]; setTimeout(() => this.init_seller_traffic_chart(), 1); - + }); }, diff --git a/erpnext/public/js/hub/vue-plugins.js b/erpnext/public/js/hub/vue-plugins.js index 6e6a7cb5989..4912d684991 100644 --- a/erpnext/public/js/hub/vue-plugins.js +++ b/erpnext/public/js/hub/vue-plugins.js @@ -55,4 +55,4 @@ const handleImage = (el, src) => { Vue.filter('striphtml', function (text) { return strip_html(text || ''); -}); \ No newline at end of file +}); diff --git a/erpnext/public/js/leaflet/leaflet.draw.js b/erpnext/public/js/leaflet/leaflet.draw.js index 4352f7025b7..26f1e19da5a 100755 --- a/erpnext/public/js/leaflet/leaflet.draw.js +++ b/erpnext/public/js/leaflet/leaflet.draw.js @@ -140,4 +140,4 @@ e.on("click", this._removeLayer, this) }, _disableLayerDelete: function(t) { var e = t.layer || t.target || t; e.off("click", this._removeLayer, this), this._deletedLayers.removeLayer(e) }, _removeLayer: function(t) { var e = t.layer || t.target || t; this._deletableLayers.removeLayer(e), this._deletedLayers.addLayer(e) }, _onMouseMove: function(t) { this._tooltip.updatePosition(t.latlng) }, _hasAvailableLayers: function() { return 0 !== this._deletableLayers.getLayers().length } }) -}(window, document); \ No newline at end of file +}(window, document); diff --git a/erpnext/public/js/leaflet/leaflet.js b/erpnext/public/js/leaflet/leaflet.js index 41d9bb9ed4c..91dd3d434c7 100755 --- a/erpnext/public/js/leaflet/leaflet.js +++ b/erpnext/public/js/leaflet/leaflet.js @@ -768,4 +768,4 @@ r = this._locateOptions; if (r.setView) { var a = this.getBoundsZoom(s); this.setView(n, r.maxZoom ? Math.min(a, r.maxZoom) : a) } var h = { latlng: n, bounds: s, timestamp: t.timestamp }; for (var l in t.coords) "number" == typeof t.coords[l] && (h[l] = t.coords[l]); this.fire("locationfound", h) } }) -}(window, document); \ No newline at end of file +}(window, document); diff --git a/erpnext/public/js/payment/payments.js b/erpnext/public/js/payment/payments.js index ddf87068097..57a2aee52e9 100644 --- a/erpnext/public/js/payment/payments.js +++ b/erpnext/public/js/payment/payments.js @@ -231,4 +231,4 @@ erpnext.payments = erpnext.stock.StockController.extend({ $(this.$body).find('.outstanding_amount').text(format_currency(this.frm.doc.outstanding_amount, frappe.get_doc(":Company", this.frm.doc.company).default_currency)); this.update_invoice(); } -}) \ No newline at end of file +}) diff --git a/erpnext/public/js/projects/timer.js b/erpnext/public/js/projects/timer.js index 26be997d482..0e5c0d3720e 100644 --- a/erpnext/public/js/projects/timer.js +++ b/erpnext/public/js/projects/timer.js @@ -159,4 +159,4 @@ erpnext.timesheet.control_timer = function(frm, dialog, row, timestamp=0) { $btn_complete.hide(); $btn_start.show(); } -}; \ No newline at end of file +}; diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js index 6f5d67c7462..38e1eb5156c 100644 --- a/erpnext/public/js/setup_wizard.js +++ b/erpnext/public/js/setup_wizard.js @@ -147,7 +147,7 @@ erpnext.setup.slides_settings = [ } // Validate bank name - if(me.values.bank_account) { + if(me.values.bank_account) { frappe.call({ async: false, method: "erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts.validate_bank_account", diff --git a/erpnext/public/js/stock_analytics.js b/erpnext/public/js/stock_analytics.js index 140c9dc90b6..7085315485e 100644 --- a/erpnext/public/js/stock_analytics.js +++ b/erpnext/public/js/stock_analytics.js @@ -202,4 +202,3 @@ erpnext.StockAnalytics = erpnext.StockGridReport.extend({ frappe.set_route("query-report", "Stock Ledger"); } }); - diff --git a/erpnext/public/js/telephony.js b/erpnext/public/js/telephony.js index 9548d6c5f36..54f99e6e5db 100644 --- a/erpnext/public/js/telephony.js +++ b/erpnext/public/js/telephony.js @@ -6,7 +6,7 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlData.extend( { } if (this.frm && this.frm.fields_dict) { Object.values(this.frm.fields_dict).forEach(function(field) { - if (field.df.read_only === 1 && field.df.options === 'Phone' + if (field.df.read_only === 1 && field.df.options === 'Phone' && field.disp_area.style[0] != 'display' && !field.has_icon) { field.setup_phone(); field.has_icon = true; diff --git a/erpnext/public/js/templates/item_quick_entry.html b/erpnext/public/js/templates/item_quick_entry.html index 6a5f36da77c..e5e78690622 100644 --- a/erpnext/public/js/templates/item_quick_entry.html +++ b/erpnext/public/js/templates/item_quick_entry.html @@ -1,3 +1,3 @@
          {{ __("Variant Attributes") }}
          -
          \ No newline at end of file +
          diff --git a/erpnext/public/js/templates/item_selector.html b/erpnext/public/js/templates/item_selector.html index 58fb26c0e4c..86a15f49072 100644 --- a/erpnext/public/js/templates/item_selector.html +++ b/erpnext/public/js/templates/item_selector.html @@ -34,4 +34,4 @@ {% if ((i % 4 === 3) || (i===data.length - 1)) { %}{% } %} {% endfor %} - \ No newline at end of file + diff --git a/erpnext/public/js/templates/node_card.html b/erpnext/public/js/templates/node_card.html new file mode 100644 index 00000000000..4cb6ee03c0c --- /dev/null +++ b/erpnext/public/js/templates/node_card.html @@ -0,0 +1,33 @@ +
          +
          +
          + + + +
          +
          +
          + {{ name }} +
          + {{ frappe.utils.icon("edit", "xs") }} + {{ __("Edit") }} +
          +
          +
          +
          {{ title }}
          + + {% if is_mobile %} +
          + · {{ connections }} +
          + {% else %} + {% if connections == 1 %} +
          · {{ connections }} Connection
          + {% else %} +
          · {{ connections }} Connections
          + {% endif %} + {% endif %} +
          +
          +
          +
          diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index ce40ced11f2..58307a0b74d 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -465,7 +465,23 @@ erpnext.utils.update_child_items = function(opts) { in_list_view: 1, read_only: 0, disabled: 0, - label: __('Item Code') + label: __('Item Code'), + get_query: function() { + let filters; + if (frm.doc.doctype == 'Sales Order') { + filters = {"is_sales_item": 1}; + } else if (frm.doc.doctype == 'Purchase Order') { + if (frm.doc.is_subcontracted == "Yes") { + filters = {"is_sub_contracted_item": 1}; + } else { + filters = {"is_purchase_item": 1}; + } + } + return { + query: "erpnext.controllers.queries.item_query", + filters: filters + }; + } }, { fieldtype:'Link', fieldname:'uom', @@ -547,7 +563,7 @@ erpnext.utils.update_child_items = function(opts) { }, ], primary_action: function() { - const trans_items = this.get_values()["trans_items"]; + const trans_items = this.get_values()["trans_items"].filter((item) => !!item.item_code); frappe.call({ method: 'erpnext.controllers.accounts_controller.update_child_qty_rate', freeze: true, diff --git a/erpnext/public/js/utils/customer_quick_entry.js b/erpnext/public/js/utils/customer_quick_entry.js index ebe6cd98f81..53aadd8166f 100644 --- a/erpnext/public/js/utils/customer_quick_entry.js +++ b/erpnext/public/js/utils/customer_quick_entry.js @@ -1,9 +1,9 @@ frappe.provide('frappe.ui.form'); frappe.ui.form.CustomerQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({ - init: function(doctype, after_insert) { + init: function(doctype, after_insert, init_callback, doc, force) { + this._super(doctype, after_insert, init_callback, doc, force); this.skip_redirect_on_error = true; - this._super(doctype, after_insert); }, render_dialog: function() { @@ -78,4 +78,4 @@ frappe.ui.form.CustomerQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({ return variant_fields; }, -}) \ No newline at end of file +}) diff --git a/erpnext/public/js/utils/dimension_tree_filter.js b/erpnext/public/js/utils/dimension_tree_filter.js index 96e181788e3..bb23f1512b9 100644 --- a/erpnext/public/js/utils/dimension_tree_filter.js +++ b/erpnext/public/js/utils/dimension_tree_filter.js @@ -100,4 +100,4 @@ erpnext.accounts.dimensions = { }); } } -}; \ No newline at end of file +}; diff --git a/erpnext/public/js/utils/item_quick_entry.js b/erpnext/public/js/utils/item_quick_entry.js index 27ef107acef..2930d8d9e34 100644 --- a/erpnext/public/js/utils/item_quick_entry.js +++ b/erpnext/public/js/utils/item_quick_entry.js @@ -404,4 +404,4 @@ frappe.ui.form.ItemQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({ } return attribute; } -}); \ No newline at end of file +}); diff --git a/erpnext/public/js/utils/item_selector.js b/erpnext/public/js/utils/item_selector.js index d04c488a59d..a4775c7ec39 100644 --- a/erpnext/public/js/utils/item_selector.js +++ b/erpnext/public/js/utils/item_selector.js @@ -107,4 +107,4 @@ erpnext.ItemSelector = Class.extend({ me.dialog.results.html(frappe.render_template('item_selector', {'data':r.values})); }); } -}); \ No newline at end of file +}); diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index a79eadc7619..4d432e3d5cc 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -76,6 +76,7 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) { if (args) { args.posting_date = frm.doc.posting_date || frm.doc.transaction_date; + args.fetch_payment_terms_template = cint(!frm.doc.ignore_default_payment_terms_template); } } if (!args || !args.party) return; diff --git a/erpnext/public/less/email.less b/erpnext/public/less/email.less index 4077c4940d1..738290fd85f 100644 --- a/erpnext/public/less/email.less +++ b/erpnext/public/less/email.less @@ -29,4 +29,4 @@ color: @border-color; border: 1px solid @border-color; background-color: #fff; -} \ No newline at end of file +} diff --git a/erpnext/public/less/erpnext.less b/erpnext/public/less/erpnext.less index 4076ebec1fd..4dea747f8b8 100644 --- a/erpnext/public/less/erpnext.less +++ b/erpnext/public/less/erpnext.less @@ -491,4 +491,4 @@ body[data-route="pos"] { .exercise-col { padding: 10px; -} \ No newline at end of file +} diff --git a/erpnext/public/less/pos.less b/erpnext/public/less/pos.less index b081ed4414b..fdb813ff41c 100644 --- a/erpnext/public/less/pos.less +++ b/erpnext/public/less/pos.less @@ -226,4 +226,4 @@ input[type=number]::-webkit-outer-spin-button { .quantity-total { font-size: 18px; -} \ No newline at end of file +} diff --git a/erpnext/public/less/products.less b/erpnext/public/less/products.less index 5e744ceac5b..d44c5173786 100644 --- a/erpnext/public/less/products.less +++ b/erpnext/public/less/products.less @@ -68,4 +68,4 @@ font-size: 12px; width: 24px; height: 24px; -} \ No newline at end of file +} diff --git a/erpnext/public/less/website.less b/erpnext/public/less/website.less index ac878de105b..171b4906443 100644 --- a/erpnext/public/less/website.less +++ b/erpnext/public/less/website.less @@ -385,4 +385,4 @@ max-height: 300px; object-fit: contain; } -} \ No newline at end of file +} diff --git a/erpnext/public/scss/hierarchy_chart.scss b/erpnext/public/scss/hierarchy_chart.scss new file mode 100644 index 00000000000..57d5e8414ae --- /dev/null +++ b/erpnext/public/scss/hierarchy_chart.scss @@ -0,0 +1,313 @@ +.node-card { + background: white; + stroke: 1px solid var(--gray-200); + box-shadow: var(--shadow-base); + border-radius: 0.5rem; + padding: 0.75rem; + margin-left: 3rem; + width: 18rem; + overflow: hidden; + + .btn-edit-node { + display: none; + } + + .edit-chart-node { + display: none; + } + + .node-edit-icon { + display: none; + } +} + +.node-card.exported { + box-shadow: none +} + +.node-image { + width: 3.0rem; + height: 3.0rem; +} + +.node-name { + font-size: 1rem; + line-height: 1.72; +} + +.node-title { + font-size: 0.75rem; + line-height: 1.35; +} + +.node-info { + width: 12.7rem; +} + +.node-connections { + font-size: 0.75rem; + line-height: 1.35; +} + +.node-card.active { + background: var(--blue-50); + border: 1px solid var(--blue-500); + box-shadow: var(--shadow-md); + border-radius: 0.5rem; + padding: 0.75rem; + width: 18rem; + + .btn-edit-node { + display: flex; + background: var(--blue-100); + color: var(--blue-500); + padding: .25rem .5rem; + font-size: .75rem; + justify-content: center; + box-shadow: var(--shadow-sm); + margin-left: auto; + } + + .edit-chart-node { + display: block; + margin-right: 0.25rem; + } + + .node-edit-icon { + display: block; + } + + .node-edit-icon > .icon{ + stroke: var(--blue-500); + } + + .node-name { + align-items: center; + justify-content: space-between; + margin-bottom: 2px; + width: 12.2rem; + } +} + +.node-card.active-path { + background: var(--blue-100); + border: 1px solid var(--blue-300); + box-shadow: var(--shadow-sm); + border-radius: 0.5rem; + padding: 0.75rem; + width: 15rem; + height: 3.0rem; + + .btn-edit-node { + display: none !important; + } + + .edit-chart-node { + display: none; + } + + .node-edit-icon { + display: none; + } + + .node-info { + display: none; + } + + .node-title { + display: none; + } + + .node-connections { + display: none; + } + + .node-name { + font-size: 0.85rem; + line-height: 1.35; + } + + .node-image { + width: 1.5rem; + height: 1.5rem; + } + + .node-meta { + align-items: baseline; + } +} + +.node-card.collapsed { + background: white; + stroke: 1px solid var(--gray-200); + box-shadow: var(--shadow-sm); + border-radius: 0.5rem; + padding: 0.75rem; + width: 15rem; + height: 3.0rem; + + .btn-edit-node { + display: none !important; + } + + .edit-chart-node { + display: none; + } + + .node-edit-icon { + display: none; + } + + .node-info { + display: none; + } + + .node-title { + display: none; + } + + .node-connections { + display: none; + } + + .node-name { + font-size: 0.85rem; + line-height: 1.35; + } + + .node-image { + width: 1.5rem; + height: 1.5rem; + } + + .node-meta { + align-items: baseline; + } +} + +// horizontal hierarchy tree view +#hierarchy-chart-wrapper { + padding-top: 30px; + + #arrows { + margin-top: -80px; + } +} + +.hierarchy { + display: flex; +} + +.hierarchy li { + list-style-type: none; +} + +.child-node { + margin: 0px 0px 16px 0px; +} + +.hierarchy, .hierarchy-mobile { + .level { + margin-right: 8px; + align-items: flex-start; + flex-direction: column; + } +} + +#arrows { + position: absolute; + overflow: visible; +} + +.active-connector { + stroke: var(--blue-500); +} + +.collapsed-connector { + stroke: var(--blue-300); +} + +// mobile + +.hierarchy-mobile { + display: flex; + flex-direction: column; + align-items: center; + padding-top: 10px; + padding-left: 0px; +} + +.hierarchy-mobile li { + list-style-type: none; + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.mobile-node { + margin-left: 0; +} + +.mobile-node.active-path { + width: 12.25rem; +} + +.active-child { + width: 15.5rem; +} + +.mobile-node .node-connections { + max-width: 80px; +} + +.hierarchy-mobile .node-children { + margin-top: 16px; +} + +.root-level .node-card { + margin: 0 0 16px; +} + +// node group + +.collapsed-level { + margin-bottom: 16px; + width: 18rem; +} + +.node-group { + background: white; + border: 1px solid var(--gray-300); + box-shadow: var(--shadow-sm); + border-radius: 0.5rem; + padding: 0.75rem; + width: 18rem; + height: 3rem; + overflow: hidden; + align-items: center; +} + +.node-group .avatar-group { + margin-left: 0px; +} + +.node-group .avatar-extra-count { + background-color: var(--blue-100); + color: var(--blue-500); +} + +.node-group .avatar-frame { + width: 1.5rem; + height: 1.5rem; +} + +.node-group.collapsed { + width: 5rem; + margin-left: 12px; +} + +.sibling-group { + display: flex; + flex-direction: column; + align-items: center; +} diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index 5962859be5a..490a7c4af73 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -483,4 +483,3 @@ body.product-page { border: 1px solid var(--dark-border-color); } } - diff --git a/erpnext/public/scss/website.scss b/erpnext/public/scss/website.scss index f4325c03f5b..9ea84160342 100644 --- a/erpnext/public/scss/website.scss +++ b/erpnext/public/scss/website.scss @@ -67,4 +67,4 @@ .card-body > .card-title { line-height: 1.3; } -} \ No newline at end of file +} diff --git a/erpnext/quality_management/doctype/quality_action/quality_action.js b/erpnext/quality_management/doctype/quality_action/quality_action.js index e216a7539c8..b44f2a20344 100644 --- a/erpnext/quality_management/doctype/quality_action/quality_action.js +++ b/erpnext/quality_management/doctype/quality_action/quality_action.js @@ -3,4 +3,4 @@ frappe.ui.form.on('Quality Action', { -}); \ No newline at end of file +}); diff --git a/erpnext/quality_management/doctype/quality_action/quality_action.py b/erpnext/quality_management/doctype/quality_action/quality_action.py index d6fa5051ee6..02401ba689d 100644 --- a/erpnext/quality_management/doctype/quality_action/quality_action.py +++ b/erpnext/quality_management/doctype/quality_action/quality_action.py @@ -8,4 +8,4 @@ from frappe.model.document import Document class QualityAction(Document): def validate(self): - self.status = 'Open' if any([d.status=='Open' for d in self.resolutions]) else 'Completed' \ No newline at end of file + self.status = 'Open' if any([d.status=='Open' for d in self.resolutions]) else 'Completed' diff --git a/erpnext/quality_management/doctype/quality_action/test_quality_action.py b/erpnext/quality_management/doctype/quality_action/test_quality_action.py index 24b97ca3a09..98d665f3910 100644 --- a/erpnext/quality_management/doctype/quality_action/test_quality_action.py +++ b/erpnext/quality_management/doctype/quality_action/test_quality_action.py @@ -8,4 +8,4 @@ import unittest class TestQualityAction(unittest.TestCase): # quality action has no code - pass \ No newline at end of file + pass diff --git a/erpnext/quality_management/doctype/quality_feedback/quality_feedback.py b/erpnext/quality_management/doctype/quality_feedback/quality_feedback.py index 5a8ec73cfe1..d3e96cf2d94 100644 --- a/erpnext/quality_management/doctype/quality_feedback/quality_feedback.py +++ b/erpnext/quality_management/doctype/quality_feedback/quality_feedback.py @@ -21,4 +21,3 @@ class QualityFeedback(Document): self.document_type ='User' self.document_name = frappe.session.user self.set_parameters() - diff --git a/erpnext/quality_management/doctype/quality_feedback_template/test_quality_feedback_template.py b/erpnext/quality_management/doctype/quality_feedback_template/test_quality_feedback_template.py index b3eed103836..afed14b6ad0 100644 --- a/erpnext/quality_management/doctype/quality_feedback_template/test_quality_feedback_template.py +++ b/erpnext/quality_management/doctype/quality_feedback_template/test_quality_feedback_template.py @@ -7,4 +7,4 @@ import frappe import unittest class TestQualityFeedbackTemplate(unittest.TestCase): - pass \ No newline at end of file + pass diff --git a/erpnext/quality_management/doctype/quality_goal/quality_goal.py b/erpnext/quality_management/doctype/quality_goal/quality_goal.py index f3fe986d539..3e616b75ceb 100644 --- a/erpnext/quality_management/doctype/quality_goal/quality_goal.py +++ b/erpnext/quality_management/doctype/quality_goal/quality_goal.py @@ -9,4 +9,4 @@ from frappe.model.document import Document class QualityGoal(Document): def validate(self): - pass \ No newline at end of file + pass diff --git a/erpnext/quality_management/doctype/quality_goal/test_quality_goal.py b/erpnext/quality_management/doctype/quality_goal/test_quality_goal.py index f61d6e581d7..0e135b50212 100644 --- a/erpnext/quality_management/doctype/quality_goal/test_quality_goal.py +++ b/erpnext/quality_management/doctype/quality_goal/test_quality_goal.py @@ -22,4 +22,4 @@ def get_quality_goal(): objectives = [ dict(objective = 'Check test cases', target='100', uom='Percent') ] - )).insert() \ No newline at end of file + )).insert() diff --git a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.py b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.py index f8de22958bd..9e453ebfc2e 100644 --- a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.py +++ b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.py @@ -6,4 +6,4 @@ from __future__ import unicode_literals from frappe.model.document import Document class QualityMeeting(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/quality_management/doctype/quality_meeting/quality_meeting_list.js b/erpnext/quality_management/doctype/quality_meeting/quality_meeting_list.js index ff85c84dc9d..5fd1b30eb45 100644 --- a/erpnext/quality_management/doctype/quality_meeting/quality_meeting_list.js +++ b/erpnext/quality_management/doctype/quality_meeting/quality_meeting_list.js @@ -8,4 +8,4 @@ frappe.listview_settings['Quality Meeting'] = { return [__("Close"), "green", ",status=,Close"]; } } -}; \ No newline at end of file +}; diff --git a/erpnext/quality_management/doctype/quality_meeting/test_quality_meeting.py b/erpnext/quality_management/doctype/quality_meeting/test_quality_meeting.py index 754bccb06e0..6bf4c179c6b 100644 --- a/erpnext/quality_management/doctype/quality_meeting/test_quality_meeting.py +++ b/erpnext/quality_management/doctype/quality_meeting/test_quality_meeting.py @@ -8,4 +8,4 @@ import unittest class TestQualityMeeting(unittest.TestCase): # nothing to test - pass \ No newline at end of file + pass diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js index ac876229ecb..fd2b6a4eaa0 100644 --- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js +++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js @@ -19,4 +19,4 @@ frappe.ui.form.on('Quality Procedure', { }; }); } -}); \ No newline at end of file +}); diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py index 53f4e6c70fe..117db0012ba 100644 --- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py +++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py @@ -77,4 +77,4 @@ def add_node(): if args.parent_quality_procedure == 'All Quality Procedures': args.parent_quality_procedure = None - return frappe.get_doc(args).insert() \ No newline at end of file + return frappe.get_doc(args).insert() diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js b/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js index eeb4cf617c3..2851fcc5969 100644 --- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js +++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js @@ -31,4 +31,4 @@ frappe.treeview_settings["Quality Procedure"] = { onload: function(treeview) { treeview.make_tree(); }, -}; \ No newline at end of file +}; diff --git a/erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py b/erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py index 36bdf26acf5..4fa7734bc68 100644 --- a/erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py +++ b/erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py @@ -47,4 +47,4 @@ def create_procedure(): processes = [ dict(process_description = 'Test Step 1') ] - )).insert() \ No newline at end of file + )).insert() diff --git a/erpnext/quality_management/doctype/quality_review/quality_review.js b/erpnext/quality_management/doctype/quality_review/quality_review.js index 67371bfc5c6..0e6b7034101 100644 --- a/erpnext/quality_management/doctype/quality_review/quality_review.js +++ b/erpnext/quality_management/doctype/quality_review/quality_review.js @@ -22,4 +22,4 @@ frappe.ui.form.on('Quality Review', { } }); }, -}); \ No newline at end of file +}); diff --git a/erpnext/quality_management/doctype/quality_review/quality_review.py b/erpnext/quality_management/doctype/quality_review/quality_review.py index e3a8b073f0f..34cc890e219 100644 --- a/erpnext/quality_management/doctype/quality_review/quality_review.py +++ b/erpnext/quality_management/doctype/quality_review/quality_review.py @@ -61,4 +61,4 @@ def get_quarter(month): if month in ["January", "April", "July", "October"]: return True else: - return False \ No newline at end of file + return False diff --git a/erpnext/quality_management/doctype/quality_review/quality_review_list.js b/erpnext/quality_management/doctype/quality_review/quality_review_list.js index e2eb31b55a3..b0be783de56 100644 --- a/erpnext/quality_management/doctype/quality_review/quality_review_list.js +++ b/erpnext/quality_management/doctype/quality_review/quality_review_list.js @@ -9,4 +9,4 @@ frappe.listview_settings['Quality Review'] = { return [__("Action Initialised"), "red", "action,=,Action Initialised"]; } } -}; \ No newline at end of file +}; diff --git a/erpnext/quality_management/doctype/quality_review/test_quality_review.py b/erpnext/quality_management/doctype/quality_review/test_quality_review.py index a7d92da8ace..161ecd01ef1 100644 --- a/erpnext/quality_management/doctype/quality_review/test_quality_review.py +++ b/erpnext/quality_management/doctype/quality_review/test_quality_review.py @@ -19,4 +19,4 @@ class TestQualityReview(unittest.TestCase): self.assertEqual(quality_goal.objectives[0].target, quality_review.reviews[0].target) quality_review.delete() - quality_goal.delete() \ No newline at end of file + quality_goal.delete() diff --git a/erpnext/regional/address_template/setup.py b/erpnext/regional/address_template/setup.py index 9f318de3451..1b4087d77ba 100644 --- a/erpnext/regional/address_template/setup.py +++ b/erpnext/regional/address_template/setup.py @@ -10,7 +10,7 @@ def set_up_address_templates(default_country=None): def get_address_templates(): """ Return country and path for all HTML files in this directory. - + Returns a list of dicts. """ def country(file_name): diff --git a/erpnext/regional/address_template/templates/france.html b/erpnext/regional/address_template/templates/france.html new file mode 100644 index 00000000000..752331eeec9 --- /dev/null +++ b/erpnext/regional/address_template/templates/france.html @@ -0,0 +1,5 @@ +{% if address_line1 %}{{ address_line1 }}{% endif -%} +{% if address_line2 %}
          {{ address_line2 }}{% endif -%} +{% if pincode %}
          {{ pincode }}{% endif -%} +{% if city %} {{ city }}{% endif -%} +{% if country %}
          {{ country }}{% endif -%} diff --git a/erpnext/regional/address_template/templates/germany.html b/erpnext/regional/address_template/templates/germany.html index 7fa4c32612a..25c9c9d32e6 100644 --- a/erpnext/regional/address_template/templates/germany.html +++ b/erpnext/regional/address_template/templates/germany.html @@ -3,6 +3,6 @@ {% if country in ["Germany", "Deutschland"] %} {{ pincode }} {{ city }} {% else %} - {{ pincode }} {{ city | upper }}
          + {{ pincode }} {{ city | upper }}
          {{ country | upper }} {% endif %} diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js index cc2d9f06d2d..54e488610df 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js @@ -3,7 +3,7 @@ frappe.ui.form.on('E Invoice Settings', { refresh(frm) { - const docs_link = 'https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing'; + const docs_link = 'https://docs.erpnext.com/docs/v13/user/manual/en/regional/india/setup-e-invoicing'; frm.dashboard.set_headline( __("Read {0} for more information on E Invoicing features.", [`documentation`]) ); diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py index c24ad886ea1..4f6b3eca7a6 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py @@ -11,4 +11,3 @@ class EInvoiceSettings(Document): def validate(self): if self.enable and not self.credentials: frappe.throw(_('You must add atleast one credentials to be able to use E Invoicing.')) - diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js index 7ff4de48639..347fdfe61b6 100644 --- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js +++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js @@ -25,4 +25,4 @@ frappe.ui.form.on('GST HSN Code', { }); } } -}); \ No newline at end of file +}); diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py index 86cd4d1545d..4791dc26753 100644 --- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py +++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py @@ -30,4 +30,4 @@ def update_item_document(items, taxes): 'tax_category': tax.tax_category, 'valid_from': tax.valid_from }) - item_to_be_updated.save() \ No newline at end of file + item_to_be_updated.save() diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html index 3b6a45a3b42..f3fc60fdb63 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html @@ -294,4 +294,4 @@ text-align: right; } - \ No newline at end of file + 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 641520437fb..0ee5b097b54 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -214,9 +214,8 @@ class GSTR3BReport(Document): for d in item_details: if d.item_code not in self.invoice_items.get(d.parent, {}): - self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, - sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in item_details - if i.item_code == d.item_code and i.parent == d.parent)) + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0) + self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0) if d.is_nil_exempt and d.item_code not in self.is_nil_exempt: self.is_nil_exempt.append(d.item_code) @@ -281,9 +280,15 @@ class GSTR3BReport(Document): if self.get('invoice_items'): # Build itemised tax for export invoices, nil and exempted where tax table is blank for invoice, items in iteritems(self.invoice_items): - if invoice not in self.items_based_on_tax_rate and (self.invoice_detail_map.get(invoice, {}).get('export_type') - == "Without Payment of Tax"): + if invoice not in self.items_based_on_tax_rate and self.invoice_detail_map.get(invoice, {}).get('export_type') \ + == "Without Payment of Tax" and self.invoice_detail_map.get(invoice, {}).get('gst_category') == "Overseas": self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys()) + else: + for item in items.keys(): + if item in self.is_nil_exempt + self.is_non_gst and \ + item not in self.items_based_on_tax_rate.get(invoice, {}).get(0, []): + self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, []) + self.items_based_on_tax_rate[invoice][0].append(item) def set_outward_taxable_supplies(self): inter_state_supply_details = {} @@ -322,6 +327,9 @@ class GSTR3BReport(Document): inter_state_supply_details[(gst_category, place_of_supply)]['txval'] += taxable_value inter_state_supply_details[(gst_category, place_of_supply)]['iamt'] += (taxable_value * rate /100) + if self.invoice_cess.get(inv): + self.report_dict['sup_details']['osup_det']['csamt'] += flt(self.invoice_cess.get(inv), 2) + self.set_inter_state_supply(inter_state_supply_details) def set_supplies_liable_to_reverse_charge(self): diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js index c2d6edfc773..5918ec8b316 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js @@ -43,4 +43,4 @@ frappe.ui.form.on('Import Supplier Invoice', { } } -}); \ No newline at end of file +}); diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py index ad60db05595..656c3296e58 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py +++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py @@ -13,7 +13,7 @@ class LowerDeductionCertificate(Document): def validate(self): self.validate_dates() self.validate_supplier_against_section_code() - + def validate_dates(self): if getdate(self.valid_upto) < getdate(self.valid_from): frappe.throw(_("Valid Upto date cannot be before Valid From date")) @@ -44,4 +44,4 @@ class LowerDeductionCertificate(Document): return True elif getdate(self.valid_from) <= valid_from and valid_upto <= getdate(self.valid_upto): return True - return False \ No newline at end of file + return False diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py index c478b0f3228..41b42036687 100644 --- a/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py @@ -98,4 +98,4 @@ def create_80g_certificate(args): certificate.update(args) - return certificate \ No newline at end of file + return certificate diff --git a/erpnext/regional/germany/utils/datev/datev_constants.py b/erpnext/regional/germany/utils/datev/datev_constants.py index 63f9a777bb5..be3d7a3e542 100644 --- a/erpnext/regional/germany/utils/datev/datev_constants.py +++ b/erpnext/regional/germany/utils/datev/datev_constants.py @@ -455,7 +455,7 @@ ACCOUNT_NAME_COLUMNS = [ "Konto", # Account name "Kontenbeschriftung", - # Language of the account name + # Language of the account name # "de-DE" or "en-GB" "Sprach-ID" ] diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 8ad30fa9106..348f0c6feed 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -289,4 +289,4 @@ const show_einvoice_preview = (frm, einvoice) => { } } }); -}; \ No newline at end of file +}; diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index ea600d90973..fe4c172e237 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -190,8 +190,10 @@ def get_item_list(invoice): item.description = sanitize_for_json(d.item_name) item.qty = abs(item.qty) - - item.unit_rate = abs(item.taxable_value / item.qty) + if flt(item.qty) != 0.0: + item.unit_rate = abs(item.taxable_value / item.qty) + else: + item.unit_rate = abs(item.taxable_value) item.gross_amount = abs(item.taxable_value) item.taxable_value = abs(item.taxable_value) item.discount_amount = 0 @@ -316,10 +318,6 @@ def get_payment_details(invoice): )) def get_return_doc_reference(invoice): - if not invoice.return_against: - frappe.throw(_('For generating IRN, reference to the original invoice is mandatory for a credit note. Please set {} field to generate e-invoice.') - .format(frappe.bold('Return Against')), title=_('Missing Field')) - invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date') return frappe._dict(dict( invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy') @@ -435,7 +433,7 @@ def make_einvoice(invoice): if invoice.is_pos and invoice.base_paid_amount: payment_details = get_payment_details(invoice) - if invoice.is_return: + if invoice.is_return and invoice.return_against: prev_doc_details = get_return_doc_reference(invoice) if invoice.transporter and not invoice.is_return: @@ -966,7 +964,7 @@ class GSPConnector(): "attached_to_doctype": doctype, "attached_to_name": docname, "attached_to_field": "qrcode_image", - "is_private": 1, + "is_private": 0, "content": qr_image.getvalue()}) _file.save() frappe.db.commit() @@ -1127,4 +1125,4 @@ def check_scheduler_status(): def job_already_enqueued(job_name): enqueued_jobs = [d.get("job_name") for d in get_info()] if job_name in enqueued_jobs: - return True \ No newline at end of file + return True diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 5f9d5ed0d61..2d6b9133900 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -12,7 +12,10 @@ from erpnext.accounts.utils import get_fiscal_year, FiscalYearError from frappe.utils import today def setup(company=None, patch=True): - setup_company_independent_fixtures(patch=patch) + # Company independent fixtures should be called only once at the first company setup + if frappe.db.count('Company', {'country': 'India'}) <=1: + setup_company_independent_fixtures(patch=patch) + if not patch: make_fixtures(company) @@ -122,10 +125,12 @@ def add_print_formats(): def make_property_setters(patch=False): # GST rules do not allow for an invoice no. bigger than 16 characters journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC'] + sales_invoice_series = ['SINV-.YY.-', 'SRET-.YY.-', ''] + frappe.get_meta("Sales Invoice").get_options("naming_series").split("\n") + purchase_invoice_series = ['PINV-.YY.-', 'PRET-.YY.-', ''] + frappe.get_meta("Purchase Invoice").get_options("naming_series").split("\n") if not patch: - make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '') - make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '') + make_property_setter('Sales Invoice', 'naming_series', 'options', '\n'.join(sales_invoice_series), '') + make_property_setter('Purchase Invoice', 'naming_series', 'options', '\n'.join(purchase_invoice_series), '') make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '') def make_custom_fields(update=True): @@ -452,7 +457,7 @@ def make_custom_fields(update=True): depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, - depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + depends_on='eval: doc.irn', allow_on_submit=1, insert_after='customer'), dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1, depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill'), @@ -636,9 +641,9 @@ def make_custom_fields(update=True): 'label': 'Export Type', 'fieldtype': 'Select', 'insert_after': 'gst_category', - 'default': 'Without Payment of Tax', 'depends_on':'eval:in_list(["SEZ", "Overseas"], doc.gst_category)', - 'options': '\nWith Payment of Tax\nWithout Payment of Tax' + 'options': '\nWith Payment of Tax\nWithout Payment of Tax', + 'mandatory_depends_on': 'eval:in_list(["SEZ", "Overseas"], doc.gst_category)' } ], 'Customer': [ @@ -655,9 +660,9 @@ def make_custom_fields(update=True): 'label': 'Export Type', 'fieldtype': 'Select', 'insert_after': 'gst_category', - 'default': 'Without Payment of Tax', 'depends_on':'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)', - 'options': '\nWith Payment of Tax\nWithout Payment of Tax' + 'options': '\nWith Payment of Tax\nWithout Payment of Tax', + 'mandatory_depends_on': 'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)' } ], 'Member': [ @@ -786,7 +791,7 @@ def set_tax_withholding_category(company): doc.flags.ignore_mandatory = True doc.insert() else: - doc = frappe.get_doc("Tax Withholding Category", d.get("name")) + doc = frappe.get_doc("Tax Withholding Category", d.get("name"), for_update=True) if accounts: doc.append("accounts", accounts[0]) @@ -982,4 +987,4 @@ def create_gratuity_rule(): def update_accounts_settings_for_taxes(): if frappe.db.count('Company') == 1: - frappe.db.set_value('Accounts Settings', None, "add_taxes_from_item_tax_template", 0) \ No newline at end of file + frappe.db.set_value('Accounts Settings', None, "add_taxes_from_item_tax_template", 0) diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index d3b7ea3b1a0..5f6dcdeb922 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -49,4 +49,3 @@ erpnext.setup_auto_gst_taxation = (doctype) => { } }); } - diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index a4466e78f28..ce5aa10902e 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -431,9 +431,11 @@ def get_ewb_data(dt, dn): company_address = frappe.get_doc('Address', doc.company_address) billing_address = frappe.get_doc('Address', doc.customer_address) + #added dispatch address + dispatch_address = frappe.get_doc('Address', doc.dispatch_address_name) if doc.dispatch_address_name else company_address shipping_address = frappe.get_doc('Address', doc.shipping_address_name) - data = get_address_details(data, doc, company_address, billing_address) + data = get_address_details(data, doc, company_address, billing_address, dispatch_address) data.itemList = [] data.totalValue = doc.total @@ -473,7 +475,7 @@ def get_ewb_data(dt, dn): ewaybills.append(data) data = { - 'version': '1.0.1118', + 'version': '1.0.0421', 'billLists': ewaybills } @@ -519,10 +521,10 @@ def get_gstins_for_company(company): `tabDynamic Link`.link_name = %(company)s""", {"company": company}) return company_gstins -def get_address_details(data, doc, company_address, billing_address): +def get_address_details(data, doc, company_address, billing_address, dispatch_address): data.fromPincode = validate_pincode(company_address.pincode, 'Company Address') - data.fromStateCode = data.actualFromStateCode = validate_state_code( - company_address.gst_state_number, 'Company Address') + data.fromStateCode = validate_state_code(company_address.gst_state_number, 'Company Address') + data.actualFromStateCode = validate_state_code(dispatch_address.gst_state_number, 'Dispatch Address') if not doc.billing_address_gstin or len(doc.billing_address_gstin) < 15: data.toGstin = 'URP' @@ -834,14 +836,22 @@ def get_depreciation_amount(asset, depreciable_value, row): depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) if row.depreciation_method in ("Straight Line", "Manual"): - depreciation_amount = (flt(row.value_after_depreciation) - - flt(row.expected_value_after_useful_life)) / depreciation_left + # if the Depreciation Schedule is being prepared for the first time + if not asset.flags.increase_in_asset_life: + depreciation_amount = (flt(row.value_after_depreciation) - + flt(row.expected_value_after_useful_life)) / depreciation_left + + # if the Depreciation Schedule is being modified after Asset Repair + else: + depreciation_amount = (flt(row.value_after_depreciation) - + flt(row.expected_value_after_useful_life)) / (date_diff(asset.to_date, asset.available_for_use_date) / 365) + else: rate_of_depreciation = row.rate_of_depreciation # if its the first depreciation if depreciable_value == asset.gross_purchase_amount: # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2 - diff = date_diff(asset.available_for_use_date, row.depreciation_start_date) + diff = date_diff(row.depreciation_start_date, asset.available_for_use_date) if diff <= 180: rate_of_depreciation = rate_of_depreciation / 2 frappe.msgprint( @@ -849,4 +859,32 @@ def get_depreciation_amount(asset, depreciable_value, row): depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100)) - return depreciation_amount \ No newline at end of file + return depreciation_amount + +def set_item_tax_from_hsn_code(item): + if not item.taxes and item.gst_hsn_code: + hsn_doc = frappe.get_doc("GST HSN Code", item.gst_hsn_code) + + for tax in hsn_doc.taxes: + item.append('taxes', { + 'item_tax_template': tax.item_tax_template, + 'tax_category': tax.tax_category, + 'valid_from': tax.valid_from + }) + +def delete_gst_settings_for_company(doc, method): + if doc.country != 'India': + return + + gst_settings = frappe.get_doc("GST Settings") + records_to_delete = [] + + for d in reversed(gst_settings.get('gst_accounts')): + if d.company == doc.name: + records_to_delete.append(d) + + for d in records_to_delete: + gst_settings.remove(d) + + gst_settings.save() + diff --git a/erpnext/regional/italy/__init__.py b/erpnext/regional/italy/__init__.py index ef1d5822ba1..4932f660ca5 100644 --- a/erpnext/regional/italy/__init__.py +++ b/erpnext/regional/italy/__init__.py @@ -76,4 +76,4 @@ state_codes = {'Siracusa': 'SR', 'Bologna': 'BO', 'Grosseto': 'GR', 'Caserta': ' 'Cagliari': 'CA', 'Siena': 'SI', 'Vibo Valentia': 'VV', 'Reggio Calabria': 'RC', 'Ascoli Piceno': 'AP', 'Carbonia-Iglesias': 'CI', 'Oristano': 'OR', 'Asti': 'AT', 'Ravenna': 'RA', 'Vicenza': 'VI', 'Savona': 'SV', 'Biella': 'BI', 'Rimini': 'RN', 'Agrigento': 'AG', 'Prato': 'PO', 'Cuneo': 'CN', 'Cosenza': 'CS', 'Livorno or Leghorn': 'LI', 'Sondrio': 'SO', 'Cremona': 'CR', 'Isernia': 'IS', 'Trento': 'TN', 'Terni': 'TR', 'Bolzano/Bozen': 'BZ', - 'Parma': 'PR', 'Varese': 'VA', 'Venezia': 'VE', 'Sassari': 'SS', 'Arezzo': 'AR'} \ No newline at end of file + 'Parma': 'PR', 'Varese': 'VA', 'Venezia': 'VE', 'Sassari': 'SS', 'Arezzo': 'AR'} diff --git a/erpnext/regional/print_format/detailed_tax_invoice/detailed_tax_invoice.json b/erpnext/regional/print_format/detailed_tax_invoice/detailed_tax_invoice.json index ab56c6bc3cf..f67e2450a29 100644 --- a/erpnext/regional/print_format/detailed_tax_invoice/detailed_tax_invoice.json +++ b/erpnext/regional/print_format/detailed_tax_invoice/detailed_tax_invoice.json @@ -16,7 +16,7 @@ "name": "Detailed Tax Invoice", "owner": "Administrator", "print_format_builder": 1, - "print_format_type": "Server", + "print_format_type": "Jinja", "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/regional/print_format/gst_tax_invoice/gst_tax_invoice.json b/erpnext/regional/print_format/gst_tax_invoice/gst_tax_invoice.json index 7d8e675e9fe..b23206b0bdd 100644 --- a/erpnext/regional/print_format/gst_tax_invoice/gst_tax_invoice.json +++ b/erpnext/regional/print_format/gst_tax_invoice/gst_tax_invoice.json @@ -16,7 +16,7 @@ "name": "GST Tax Invoice", "owner": "Administrator", "print_format_builder": 1, - "print_format_type": "Server", + "print_format_type": "Jinja", "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/regional/print_format/simplified_tax_invoice/simplified_tax_invoice.json b/erpnext/regional/print_format/simplified_tax_invoice/simplified_tax_invoice.json index b324f6ee683..aed2e89ed79 100644 --- a/erpnext/regional/print_format/simplified_tax_invoice/simplified_tax_invoice.json +++ b/erpnext/regional/print_format/simplified_tax_invoice/simplified_tax_invoice.json @@ -16,7 +16,7 @@ "name": "Simplified Tax Invoice", "owner": "Administrator", "print_format_builder": 1, - "print_format_type": "Server", + "print_format_type": "Jinja", "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/regional/print_format/tax_invoice/tax_invoice.json b/erpnext/regional/print_format/tax_invoice/tax_invoice.json index 74db06727bf..7479891595e 100644 --- a/erpnext/regional/print_format/tax_invoice/tax_invoice.json +++ b/erpnext/regional/print_format/tax_invoice/tax_invoice.json @@ -16,7 +16,7 @@ "name": "Tax Invoice", "owner": "Administrator", "print_format_builder": 1, - "print_format_type": "Server", + "print_format_type": "Jinja", "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index a5ca7eee5d4..86aed2ef814 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -202,7 +202,7 @@ def get_transactions(filters, as_dict=1): FROM `tabGL Entry` gl /* Kontonummer */ - left join `tabAccount` acc + left join `tabAccount` acc on gl.account = acc.name left join `tabCustomer` cus @@ -218,7 +218,7 @@ def get_transactions(filters, as_dict=1): and par.parenttype = gl.party_type and par.company = %(company)s - WHERE gl.company = %(company)s + WHERE gl.company = %(company)s AND DATE(gl.posting_date) >= %(from_date)s AND DATE(gl.posting_date) <= %(to_date)s {} diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py index 47acf291a39..66ffceae539 100644 --- a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py +++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py @@ -54,53 +54,53 @@ def get_columns(): "width": 0 }, { - "fieldtype": "Link", - "fieldname": "name", + "fieldtype": "Link", + "fieldname": "name", "label": _("Sales Invoice"), "options": "Sales Invoice", "width": 140 }, - { - "fieldtype": "Data", - "fieldname": "einvoice_status", - "label": _("Status"), + { + "fieldtype": "Data", + "fieldname": "einvoice_status", + "label": _("Status"), "width": 100 }, - { + { "fieldtype": "Link", "fieldname": "customer", "options": "Customer", "label": _("Customer") }, - { + { "fieldtype": "Check", "fieldname": "is_return", "label": _("Is Return"), "width": 85 }, { - "fieldtype": "Data", - "fieldname": "ack_no", - "label": "Ack. No.", + "fieldtype": "Data", + "fieldname": "ack_no", + "label": "Ack. No.", "width": 145 }, - { - "fieldtype": "Data", - "fieldname": "ack_date", - "label": "Ack. Date", + { + "fieldtype": "Data", + "fieldname": "ack_date", + "label": "Ack. Date", "width": 165 }, { - "fieldtype": "Data", - "fieldname": "irn", + "fieldtype": "Data", + "fieldname": "irn", "label": _("IRN No."), "width": 250 }, { "fieldtype": "Currency", - "options": "Company:company:default_currency", - "fieldname": "base_grand_total", + "options": "Company:company:default_currency", + "fieldname": "base_grand_total", "label": _("Grand Total"), "width": 120 } - ] \ No newline at end of file + ] diff --git a/erpnext/regional/report/electronic_invoice_register/electronic_invoice_register.js b/erpnext/regional/report/electronic_invoice_register/electronic_invoice_register.js index 67297f757ca..d7e3ac9a5d3 100644 --- a/erpnext/regional/report/electronic_invoice_register/electronic_invoice_register.js +++ b/erpnext/regional/report/electronic_invoice_register/electronic_invoice_register.js @@ -41,7 +41,7 @@ frappe.query_reports["Electronic Invoice Register"] = { var w = window.open( frappe.urllib.get_full_url( - "/api/method/erpnext.regional.italy.utils.export_invoices?" + "/api/method/erpnext.regional.italy.utils.export_invoices?" + "filters=" + JSON.stringify(reportview.get_filter_values()) ) ); diff --git a/erpnext/regional/report/eway_bill/eway_bill.py b/erpnext/regional/report/eway_bill/eway_bill.py index 5b9896be2a1..4f777fcf7e3 100644 --- a/erpnext/regional/report/eway_bill/eway_bill.py +++ b/erpnext/regional/report/eway_bill/eway_bill.py @@ -388,4 +388,4 @@ def get_columns(): }, ] - return columns \ No newline at end of file + return columns diff --git a/erpnext/regional/report/gst_purchase_register/gst_purchase_register.js b/erpnext/regional/report/gst_purchase_register/gst_purchase_register.js index 2b4359a7493..bbcd355d13b 100644 --- a/erpnext/regional/report/gst_purchase_register/gst_purchase_register.js +++ b/erpnext/regional/report/gst_purchase_register/gst_purchase_register.js @@ -4,4 +4,4 @@ {% include "erpnext/accounts/report/purchase_register/purchase_register.js" %} -frappe.query_reports["GST Purchase Register"] = frappe.query_reports["Purchase Register"] \ No newline at end of file +frappe.query_reports["GST Purchase Register"] = frappe.query_reports["Purchase Register"] diff --git a/erpnext/regional/report/gst_purchase_register/gst_purchase_register.py b/erpnext/regional/report/gst_purchase_register/gst_purchase_register.py index 7274e0accea..12e9676b4ba 100644 --- a/erpnext/regional/report/gst_purchase_register/gst_purchase_register.py +++ b/erpnext/regional/report/gst_purchase_register/gst_purchase_register.py @@ -21,4 +21,3 @@ def execute(filters=None): 'export_type', 'ecommerce_gstin' ]) - diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 10961593e1c..4b7309440ce 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -217,9 +217,8 @@ class Gstr1Report(object): for d in items: if d.item_code not in self.invoice_items.get(d.parent, {}): - self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, - sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items - if i.item_code == d.item_code and i.parent == d.parent)) + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0) + self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0) item_tax_rate = {} @@ -287,7 +286,8 @@ class Gstr1Report(object): # Build itemised tax for export invoices where tax table is blank for invoice, items in iteritems(self.invoice_items): if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \ - and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax": + and self.invoices.get(invoice, {}).get('export_type') == "Without Payment of Tax" \ + and self.invoices.get(invoice, {}).get('gst_category') == "Overseas": self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys()) def get_columns(self): @@ -584,7 +584,7 @@ class Gstr1Report(object): def get_json(filters, report_name, data): filters = json.loads(filters) report_data = json.loads(data) - gstin = get_company_gstin_number(filters["company"], filters["company_address"]) + gstin = get_company_gstin_number(filters.get("company"), filters.get("company_address")) fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year) diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py index 59389ce3269..1adddbdae57 100644 --- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py +++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py @@ -285,5 +285,3 @@ def get_hsn_wise_json_data(filters, report_data): count +=1 return data - - diff --git a/erpnext/regional/report/india_gst_common/india_gst_common.js b/erpnext/regional/report/india_gst_common/india_gst_common.js index 49606013946..bddc32096f1 100644 --- a/erpnext/regional/report/india_gst_common/india_gst_common.js +++ b/erpnext/regional/report/india_gst_common/india_gst_common.js @@ -18,4 +18,4 @@ function fetch_gstins(report) { company_gstins.df.options = [""]; company_gstins.refresh(); } -} \ No newline at end of file +} diff --git a/erpnext/regional/report/irs_1099/irs_1099.py b/erpnext/regional/report/irs_1099/irs_1099.py index 4e57ff7ea37..f67d622fdf8 100644 --- a/erpnext/regional/report/irs_1099/irs_1099.py +++ b/erpnext/regional/report/irs_1099/irs_1099.py @@ -52,7 +52,7 @@ def execute(filters=None): AND gl.party_type = "Supplier" AND gl.company = %(company)s {conditions} - + GROUP BY gl.party diff --git a/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.js b/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.js index 29c7dbf43c6..bb75238b8c0 100644 --- a/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.js +++ b/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.js @@ -4,4 +4,4 @@ frappe.require("assets/erpnext/js/salary_slip_deductions_report_filters.js", function() { frappe.query_reports["Professional Tax Deductions"] = erpnext.salary_slip_deductions_report_filters; -}); \ No newline at end of file +}); diff --git a/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.py b/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.py index acde68a942b..54808e59e1a 100644 --- a/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.py +++ b/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.py @@ -69,4 +69,4 @@ def get_data(filters): data.append(employee) - return data \ No newline at end of file + return data diff --git a/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.js b/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.js index b4dc28d177d..a91a30796bc 100644 --- a/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.js +++ b/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.js @@ -4,4 +4,4 @@ frappe.require("assets/erpnext/js/salary_slip_deductions_report_filters.js", function() { frappe.query_reports["Provident Fund Deductions"] = erpnext.salary_slip_deductions_report_filters; -}); \ No newline at end of file +}); diff --git a/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py b/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py index 597072c53a1..82423f005cc 100644 --- a/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py +++ b/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py @@ -165,4 +165,4 @@ def get_years(): if not year_list: year_list = [getdate().year] - return "\n".join(str(year) for year in year_list) \ No newline at end of file + return "\n".join(str(year) for year in year_list) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.html b/erpnext/regional/report/uae_vat_201/uae_vat_201.html index d9b9968d90c..7328f3f218e 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.html +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.html @@ -74,4 +74,4 @@ {% } %} - \ No newline at end of file + diff --git a/erpnext/regional/turkey/setup.py b/erpnext/regional/turkey/setup.py index ebf3b2bee1f..2396aab91f5 100644 --- a/erpnext/regional/turkey/setup.py +++ b/erpnext/regional/turkey/setup.py @@ -1,4 +1,4 @@ from __future__ import unicode_literals def setup(company=None, patch=True): - pass \ No newline at end of file + pass diff --git a/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py b/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py index ec62ba22b4d..adce5c73352 100644 --- a/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py +++ b/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py @@ -14,4 +14,4 @@ def get_data(): 'items': ['Restaurant Reservation', 'Sales Invoice'] } ] - } \ No newline at end of file + } diff --git a/erpnext/restaurant/doctype/restaurant/test_restaurant.js b/erpnext/restaurant/doctype/restaurant/test_restaurant.js index 26de5d04aaa..8fe4e7b84d5 100644 --- a/erpnext/restaurant/doctype/restaurant/test_restaurant.js +++ b/erpnext/restaurant/doctype/restaurant/test_restaurant.js @@ -18,7 +18,7 @@ QUnit.test("test: Restaurant", function (assert) { frappe.run_serially([ // insert a new Restaurant - () => frappe.tests.setup_doctype('Customer', customer), + () => frappe.tests.setup_doctype('Customer', customer), () => { return frappe.tests.make('Restaurant', [ // values to be set diff --git a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.py b/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.py index 83020b6cca8..952c46769b7 100644 --- a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.py +++ b/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.py @@ -57,5 +57,3 @@ class RestaurantMenu(Document): price_list.save() return price_list - - diff --git a/erpnext/selling/doctype/campaign/test_campaign.py b/erpnext/selling/doctype/campaign/test_campaign.py index 4d062ff84c6..8c6617fe79a 100644 --- a/erpnext/selling/doctype/campaign/test_campaign.py +++ b/erpnext/selling/doctype/campaign/test_campaign.py @@ -4,4 +4,4 @@ from __future__ import unicode_literals import frappe -test_records = frappe.get_test_records('Campaign') \ No newline at end of file +test_records = frappe.get_test_records('Campaign') diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index 28494662673..fb027be11c7 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -149,6 +149,7 @@ frappe.ui.form.on("Customer", { if(frm.doc.lead_name) frappe.model.clear_doc("Lead", frm.doc.lead_name); }, + get_customer_group_details: function(frm) { frappe.call({ method: "get_customer_group_details", @@ -159,5 +160,4 @@ frappe.ui.form.on("Customer", { }); } -}); - +}); \ No newline at end of file diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 3b62081e24c..30809978bb9 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -157,9 +157,7 @@ class Customer(TransactionBase): '''If Customer created from Lead, update lead status to "Converted" update Customer link in Quotation, Opportunity''' if self.lead_name: - lead = frappe.get_doc('Lead', self.lead_name) - lead.status = 'Converted' - lead.save() + frappe.db.set_value("Lead", self.lead_name, "status", "Converted") def create_lead_address_contact(self): if self.lead_name: diff --git a/erpnext/selling/doctype/customer/regional/india.js b/erpnext/selling/doctype/customer/regional/india.js index edb83838b61..cad9a27ace6 100644 --- a/erpnext/selling/doctype/customer/regional/india.js +++ b/erpnext/selling/doctype/customer/regional/india.js @@ -1,3 +1,3 @@ {% include "erpnext/regional/india/party.js" %} -erpnext.setup_gst_reminder_button('Customer') \ No newline at end of file +erpnext.setup_gst_reminder_button('Customer') diff --git a/erpnext/selling/doctype/industry_type/industry_type.js b/erpnext/selling/doctype/industry_type/industry_type.js index 3878a791db6..3680906057f 100644 --- a/erpnext/selling/doctype/industry_type/industry_type.js +++ b/erpnext/selling/doctype/industry_type/industry_type.js @@ -1,13 +1,13 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt - + //--------- ONLOAD ------------- cur_frm.cscript.onload = function(doc, cdt, cdn) { - + } cur_frm.cscript.refresh = function(doc, cdt, cdn) { - -} \ No newline at end of file + +} diff --git a/erpnext/selling/doctype/industry_type/industry_type.py b/erpnext/selling/doctype/industry_type/industry_type.py index 65b17e976a6..7a30d6524a0 100644 --- a/erpnext/selling/doctype/industry_type/industry_type.py +++ b/erpnext/selling/doctype/industry_type/industry_type.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class IndustryType(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/selling/doctype/industry_type/test_industry_type.py b/erpnext/selling/doctype/industry_type/test_industry_type.py index 1246a241c7e..ebc6366155e 100644 --- a/erpnext/selling/doctype/industry_type/test_industry_type.py +++ b/erpnext/selling/doctype/industry_type/test_industry_type.py @@ -4,4 +4,4 @@ from __future__ import unicode_literals import frappe -test_records = frappe.get_test_records('Industry Type') \ No newline at end of file +test_records = frappe.get_test_records('Industry Type') diff --git a/erpnext/selling/doctype/installation_note/installation_note.js b/erpnext/selling/doctype/installation_note/installation_note.js index 7fd0877d11a..12e258c5384 100644 --- a/erpnext/selling/doctype/installation_note/installation_note.js +++ b/erpnext/selling/doctype/installation_note/installation_note.js @@ -57,4 +57,4 @@ erpnext.selling.InstallationNote = frappe.ui.form.Controller.extend({ }, }); -$.extend(cur_frm.cscript, new erpnext.selling.InstallationNote({frm: cur_frm})); \ No newline at end of file +$.extend(cur_frm.cscript, new erpnext.selling.InstallationNote({frm: cur_frm})); diff --git a/erpnext/selling/doctype/installation_note_item/installation_note_item.py b/erpnext/selling/doctype/installation_note_item/installation_note_item.py index 681b8171e2c..7e1205231bb 100644 --- a/erpnext/selling/doctype/installation_note_item/installation_note_item.py +++ b/erpnext/selling/doctype/installation_note_item/installation_note_item.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class InstallationNoteItem(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/selling/doctype/product_bundle/test_product_bundle.js b/erpnext/selling/doctype/product_bundle/test_product_bundle.js index ba5ba0dc3ba..0dc90ec2114 100644 --- a/erpnext/selling/doctype/product_bundle/test_product_bundle.js +++ b/erpnext/selling/doctype/product_bundle/test_product_bundle.js @@ -33,4 +33,3 @@ QUnit.test("test sales order", function(assert) { () => done() ]); }); - diff --git a/erpnext/selling/doctype/quotation/quotation_dashboard.py b/erpnext/selling/doctype/quotation/quotation_dashboard.py index f1ac951ef94..d1bb788937b 100644 --- a/erpnext/selling/doctype/quotation/quotation_dashboard.py +++ b/erpnext/selling/doctype/quotation/quotation_dashboard.py @@ -17,4 +17,4 @@ def get_data(): 'items': ['Auto Repeat'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/selling/doctype/quotation/tests/test_quotation_with_discount_on_grand_total.js b/erpnext/selling/doctype/quotation/tests/test_quotation_with_discount_on_grand_total.js index aeb5d1b9eb7..b59bb0510e8 100644 --- a/erpnext/selling/doctype/quotation/tests/test_quotation_with_discount_on_grand_total.js +++ b/erpnext/selling/doctype/quotation/tests/test_quotation_with_discount_on_grand_total.js @@ -41,4 +41,3 @@ QUnit.test("test quotation with additional discount in grand total", function(as () => done() ]); }); - diff --git a/erpnext/selling/doctype/quotation/tests/test_quotation_with_item_wise_discount.js b/erpnext/selling/doctype/quotation/tests/test_quotation_with_item_wise_discount.js index e7349e3201c..f5172fbae2e 100644 --- a/erpnext/selling/doctype/quotation/tests/test_quotation_with_item_wise_discount.js +++ b/erpnext/selling/doctype/quotation/tests/test_quotation_with_item_wise_discount.js @@ -35,4 +35,3 @@ QUnit.test("test quotation with item wise discount", function(assert) { () => done() ]); }); - diff --git a/erpnext/selling/doctype/quotation/tests/test_quotation_with_margin.js b/erpnext/selling/doctype/quotation/tests/test_quotation_with_margin.js index 5b4224dfe9a..0d340997ad9 100644 --- a/erpnext/selling/doctype/quotation/tests/test_quotation_with_margin.js +++ b/erpnext/selling/doctype/quotation/tests/test_quotation_with_margin.js @@ -33,4 +33,3 @@ QUnit.test("test quotation with margin", function(assert) { () => done() ]); }); - diff --git a/erpnext/selling/doctype/quotation/tests/test_quotation_with_multi_uom.js b/erpnext/selling/doctype/quotation/tests/test_quotation_with_multi_uom.js index 50b8a8396d7..84be56f4605 100644 --- a/erpnext/selling/doctype/quotation/tests/test_quotation_with_multi_uom.js +++ b/erpnext/selling/doctype/quotation/tests/test_quotation_with_multi_uom.js @@ -36,4 +36,3 @@ QUnit.test("test quotation with multi uom", function(assert) { () => done() ]); }); - diff --git a/erpnext/selling/doctype/quotation/tests/test_quotation_with_taxes_and_charges.js b/erpnext/selling/doctype/quotation/tests/test_quotation_with_taxes_and_charges.js index ac7ed65ec02..5e21f817573 100644 --- a/erpnext/selling/doctype/quotation/tests/test_quotation_with_taxes_and_charges.js +++ b/erpnext/selling/doctype/quotation/tests/test_quotation_with_taxes_and_charges.js @@ -38,4 +38,3 @@ QUnit.test("test quotation with taxes and charges", function(assert) { () => done() ]); }); - diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 762b6f1d6c9..38ea5c81d49 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -38,6 +38,8 @@ "col_break46", "shipping_address_name", "shipping_address", + "dispatch_address_name", + "dispatch_address", "customer_group", "territory", "currency_and_price_list", @@ -569,7 +571,8 @@ "fieldtype": "Data", "hide_days": 1, "hide_seconds": 1, - "label": "Scan Barcode" + "label": "Scan Barcode", + "options": "Barcode" }, { "allow_bulk_edit": 1, @@ -1486,13 +1489,29 @@ "fieldname": "disable_rounded_total", "fieldtype": "Check", "label": "Disable Rounded Total" + }, + { + "allow_on_submit": 1, + "fieldname": "dispatch_address_name", + "fieldtype": "Link", + "label": "Dispatch Address Name", + "options": "Address", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "depends_on": "dispatch_address_name", + "fieldname": "dispatch_address", + "fieldtype": "Small Text", + "label": "Dispatch Address", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-04-15 23:55:13.439068", + "modified": "2021-08-17 20:15:26.531553", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 41f57a34d3d..bba54018aef 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -670,6 +670,7 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): "party_account_currency": "party_account_currency", "payment_terms_template": "payment_terms_template" }, + "field_no_map": ["payment_terms_template"], "validation": { "docstatus": ["=", 1] } @@ -693,6 +694,10 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): } }, target_doc, postprocess, ignore_permissions=ignore_permissions) + automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms')) + if automatically_fetch_payment_terms: + doclist.set_payment_schedule() + return doclist @frappe.whitelist() diff --git a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py index 05a760de273..2a71c27009f 100644 --- a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py +++ b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py @@ -41,4 +41,4 @@ def get_data(): 'items': ['Payment Entry', 'Payment Request', 'Journal Entry'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 974648d6d44..d685fbff82b 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -5,7 +5,7 @@ import json import unittest import frappe import frappe.permissions -from frappe.utils import flt, add_days, nowdate +from frappe.utils import flt, add_days, nowdate, getdate from frappe.core.doctype.user_permission.test_user_permission import create_user from erpnext.selling.doctype.sales_order.sales_order \ import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired @@ -673,6 +673,8 @@ class TestSalesOrder(unittest.TestCase): so.cancel() + dn.load_from_db() + self.assertRaises(frappe.CancelledLinkError, dn.submit) def test_service_type_product_bundle(self): @@ -1220,7 +1222,7 @@ class TestSalesOrder(unittest.TestCase): def test_so_cancellation_when_si_drafted(self): """ Test to check if Sales Order gets cancelled if Sales Invoice is in Draft state - Expected result: sales order should not get cancelled + Expected result: sales order should not get cancelled """ so = make_sales_order() so.submit() @@ -1229,7 +1231,38 @@ class TestSalesOrder(unittest.TestCase): self.assertRaises(frappe.ValidationError, so.cancel) + def test_payment_terms_are_fetched_when_creating_sales_invoice(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + automatically_fetch_payment_terms() + + so = make_sales_order(uom="Nos", do_not_save=1) + create_payment_terms_template() + so.payment_terms_template = 'Test Receivable Template' + so.submit() + + si = create_sales_invoice(qty=10, do_not_save=1) + si.items[0].sales_order = so.name + si.items[0].so_detail = so.items[0].name + si.insert() + + self.assertEqual(so.payment_terms_template, si.payment_terms_template) + compare_payment_schedules(self, so, si) + + automatically_fetch_payment_terms(enable=0) + +def automatically_fetch_payment_terms(enable=1): + accounts_settings = frappe.get_doc("Accounts Settings") + accounts_settings.automatically_fetch_payment_terms = enable + accounts_settings.save() + +def compare_payment_schedules(doc, doc1, doc2): + for index, schedule in enumerate(doc1.get('payment_schedule')): + doc.assertEqual(schedule.payment_term, doc2.payment_schedule[index].payment_term) + doc.assertEqual(getdate(schedule.due_date), doc2.payment_schedule[index].due_date) + doc.assertEqual(schedule.invoice_portion, doc2.payment_schedule[index].invoice_portion) + doc.assertEqual(schedule.payment_amount, doc2.payment_schedule[index].payment_amount) def make_sales_order(**args): so = frappe.new_doc("Sales Order") diff --git a/erpnext/selling/doctype/sales_order/tests/test_sales_order_with_margin.js b/erpnext/selling/doctype/sales_order/tests/test_sales_order_with_margin.js index 74268685079..9eebfdaf21a 100644 --- a/erpnext/selling/doctype/sales_order/tests/test_sales_order_with_margin.js +++ b/erpnext/selling/doctype/sales_order/tests/test_sales_order_with_margin.js @@ -35,4 +35,3 @@ QUnit.test("test sales order with margin", function(assert) { () => done() ]); }); - diff --git a/erpnext/selling/doctype/sales_order/tests/test_sales_order_with_multiple_delivery_date.js b/erpnext/selling/doctype/sales_order/tests/test_sales_order_with_multiple_delivery_date.js index 8e0538511af..be76c49f845 100644 --- a/erpnext/selling/doctype/sales_order/tests/test_sales_order_with_multiple_delivery_date.js +++ b/erpnext/selling/doctype/sales_order/tests/test_sales_order_with_multiple_delivery_date.js @@ -56,4 +56,4 @@ QUnit.test("test: Sales Order", function (assert) { }, () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.py b/erpnext/selling/doctype/sales_order_item/sales_order_item.py index 27f303d43b1..62afef3e170 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.py +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.py @@ -10,4 +10,4 @@ class SalesOrderItem(Document): pass def on_doctype_update(): - frappe.db.add_index("Sales Order Item", ["item_code", "warehouse"]) \ No newline at end of file + frappe.db.add_index("Sales Order Item", ["item_code", "warehouse"]) diff --git a/erpnext/selling/doctype/sales_team/sales_team.py b/erpnext/selling/doctype/sales_team/sales_team.py index 1832108399f..28bea254d68 100644 --- a/erpnext/selling/doctype/sales_team/sales_team.py +++ b/erpnext/selling/doctype/sales_team/sales_team.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class SalesTeam(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.js b/erpnext/selling/doctype/selling_settings/selling_settings.js index 95a4243fb45..d8d30515f8f 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.js +++ b/erpnext/selling/doctype/selling_settings/selling_settings.js @@ -28,4 +28,4 @@ frappe.tour['Selling Settings'] = [ title: "Delivery Note Required for Sales Invoice Creation", description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice without creating a Delivery Note first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Delivery Note' checkbox in the Customer master.") } -]; \ No newline at end of file +]; diff --git a/erpnext/selling/doctype/sms_center/sms_center.py b/erpnext/selling/doctype/sms_center/sms_center.py index d142d16248f..87846a84d30 100644 --- a/erpnext/selling/doctype/sms_center/sms_center.py +++ b/erpnext/selling/doctype/sms_center/sms_center.py @@ -83,4 +83,3 @@ class SMSCenter(Document): receiver_list = self.get_receiver_nos() if receiver_list: send_sms(receiver_list, cstr(self.message)) - diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js index e3405e0ce89..56b070bb285 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.js +++ b/erpnext/selling/page/point_of_sale/point_of_sale.js @@ -19,4 +19,4 @@ frappe.pages['point-of-sale'].refresh = function(wrapper) { wrapper.pos.wrapper.html(""); wrapper.pos.check_opening_entry(); } -}; \ No newline at end of file +}; diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 296c8c2fd9d..3dc9094ac27 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -146,7 +146,7 @@ def filter_service_items(items): if not item['is_stock_item']: if not frappe.db.exists('Product Bundle', item['item_code']): items.remove(item) - + return items def get_conditions(search_term): diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index c827368dbf5..e61a634aaee 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -525,7 +525,7 @@ erpnext.PointOfSale.Controller = class { } } else { - if (!this.frm.doc.customer) + if (!this.frm.doc.customer) return this.raise_customer_selection_alert(); const { item_code, batch_no, serial_no, rate } = item; @@ -549,7 +549,7 @@ erpnext.PointOfSale.Controller = class { await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse); await this.trigger_new_item_events(item_row); - + this.update_cart_html(item_row); if (this.item_details.$component.is(':visible')) @@ -708,4 +708,3 @@ erpnext.PointOfSale.Controller = class { .catch(e => console.log(e)); } }; - diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 38508c219b3..9d8338e5fed 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -367,15 +367,16 @@ erpnext.PointOfSale.ItemCart = class { `
          ` ); const me = this; + const frm = me.events.get_frm(); + let discount = frm.doc.additional_discount_percentage; this.discount_field = frappe.ui.form.make_control({ df: { label: __('Discount'), fieldtype: 'Data', - placeholder: __('Enter discount percentage.'), + placeholder: ( discount ? discount + '%' : __('Enter discount percentage.') ), input_class: 'input-xs', onchange: function() { - const frm = me.events.get_frm(); if (flt(this.value) != 0) { frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', flt(this.value)); me.hide_discount_control(this.value); @@ -563,7 +564,6 @@ erpnext.PointOfSale.ItemCart = class { ) set_dynamic_rate_header_width(); - this.scroll_to_item($item_to_update); function set_dynamic_rate_header_width() { const rate_cols = Array.from(me.$cart_items_wrapper.find(".item-rate-amount")); @@ -638,12 +638,6 @@ erpnext.PointOfSale.ItemCart = class { $($img).parent().replaceWith(`
          ${item_abbr}
          `); } - scroll_to_item($item) { - if ($item.length === 0) return; - const scrollTop = $item.offset().top - this.$cart_items_wrapper.offset().top + this.$cart_items_wrapper.scrollTop(); - this.$cart_items_wrapper.animate({ scrollTop }); - } - update_selector_value_in_cart_item(selector, value, item) { const $item_to_update = this.get_cart_item(item); $item_to_update.attr(`data-${selector}`, escape(value)); @@ -965,8 +959,23 @@ erpnext.PointOfSale.ItemCart = class { }); } + attach_refresh_field_event(frm) { + $(frm.wrapper).off('refresh-fields'); + $(frm.wrapper).on('refresh-fields', () => { + if (frm.doc.items.length) { + frm.doc.items.forEach(item => { + this.update_item_html(item); + }); + } + this.update_totals_section(frm); + }); + } + load_invoice() { const frm = this.events.get_frm(); + + this.attach_refresh_field_event(frm); + this.fetch_customer_details(frm.doc.customer).then(() => { this.events.customer_details_updated(this.customer_info); this.update_customer_section(); diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index 6a4d3d5214d..d899c5c19b4 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -65,7 +65,7 @@ erpnext.PointOfSale.ItemDetails = class { // if item is null or highlighted cart item is clicked twice const hide_item_details = !Boolean(item) || !current_item_changed; - + this.events.toggle_item_selector(!hide_item_details); this.toggle_component(!hide_item_details); @@ -127,7 +127,7 @@ erpnext.PointOfSale.ItemDetails = class { this.$item_price.html(format_currency(price_list_rate, this.currency)); if (!this.hide_images && image) { this.$item_image.html( - `${frappe.get_abbr(item_name)}${qty_to_display}
          - ${frappe.get_abbr(item.item_name)} { diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.css b/erpnext/selling/page/sales_funnel/sales_funnel.css index 89e904fcfc9..60b2392ac43 100644 --- a/erpnext/selling/page/sales_funnel/sales_funnel.css +++ b/erpnext/selling/page/sales_funnel/sales_funnel.css @@ -1,3 +1,4 @@ .funnel-wrapper { margin: 15px; -} \ No newline at end of file + width: 100%; +} diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.py b/erpnext/selling/page/sales_funnel/sales_funnel.py index b613718c7e2..78aaa49a668 100644 --- a/erpnext/selling/page/sales_funnel/sales_funnel.py +++ b/erpnext/selling/page/sales_funnel/sales_funnel.py @@ -32,7 +32,7 @@ def get_funnel_data(from_date, to_date, company): and (opportunity!="" or quotation_to="Lead") and company=%s""", (from_date, to_date, company))[0][0] converted = frappe.db.sql("""select count(*) from `tabCustomer` - JOIN `tabLead` ON `tabLead`.name = `tabCustomer`.lead_name + JOIN `tabLead` ON `tabLead`.name = `tabCustomer`.lead_name WHERE (date(`tabCustomer`.creation) between %s and %s) and `tabLead`.company=%s""", (from_date, to_date, company))[0][0] @@ -97,4 +97,4 @@ def get_pipeline_data(from_date, to_date, company): return result else: - return 'empty' \ No newline at end of file + return 'empty' diff --git a/erpnext/selling/report/address_and_contacts/address_and_contacts.py b/erpnext/selling/report/address_and_contacts/address_and_contacts.py index a9e43034b48..f295333322c 100644 --- a/erpnext/selling/report/address_and_contacts/address_and_contacts.py +++ b/erpnext/selling/report/address_and_contacts/address_and_contacts.py @@ -117,4 +117,4 @@ def get_party_group(party_type): "Sales Partner": "partner_type" } - return group[party_type] \ No newline at end of file + return group[party_type] diff --git a/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py b/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py index 056492a3274..5523bad5715 100644 --- a/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py +++ b/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py @@ -76,4 +76,4 @@ def get_item_warehouse_quantity_map(): last_sbom = line.get("parent") actual_dict = sbom_map.setdefault(last_sbom, {}) actual_dict.setdefault(line.get("warehouse"), line.get("qty")) - return sbom_map \ No newline at end of file + return sbom_map diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js index d93ffb7266b..1b931e12de3 100644 --- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js +++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js @@ -41,4 +41,4 @@ frappe.query_reports["Customer Acquisition and Loyalty"] = { } return value; } -} \ No newline at end of file +} diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js index f47d67fe494..073be789791 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js @@ -60,4 +60,4 @@ frappe.query_reports["Item-wise Sales History"] = { } return value; } -}; \ No newline at end of file +}; diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py index 84732760019..1700fc7bdd5 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py @@ -266,4 +266,4 @@ def get_chart_data(data): ] }, "type" : "bar" - } \ No newline at end of file + } diff --git a/erpnext/selling/report/quotation_trends/quotation_trends.js b/erpnext/selling/report/quotation_trends/quotation_trends.js index f00ca278b00..97a19315ec3 100644 --- a/erpnext/selling/report/quotation_trends/quotation_trends.js +++ b/erpnext/selling/report/quotation_trends/quotation_trends.js @@ -6,4 +6,3 @@ frappe.require("assets/erpnext/js/sales_trends_filters.js", function() { filters: erpnext.get_sales_trends_filters() } }); - diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.js b/erpnext/selling/report/sales_analytics/sales_analytics.js index 9089b53fb04..6b03c7d92fe 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.js +++ b/erpnext/selling/report/sales_analytics/sales_analytics.js @@ -141,5 +141,3 @@ frappe.query_reports["Sales Analytics"] = { }); }, } - - diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py index 8cb24460f7b..00dcd69c6e6 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py @@ -276,4 +276,4 @@ def get_columns(filters): }) - return columns \ No newline at end of file + return columns diff --git a/erpnext/selling/report/sales_order_trends/sales_order_trends.js b/erpnext/selling/report/sales_order_trends/sales_order_trends.js index ea320d6b481..b22ea8fa2ca 100644 --- a/erpnext/selling/report/sales_order_trends/sales_order_trends.js +++ b/erpnext/selling/report/sales_order_trends/sales_order_trends.js @@ -5,4 +5,4 @@ frappe.require("assets/erpnext/js/sales_trends_filters.js", function() { frappe.query_reports["Sales Order Trends"] = { filters: erpnext.get_sales_trends_filters() } -}); \ No newline at end of file +}); diff --git a/erpnext/selling/report/sales_partner_commission_summary/sales_partner_commission_summary.py b/erpnext/selling/report/sales_partner_commission_summary/sales_partner_commission_summary.py index 66f9aaeffc6..2c49d51a920 100644 --- a/erpnext/selling/report/sales_partner_commission_summary/sales_partner_commission_summary.py +++ b/erpnext/selling/report/sales_partner_commission_summary/sales_partner_commission_summary.py @@ -110,4 +110,4 @@ def get_conditions(filters, date_field): if filters.get("to_date"): conditions += " and {0} <= %(to_date)s".format(date_field) - return conditions \ No newline at end of file + return conditions diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py index ae216ca5d69..24ca666f6b1 100644 --- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py +++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py @@ -44,6 +44,18 @@ def get_data(filters, period_list, partner_doctype): if d.item_group not in item_groups: item_groups.append(d.item_group) + if item_groups: + child_items = [] + for item_group in item_groups: + if frappe.db.get_value("Item Group", {"name":item_group}, "is_group"): + for child_item_group in frappe.get_all("Item Group", {"parent_item_group":item_group}): + if child_item_group['name'] not in child_items: + child_items.append(child_item_group['name']) + + for item in child_items: + if item not in item_groups: + item_groups.append(item) + date_field = ("transaction_date" if filters.get('doctype') == "Sales Order" else "posting_date") @@ -208,4 +220,4 @@ def get_parents_data(filters, partner_doctype): return frappe.get_all('Target Detail', filters = filters_dict, - fields = ["parent", "item_group", target_qty_amt_field, "fiscal_year", "distribution_id"]) \ No newline at end of file + fields = ["parent", "item_group", target_qty_amt_field, "fiscal_year", "distribution_id"]) diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js index 38bb127e235..adae47b87da 100644 --- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js +++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js @@ -47,9 +47,9 @@ frappe.query_reports["Sales Partner Target Variance based on Item Group"] = { ], "formatter": function (value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); - + if (column.fieldname.includes('variance')) { - + if (data[column.fieldname] < 0) { value = "" + value + ""; } diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.py index e41011fba2c..87ed5a8ea21 100644 --- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.py +++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.py @@ -9,4 +9,3 @@ def execute(filters=None): data = [] return get_data_column(filters, "Sales Partner") - diff --git a/erpnext/selling/report/sales_partner_transaction_summary/sales_partner_transaction_summary.py b/erpnext/selling/report/sales_partner_transaction_summary/sales_partner_transaction_summary.py index 53560285b3f..f07293d8ec2 100644 --- a/erpnext/selling/report/sales_partner_transaction_summary/sales_partner_transaction_summary.py +++ b/erpnext/selling/report/sales_partner_transaction_summary/sales_partner_transaction_summary.py @@ -165,4 +165,4 @@ def get_conditions(filters, date_field): `tabItem Group` where lft >= %s and rgt <= %s)""" % (lft, rgt) - return conditions \ No newline at end of file + return conditions diff --git a/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.py b/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.py index 0c849096116..9917d72af86 100644 --- a/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.py +++ b/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.py @@ -101,7 +101,7 @@ def get_columns(filters): def get_entries(filters): date_field = filters["doc_type"] == "Sales Order" and "transaction_date" or "posting_date" - + conditions, values = get_conditions(filters, date_field) entries = frappe.db.sql(""" select @@ -111,7 +111,7 @@ def get_entries(filters): `tab%s` dt, `tabSales Team` st where st.parent = dt.name and st.parenttype = %s - and dt.docstatus = 1 %s order by dt.name desc,st.sales_person + and dt.docstatus = 1 %s order by dt.name desc,st.sales_person """ %(date_field, filters["doc_type"], '%s', conditions), tuple([filters["doc_type"]] + values), as_dict=1) @@ -138,5 +138,3 @@ def get_conditions(filters, date_field): values.append(filters["to_date"]) return " and ".join(conditions), values - - diff --git a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.js b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.js index a8e2fad3734..2b8443627d5 100644 --- a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.js +++ b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.js @@ -47,9 +47,9 @@ frappe.query_reports["Sales Person Target Variance Based On Item Group"] = { ], "formatter": function (value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); - + if (column.fieldname.includes('variance')) { - + if (data[column.fieldname] < 0) { value = "" + value + ""; } diff --git a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.py b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.py index 5166cc808e9..ea9bbab0c72 100644 --- a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.py +++ b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.py @@ -8,4 +8,4 @@ from erpnext.selling.report.sales_partner_target_variance_based_on_item_group.it def execute(filters=None): data = [] - return get_data_column(filters, "Sales Person") \ No newline at end of file + return get_data_column(filters, "Sales Person") diff --git a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.js b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.js index b236151bad9..e269f02d0ce 100644 --- a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.js +++ b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.js @@ -67,4 +67,4 @@ frappe.query_reports["Sales Person-wise Transaction Summary"] = { default: 0, }, ] -} \ No newline at end of file +} diff --git a/erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.js b/erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.js index 263391a7f72..9f3d255e662 100644 --- a/erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.js +++ b/erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.js @@ -47,9 +47,9 @@ frappe.query_reports["Territory Target Variance Based On Item Group"] = { ], "formatter": function (value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); - + if (column.fieldname.includes('variance')) { - + if (data[column.fieldname] < 0) { value = "" + value + ""; } diff --git a/erpnext/setup/default_energy_point_rules.py b/erpnext/setup/default_energy_point_rules.py index 94f5aa488dc..8dbccc497b7 100644 --- a/erpnext/setup/default_energy_point_rules.py +++ b/erpnext/setup/default_energy_point_rules.py @@ -55,4 +55,3 @@ def get_default_energy_point_rules(): 'points': rule.get('points'), 'user_field': rule.get('user_field') or 'owner' } for doctype, rule in doctype_rule_map.items()] - diff --git a/erpnext/setup/default_success_action.py b/erpnext/setup/default_success_action.py index b8b09cbc53f..827839f8b75 100644 --- a/erpnext/setup/default_success_action.py +++ b/erpnext/setup/default_success_action.py @@ -24,4 +24,3 @@ def get_default_success_action(): 'first_success_message': get_first_success_message(doctype), 'next_actions': 'new\nprint\nemail' } for doctype in doctype_list] - diff --git a/erpnext/setup/doctype/brand/brand.js b/erpnext/setup/doctype/brand/brand.js index 3878a791db6..3680906057f 100644 --- a/erpnext/setup/doctype/brand/brand.js +++ b/erpnext/setup/doctype/brand/brand.js @@ -1,13 +1,13 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt - + //--------- ONLOAD ------------- cur_frm.cscript.onload = function(doc, cdt, cdn) { - + } cur_frm.cscript.refresh = function(doc, cdt, cdn) { - -} \ No newline at end of file + +} diff --git a/erpnext/setup/doctype/brand/brand.py b/erpnext/setup/doctype/brand/brand.py index 12839d18aea..a8d1cf8ff2d 100644 --- a/erpnext/setup/doctype/brand/brand.py +++ b/erpnext/setup/doctype/brand/brand.py @@ -21,4 +21,4 @@ def get_brand_defaults(item, company): row.pop("name") return row - return frappe._dict() \ No newline at end of file + return frappe._dict() diff --git a/erpnext/setup/doctype/brand/test_brand.py b/erpnext/setup/doctype/brand/test_brand.py index 265d2fe577e..25ed86ef1dd 100644 --- a/erpnext/setup/doctype/brand/test_brand.py +++ b/erpnext/setup/doctype/brand/test_brand.py @@ -4,4 +4,4 @@ from __future__ import unicode_literals import frappe -test_records = frappe.get_test_records('Brand') \ No newline at end of file +test_records = frappe.get_test_records('Brand') diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index d05541b6344..8f83d3cd73a 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -313,4 +313,3 @@ var disbale_coa_fields = function(frm, bool=true) { frm.set_df_property("chart_of_accounts", "read_only", bool); frm.set_df_property("existing_company", "read_only", bool); } - diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 061986d92d7..e6ec496a65e 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -74,7 +74,7 @@ "stock_received_but_not_billed", "service_received_but_not_billed", "expenses_included_in_valuation", - "fixed_asset_depreciation_settings", + "fixed_asset_defaults", "accumulated_depreciation_account", "depreciation_expense_account", "series_for_depreciation_entry", @@ -83,6 +83,7 @@ "disposal_account", "depreciation_cost_center", "capital_work_in_progress_account", + "repair_and_maintenance_account", "asset_received_but_not_billed", "budget_detail", "exception_budget_approver_role", @@ -519,12 +520,6 @@ "no_copy": 1, "options": "Account" }, - { - "collapsible": 1, - "fieldname": "fixed_asset_depreciation_settings", - "fieldtype": "Section Break", - "label": "Fixed Asset Depreciation Settings" - }, { "fieldname": "accumulated_depreciation_account", "fieldtype": "Link", @@ -734,6 +729,18 @@ "fieldtype": "Link", "label": "Default Payment Discount Account", "options": "Account" + }, + { + "collapsible": 1, + "fieldname": "fixed_asset_defaults", + "fieldtype": "Section Break", + "label": "Fixed Asset Defaults" + }, + { + "fieldname": "repair_and_maintenance_account", + "fieldtype": "Link", + "label": "Repair and Maintenance Account", + "options": "Account" } ], "icon": "fa fa-building", @@ -741,7 +748,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2021-05-07 03:11:28.189740", + "modified": "2021-05-12 16:51:08.187233", "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 36a7d20a8ff..45d5ce0c1c7 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -109,6 +109,9 @@ class Company(NestedSet): self.create_default_accounts() self.create_default_warehouses() + if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": self.name}): + self.create_default_cost_center() + if frappe.flags.country_change: install_country_fixtures(self.name, self.country) self.create_default_tax_template() @@ -117,9 +120,6 @@ class Company(NestedSet): from erpnext.setup.setup_wizard.operations.install_fixtures import install_post_company_fixtures install_post_company_fixtures(frappe._dict({'company_name': self.name})) - if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": self.name}): - self.create_default_cost_center() - if not frappe.local.flags.ignore_chart_of_accounts: self.set_default_accounts() if self.default_cash_account: @@ -291,7 +291,7 @@ class Company(NestedSet): cash = frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name') if cash and self.default_cash_account \ and not frappe.db.get_value('Mode of Payment Account', {'company': self.name, 'parent': cash}): - mode_of_payment = frappe.get_doc('Mode of Payment', cash) + mode_of_payment = frappe.get_doc('Mode of Payment', cash, for_update=True) mode_of_payment.append('accounts', { 'company': self.name, 'default_account': self.default_cash_account @@ -393,6 +393,10 @@ class Company(NestedSet): frappe.db.sql("delete from `tabPurchase Taxes and Charges Template` where company=%s", self.name) frappe.db.sql("delete from `tabItem Tax Template` where company=%s", self.name) + # delete Process Deferred Accounts if no GL Entry found + if not frappe.db.get_value('GL Entry', {'company': self.name}): + frappe.db.sql("delete from `tabProcess Deferred Accounting` where company=%s", self.name) + @frappe.whitelist() def enqueue_replace_abbr(company, old, new): kwargs = dict(queue="long", company=company, old=old, new=new) diff --git a/erpnext/setup/doctype/company/company_dashboard.py b/erpnext/setup/doctype/company/company_dashboard.py index 9b483dd55e5..2d760284e5a 100644 --- a/erpnext/setup/doctype/company/company_dashboard.py +++ b/erpnext/setup/doctype/company/company_dashboard.py @@ -37,4 +37,4 @@ def get_data(): 'items': ['Project'] } ] - } \ No newline at end of file + } diff --git a/erpnext/setup/doctype/company/company_tree.js b/erpnext/setup/doctype/company/company_tree.js index 19b276c77db..160481cc952 100644 --- a/erpnext/setup/doctype/company/company_tree.js +++ b/erpnext/setup/doctype/company/company_tree.js @@ -30,4 +30,4 @@ frappe.treeview_settings["Company"] = { onload: function(treeview) { treeview.make_tree(); } -}; \ No newline at end of file +}; diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py index e1c803a038b..1b7fd4fd5c8 100644 --- a/erpnext/setup/doctype/company/test_company.py +++ b/erpnext/setup/doctype/company/test_company.py @@ -130,4 +130,3 @@ def create_test_lead_in_company(company): lead.company = company lead.save() return lead.name - diff --git a/erpnext/setup/doctype/company/tests/test_company.js b/erpnext/setup/doctype/company/tests/test_company.js index 8c0b609775a..b568494c84a 100644 --- a/erpnext/setup/doctype/company/tests/test_company.js +++ b/erpnext/setup/doctype/company/tests/test_company.js @@ -22,4 +22,4 @@ QUnit.test("Test: Company [SetUp]", function (assert) { 'chart of cost centers created'), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/setup/doctype/company/tests/test_company_production.js b/erpnext/setup/doctype/company/tests/test_company_production.js index bf6e5405b42..a4c1e2e7dea 100644 --- a/erpnext/setup/doctype/company/tests/test_company_production.js +++ b/erpnext/setup/doctype/company/tests/test_company_production.js @@ -16,4 +16,4 @@ QUnit.test("Test: Company", function (assert) { () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/setup/doctype/currency_exchange/currency_exchange.js b/erpnext/setup/doctype/currency_exchange/currency_exchange.js index a8ea55ca0cb..6e212affdd7 100644 --- a/erpnext/setup/doctype/currency_exchange/currency_exchange.js +++ b/erpnext/setup/doctype/currency_exchange/currency_exchange.js @@ -7,24 +7,24 @@ $.extend(cur_frm.cscript, { cur_frm.set_value("to_currency", frappe.defaults.get_global_default("currency")); } }, - + refresh: function() { cur_frm.cscript.set_exchange_rate_label(); }, - + from_currency: function() { cur_frm.cscript.set_exchange_rate_label(); }, - + to_currency: function() { cur_frm.cscript.set_exchange_rate_label(); }, - + set_exchange_rate_label: function() { if(cur_frm.doc.from_currency && cur_frm.doc.to_currency) { var default_label = __(frappe.meta.docfield_map[cur_frm.doctype]["exchange_rate"].label); - cur_frm.fields_dict.exchange_rate.set_label(default_label + + cur_frm.fields_dict.exchange_rate.set_label(default_label + repl(" (1 %(from_currency)s = [?] %(to_currency)s)", cur_frm.doc)); } } -}); \ No newline at end of file +}); diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py index c5c01c57758..1c928cd87d0 100644 --- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py @@ -62,7 +62,7 @@ class TestCurrencyExchange(unittest.TestCase): exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling") self.assertEqual(exchange_rate, 62.9) - + # Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io self.clear_cache() exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_selling") diff --git a/erpnext/setup/doctype/customer_group/customer_group.py b/erpnext/setup/doctype/customer_group/customer_group.py index 68e1ccb6356..c06669b16b4 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.py +++ b/erpnext/setup/doctype/customer_group/customer_group.py @@ -30,4 +30,4 @@ def get_parent_customer_groups(customer_group): order by lft asc""", (lft, rgt), as_dict=True) def on_doctype_update(): - frappe.db.add_index("Customer Group", ["lft", "rgt"]) \ No newline at end of file + frappe.db.add_index("Customer Group", ["lft", "rgt"]) diff --git a/erpnext/setup/doctype/customer_group/customer_group_tree.js b/erpnext/setup/doctype/customer_group/customer_group_tree.js index b52c79c497b..d50e9c8835c 100644 --- a/erpnext/setup/doctype/customer_group/customer_group_tree.js +++ b/erpnext/setup/doctype/customer_group/customer_group_tree.js @@ -1,3 +1,3 @@ frappe.treeview_settings["Customer Group"] = { ignore_fields:["parent_customer_group"] -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/customer_group/test_customer_group.py b/erpnext/setup/doctype/customer_group/test_customer_group.py index ec1af7a6765..ec90b376cdc 100644 --- a/erpnext/setup/doctype/customer_group/test_customer_group.py +++ b/erpnext/setup/doctype/customer_group/test_customer_group.py @@ -7,4 +7,4 @@ test_ignore = ["Price List"] import frappe -test_records = frappe.get_test_records('Customer Group') \ No newline at end of file +test_records = frappe.get_test_records('Customer Group') diff --git a/erpnext/setup/doctype/email_digest/email_digest.js b/erpnext/setup/doctype/email_digest/email_digest.js index 1071ea20209..c2c2710b025 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.js +++ b/erpnext/setup/doctype/email_digest/email_digest.js @@ -1,78 +1,31 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -cur_frm.cscript.refresh = function(doc, dt, dn) { - doc = locals[dt][dn]; - cur_frm.add_custom_button(__('View Now'), function() { - frappe.call({ - method: 'erpnext.setup.doctype.email_digest.email_digest.get_digest_msg', - args: { - name: doc.name - }, - callback: function(r) { - var d = new frappe.ui.Dialog({ - title: __('Email Digest: ') + dn, - width: 800 +frappe.ui.form.on("Email Digest", { + refresh: function(frm) { + if (!frm.is_new()) { + frm.add_custom_button(__('View Now'), function() { + frappe.call({ + method: 'erpnext.setup.doctype.email_digest.email_digest.get_digest_msg', + args: { + name: frm.doc.name + }, + callback: function(r) { + let d = new frappe.ui.Dialog({ + title: __('Email Digest: {0}', [frm.doc.name]), + width: 800 + }); + $(d.body).html(r.message); + d.show(); + } }); - $(d.body).html(r.message); - d.show(); - } - }); - }, "fa fa-eye-open", "btn-default"); - - if (!cur_frm.is_new()) { - cur_frm.add_custom_button(__('Send Now'), function() { - return cur_frm.call('send', null, (r) => { - frappe.show_alert(__('Message Sent')); }); - }); + + frm.add_custom_button(__('Send Now'), function() { + return frm.call('send', null, () => { + frappe.show_alert({ message: __("Message Sent"), indicator: 'green'}); + }); + }); + } } -}; - -cur_frm.cscript.addremove_recipients = function(doc, dt, dn) { - // Get user list - - return cur_frm.call('get_users', null, function(r) { - // Open a dialog and display checkboxes against email addresses - doc = locals[dt][dn]; - var d = new frappe.ui.Dialog({ - title: __('Add/Remove Recipients'), - width: 400 - }); - - $.each(r.user_list, function(i, v) { - var fullname = frappe.user.full_name(v.name); - if(fullname !== v.name) fullname = fullname + " <" + v.name + ">"; - - if(v.enabled==0) { - fullname = repl(" %(name)s (" + __("disabled user") + ")", {name: v.name}); - } - - $('
          ').appendTo(d.body); - }); - - // Display add recipients button - d.set_primary_action("Update", function() { - cur_frm.cscript.add_to_rec_list(doc, d.body, r.user_list.length); - }); - - cur_frm.rec_dialog = d; - d.show(); - }); -} - -cur_frm.cscript.add_to_rec_list = function(doc, dialog, length) { - // add checked users to list of recipients - var rec_list = []; - $(dialog).find('input:checked').each(function(i, input) { - rec_list.push($(input).attr('data-id')); - }); - - doc.recipient_list = rec_list.join('\n'); - cur_frm.rec_dialog.hide(); - cur_frm.save(); - cur_frm.refresh_fields(); -} +}); diff --git a/erpnext/setup/doctype/email_digest/email_digest.json b/erpnext/setup/doctype/email_digest/email_digest.json index 125aca17c75..06c98e5ff94 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.json +++ b/erpnext/setup/doctype/email_digest/email_digest.json @@ -1,1482 +1,338 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "Prompt", - "beta": 0, - "creation": "2018-09-16 22:00:00", - "custom": 0, - "description": "Send regular summary reports via Email.", - "docstatus": 0, - "doctype": "DocType", - "document_type": "System", - "editable_grid": 0, + "actions": [], + "autoname": "Prompt", + "creation": "2018-09-16 22:00:00", + "description": "Send regular summary reports via Email.", + "doctype": "DocType", + "document_type": "System", + "engine": "InnoDB", + "field_order": [ + "settings", + "column_break0", + "enabled", + "company", + "frequency", + "next_send", + "column_break1", + "recipients", + "accounts", + "accounts_module", + "income", + "expenses_booked", + "income_year_to_date", + "expense_year_to_date", + "column_break_16", + "bank_balance", + "credit_balance", + "invoiced_amount", + "payables", + "work_in_progress", + "sales_orders_to_bill", + "purchase_orders_to_bill", + "operation", + "column_break_21", + "sales_order", + "purchase_order", + "sales_orders_to_deliver", + "purchase_orders_to_receive", + "sales_invoice", + "purchase_invoice", + "column_break_operation", + "new_quotations", + "pending_quotations", + "issue", + "project", + "purchase_orders_items_overdue", + "other", + "tools", + "calendar_events", + "todo_list", + "notifications", + "column_break_32", + "add_quote" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "settings", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Email Digest Settings", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "settings", + "fieldtype": "Section Break", + "label": "Email Digest Settings" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break0", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break0", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "enabled", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Enabled", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "enabled", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Enabled" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "For Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "For Company", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "frequency", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "How frequently?", - "length": 0, - "no_copy": 0, - "options": "Daily\nWeekly\nMonthly", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "frequency", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "How frequently?", + "options": "Daily\nWeekly\nMonthly", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.enabled", - "fieldname": "next_send", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Next email will be sent on:", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.enabled", + "fieldname": "next_send", + "fieldtype": "Data", + "label": "Next email will be sent on:", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break1", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Note: Email will not be sent to disabled users", - "fieldname": "recipient_list", - "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Recipients", - "length": 0, - "no_copy": 0, - "options": "Email", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "accounts", + "fieldtype": "Section Break", + "label": "Accounts" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "addremove_recipients", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Add/Remove Recipients", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "accounts_module", + "fieldtype": "Column Break", + "hidden": 1, + "label": "Profit & Loss" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "accounts", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Accounts", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "income", + "fieldtype": "Check", + "label": "New Income" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "accounts_module", - "fieldtype": "Column Break", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Profit & Loss", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "expenses_booked", + "fieldtype": "Check", + "label": "New Expenses" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "income", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "New Income", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "income_year_to_date", + "fieldtype": "Check", + "label": "Annual Income" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "expenses_booked", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "New Expenses", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "expense_year_to_date", + "fieldtype": "Check", + "label": "Annual Expenses" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "income_year_to_date", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Annual Income", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_16", + "fieldtype": "Column Break", + "label": "Balance Sheet" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "expense_year_to_date", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Annual Expenses", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "bank_balance", + "fieldtype": "Check", + "label": "Bank Balance" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "column_break_16", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Balance Sheet", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "credit_balance", + "fieldtype": "Check", + "label": "Bank Credit Balance" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "bank_balance", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Bank Balance", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "invoiced_amount", + "fieldtype": "Check", + "label": "Receivables" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "credit_balance", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Bank Credit Balance", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "payables", + "fieldtype": "Check", + "label": "Payables" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "invoiced_amount", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Receivables", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "work_in_progress", + "fieldtype": "Column Break", + "label": "Work in Progress" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "payables", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Payables", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "sales_orders_to_bill", + "fieldtype": "Check", + "label": "Sales Orders to Bill" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "work_in_progress", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Work in Progress", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "purchase_orders_to_bill", + "fieldtype": "Check", + "label": "Purchase Orders to Bill" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_orders_to_bill", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Orders to Bill", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "operation", + "fieldtype": "Section Break", + "label": "Operations" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "purchase_orders_to_bill", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Purchase Orders to Bill", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_21", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "operation", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Operations", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "sales_order", + "fieldtype": "Check", + "label": "New Sales Orders" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_21", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "purchase_order", + "fieldtype": "Check", + "label": "New Purchase Orders" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_order", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "New Sales Orders", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "sales_orders_to_deliver", + "fieldtype": "Check", + "label": "Sales Orders to Deliver" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "purchase_order", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "New Purchase Orders", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "purchase_orders_to_receive", + "fieldtype": "Check", + "label": "Purchase Orders to Receive" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_orders_to_deliver", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Orders to Deliver", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "sales_invoice", + "fieldtype": "Check", + "label": "New Sales Invoice" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "purchase_orders_to_receive", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Purchase Orders to Receive", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "purchase_invoice", + "fieldtype": "Check", + "label": "New Purchase Invoice" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_invoice", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "New Sales Invoice", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_operation", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "purchase_invoice", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "New Purchase Invoice", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "new_quotations", + "fieldtype": "Check", + "label": "New Quotations" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_operation", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "pending_quotations", + "fieldtype": "Check", + "label": "Open Quotations" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "new_quotations", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "New Quotations", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "issue", + "fieldtype": "Check", + "label": "Open Issues" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "pending_quotations", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Open Quotations", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "project", + "fieldtype": "Check", + "label": "Open Projects" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "issue", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Open Issues", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "purchase_orders_items_overdue", + "fieldtype": "Check", + "label": "Purchase Orders Items Overdue" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "project", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Open Projects", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "other", + "fieldtype": "Section Break", + "label": "Other" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "purchase_orders_items_overdue", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Purchase Orders Items Overdue", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "tools", + "fieldtype": "Column Break", + "label": "Tools" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "other", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Other", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "calendar_events", + "fieldtype": "Check", + "label": "Upcoming Calendar Events" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "tools", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Tools", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "todo_list", + "fieldtype": "Check", + "label": "Open To Do" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "calendar_events", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Upcoming Calendar Events", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "notifications", + "fieldtype": "Check", + "label": "Open Notifications" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "todo_list", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Open To Do", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_32", + "fieldtype": "Column Break", + "label": " " + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "notifications", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Open Notifications", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "add_quote", + "fieldtype": "Check", + "label": "Add Quote" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_32", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": " ", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "add_quote", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Add Quote", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "description": "Note: Email will not be sent to disabled users", + "fieldname": "recipients", + "fieldtype": "Table MultiSelect", + "label": "Recipients", + "options": "Email Digest Recipient", + "reqd": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-envelope", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2019-01-16 09:52:15.149908", - "modified_by": "Administrator", - "module": "Setup", - "name": "Email Digest", - "owner": "Administrator", + ], + "icon": "fa fa-envelope", + "idx": 1, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-08-24 23:49:00.081695", + "modified_by": "Administrator", + "module": "Setup", + "name": "Email Digest", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 1, - "print": 0, - "read": 1, - "report": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "permlevel": 1, + "read": 1, + "role": "System Manager" } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index 340d89bdf8e..6fbd4cd78cf 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -47,19 +47,13 @@ class EmailDigest(Document): # send email only to enabled users valid_users = [p[0] for p in frappe.db.sql("""select name from `tabUser` where enabled=1""")] - recipients = list(filter(lambda r: r in valid_users, - self.recipient_list.split("\n"))) - original_user = frappe.session.user - - if recipients: - for user_id in recipients: - frappe.set_user(user_id) - frappe.set_user_lang(user_id) + if self.recipients: + for row in self.recipients: msg_for_this_recipient = self.get_msg_html() - if msg_for_this_recipient: + if msg_for_this_recipient and row.recipient in valid_users: frappe.sendmail( - recipients=user_id, + recipients=row.recipient, subject=_("{0} Digest").format(self.frequency), message=msg_for_this_recipient, reference_doctype = self.doctype, diff --git a/erpnext/setup/doctype/email_digest/quotes.py b/erpnext/setup/doctype/email_digest/quotes.py index 95afe974b29..5451ee1daff 100644 --- a/erpnext/setup/doctype/email_digest/quotes.py +++ b/erpnext/setup/doctype/email_digest/quotes.py @@ -32,4 +32,3 @@ def get_random_quote(): ] return random.choice(quotes) - diff --git a/erpnext/setup/doctype/email_digest/templates/default.html b/erpnext/setup/doctype/email_digest/templates/default.html index 4ee4b0ff166..666301a643e 100644 --- a/erpnext/setup/doctype/email_digest/templates/default.html +++ b/erpnext/setup/doctype/email_digest/templates/default.html @@ -180,8 +180,8 @@
          {% endif %} - - + + {% if purchase_orders_items_overdue_list %}

          {{ _("Purchase Order Items not received on time") }}

          @@ -254,6 +254,6 @@


          Please take necessary action
          -{% endif %} - +{% endif %} +
          diff --git a/erpnext/setup/doctype/email_digest_recipient/__init__.py b/erpnext/setup/doctype/email_digest_recipient/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.json b/erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.json new file mode 100644 index 00000000000..8b2a6dcf49f --- /dev/null +++ b/erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.json @@ -0,0 +1,33 @@ +{ + "actions": [], + "creation": "2020-06-08 12:19:40.428949", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "recipient" + ], + "fields": [ + { + "fieldname": "recipient", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Recipient", + "options": "User", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2020-08-24 23:10:23.217572", + "modified_by": "Administrator", + "module": "Setup", + "name": "Email Digest Recipient", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.py b/erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.py new file mode 100644 index 00000000000..968c51c345b --- /dev/null +++ b/erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class EmailDigestRecipient(Document): + pass diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 1c72cebfa9d..5fcad00af16 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -87,8 +87,8 @@ class ItemGroup(NestedSet, WebsiteGenerator): if not field_filters: field_filters = {} - # Ensure the query remains within current item group - field_filters['item_group'] = self.name + # Ensure the query remains within current item group & sub group + field_filters['item_group'] = [ig[0] for ig in get_child_groups(self.name)] engine = ProductQuery() context.items = engine.query(attribute_filters, field_filters, search, start, item_group=self.name) diff --git a/erpnext/setup/doctype/item_group/item_group_tree.js b/erpnext/setup/doctype/item_group/item_group_tree.js index 57afe02d796..b2628f4f4f8 100644 --- a/erpnext/setup/doctype/item_group/item_group_tree.js +++ b/erpnext/setup/doctype/item_group/item_group_tree.js @@ -1,3 +1,3 @@ frappe.treeview_settings["Item Group"] = { ignore_fields:["parent_item_group"] -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/print_heading/print_heading.js b/erpnext/setup/doctype/print_heading/print_heading.js index 3878a791db6..3680906057f 100644 --- a/erpnext/setup/doctype/print_heading/print_heading.js +++ b/erpnext/setup/doctype/print_heading/print_heading.js @@ -1,13 +1,13 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt - + //--------- ONLOAD ------------- cur_frm.cscript.onload = function(doc, cdt, cdn) { - + } cur_frm.cscript.refresh = function(doc, cdt, cdn) { - -} \ No newline at end of file + +} diff --git a/erpnext/setup/doctype/print_heading/print_heading.py b/erpnext/setup/doctype/print_heading/print_heading.py index 00dc0f3d919..3d5cd2d6f9e 100644 --- a/erpnext/setup/doctype/print_heading/print_heading.py +++ b/erpnext/setup/doctype/print_heading/print_heading.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class PrintHeading(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/setup/doctype/print_heading/test_print_heading.py b/erpnext/setup/doctype/print_heading/test_print_heading.py index 59455d2b1dd..b2be2e375e4 100644 --- a/erpnext/setup/doctype/print_heading/test_print_heading.py +++ b/erpnext/setup/doctype/print_heading/test_print_heading.py @@ -4,4 +4,4 @@ from __future__ import unicode_literals import frappe -test_records = frappe.get_test_records('Print Heading') \ No newline at end of file +test_records = frappe.get_test_records('Print Heading') diff --git a/erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.js b/erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.js index 3878a791db6..3680906057f 100644 --- a/erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.js +++ b/erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.js @@ -1,13 +1,13 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt - + //--------- ONLOAD ------------- cur_frm.cscript.onload = function(doc, cdt, cdn) { - + } cur_frm.cscript.refresh = function(doc, cdt, cdn) { - -} \ No newline at end of file + +} diff --git a/erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.py b/erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.py index 2cc6235b946..42c5a5a54fb 100644 --- a/erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.py +++ b/erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class QuotationLostReason(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/setup/doctype/quotation_lost_reason/test_quotation_lost_reason.py b/erpnext/setup/doctype/quotation_lost_reason/test_quotation_lost_reason.py index ff4c7885bc7..f6b30b649b5 100644 --- a/erpnext/setup/doctype/quotation_lost_reason/test_quotation_lost_reason.py +++ b/erpnext/setup/doctype/quotation_lost_reason/test_quotation_lost_reason.py @@ -4,4 +4,4 @@ from __future__ import unicode_literals import frappe -test_records = frappe.get_test_records('Quotation Lost Reason') \ No newline at end of file +test_records = frappe.get_test_records('Quotation Lost Reason') diff --git a/erpnext/setup/doctype/sales_person/sales_person_dashboard.py b/erpnext/setup/doctype/sales_person/sales_person_dashboard.py index 3d0b2ff7f8d..662008ec8db 100644 --- a/erpnext/setup/doctype/sales_person/sales_person_dashboard.py +++ b/erpnext/setup/doctype/sales_person/sales_person_dashboard.py @@ -12,4 +12,4 @@ def get_data(): 'items': ['Sales Order', 'Delivery Note', 'Sales Invoice'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/setup/doctype/sales_person/sales_person_tree.js b/erpnext/setup/doctype/sales_person/sales_person_tree.js index bcdfac926c4..00056fde869 100644 --- a/erpnext/setup/doctype/sales_person/sales_person_tree.js +++ b/erpnext/setup/doctype/sales_person/sales_person_tree.js @@ -9,4 +9,4 @@ frappe.treeview_settings["Sales Person"] = { {fieldtype:'Check', fieldname:'is_group', label:__('Group Node'), description: __("Further nodes can be only created under 'Group' type nodes")} ], -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/supplier_group/supplier_group_tree.js b/erpnext/setup/doctype/supplier_group/supplier_group_tree.js index 0788e2e167a..728793eb25f 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group_tree.js +++ b/erpnext/setup/doctype/supplier_group/supplier_group_tree.js @@ -1,4 +1,4 @@ frappe.treeview_settings["Supplier Group"] = { breadcrumbs: "Buying", ignore_fields:["parent_supplier_group"] -}; \ No newline at end of file +}; diff --git a/erpnext/setup/doctype/target_detail/target_detail.py b/erpnext/setup/doctype/target_detail/target_detail.py index d2e2597cb47..633be45d20b 100644 --- a/erpnext/setup/doctype/target_detail/target_detail.py +++ b/erpnext/setup/doctype/target_detail/target_detail.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class TargetDetail(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.js b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.js index 3878a791db6..3680906057f 100644 --- a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.js +++ b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.js @@ -1,13 +1,13 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt - + //--------- ONLOAD ------------- cur_frm.cscript.onload = function(doc, cdt, cdn) { - + } cur_frm.cscript.refresh = function(doc, cdt, cdn) { - -} \ No newline at end of file + +} diff --git a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.py b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.py index 372cc6d3e3e..5b00ccbdbb3 100644 --- a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.py +++ b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.py @@ -24,6 +24,6 @@ def get_terms_and_conditions(template_name, doc): doc = json.loads(doc) terms_and_conditions = frappe.get_doc("Terms and Conditions", template_name) - + if terms_and_conditions.terms: - return frappe.render_template(terms_and_conditions.terms, doc) \ No newline at end of file + return frappe.render_template(terms_and_conditions.terms, doc) diff --git a/erpnext/setup/doctype/territory/territory.js b/erpnext/setup/doctype/territory/territory.js index ceec47ae8c6..3caf814c90b 100644 --- a/erpnext/setup/doctype/territory/territory.js +++ b/erpnext/setup/doctype/territory/territory.js @@ -36,4 +36,4 @@ cur_frm.fields_dict['parent_territory'].get_query = function(doc,cdt,cdn) { ['Territory', 'name', '!=', doc.territory_name] ] } -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/territory/territory.py b/erpnext/setup/doctype/territory/territory.py index 05e8f666cfb..7eefe77495c 100644 --- a/erpnext/setup/doctype/territory/territory.py +++ b/erpnext/setup/doctype/territory/territory.py @@ -24,4 +24,4 @@ class Territory(NestedSet): self.validate_one_root() def on_doctype_update(): - frappe.db.add_index("Territory", ["lft", "rgt"]) \ No newline at end of file + frappe.db.add_index("Territory", ["lft", "rgt"]) diff --git a/erpnext/setup/doctype/territory/territory_tree.js b/erpnext/setup/doctype/territory/territory_tree.js index edd11dfa69b..dadeeef09e9 100644 --- a/erpnext/setup/doctype/territory/territory_tree.js +++ b/erpnext/setup/doctype/territory/territory_tree.js @@ -1,3 +1,3 @@ frappe.treeview_settings["Territory"] = { ignore_fields:["parent_territory"] -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py index bbe68369ffd..933a8c3bed8 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py @@ -23,7 +23,7 @@ class TestTransactionDeletionRecord(unittest.TestCase): contains_company = True break self.assertTrue(contains_company) - + def test_no_of_docs_is_correct(self): for i in range(5): create_task('Dunder Mifflin Paper Co') @@ -40,13 +40,13 @@ class TestTransactionDeletionRecord(unittest.TestCase): 'company' : 'Dunder Mifflin Paper Co' }) self.assertEqual(tasks_containing_company, []) - + def create_company(company_name): company = frappe.get_doc({ 'doctype': 'Company', 'company_name': company_name, 'default_currency': 'INR' - }) + }) company.insert(ignore_if_duplicate = True) def create_transaction_deletion_request(company): diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js index 20caa15ee41..6a50ef8bbd9 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js @@ -4,7 +4,7 @@ frappe.ui.form.on('Transaction Deletion Record', { onload: function(frm) { if (frm.doc.docstatus == 0) { - let doctypes_to_be_ignored_array; + let doctypes_to_be_ignored_array; frappe.call({ method: 'erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.get_doctypes_to_be_ignored', callback: function(r) { @@ -25,15 +25,15 @@ frappe.ui.form.on('Transaction Deletion Record', { frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false); frm.refresh_field('doctypes_to_be_ignored'); } - + }); function populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm) { if (!(frm.doc.doctypes_to_be_ignored)) { var i; - for (i = 0; i < doctypes_to_be_ignored_array.length; i++) { + for (i = 0; i < doctypes_to_be_ignored_array.length; i++) { frm.add_child('doctypes_to_be_ignored', { - doctype_name: doctypes_to_be_ignored_array[i] + doctype_name: doctypes_to_be_ignored_array[i] }); } } diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json index 9313f955167..23e59472a6d 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -54,7 +54,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-08 23:13:48.049879", + "modified": "2021-08-04 20:15:59.071493", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record", @@ -70,6 +70,7 @@ "report": 1, "role": "System Manager", "share": 1, + "submit": 1, "write": 1 } ], diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index ece9fb56992..8a491554801 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -12,10 +12,14 @@ from frappe.desk.notifications import clear_notifications class TransactionDeletionRecord(Document): def validate(self): frappe.only_for('System Manager') + self.validate_doctypes_to_be_ignored() + + def validate_doctypes_to_be_ignored(self): doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() for doctype in self.doctypes_to_be_ignored: if doctype.doctype_name not in doctypes_to_be_ignored_list: - frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "), title=_("Not Allowed")) + frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it."), + title=_("Not Allowed")) def before_submit(self): if not self.doctypes_to_be_ignored: @@ -23,62 +27,122 @@ class TransactionDeletionRecord(Document): self.delete_bins() self.delete_lead_addresses() - - company_obj = frappe.get_doc('Company', self.company) - # reset company values - company_obj.total_monthly_sales = 0 - company_obj.sales_monthly_history = None - company_obj.save() - # Clear notification counts + self.reset_company_values() clear_notifications() + self.delete_company_transactions() - singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name') - tables = frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name') - doctypes_to_be_ignored_list = singles - for doctype in self.doctypes_to_be_ignored: - doctypes_to_be_ignored_list.append(doctype.doctype_name) - - docfields = frappe.get_all('DocField', - filters = { - 'fieldtype': 'Link', - 'options': 'Company', - 'parent': ['not in', doctypes_to_be_ignored_list]}, - fields=['parent', 'fieldname']) - - for docfield in docfields: - if docfield['parent'] != self.doctype: - no_of_docs = frappe.db.count(docfield['parent'], { - docfield['fieldname'] : self.company - }) - - if no_of_docs > 0: - self.delete_version_log(docfield['parent'], docfield['fieldname']) - self.delete_communications(docfield['parent'], docfield['fieldname']) - - # populate DocTypes table - if docfield['parent'] not in tables: - self.append('doctypes', { - 'doctype_name' : docfield['parent'], - 'no_of_docs' : no_of_docs - }) - - # delete the docs linked with the specified company - frappe.db.delete(docfield['parent'], { - docfield['fieldname'] : self.company - }) - - naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname') - if naming_series: - if '#' in naming_series: - self.update_naming_series(naming_series, docfield['parent']) - - def populate_doctypes_to_be_ignored_table(self): + def populate_doctypes_to_be_ignored_table(self): doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() for doctype in doctypes_to_be_ignored_list: self.append('doctypes_to_be_ignored', { 'doctype_name' : doctype }) + def delete_bins(self): + frappe.db.sql("""delete from tabBin where warehouse in + (select name from tabWarehouse where company=%s)""", self.company) + + def delete_lead_addresses(self): + """Delete addresses to which leads are linked""" + leads = frappe.get_all('Lead', filters={'company': self.company}) + leads = ["'%s'" % row.get("name") for row in leads] + addresses = [] + if leads: + addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name + in ({leads})""".format(leads=",".join(leads))) + + if addresses: + addresses = ["%s" % frappe.db.escape(addr) for addr in addresses] + + frappe.db.sql("""delete from tabAddress where name in ({addresses}) and + name not in (select distinct dl1.parent from `tabDynamic Link` dl1 + inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent + and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses))) + + frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead' + and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads))) + + frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads))) + + def reset_company_values(self): + company_obj = frappe.get_doc('Company', self.company) + company_obj.total_monthly_sales = 0 + company_obj.sales_monthly_history = None + company_obj.save() + + def delete_company_transactions(self): + doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list() + docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list) + + tables = self.get_all_child_doctypes() + for docfield in docfields: + if docfield['parent'] != self.doctype: + no_of_docs = self.get_number_of_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname']) + + if no_of_docs > 0: + self.delete_version_log(docfield['parent'], docfield['fieldname']) + self.delete_communications(docfield['parent'], docfield['fieldname']) + self.populate_doctypes_table(tables, docfield['parent'], no_of_docs) + + self.delete_child_tables(docfield['parent'], docfield['fieldname']) + self.delete_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname']) + + naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname') + if naming_series: + if '#' in naming_series: + self.update_naming_series(naming_series, docfield['parent']) + + def get_doctypes_to_be_ignored_list(self): + singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name') + doctypes_to_be_ignored_list = singles + for doctype in self.doctypes_to_be_ignored: + doctypes_to_be_ignored_list.append(doctype.doctype_name) + + return doctypes_to_be_ignored_list + + def get_doctypes_with_company_field(self, doctypes_to_be_ignored_list): + docfields = frappe.get_all('DocField', + filters = { + 'fieldtype': 'Link', + 'options': 'Company', + 'parent': ['not in', doctypes_to_be_ignored_list]}, + fields=['parent', 'fieldname']) + + return docfields + + def get_all_child_doctypes(self): + return frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name') + + def get_number_of_docs_linked_with_specified_company(self, doctype, company_fieldname): + return frappe.db.count(doctype, {company_fieldname : self.company}) + + def populate_doctypes_table(self, tables, doctype, no_of_docs): + if doctype not in tables: + self.append('doctypes', { + 'doctype_name' : doctype, + 'no_of_docs' : no_of_docs + }) + + def delete_child_tables(self, doctype, company_fieldname): + parent_docs_to_be_deleted = frappe.get_all(doctype, { + company_fieldname : self.company + }, pluck = 'name') + + child_tables = frappe.get_all('DocField', filters = { + 'fieldtype': 'Table', + 'parent': doctype + }, pluck = 'options') + + for table in child_tables: + frappe.db.delete(table, { + 'parent': ['in', parent_docs_to_be_deleted] + }) + + def delete_docs_linked_with_specified_company(self, doctype, company_fieldname): + frappe.db.delete(doctype, { + company_fieldname : self.company + }) + def update_naming_series(self, naming_series, doctype_name): if '.' in naming_series: prefix, hashes = naming_series.rsplit('.', 1) @@ -107,32 +171,6 @@ class TransactionDeletionRecord(Document): frappe.delete_doc('Communication', communication_names, ignore_permissions=True) - def delete_bins(self): - frappe.db.sql("""delete from tabBin where warehouse in - (select name from tabWarehouse where company=%s)""", self.company) - - def delete_lead_addresses(self): - """Delete addresses to which leads are linked""" - leads = frappe.get_all('Lead', filters={'company': self.company}) - leads = ["'%s'" % row.get("name") for row in leads] - addresses = [] - if leads: - addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name - in ({leads})""".format(leads=",".join(leads))) - - if addresses: - addresses = ["%s" % frappe.db.escape(addr) for addr in addresses] - - frappe.db.sql("""delete from tabAddress where name in ({addresses}) and - name not in (select distinct dl1.parent from `tabDynamic Link` dl1 - inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent - and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses))) - - frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead' - and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads))) - - frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads))) - @frappe.whitelist() def get_doctypes_to_be_ignored(): doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget', diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js index d7175ddac43..c238f18abad 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js @@ -9,4 +9,4 @@ frappe.listview_settings['Transaction Deletion Record'] = { return [__("Completed"), "green"]; } } -}; \ No newline at end of file +}; diff --git a/erpnext/setup/doctype/uom/uom.js b/erpnext/setup/doctype/uom/uom.js index 3878a791db6..3680906057f 100644 --- a/erpnext/setup/doctype/uom/uom.js +++ b/erpnext/setup/doctype/uom/uom.js @@ -1,13 +1,13 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt - + //--------- ONLOAD ------------- cur_frm.cscript.onload = function(doc, cdt, cdn) { - + } cur_frm.cscript.refresh = function(doc, cdt, cdn) { - -} \ No newline at end of file + +} diff --git a/erpnext/setup/doctype/uom/uom.py b/erpnext/setup/doctype/uom/uom.py index f7f86d67509..404b84b1134 100644 --- a/erpnext/setup/doctype/uom/uom.py +++ b/erpnext/setup/doctype/uom/uom.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class UOM(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/setup/doctype/website_item_group/website_item_group.py b/erpnext/setup/doctype/website_item_group/website_item_group.py index 9ac7df2c667..e416b509b98 100644 --- a/erpnext/setup/doctype/website_item_group/website_item_group.py +++ b/erpnext/setup/doctype/website_item_group/website_item_group.py @@ -9,4 +9,4 @@ import frappe from frappe.model.document import Document class WebsiteItemGroup(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/setup/setup_wizard/operations/company_setup.py b/erpnext/setup/setup_wizard/operations/company_setup.py index 4edf9485dc1..4833d93c4a7 100644 --- a/erpnext/setup/setup_wizard/operations/company_setup.py +++ b/erpnext/setup/setup_wizard/operations/company_setup.py @@ -45,9 +45,16 @@ def enable_shopping_cart(args): def create_email_digest(): from frappe.utils.user import get_system_managers system_managers = get_system_managers(only_name=True) + if not system_managers: return + recipients = [] + for d in system_managers: + recipients.append({ + 'recipient': d + }) + companies = frappe.db.sql_list("select name FROM `tabCompany`") for company in companies: if not frappe.db.exists("Email Digest", "Default Weekly Digest - " + company): @@ -56,7 +63,7 @@ def create_email_digest(): "name": "Default Weekly Digest - " + company, "company": company, "frequency": "Weekly", - "recipient_list": "\n".join(system_managers) + "recipients": recipients }) for df in edigest.meta.get("fields", {"fieldtype": "Check"}): @@ -72,7 +79,7 @@ def create_email_digest(): "name": "Scheduler Errors", "company": companies[0], "frequency": "Daily", - "recipient_list": "\n".join(system_managers), + "recipients": recipients, "scheduler_errors": 1, "enabled": 1 }) diff --git a/erpnext/setup/setup_wizard/operations/sample_data.py b/erpnext/setup/setup_wizard/operations/sample_data.py index c11a3885c9b..c6d9f0851b1 100644 --- a/erpnext/setup/setup_wizard/operations/sample_data.py +++ b/erpnext/setup/setup_wizard/operations/sample_data.py @@ -173,4 +173,4 @@ def test_sample(): frappe.db.sql('delete from tabProject') frappe.db.sql('delete from tabTask') make_projects('Education') - import_notification() \ No newline at end of file + import_notification() diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index c7cc0005186..09b91709642 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -124,7 +124,8 @@ def make_taxes_and_charges_template(company_name, doctype, template): account_data = tax_row.get('account_head') tax_row_defaults = { 'category': 'Total', - 'charge_type': 'On Net Total' + 'charge_type': 'On Net Total', + 'cost_center': frappe.db.get_value('Company', company_name, 'cost_center') } if doctype == 'Purchase Taxes and Charges Template': @@ -144,7 +145,7 @@ def make_taxes_and_charges_template(company_name, doctype, template): doc = frappe.get_doc(template) - # Data in country wise json is already pre validated, hence validations can be ignored + # Data in country wise json is already pre validated, hence validations can be ignored # Ingone validations to make doctypes faster doc.flags.ignore_links = True doc.flags.ignore_validate = True @@ -176,7 +177,7 @@ def make_item_tax_template(company_name, template): doc = frappe.get_doc(template) - # Data in country wise json is already pre validated, hence validations can be ignored + # Data in country wise json is already pre validated, hence validations can be ignored # Ingone validations to make doctypes faster doc.flags.ignore_links = True doc.flags.ignore_validate = True diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index 56afe95efd0..e9f4bd57a6a 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -308,7 +308,7 @@ def update_party(fullname, company_name=None, mobile_no=None, phone=None): party = get_party() party.customer_name = company_name or fullname - party.customer_type == "Company" if company_name else "Individual" + party.customer_type = "Company" if company_name else "Individual" contact_name = frappe.db.get_value("Contact", {"email_id": frappe.session.user}) contact = frappe.get_doc("Contact", contact_name) diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py index c069b90e986..2a497225fbc 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py @@ -31,7 +31,7 @@ class ShoppingCartSettings(Document): [self.price_list], "currency") price_list_currency_map = dict(price_list_currency_map) - + # check if all price lists have a currency for price_list, currency in price_list_currency_map.items(): if not currency: diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py index 75899e121a5..008751e2088 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py @@ -36,7 +36,7 @@ class TestShoppingCartSettings(unittest.TestCase): cart_settings.enabled = 1 if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1}, "name"): self.assertRaises(ShoppingCartSetupError, cart_settings.validate_tax_rule) - + frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 1") -test_dependencies = ["Tax Rule"] \ No newline at end of file +test_dependencies = ["Tax Rule"] diff --git a/erpnext/shopping_cart/product_info.py b/erpnext/shopping_cart/product_info.py index 29617a87485..6c9e531a4d1 100644 --- a/erpnext/shopping_cart/product_info.py +++ b/erpnext/shopping_cart/product_info.py @@ -66,4 +66,4 @@ def set_product_info_for_website(item): item["price_sales_uom"] = product_info.get("price").get("formatted_price_sales_uom") else: item["price_stock_uom"] = "" - item["price_sales_uom"] = "" \ No newline at end of file + item["price_sales_uom"] = "" diff --git a/erpnext/shopping_cart/search.py b/erpnext/shopping_cart/search.py index 63e9fe1b31b..9f674dcebf8 100644 --- a/erpnext/shopping_cart/search.py +++ b/erpnext/shopping_cart/search.py @@ -123,4 +123,4 @@ def remove_document_from_index(path): def build_index_for_all_routes(): search = ProductSearch(INDEX_NAME) - return search.build() \ No newline at end of file + return search.build() diff --git a/erpnext/shopping_cart/utils.py b/erpnext/shopping_cart/utils.py index 3241234af5f..0e1466fd1fa 100644 --- a/erpnext/shopping_cart/utils.py +++ b/erpnext/shopping_cart/utils.py @@ -38,4 +38,4 @@ def check_customer_or_supplier(): if link.link_doctype in ('Customer', 'Supplier'): return link.link_doctype, link.link_name - return 'Customer', None \ No newline at end of file + return 'Customer', None diff --git a/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html b/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html index 1b3953435e4..1e3d0d069a1 100644 --- a/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html +++ b/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html @@ -82,4 +82,4 @@ \ No newline at end of file + diff --git a/erpnext/shopping_cart/web_template/item_card_group/item_card_group.html b/erpnext/shopping_cart/web_template/item_card_group/item_card_group.html index 890ae502c82..fe061d5f5f5 100644 --- a/erpnext/shopping_cart/web_template/item_card_group/item_card_group.html +++ b/erpnext/shopping_cart/web_template/item_card_group/item_card_group.html @@ -35,4 +35,4 @@ \ No newline at end of file + diff --git a/erpnext/startup/filters.py b/erpnext/startup/filters.py index ec07329dedf..98210165dfa 100644 --- a/erpnext/startup/filters.py +++ b/erpnext/startup/filters.py @@ -11,4 +11,4 @@ def get_filters_config(): } } - return filters_config \ No newline at end of file + return filters_config diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index 8819a55c0ab..a89435d4866 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -202,4 +202,4 @@ def get_date_condition(date_range, field): date_condition = "and {0} between {1} and {2}".format( field, frappe.db.escape(from_date), frappe.db.escape(to_date) ) - return date_condition \ No newline at end of file + return date_condition diff --git a/erpnext/stock/dashboard/item_dashboard.html b/erpnext/stock/dashboard/item_dashboard.html index 1e18969e63e..99698ba69a9 100644 --- a/erpnext/stock/dashboard/item_dashboard.html +++ b/erpnext/stock/dashboard/item_dashboard.html @@ -4,4 +4,4 @@ - \ No newline at end of file + diff --git a/erpnext/stock/dashboard/warehouse_capacity_dashboard.py b/erpnext/stock/dashboard/warehouse_capacity_dashboard.py index ab573e566ac..70b030e48f2 100644 --- a/erpnext/stock/dashboard/warehouse_capacity_dashboard.py +++ b/erpnext/stock/dashboard/warehouse_capacity_dashboard.py @@ -66,4 +66,4 @@ def get_warehouse_capacity_data(filters, start): 'percent_occupied': flt((flt(balance_qty) / flt(entry.stock_capacity)) * 100, 0) }) - return capacity_data \ No newline at end of file + return capacity_data diff --git a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.js b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.js index a4137547f7e..2b9d46e4ab0 100644 --- a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.js +++ b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.js @@ -11,4 +11,4 @@ frappe.dashboards.chart_sources["Warehouse wise Stock Value"] = { default: frappe.defaults.get_user_default("Company") } ] -}; \ No newline at end of file +}; diff --git a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py index 374a34ea7ca..2258532c6fe 100644 --- a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py +++ b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py @@ -45,4 +45,4 @@ def get(chart_name = None, chart = None, no_cache = None, filters = None, from_d "values": datapoints }], "type": "bar" - } \ No newline at end of file + } diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index b6eef6ca484..b37ae3f4f61 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -162,19 +162,19 @@ def get_batch_qty(batch_no=None, warehouse=None, item_code=None, posting_date=No out = float(frappe.db.sql("""select sum(actual_qty) from `tabStock Ledger Entry` - where warehouse=%s and batch_no=%s {0}""".format(cond), + where is_cancelled = 0 and warehouse=%s and batch_no=%s {0}""".format(cond), (warehouse, batch_no))[0][0] or 0) if batch_no and not warehouse: out = frappe.db.sql('''select warehouse, sum(actual_qty) as qty from `tabStock Ledger Entry` - where batch_no=%s + where is_cancelled = 0 and batch_no=%s group by warehouse''', batch_no, as_dict=1) if not batch_no and item_code and warehouse: out = frappe.db.sql('''select batch_no, sum(actual_qty) as qty from `tabStock Ledger Entry` - where item_code = %s and warehouse=%s + where is_cancelled = 0 and item_code = %s and warehouse=%s group by batch_no''', (item_code, warehouse), as_dict=1) return out diff --git a/erpnext/stock/doctype/batch/test_batch.js b/erpnext/stock/doctype/batch/test_batch.js index af7f50ff91e..2d2150b8acd 100644 --- a/erpnext/stock/doctype/batch/test_batch.js +++ b/erpnext/stock/doctype/batch/test_batch.js @@ -20,4 +20,3 @@ QUnit.test("test Batch", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py index cbd272df4b8..a85a0222b55 100644 --- a/erpnext/stock/doctype/batch/test_batch.py +++ b/erpnext/stock/doctype/batch/test_batch.py @@ -269,11 +269,14 @@ class TestBatch(unittest.TestCase): batch2 = create_batch('_Test Batch Price Item', 300, 1) batch3 = create_batch('_Test Batch Price Item', 400, 0) + company = "_Test Company with perpetual inventory" + currency = frappe.get_cached_value("Company", company, "default_currency") + args = frappe._dict({ "item_code": "_Test Batch Price Item", - "company": "_Test Company with perpetual inventory", + "company": company, "price_list": "_Test Price List", - "currency": "_Test Currency", + "currency": currency, "doctype": "Sales Invoice", "conversion_rate": 1, "price_list_currency": "_Test Currency", @@ -333,4 +336,4 @@ def make_new_batch(**args): except frappe.DuplicateEntryError: batch = frappe.get_doc("Batch", args.batch_id) - return batch \ No newline at end of file + return batch diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index f20e76f5bf6..958189614fd 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -32,6 +32,8 @@ "contact_info", "shipping_address_name", "shipping_address", + "dispatch_address_name", + "dispatch_address", "contact_person", "contact_display", "contact_mobile", @@ -513,7 +515,8 @@ { "fieldname": "scan_barcode", "fieldtype": "Data", - "label": "Scan Barcode" + "label": "Scan Barcode", + "options": "Barcode" }, { "allow_bulk_edit": 1, @@ -1282,13 +1285,28 @@ "fieldname": "disable_rounded_total", "fieldtype": "Check", "label": "Disable Rounded Total" + }, + { + "fieldname": "dispatch_address_name", + "fieldtype": "Link", + "label": "Dispatch Address Name", + "options": "Address", + "print_hide": 1 + }, + { + "depends_on": "dispatch_address_name", + "fieldname": "dispatch_address", + "fieldtype": "Small Text", + "label": "Dispatch Address", + "print_hide": 1, + "read_only": 1 } ], "icon": "fa fa-truck", "idx": 146, "is_submittable": 1, "links": [], - "modified": "2021-06-11 19:27:30.901112", + "modified": "2021-08-17 20:15:50.574966", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 4808e948fc6..f99a01b8202 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -503,6 +503,10 @@ def make_sales_invoice(source_name, target_doc=None): } }, target_doc, set_missing_values) + automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms')) + if automatically_fetch_payment_terms: + doc.set_payment_schedule() + return doc @frappe.whitelist() diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py index 47684d5c6ec..9db5db865f5 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py @@ -30,4 +30,4 @@ def get_data(): 'items': ['Auto Repeat'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/stock/doctype/delivery_note/regional/india.js b/erpnext/stock/doctype/delivery_note/regional/india.js index 5e1ff980009..e853858b60b 100644 --- a/erpnext/stock/doctype/delivery_note/regional/india.js +++ b/erpnext/stock/doctype/delivery_note/regional/india.js @@ -27,4 +27,3 @@ frappe.ui.form.on('Delivery Note', { } } }) - diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.js b/erpnext/stock/doctype/delivery_note/test_delivery_note.js index 3f6e8d15035..76f79894290 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.js @@ -33,4 +33,3 @@ QUnit.test("test delivery note", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index f981aeb13bb..91e7c006eef 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -17,7 +17,8 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry \ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, SerialNoWarehouseError from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation \ import create_stock_reconciliation, set_valuation_method -from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order, create_dn_against_so +from erpnext.selling.doctype.sales_order.test_sales_order \ + import make_sales_order, create_dn_against_so, automatically_fetch_payment_terms, compare_payment_schedules from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse from erpnext.stock.doctype.item.test_item import make_item @@ -759,6 +760,32 @@ class TestDeliveryNote(unittest.TestCase): self.assertTrue("TESTBATCH" in dn.packed_items[0].batch_no, "Batch number not added in packed item") + def test_payment_terms_are_fetched_when_creating_sales_invoice(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + + automatically_fetch_payment_terms() + + so = make_sales_order(uom="Nos", do_not_save=1) + create_payment_terms_template() + so.payment_terms_template = 'Test Receivable Template' + so.submit() + + dn = create_dn_against_so(so.name, delivered_qty=10) + + si = create_sales_invoice(qty=10, do_not_save=1) + si.items[0].delivery_note= dn.name + si.items[0].dn_detail = dn.items[0].name + si.items[0].sales_order = so.name + si.items[0].so_detail = so.items[0].name + + si.insert() + si.submit() + + self.assertEqual(so.payment_terms_template, si.payment_terms_template) + compare_payment_schedules(self, so, si) + + automatically_fetch_payment_terms(enable=0) def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note_with_margin.js b/erpnext/stock/doctype/delivery_note/test_delivery_note_with_margin.js index 21eb35ce378..9f1375f563c 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note_with_margin.js +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note_with_margin.js @@ -34,4 +34,3 @@ QUnit.test("test delivery note with margin", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.py b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.py index 50305957892..8bd381a2ed3 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.py +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class DeliveryNoteItem(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py index 9ec28d89814..f76bb87efed 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py @@ -406,4 +406,4 @@ def make_expense_claim(source_name, target_doc=None): } }}, target_doc) - return doc \ No newline at end of file + return doc diff --git a/erpnext/stock/doctype/delivery_trip/dispatch_notification_template.html b/erpnext/stock/doctype/delivery_trip/dispatch_notification_template.html index 9c062bc34ca..d12334e355a 100644 --- a/erpnext/stock/doctype/delivery_trip/dispatch_notification_template.html +++ b/erpnext/stock/doctype/delivery_trip/dispatch_notification_template.html @@ -47,4 +47,4 @@ {{ vehicle }} - \ No newline at end of file + diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index b55374b8d81..6c5ef8b4f03 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -100,10 +100,11 @@ frappe.ui.form.on("Item", { frm.add_custom_button(__('Duplicate'), function() { var new_item = frappe.model.copy_doc(frm.doc); - if(new_item.item_name===new_item.item_code) { + // Duplicate item could have different name, causing "copy paste" error. + if (new_item.item_name===new_item.item_code) { new_item.item_name = null; } - if(new_item.description===new_item.description) { + if (new_item.item_code===new_item.description || new_item.item_code===new_item.description) { new_item.description = null; } frappe.set_route('Form', 'Item', new_item.name); @@ -137,20 +138,6 @@ frappe.ui.form.on("Item", { frm.toggle_reqd('customer', frm.doc.is_customer_provided_item ? 1:0); }, - gst_hsn_code: function(frm) { - if((!frm.doc.taxes || !frm.doc.taxes.length) && frm.doc.gst_hsn_code) { - frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc => { - $.each(hsn_doc.taxes || [], function(i, tax) { - let a = frappe.model.add_child(cur_frm.doc, 'Item Tax', 'taxes'); - a.item_tax_template = tax.item_tax_template; - a.tax_category = tax.tax_category; - a.valid_from = tax.valid_from; - frm.refresh_field('taxes'); - }); - }); - } - }, - is_fixed_asset: function(frm) { // set serial no to false & toggles its visibility frm.set_value('has_serial_no', 0); @@ -186,8 +173,6 @@ frappe.ui.form.on("Item", { item_code: function(frm) { if(!frm.doc.item_name) frm.set_value("item_name", frm.doc.item_code); - if(!frm.doc.description) - frm.set_value("description", frm.doc.item_code); }, is_stock_item: function(frm) { @@ -275,6 +260,17 @@ $.extend(erpnext.item, { } } + frm.fields_dict["item_defaults"].grid.get_field("default_discount_account").get_query = function(doc, cdt, cdn) { + const row = locals[cdt][cdn]; + return { + filters: { + 'report_type': 'Profit and Loss', + 'company': row.company, + "is_group": 0 + } + }; + }; + frm.fields_dict["item_defaults"].grid.get_field("buying_cost_center").get_query = function(doc, cdt, cdn) { const row = locals[cdt][cdn]; return { diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 6fed9efa63f..f662bbd1c79 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -1067,7 +1067,7 @@ "index_web_pages_for_search": 1, "links": [], "max_attachments": 1, - "modified": "2021-03-18 14:04:38.575519", + "modified": "2021-07-13 01:29:06.071827", "modified_by": "Administrator", "module": "Stock", "name": "Item", @@ -1138,4 +1138,4 @@ "sort_order": "DESC", "title_field": "item_name", "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index fbd30cf6c78..ca587bfe82c 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -123,6 +123,7 @@ class Item(WebsiteGenerator): self.cant_change() self.update_show_in_website() self.validate_item_tax_net_rate_range() + set_item_tax_from_hsn_code(self) if not self.is_new(): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") @@ -1305,3 +1306,7 @@ def update_variants(variants, template, publish_progress=True): def on_doctype_update(): # since route is a Text column, it needs a length for indexing frappe.db.add_index("Item", ["route(500)"]) + +@erpnext.allow_regional +def set_item_tax_from_hsn_code(item): + pass diff --git a/erpnext/stock/doctype/item/regional/india.js b/erpnext/stock/doctype/item/regional/india.js new file mode 100644 index 00000000000..cceb1ec895f --- /dev/null +++ b/erpnext/stock/doctype/item/regional/india.js @@ -0,0 +1,15 @@ +frappe.ui.form.on('Item', { + gst_hsn_code: function(frm) { + if ((!frm.doc.taxes || !frm.doc.taxes.length) && frm.doc.gst_hsn_code) { + frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc => { + $.each(hsn_doc.taxes || [], function(i, tax) { + let a = frappe.model.add_child(cur_frm.doc, 'Item Tax', 'taxes'); + a.item_tax_template = tax.item_tax_template; + a.tax_category = tax.tax_category; + a.valid_from = tax.valid_from; + frm.refresh_field('taxes'); + }); + }); + } + }, +}); diff --git a/erpnext/stock/doctype/item/templates/item.html b/erpnext/stock/doctype/item/templates/item.html index db123090aae..5c42f3b1247 100644 --- a/erpnext/stock/doctype/item/templates/item.html +++ b/erpnext/stock/doctype/item/templates/item.html @@ -4,4 +4,4 @@

          {{ title }}

          {% endblock %} - \ No newline at end of file + diff --git a/erpnext/stock/doctype/item/templates/item_row.html b/erpnext/stock/doctype/item/templates/item_row.html index 2b999819cbb..f81fc1d8743 100644 --- a/erpnext/stock/doctype/item/templates/item_row.html +++ b/erpnext/stock/doctype/item/templates/item_row.html @@ -1,4 +1,4 @@ - \ No newline at end of file + diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index c7467a5a0f5..9ec44d2e2e4 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -83,14 +83,17 @@ class TestItem(unittest.TestCase): make_test_objects("Item Price") + company = "_Test Company" + currency = frappe.get_cached_value("Company", company, "default_currency") + details = get_item_details({ "item_code": "_Test Item", - "company": "_Test Company", + "company": company, "price_list": "_Test Price List", - "currency": "_Test Currency", + "currency": currency, "doctype": "Sales Order", "conversion_rate": 1, - "price_list_currency": "_Test Currency", + "price_list_currency": currency, "plc_conversion_rate": 1, "order_type": "Sales", "customer": "_Test Customer", diff --git a/erpnext/stock/doctype/item/tests/test_item.js b/erpnext/stock/doctype/item/tests/test_item.js index 5e3524e5b6d..7f7e72d5c0f 100644 --- a/erpnext/stock/doctype/item/tests/test_item.js +++ b/erpnext/stock/doctype/item/tests/test_item.js @@ -118,4 +118,4 @@ QUnit.test("test: item", function (assert) { ), () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/stock/doctype/item_attribute/test_item_attribute.py b/erpnext/stock/doctype/item_attribute/test_item_attribute.py index 61e53d24a46..07af176a944 100644 --- a/erpnext/stock/doctype/item_attribute/test_item_attribute.py +++ b/erpnext/stock/doctype/item_attribute/test_item_attribute.py @@ -28,4 +28,3 @@ class TestItemAttribute(unittest.TestCase): item_attribute.increment = 0.5 item_attribute.save() - diff --git a/erpnext/stock/doctype/item_customer_detail/item_customer_detail.py b/erpnext/stock/doctype/item_customer_detail/item_customer_detail.py index a9183ce5866..3e4e8500467 100644 --- a/erpnext/stock/doctype/item_customer_detail/item_customer_detail.py +++ b/erpnext/stock/doctype/item_customer_detail/item_customer_detail.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class ItemCustomerDetail(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/stock/doctype/item_default/item_default.json b/erpnext/stock/doctype/item_default/item_default.json index 96b5dfdc8f7..bc171604f43 100644 --- a/erpnext/stock/doctype/item_default/item_default.json +++ b/erpnext/stock/doctype/item_default/item_default.json @@ -1,464 +1,118 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-05-03 02:29:24.444341", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2018-05-03 02:29:24.444341", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "default_warehouse", + "column_break_3", + "default_price_list", + "default_discount_account", + "purchase_defaults", + "buying_cost_center", + "default_supplier", + "column_break_8", + "expense_account", + "selling_defaults", + "selling_cost_center", + "column_break_12", + "income_account" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "default_warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "default_warehouse", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Default Warehouse", + "options": "Warehouse" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "default_price_list", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default Price List", - "length": 0, - "no_copy": 0, - "options": "Price List", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "default_price_list", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Default Price List", + "options": "Price List" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "purchase_defaults", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Purchase Defaults", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "purchase_defaults", + "fieldtype": "Section Break", + "label": "Purchase Defaults" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "buying_cost_center", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Buying Cost Center", - "length": 0, - "no_copy": 0, - "options": "Cost Center", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "buying_cost_center", + "fieldtype": "Link", + "label": "Default Buying Cost Center", + "options": "Cost Center" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "default_supplier", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Supplier", - "length": 0, - "no_copy": 0, - "options": "Supplier", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "default_supplier", + "fieldtype": "Link", + "label": "Default Supplier", + "options": "Supplier" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_8", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "expense_account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Expense Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "expense_account", + "fieldtype": "Link", + "label": "Default Expense Account", + "options": "Account" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "selling_defaults", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Defaults", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "selling_defaults", + "fieldtype": "Section Break", + "label": "Sales Defaults" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "selling_cost_center", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Selling Cost Center", - "length": 0, - "no_copy": 0, - "options": "Cost Center", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "selling_cost_center", + "fieldtype": "Link", + "label": "Default Selling Cost Center", + "options": "Cost Center" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_12", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "income_account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Income Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "income_account", + "fieldtype": "Link", + "label": "Default Income Account", + "options": "Account" + }, + { + "fieldname": "default_discount_account", + "fieldtype": "Link", + "label": "Default Discount Account", + "options": "Account" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-12-07 11:48:07.638935", - "modified_by": "Administrator", - "module": "Stock", - "name": "Item Default", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "istable": 1, + "links": [], + "modified": "2021-07-13 01:26:03.860065", + "modified_by": "Administrator", + "module": "Stock", + "name": "Item Default", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py index c27d1be7892..939abf8d324 100644 --- a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py +++ b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py @@ -65,4 +65,4 @@ class ItemManufacturer(Document): @frappe.whitelist() def get_item_manufacturer_part_no(item_code, manufacturer): return frappe.db.get_value("Item Manufacturer", - {'item_code': item_code, 'manufacturer': manufacturer}, 'manufacturer_part_no') \ No newline at end of file + {'item_code': item_code, 'manufacturer': manufacturer}, 'manufacturer_part_no') diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json index 9b1a47eed6c..5de45cbcad9 100644 --- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json +++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json @@ -47,7 +47,8 @@ "description": "Simple Python formula applied on Reading fields.
          Numeric eg. 1: reading_1 > 0.2 and reading_1 < 0.5
          \nNumeric eg. 2: mean > 3.5 (mean of populated fields)
          \nValue based eg.: reading_value in (\"A\", \"B\", \"C\")", "fieldname": "acceptance_formula", "fieldtype": "Code", - "label": "Acceptance Criteria Formula" + "label": "Acceptance Criteria Formula", + "options": "PythonExpression" }, { "default": "0", @@ -89,7 +90,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-02-04 18:50:02.056173", + "modified": "2021-08-06 15:08:20.911338", "modified_by": "Administrator", "module": "Stock", "name": "Item Quality Inspection Parameter", diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.py b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.py index 92aefc8d9e9..785737b267f 100644 --- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.py +++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class ItemQualityInspectionParameter(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/stock/doctype/item_reorder/item_reorder.py b/erpnext/stock/doctype/item_reorder/item_reorder.py index 0f9c593d36a..5cdaa229565 100644 --- a/erpnext/stock/doctype/item_reorder/item_reorder.py +++ b/erpnext/stock/doctype/item_reorder/item_reorder.py @@ -9,4 +9,4 @@ import frappe from frappe.model.document import Document class ItemReorder(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/stock/doctype/item_supplier/item_supplier.py b/erpnext/stock/doctype/item_supplier/item_supplier.py index 1a07f03ec58..5dda535f810 100644 --- a/erpnext/stock/doctype/item_supplier/item_supplier.py +++ b/erpnext/stock/doctype/item_supplier/item_supplier.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class ItemSupplier(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/stock/doctype/item_tax/item_tax.py b/erpnext/stock/doctype/item_tax/item_tax.py index 1fe2f454681..7c9e8115758 100644 --- a/erpnext/stock/doctype/item_tax/item_tax.py +++ b/erpnext/stock/doctype/item_tax/item_tax.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class ItemTax(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/stock/doctype/item_website_specification/item_website_specification.py b/erpnext/stock/doctype/item_website_specification/item_website_specification.py index 6d0dbad2a5e..e3041cf3eef 100644 --- a/erpnext/stock/doctype/item_website_specification/item_website_specification.py +++ b/erpnext/stock/doctype/item_website_specification/item_website_specification.py @@ -9,4 +9,4 @@ import frappe from frappe.model.document import Document class ItemWebsiteSpecification(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.py b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.py index 0521a7ad1cb..493e8b239a4 100644 --- a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.py +++ b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class LandedCostItem(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.py b/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.py index f7ccb9b6e29..38f4eafc3aa 100644 --- a/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.py +++ b/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class LandedCostPurchaseReceipt(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.py b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.py index e4458207dbb..0dc396aefac 100644 --- a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.py +++ b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.py @@ -6,4 +6,4 @@ import frappe from frappe.model.document import Document class LandedCostTaxesandCharges(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 32b08f60c4a..cb09d933801 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -11,6 +11,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \ from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.accounts.doctype.account.test_account import create_account +from erpnext.assets.doctype.asset.test_asset import create_asset_category, create_fixed_asset_item class TestLandedCostVoucher(unittest.TestCase): def test_landed_cost_voucher(self): @@ -250,6 +251,39 @@ class TestLandedCostVoucher(unittest.TestCase): self.assertEqual(entry.credit, amounts[0]) self.assertEqual(entry.credit_in_account_currency, amounts[1]) + def test_asset_lcv(self): + "Check if LCV for an Asset updates the Assets Gross Purchase Amount correctly." + frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC") + + if not frappe.db.exists("Asset Category", "Computers"): + create_asset_category() + + if not frappe.db.exists("Item", "Macbook Pro"): + create_fixed_asset_item() + + pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=50000) + + # check if draft asset was created + assets = frappe.db.get_all('Asset', filters={'purchase_receipt': pr.name}) + self.assertEqual(len(assets), 1) + + lcv = make_landed_cost_voucher( + company = pr.company, + receipt_document_type = "Purchase Receipt", + receipt_document=pr.name, + charges=80, + expense_account="Expenses Included In Valuation - _TC") + + lcv.save() + lcv.submit() + + # lcv updates amount in draft asset + self.assertEqual(frappe.db.get_value("Asset", assets[0].name, "gross_purchase_amount"), 50080) + + # tear down + lcv.cancel() + pr.cancel() + def make_landed_cost_voucher(** args): args = frappe._dict(args) ref_doc = frappe.get_doc(args.receipt_document_type, args.receipt_document) @@ -268,7 +302,7 @@ def make_landed_cost_voucher(** args): lcv.set("taxes", [{ "description": "Shipping Charges", - "expense_account": "Expenses Included In Valuation - TCP1", + "expense_account": args.expense_account or "Expenses Included In Valuation - TCP1", "amount": args.charges }]) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 6e66f9869c0..087a7883e09 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -434,7 +434,7 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten if (doc.material_request_type == "Customer Provided") { return{ query: "erpnext.controllers.queries.item_query", - filters:{ + filters:{ 'customer': me.frm.doc.customer, 'is_stock_item':1 } diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index 4e2d9e61704..7cdf5b7e8f0 100644 --- a/erpnext/stock/doctype/material_request/material_request.json +++ b/erpnext/stock/doctype/material_request/material_request.json @@ -133,7 +133,8 @@ { "fieldname": "scan_barcode", "fieldtype": "Data", - "label": "Scan Barcode" + "label": "Scan Barcode", + "options": "Barcode" }, { "allow_bulk_edit": 1, @@ -314,7 +315,7 @@ "idx": 70, "is_submittable": 1, "links": [], - "modified": "2021-03-31 23:52:55.392512", + "modified": "2021-08-17 20:16:12.737743", "modified_by": "Administrator", "module": "Stock", "name": "Material Request", diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 3ad9909ad07..026b85e26d2 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -162,8 +162,15 @@ class MaterialRequest(BuyingController): from `tabStock Entry Detail` where material_request = %s and material_request_item = %s and docstatus = 1""", (self.name, d.name))[0][0]) + mr_qty_allowance = frappe.db.get_single_value('Stock Settings', 'mr_qty_allowance') - if d.ordered_qty and d.ordered_qty > d.stock_qty: + if mr_qty_allowance: + allowed_qty = d.qty + (d.qty * (mr_qty_allowance/100)) + if d.ordered_qty and d.ordered_qty > allowed_qty: + frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \ + cannot be greater than allowed requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, allowed_qty, d.item_code)) + + elif d.ordered_qty and d.ordered_qty > d.stock_qty: frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \ cannot be greater than requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, d.qty, d.item_code)) diff --git a/erpnext/stock/doctype/material_request/material_request_dashboard.py b/erpnext/stock/doctype/material_request/material_request_dashboard.py index f3e5e5db250..e1e4faf6825 100644 --- a/erpnext/stock/doctype/material_request/material_request_dashboard.py +++ b/erpnext/stock/doctype/material_request/material_request_dashboard.py @@ -20,4 +20,4 @@ def get_data(): 'items': ['Work Order'] } ] - } \ No newline at end of file + } diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 72a3a5e67c7..b4776ba2492 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -329,6 +329,58 @@ class TestMaterialRequest(unittest.TestCase): self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0) self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0) + def test_over_transfer_qty_allowance(self): + mr = frappe.new_doc('Material Request') + mr.company = "_Test Company" + mr.scheduled_date = today() + mr.append('items',{ + "item_code": "_Test FG Item", + "item_name": "_Test FG Item", + "qty": 10, + "schedule_date": today(), + "uom": "_Test UOM 1", + "warehouse": "_Test Warehouse - _TC" + }) + + mr.material_request_type = "Material Transfer" + mr.insert() + mr.submit() + + frappe.db.set_value('Stock Settings', None, 'mr_qty_allowance', 20) + + # map a stock entry + + se_doc = make_stock_entry(mr.name) + se_doc.update({ + "posting_date": today(), + "posting_time": "00:00", + }) + se_doc.get("items")[0].update({ + "qty": 13, + "transfer_qty": 12.0, + "s_warehouse": "_Test Warehouse - _TC", + "t_warehouse": "_Test Warehouse 1 - _TC", + "basic_rate": 1.0 + }) + + # make available the qty in _Test Warehouse 1 before transfer + sr = frappe.new_doc("Stock Reconciliation") + sr.company = "_Test Company" + sr.purpose = "Opening Stock" + sr.append('items', { + "item_code": "_Test FG Item", + "warehouse": "_Test Warehouse - _TC", + "qty": 20, + "valuation_rate": 0.01 + }) + sr.insert() + sr.submit() + se = frappe.copy_doc(se_doc) + se.insert() + self.assertRaises(frappe.ValidationError) + se.items[0].qty = 12 + se.submit() + def test_completed_qty_for_over_transfer(self): existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") diff --git a/erpnext/stock/doctype/material_request/tests/test_material_request.js b/erpnext/stock/doctype/material_request/tests/test_material_request.js index bf26cd117f8..a2cd03b6495 100644 --- a/erpnext/stock/doctype/material_request/tests/test_material_request.js +++ b/erpnext/stock/doctype/material_request/tests/test_material_request.js @@ -37,4 +37,3 @@ QUnit.test("test material request", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/material_request/tests/test_material_request_from_bom.js b/erpnext/stock/doctype/material_request/tests/test_material_request_from_bom.js index d8b39fe5aaf..6fb55ae02ac 100644 --- a/erpnext/stock/doctype/material_request/tests/test_material_request_from_bom.js +++ b/erpnext/stock/doctype/material_request/tests/test_material_request_from_bom.js @@ -25,4 +25,3 @@ QUnit.test("test material request get items from BOM", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/material_request/tests/test_material_request_type_manufacture.js b/erpnext/stock/doctype/material_request/tests/test_material_request_type_manufacture.js index 91b47bac4d9..137079b9838 100644 --- a/erpnext/stock/doctype/material_request/tests/test_material_request_type_manufacture.js +++ b/erpnext/stock/doctype/material_request/tests/test_material_request_type_manufacture.js @@ -27,4 +27,3 @@ QUnit.test("test material request", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/material_request/tests/test_material_request_type_material_issue.js b/erpnext/stock/doctype/material_request/tests/test_material_request_type_material_issue.js index 050e0f0d1c9..b03a8543c6f 100644 --- a/erpnext/stock/doctype/material_request/tests/test_material_request_type_material_issue.js +++ b/erpnext/stock/doctype/material_request/tests/test_material_request_type_material_issue.js @@ -27,4 +27,3 @@ QUnit.test("test material request for issue", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/material_request/tests/test_material_request_type_material_transfer.js b/erpnext/stock/doctype/material_request/tests/test_material_request_type_material_transfer.js index d6f9b661414..7c62c2e63a1 100644 --- a/erpnext/stock/doctype/material_request/tests/test_material_request_type_material_transfer.js +++ b/erpnext/stock/doctype/material_request/tests/test_material_request_type_material_transfer.js @@ -27,4 +27,3 @@ QUnit.test("test material request for transfer", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.py b/erpnext/stock/doctype/material_request_item/material_request_item.py index 16f007f6a20..e0066e65d2c 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.py +++ b/erpnext/stock/doctype/material_request_item/material_request_item.py @@ -12,4 +12,4 @@ class MaterialRequestItem(Document): pass def on_doctype_update(): - frappe.db.add_index("Material Request Item", ["item_code", "warehouse"]) \ No newline at end of file + frappe.db.add_index("Material Request Item", ["item_code", "warehouse"]) diff --git a/erpnext/stock/doctype/packing_slip_item/packing_slip_item.py b/erpnext/stock/doctype/packing_slip_item/packing_slip_item.py index 694ab384bf6..b0a855961f9 100644 --- a/erpnext/stock/doctype/packing_slip_item/packing_slip_item.py +++ b/erpnext/stock/doctype/packing_slip_item/packing_slip_item.py @@ -9,4 +9,4 @@ import frappe from frappe.model.document import Document class PackingSlipItem(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js index ee218f2f685..730fd7a829c 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.js +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -201,4 +201,4 @@ function get_item_details(item_code, uom=null) { uom }); } -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index e795742ea4d..516ae43089b 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -239,6 +239,7 @@ def get_available_item_locations_for_batched_item(item_code, from_warehouses, re and sle.`item_code`=%(item_code)s and sle.`company` = %(company)s and batch.disabled = 0 + and sle.is_cancelled=0 and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s {warehouse_condition} GROUP BY diff --git a/erpnext/stock/doctype/pick_list/pick_list_dashboard.py b/erpnext/stock/doctype/pick_list/pick_list_dashboard.py index 6e007df5e6b..7c321c450a6 100644 --- a/erpnext/stock/doctype/pick_list/pick_list_dashboard.py +++ b/erpnext/stock/doctype/pick_list/pick_list_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Stock Entry', 'Delivery Note'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/stock/doctype/price_list/price_list.css b/erpnext/stock/doctype/price_list/price_list.css index 61b069442f8..6832954a811 100644 --- a/erpnext/stock/doctype/price_list/price_list.css +++ b/erpnext/stock/doctype/price_list/price_list.css @@ -4,4 +4,4 @@ .table-grid thead tr { height: 50px; -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/price_list/price_list.js b/erpnext/stock/doctype/price_list/price_list.js index c362b5a765c..9291498e863 100644 --- a/erpnext/stock/doctype/price_list/price_list.js +++ b/erpnext/stock/doctype/price_list/price_list.js @@ -11,4 +11,4 @@ frappe.ui.form.on("Price List", { frappe.set_route("Report", "Item Price"); }, "fa fa-money"); } -}); \ No newline at end of file +}); diff --git a/erpnext/stock/doctype/price_list/price_list.py b/erpnext/stock/doctype/price_list/price_list.py index 33713faf696..10abde17eb2 100644 --- a/erpnext/stock/doctype/price_list/price_list.py +++ b/erpnext/stock/doctype/price_list/price_list.py @@ -62,4 +62,4 @@ def get_price_list_details(price_list): frappe.cache().hset("price_list_details", price_list, price_list_details) - return price_list_details or {} \ No newline at end of file + return price_list_details or {} diff --git a/erpnext/stock/doctype/price_list/test_price_list.py b/erpnext/stock/doctype/price_list/test_price_list.py index 5979c861294..2c287c9033c 100644 --- a/erpnext/stock/doctype/price_list/test_price_list.py +++ b/erpnext/stock/doctype/price_list/test_price_list.py @@ -6,4 +6,4 @@ import frappe # test_ignore = ["Item"] -test_records = frappe.get_test_records('Price List') \ No newline at end of file +test_records = frappe.get_test_records('Price List') diff --git a/erpnext/stock/doctype/price_list/test_price_list_uom.js b/erpnext/stock/doctype/price_list/test_price_list_uom.js index 7fbce7d59d2..3896c0e59ea 100644 --- a/erpnext/stock/doctype/price_list/test_price_list_uom.js +++ b/erpnext/stock/doctype/price_list/test_price_list_uom.js @@ -55,4 +55,4 @@ QUnit.test("test price list with uom dependancy", function(assert) { () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 44fb736304f..1a597343c0a 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -1098,7 +1098,8 @@ { "fieldname": "scan_barcode", "fieldtype": "Data", - "label": "Scan Barcode" + "label": "Scan Barcode", + "options": "Barcode" }, { "fieldname": "billing_address", @@ -1148,7 +1149,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2021-05-25 00:15:12.239017", + "modified": "2021-08-17 20:16:40.849885", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 82c87a83a50..4a4514f2497 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -286,8 +286,16 @@ class PurchaseReceipt(BuyingController): and warehouse_account_name == supplier_warehouse_account: continue - self.add_gl_entry(gl_entries, warehouse_account_name, d.cost_center, stock_value_diff, 0.0, remarks, - stock_rbnb, account_currency=warehouse_account_currency, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=warehouse_account_name, + cost_center=d.cost_center, + debit=stock_value_diff, + credit=0.0, + remarks=remarks, + against_account=stock_rbnb, + account_currency=warehouse_account_currency, + item=d) # GL Entry for from warehouse or Stock Received but not billed # Intentionally passed negative debit amount to avoid incorrect GL Entry validation @@ -300,9 +308,17 @@ class PurchaseReceipt(BuyingController): account = warehouse_account[d.from_warehouse]['account'] \ if d.from_warehouse else stock_rbnb - self.add_gl_entry(gl_entries, account, d.cost_center, - -1 * flt(d.base_net_amount, d.precision("base_net_amount")), 0.0, remarks, warehouse_account_name, - debit_in_account_currency=-1 * credit_amount, account_currency=credit_currency, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=d.cost_center, + debit=-1 * flt(d.base_net_amount, d.precision("base_net_amount")), + credit=0.0, + remarks=remarks, + against_account=warehouse_account_name, + debit_in_account_currency=-1 * credit_amount, + account_currency=credit_currency, + item=d) # Amount added through landed-cos-voucher if d.landed_cost_voucher_amount and landed_cost_entries: @@ -311,14 +327,31 @@ class PurchaseReceipt(BuyingController): credit_amount = (flt(amount["base_amount"]) if (amount["base_amount"] or account_currency!=self.company_currency) else flt(amount["amount"])) - self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, credit_amount, remarks, - warehouse_account_name, credit_in_account_currency=flt(amount["amount"]), - account_currency=account_currency, project=d.project, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=d.cost_center, + debit=0.0, + credit=credit_amount, + remarks=remarks, + against_account=warehouse_account_name, + credit_in_account_currency=flt(amount["amount"]), + account_currency=account_currency, + project=d.project, + item=d) # sub-contracting warehouse if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse): - self.add_gl_entry(gl_entries, supplier_warehouse_account, d.cost_center, 0.0, flt(d.rm_supp_cost), - remarks, warehouse_account_name, account_currency=supplier_warehouse_account_currency, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=supplier_warehouse_account, + cost_center=d.cost_center, + debit=0.0, + credit=flt(d.rm_supp_cost), + remarks=remarks, + against_account=warehouse_account_name, + account_currency=supplier_warehouse_account_currency, + item=d) # divisional loss adjustment valuation_amount_as_per_doc = flt(d.base_net_amount, d.precision("base_net_amount")) + \ @@ -335,8 +368,17 @@ class PurchaseReceipt(BuyingController): cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center") - self.add_gl_entry(gl_entries, loss_account, cost_center, divisional_loss, 0.0, remarks, - warehouse_account_name, account_currency=credit_currency, project=d.project, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=loss_account, + cost_center=cost_center, + debit=divisional_loss, + credit=0.0, + remarks=remarks, + against_account=warehouse_account_name, + account_currency=credit_currency, + project=d.project, + item=d) elif d.warehouse not in warehouse_with_no_account or \ d.rejected_warehouse not in warehouse_with_no_account: @@ -347,12 +389,30 @@ class PurchaseReceipt(BuyingController): debit_currency = get_account_currency(d.expense_account) remarks = self.get("remarks") or _("Accounting Entry for Service") - self.add_gl_entry(gl_entries, service_received_but_not_billed_account, d.cost_center, 0.0, d.amount, - remarks, d.expense_account, account_currency=credit_currency, project=d.project, + self.add_gl_entry( + gl_entries=gl_entries, + account=service_received_but_not_billed_account, + cost_center=d.cost_center, + debit=0.0, + credit=d.amount, + remarks=remarks, + against_account=d.expense_account, + account_currency=credit_currency, + project=d.project, voucher_detail_no=d.name, item=d) - self.add_gl_entry(gl_entries, d.expense_account, d.cost_center, d.amount, 0.0, remarks, service_received_but_not_billed_account, - account_currency = debit_currency, project=d.project, voucher_detail_no=d.name, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=d.expense_account, + cost_center=d.cost_center, + debit=d.amount, + credit=0.0, + remarks=remarks, + against_account=service_received_but_not_billed_account, + account_currency = debit_currency, + project=d.project, + voucher_detail_no=d.name, + item=d) if warehouse_with_no_account: frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" + @@ -402,8 +462,15 @@ class PurchaseReceipt(BuyingController): applicable_amount = negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount) amount_including_divisional_loss -= applicable_amount - self.add_gl_entry(gl_entries, account, tax.cost_center, 0.0, applicable_amount, self.remarks or _("Accounting Entry for Stock"), - against_account, item=tax) + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=tax.cost_center, + debit=0.0, + credit=applicable_amount, + remarks=self.remarks or _("Accounting Entry for Stock"), + against_account=against_account, + item=tax) i += 1 @@ -415,7 +482,7 @@ class PurchaseReceipt(BuyingController): "cost_center": cost_center, "debit": debit, "credit": credit, - "against_account": against_account, + "against": against_account, "remarks": remarks, } @@ -456,15 +523,31 @@ class PurchaseReceipt(BuyingController): # debit cwip account debit_in_account_currency = (base_asset_amount if cwip_account_currency == self.company_currency else asset_amount) - self.add_gl_entry(gl_entries, cwip_account, item.cost_center, base_asset_amount, 0.0, remarks, - arbnb_account, debit_in_account_currency=debit_in_account_currency, item=item) + self.add_gl_entry( + gl_entries=gl_entries, + account=cwip_account, + cost_center=item.cost_center, + debit=base_asset_amount, + credit=0.0, + remarks=remarks, + against_account=arbnb_account, + debit_in_account_currency=debit_in_account_currency, + item=item) asset_rbnb_currency = get_account_currency(arbnb_account) # credit arbnb account credit_in_account_currency = (base_asset_amount if asset_rbnb_currency == self.company_currency else asset_amount) - self.add_gl_entry(gl_entries, arbnb_account, item.cost_center, 0.0, base_asset_amount, remarks, - cwip_account, credit_in_account_currency=credit_in_account_currency, item=item) + self.add_gl_entry( + gl_entries=gl_entries, + account=arbnb_account, + cost_center=item.cost_center, + debit=0.0, + credit=base_asset_amount, + remarks=remarks, + against_account=cwip_account, + credit_in_account_currency=credit_in_account_currency, + item=item) def add_lcv_gl_entries(self, item, gl_entries): expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation") @@ -477,11 +560,27 @@ class PurchaseReceipt(BuyingController): remarks = self.get("remarks") or _("Accounting Entry for Stock") - self.add_gl_entry(gl_entries, expenses_included_in_asset_valuation, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount), - remarks, asset_account, project=item.project, item=item) + self.add_gl_entry( + gl_entries=gl_entries, + account=expenses_included_in_asset_valuation, + cost_center=item.cost_center, + debit=0.0, + credit=flt(item.landed_cost_voucher_amount), + remarks=remarks, + against_account=asset_account, + project=item.project, + item=item) - self.add_gl_entry(gl_entries, asset_account, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount), - remarks, expenses_included_in_asset_valuation, project=item.project, item=item) + self.add_gl_entry( + gl_entries=gl_entries, + account=asset_account, + cost_center=item.cost_center, + debit=flt(item.landed_cost_voucher_amount), + credit=0.0, + remarks=remarks, + against_account=expenses_included_in_asset_valuation, + project=item.project, + item=item) def update_assets(self, item, valuation_rate): assets = frappe.db.get_all('Asset', @@ -598,6 +697,7 @@ def make_purchase_invoice(source_name, target_doc=None): doc.run_method("onload") doc.run_method("set_missing_values") doc.run_method("calculate_taxes_and_totals") + doc.set_payment_schedule() def update_item(source_doc, target_doc, source_parent): target_doc.qty, returned_qty = get_pending_qty(source_doc) diff --git a/erpnext/stock/doctype/purchase_receipt/regional/india.js b/erpnext/stock/doctype/purchase_receipt/regional/india.js index b4f1201f36c..2d982cc1bb3 100644 --- a/erpnext/stock/doctype/purchase_receipt/regional/india.js +++ b/erpnext/stock/doctype/purchase_receipt/regional/india.js @@ -1,3 +1,3 @@ {% include "erpnext/regional/india/taxes.js" %} -erpnext.setup_auto_gst_taxation('Purchase Receipt'); \ No newline at end of file +erpnext.setup_auto_gst_taxation('Purchase Receipt'); diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 2eb8bfd5d2f..0210702250e 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -23,9 +23,7 @@ class TestPurchaseReceipt(unittest.TestCase): def test_reverse_purchase_receipt_sle(self): - frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 0) - - pr = make_purchase_receipt(qty=0.5) + pr = make_purchase_receipt(qty=0.5, item_code="_Test Item Home Desktop 200") sl_entry = frappe.db.get_all("Stock Ledger Entry", {"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, ['actual_qty']) @@ -41,8 +39,6 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(len(sl_entry_cancelled), 2) self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5) - frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 1) - def test_make_purchase_invoice(self): if not frappe.db.exists('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice'): frappe.get_doc({ @@ -328,18 +324,7 @@ class TestPurchaseReceipt(unittest.TestCase): pr1.submit() self.assertRaises(frappe.ValidationError, pr2.submit) - - pr1.cancel() - se.cancel() - se1.cancel() - se2.cancel() - se3.cancel() - po.reload() - pr2.load_from_db() - pr2.cancel() - - po.load_from_db() - po.cancel() + frappe.db.rollback() def test_serial_no_supplier(self): pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1) @@ -1044,7 +1029,7 @@ class TestPurchaseReceipt(unittest.TestCase): 'account': srbnb_account, 'voucher_detail_no': pr.items[1].name }, pluck="name") - + # check if the entries are not merged into one # seperate entries should be made since voucher_detail_no is different self.assertEqual(len(item_one_gl_entry), 1) @@ -1052,6 +1037,33 @@ class TestPurchaseReceipt(unittest.TestCase): frappe.db.set_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items', before_test_value) + def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template + from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice + from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order, make_pr_against_po + from erpnext.selling.doctype.sales_order.test_sales_order import automatically_fetch_payment_terms, compare_payment_schedules + + automatically_fetch_payment_terms() + + po = create_purchase_order(qty=10, rate=100, do_not_save=1) + create_payment_terms_template() + po.payment_terms_template = 'Test Receivable Template' + po.submit() + + pr = make_pr_against_po(po.name, received_qty=10) + + pi = make_purchase_invoice(qty=10, rate=100, do_not_save=1) + pi.items[0].purchase_receipt = pr.name + pi.items[0].pr_detail = pr.items[0].name + pi.items[0].purchase_order = po.name + pi.items[0].po_detail = po.items[0].name + pi.insert() + + # self.assertEqual(po.payment_terms_template, pi.payment_terms_template) + compare_payment_schedules(self, po, pi) + + automatically_fetch_payment_terms(enable=0) + def get_sl_entries(voucher_type, voucher_no): return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.py b/erpnext/stock/doctype/putaway_rule/putaway_rule.py index 0f50bcd6ea8..315e723fabc 100644 --- a/erpnext/stock/doctype/putaway_rule/putaway_rule.py +++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.py @@ -232,4 +232,4 @@ def get_serial_nos_to_allocate(serial_nos, to_allocate): allocated_serial_nos = serial_nos[0: cint(to_allocate)] serial_nos[:] = serial_nos[cint(to_allocate):] # pop out allocated serial nos and modify list return "\n".join(allocated_serial_nos) if allocated_serial_nos else "" - else: return "" \ No newline at end of file + else: return "" diff --git a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py index 86f7dc3e084..0590ae1abeb 100644 --- a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py +++ b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py @@ -386,4 +386,4 @@ def create_putaway_rule(**args): if not args.do_not_save: putaway.save() - return putaway \ No newline at end of file + return putaway diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js index f7565fd505c..d08dc3e8b76 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js @@ -81,4 +81,4 @@ frappe.ui.form.on("Quality Inspection", { }); } }, -}); \ No newline at end of file +}); diff --git a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.py b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.py index 65188a22c6e..b10fa310d66 100644 --- a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.py +++ b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class QualityInspectionReading(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py index 01d2031b3a4..971b3c29825 100644 --- a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py +++ b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py @@ -16,4 +16,4 @@ def get_template_details(template): fields=["specification", "value", "acceptance_formula", "numeric", "formula_based_criteria", "min_value", "max_value"], filters={'parenttype': 'Quality Inspection Template', 'parent': template}, - order_by="idx") \ No newline at end of file + order_by="idx") diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js index b3e4286bccb..4cd40bf38ec 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js @@ -29,13 +29,50 @@ frappe.ui.form.on('Repost Item Valuation', { }; }); } + + frm.trigger('setup_realtime_progress'); }, + + setup_realtime_progress: function(frm) { + frappe.realtime.on('item_reposting_progress', data => { + if (frm.doc.name !== data.name) { + return; + } + + if (frm.doc.status == 'In Progress') { + frm.doc.current_index = data.current_index; + frm.doc.items_to_be_repost = data.items_to_be_repost; + + frm.dashboard.reset(); + frm.trigger('show_reposting_progress'); + } + }); + }, + refresh: function(frm) { if (frm.doc.status == "Failed" && frm.doc.docstatus==1) { frm.add_custom_button(__('Restart'), function () { frm.trigger("restart_reposting"); }).addClass("btn-primary"); } + + frm.trigger('show_reposting_progress'); + }, + + show_reposting_progress: function(frm) { + var bars = []; + + let total_count = frm.doc.items_to_be_repost ? JSON.parse(frm.doc.items_to_be_repost).length : 0; + let progress = flt(cint(frm.doc.current_index) / total_count * 100, 2) || 0.5; + var title = __('Reposting Completed {0}%', [progress]); + + bars.push({ + 'title': title, + 'width': progress + '%', + 'progress_class': 'progress-bar-success' + }); + + frm.dashboard.add_progress(__('Reposting Progress'), bars); }, restart_reposting: function(frm) { diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json index 071fc86d9b3..a800bf87013 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json @@ -21,7 +21,10 @@ "allow_zero_rate", "amended_from", "error_section", - "error_log" + "error_log", + "items_to_be_repost", + "distinct_item_and_warehouse", + "current_index" ], "fields": [ { @@ -142,12 +145,39 @@ "fieldname": "allow_zero_rate", "fieldtype": "Check", "label": "Allow Zero Rate" + }, + { + "fieldname": "items_to_be_repost", + "fieldtype": "Code", + "hidden": 1, + "label": "Items to Be Repost", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "distinct_item_and_warehouse", + "fieldtype": "Code", + "hidden": 1, + "label": "Distinct Item and Warehouse", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "current_index", + "fieldtype": "Int", + "hidden": 1, + "label": "Current Index", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-12-10 07:52:12.476589", + "modified": "2021-07-22 18:59:43.057878", "modified_by": "Administrator", "module": "Stock", "name": "Repost Item Valuation", diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 55f2ebb2241..2e454a51596 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -80,7 +80,7 @@ def repost(doc): def repost_sl_entries(doc): if doc.based_on == 'Transaction': - repost_future_sle(voucher_type=doc.voucher_type, voucher_no=doc.voucher_no, + repost_future_sle(doc=doc, voucher_type=doc.voucher_type, voucher_no=doc.voucher_no, allow_negative_stock=doc.allow_negative_stock, via_landed_cost_voucher=doc.via_landed_cost_voucher) else: repost_future_sle(args=[frappe._dict({ @@ -133,6 +133,6 @@ def repost_entries(): def get_repost_item_valuation_entries(): return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation` - WHERE status != 'Completed' and creation <= %s and docstatus = 1 + WHERE status in ('Queued', 'In Progress') and creation <= %s and docstatus = 1 ORDER BY timestamp(posting_date, posting_time) asc, creation asc """, now(), as_dict=1) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index bad7b608acf..70312bc543b 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -165,8 +165,14 @@ class SerialNo(StockController): ) ORDER BY posting_date desc, posting_time desc, creation desc""", - (self.item_code, self.company, - serial_no, serial_no+'\n%', '%\n'+serial_no, '%\n'+serial_no+'\n%'), as_dict=1): + ( + self.item_code, self.company, + serial_no, + serial_no+'\n%', + '%\n'+serial_no, + '%\n'+serial_no+'\n%' + ), + as_dict=1): if serial_no.upper() in get_serial_nos(sle.serial_no): if cint(sle.actual_qty) > 0: sle_dict.setdefault("incoming", []).append(sle) diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index cde7fe07c63..0eccce3a58e 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -174,5 +174,23 @@ class TestSerialNo(unittest.TestCase): self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC") self.assertEqual(sn_doc.purchase_document_no, se.name) + def test_serial_no_sanitation(self): + "Test if Serial No input is sanitised before entering the DB." + item_code = "_Test Serialized Item" + test_records = frappe.get_test_records('Stock Entry') + + se = frappe.copy_doc(test_records[0]) + se.get("items")[0].item_code = item_code + se.get("items")[0].qty = 3 + se.get("items")[0].serial_no = " _TS1, _TS2 , _TS3 " + se.get("items")[0].transfer_qty = 3 + se.set_stock_entry_type() + se.insert() + se.submit() + + self.assertEqual(se.get("items")[0].serial_no, "_TS1\n_TS2\n_TS3") + + frappe.db.rollback() + def tearDown(self): - frappe.db.rollback() \ No newline at end of file + frappe.db.rollback() diff --git a/erpnext/stock/doctype/shipment/shipment.js b/erpnext/stock/doctype/shipment/shipment.js index ce2906ecbe9..13a17a25913 100644 --- a/erpnext/stock/doctype/shipment/shipment.js +++ b/erpnext/stock/doctype/shipment/shipment.js @@ -150,8 +150,8 @@ frappe.ui.form.on('Shipment', { frm.set_value('pickup_contact_name', ''); frm.set_value('pickup_contact', ''); } - frappe.throw(__("Email or Phone/Mobile of the Contact are mandatory to continue.") - + "
          " + __("Please set Email/Phone for the contact") + frappe.throw(__("Email or Phone/Mobile of the Contact are mandatory to continue.") + + "
          " + __("Please set Email/Phone for the contact") + ` ${contact_name}`); } let contact_display = r.message.contact_display; @@ -244,8 +244,8 @@ frappe.ui.form.on('Shipment', { frm.set_value('pickup_company', ''); frm.set_value('pickup_contact', ''); } - frappe.throw(__("Last Name, Email or Phone/Mobile of the user are mandatory to continue.") + "
          " - + __("Please first set Last Name, Email and Phone for the user") + frappe.throw(__("Last Name, Email or Phone/Mobile of the user are mandatory to continue.") + "
          " + + __("Please first set Last Name, Email and Phone for the user") + ` ${frappe.session.user}`); } let contact_display = r.full_name; diff --git a/erpnext/stock/doctype/shipment/shipment_list.js b/erpnext/stock/doctype/shipment/shipment_list.js index 52b052c81f3..ae6a3c154e8 100644 --- a/erpnext/stock/doctype/shipment/shipment_list.js +++ b/erpnext/stock/doctype/shipment/shipment_list.js @@ -5,4 +5,4 @@ frappe.listview_settings['Shipment'] = { return [__("Booked"), "green"]; } } -}; \ No newline at end of file +}; diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py index 9c3e22f0231..db2f1161743 100644 --- a/erpnext/stock/doctype/shipment/test_shipment.py +++ b/erpnext/stock/doctype/shipment/test_shipment.py @@ -24,7 +24,7 @@ def create_test_delivery_note(): customer = get_shipment_customer() item = get_shipment_item(company.name) posting_date = date.today() + timedelta(days=1) - + create_material_receipt(item, company.name) delivery_note = frappe.new_doc("Delivery Note") delivery_note.company = company.name @@ -73,7 +73,7 @@ def create_test_shipment(delivery_notes = None): shipment.pickup_to = '17:00' shipment.description_of_content = 'unit test entry' for delivery_note in delivery_notes: - shipment.append('shipment_delivery_note', + shipment.append('shipment_delivery_note', { "delivery_note": delivery_note.name } @@ -222,7 +222,7 @@ def create_material_receipt(item, company): ) stock.insert() stock.submit() - + def create_shipment_item(item_name, company_name): item = frappe.new_doc("Item") diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index 523d332b8f4..e6ce3c851ff 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -355,6 +355,7 @@ }, { "fieldname": "scan_barcode", + "options": "Barcode", "fieldtype": "Data", "label": "Scan Barcode" }, @@ -629,7 +630,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-26 17:07:58.015737", + "modified": "2021-08-17 20:16:12.737743", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 90b81ddb1dc..2015aa198be 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -76,6 +76,7 @@ class StockEntry(StockController): self.validate_difference_account() self.set_job_card_data() self.set_purpose_for_stock_entry() + self.clean_serial_nos() self.validate_duplicate_serial_no() if not self.from_bom: @@ -316,9 +317,6 @@ class StockEntry(StockController): d.s_warehouse = self.from_warehouse d.t_warehouse = self.to_warehouse - if not (d.s_warehouse or d.t_warehouse): - frappe.throw(_("Atleast one warehouse is mandatory")) - if self.purpose in source_mandatory and not d.s_warehouse: if self.from_warehouse: d.s_warehouse = self.from_warehouse @@ -331,6 +329,7 @@ class StockEntry(StockController): else: frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) + if self.purpose == "Manufacture": if validate_for_manufacture: if d.is_finished_item or d.is_scrap_item: @@ -345,6 +344,9 @@ class StockEntry(StockController): if cstr(d.s_warehouse) == cstr(d.t_warehouse) and not self.purpose == "Material Transfer for Manufacture": frappe.throw(_("Source and target warehouse cannot be same for row {0}").format(d.idx)) + if not (d.s_warehouse or d.t_warehouse): + frappe.throw(_("Atleast one warehouse is mandatory")) + def validate_work_order(self): if self.purpose in ("Manufacture", "Material Transfer for Manufacture", "Material Consumption for Manufacture"): # check if work order is entered @@ -719,6 +721,10 @@ class StockEntry(StockController): frappe.throw(_("Multiple items cannot be marked as finished item")) if self.purpose == "Manufacture": + if not finished_items: + frappe.throw(_('Finished Good has not set in the stock entry {0}') + .format(self.name)) + allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order")) @@ -1090,13 +1096,13 @@ class StockEntry(StockController): "is_finished_item": 1 } - if self.work_order and self.pro_doc.has_batch_no: + if self.work_order and self.pro_doc.has_batch_no and cint(frappe.db.get_single_value('Manufacturing Settings', + 'make_serial_no_batch_from_work_order', cache=True)): self.set_batchwise_finished_goods(args, item) else: - self.add_finisged_goods(args, item) + self.add_finished_goods(args, item) def set_batchwise_finished_goods(self, args, item): - qty = flt(self.fg_completed_qty) filters = { "reference_name": self.pro_doc.name, "reference_doctype": self.pro_doc.doctype, @@ -1105,7 +1111,17 @@ class StockEntry(StockController): fields = ["qty_to_produce as qty", "produced_qty", "name"] - for row in frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc"): + data = frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc") + + if not data: + self.add_finished_goods(args, item) + else: + self.add_batchwise_finished_good(data, args, item) + + def add_batchwise_finished_good(self, data, args, item): + qty = flt(self.fg_completed_qty) + + for row in data: batch_qty = flt(row.qty) - flt(row.produced_qty) if not batch_qty: continue @@ -1121,9 +1137,9 @@ class StockEntry(StockController): args["qty"] = fg_qty args["batch_no"] = row.name - self.add_finisged_goods(args, item) + self.add_finished_goods(args, item) - def add_finisged_goods(self, args, item): + def add_finished_goods(self, args, item): self.add_to_stock_entry_detail({ item.name: args }, bom_no = self.bom_no) @@ -1170,7 +1186,7 @@ class StockEntry(StockController): wo = frappe.get_doc("Work Order", self.work_order) wo_items = frappe.get_all('Work Order Item', filters={'parent': self.work_order}, - fields=["item_code", "required_qty", "consumed_qty", "transferred_qty"] + fields=["item_code", "source_warehouse", "required_qty", "consumed_qty", "transferred_qty"] ) work_order_qty = wo.material_transferred_for_manufacturing or wo.qty @@ -1190,7 +1206,7 @@ class StockEntry(StockController): if qty > 0: self.add_to_stock_entry_detail({ item.item_code: { - "from_warehouse": wo.wip_warehouse, + "from_warehouse": wo.wip_warehouse or item.source_warehouse, "to_warehouse": "", "qty": qty, "item_name": item.item_name, @@ -1775,7 +1791,7 @@ def get_expired_batch_items(): from `tabBatch` b, `tabStock Ledger Entry` sle where b.expiry_date <= %s and b.expiry_date is not NULL - and b.batch_id = sle.batch_no + and b.batch_id = sle.batch_no and sle.is_cancelled = 0 group by sle.warehouse, sle.item_code, sle.batch_no""",(nowdate()), as_dict=1) @frappe.whitelist() @@ -1842,4 +1858,4 @@ def get_supplied_items(purchase_order): supplied_item.total_supplied_qty = flt(supplied_item.supplied_qty) - flt(supplied_item.returned_qty) - return supplied_item_details \ No newline at end of file + return supplied_item_details diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue.js index 3cf4861ccb8..a87a7fb7fd8 100644 --- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue.js +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue.js @@ -28,4 +28,3 @@ QUnit.test("test material request", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue_with_serialize_item.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue_with_serialize_item.js index aac09c30cd5..cae318d8f2c 100644 --- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue_with_serialize_item.js +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue_with_serialize_item.js @@ -32,4 +32,3 @@ QUnit.test("test material issue", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js index 828738eb6ca..ef0286fe1b9 100644 --- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js @@ -29,4 +29,3 @@ QUnit.test("test material request", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js index ffd06642bf0..54e1ac81211 100644 --- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js @@ -32,4 +32,3 @@ QUnit.test("test material receipt", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js index cdeb4ab04a7..fac0b4b8922 100644 --- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js @@ -31,4 +31,3 @@ QUnit.test("test material request", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js index e8b2973c457..9f853072709 100644 --- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js @@ -31,4 +31,3 @@ QUnit.test("test material Transfer to manufacture", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_repack.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_repack.js index 699634df6d2..20f119ad617 100644 --- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_repack.js +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_repack.js @@ -39,4 +39,3 @@ QUnit.test("test repack", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js index 770f886d043..8243426032d 100644 --- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js @@ -31,4 +31,3 @@ QUnit.test("test material Transfer to manufacture", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py index f9e062f8516..a5623fded23 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class StockEntryDetail(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index cb939e63c28..be1f00e37fa 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -55,12 +55,12 @@ class StockLedgerEntry(Document): "sum(actual_qty)") or 0 frappe.db.set_value("Batch", self.batch_no, "batch_qty", batch_qty) - #check for item quantity available in stock def actual_amt_check(self): + """Validate that qty at warehouse for selected batch is >=0""" if self.batch_no and not self.get("allow_negative_stock"): batch_bal_after_transaction = flt(frappe.db.sql("""select sum(actual_qty) from `tabStock Ledger Entry` - where warehouse=%s and item_code=%s and batch_no=%s""", + where is_cancelled =0 and warehouse=%s and item_code=%s and batch_no=%s""", (self.warehouse, self.item_code, self.batch_no))[0][0]) if batch_bal_after_transaction < 0: @@ -89,17 +89,16 @@ class StockLedgerEntry(Document): if item_det.is_stock_item != 1: frappe.throw(_("Item {0} must be a stock Item").format(self.item_code)) - # check if batch number is required - if self.voucher_type != 'Stock Reconciliation': - if item_det.has_batch_no == 1: - batch_item = self.item_code if self.item_code == item_det.item_name else self.item_code + ":" + item_det.item_name - if not self.batch_no: - frappe.throw(_("Batch number is mandatory for Item {0}").format(batch_item)) - elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}): - frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item)) + # check if batch number is valid + if item_det.has_batch_no == 1: + batch_item = self.item_code if self.item_code == item_det.item_name else self.item_code + ":" + item_det.item_name + if not self.batch_no: + frappe.throw(_("Batch number is mandatory for Item {0}").format(batch_item)) + elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}): + frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item)) - elif item_det.has_batch_no == 0 and self.batch_no and self.is_cancelled == 0: - frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code)) + elif item_det.has_batch_no == 0 and self.batch_no and self.is_cancelled == 0: + frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code)) if item_det.has_variants: frappe.throw(_("Stock cannot exist for Item {0} since has variants").format(self.item_code), @@ -108,7 +107,7 @@ class StockLedgerEntry(Document): self.stock_uom = item_det.stock_uom def check_stock_frozen_date(self): - stock_settings = frappe.get_doc('Stock Settings', 'Stock Settings') + stock_settings = frappe.get_cached_doc('Stock Settings') if stock_settings.stock_frozen_upto: if (getdate(self.posting_date) <= getdate(stock_settings.stock_frozen_upto) @@ -153,7 +152,7 @@ class StockLedgerEntry(Document): last_transaction_time = frappe.db.sql(""" select MAX(timestamp(posting_date, posting_time)) as posting_time from `tabStock Ledger Entry` - where docstatus = 1 and item_code = %s + where docstatus = 1 and is_cancelled = 0 and item_code = %s and warehouse = %s""", (self.item_code, self.warehouse))[0][0] cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00") diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index a01db80da4a..349e59f31d1 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -17,6 +17,14 @@ frappe.ui.form.on("Stock Reconciliation", { } } }); + frm.set_query("batch_no", "items", function(doc, cdt, cdn) { + var item = locals[cdt][cdn]; + return { + filters: { + 'item': item.item_code + } + }; + }); if (frm.doc.company) { erpnext.queries.setup_queries(frm, "Warehouse", function() { diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 2e092865075..324bb7a62d9 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -31,6 +31,7 @@ class StockReconciliation(StockController): self.validate_expense_account() self.validate_customer_provided_item() self.set_zero_value_for_customer_provided_items() + self.clean_serial_nos() self.set_total_qty_and_amount() self.validate_putaway_capacity() diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js index 80001d63fd4..666d2c7144f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js @@ -29,4 +29,3 @@ QUnit.test("test Stock Reconciliation", function(assert) { () => done() ]); }); - diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 84cdc491282..94b006c8944 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -16,6 +16,7 @@ from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valua from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt + class TestStockReconciliation(unittest.TestCase): @classmethod def setUpClass(self): @@ -352,6 +353,26 @@ class TestStockReconciliation(unittest.TestCase): dn2.cancel() pr1.cancel() + def test_valid_batch(self): + create_batch_item_with_batch("Testing Batch Item 1", "001") + create_batch_item_with_batch("Testing Batch Item 2", "002") + sr = create_stock_reconciliation(item_code="Testing Batch Item 1", qty=1, rate=100, batch_no="002" + , do_not_submit=True) + self.assertRaises(frappe.ValidationError, sr.submit) + +def create_batch_item_with_batch(item_name, batch_id): + batch_item_doc = create_item(item_name, is_stock_item=1) + if not batch_item_doc.has_batch_no: + batch_item_doc.has_batch_no = 1 + batch_item_doc.create_new_batch = 1 + batch_item_doc.save(ignore_permissions=True) + + if not frappe.db.exists('Batch', batch_id): + b = frappe.new_doc('Batch') + b.item = item_name + b.batch_id = batch_id + b.save() + def insert_existing_sle(warehouse): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry @@ -437,4 +458,3 @@ def set_valuation_method(item_code, valuation_method): }, allow_negative_stock=1) test_dependencies = ["Item", "Warehouse"] - diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 2a9dcfb67ed..f75cb561385 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -18,6 +18,7 @@ "section_break_9", "over_delivery_receipt_allowance", "role_allowed_to_over_deliver_receive", + "mr_qty_allowance", "column_break_12", "auto_insert_price_list_rate_if_missing", "allow_negative_stock", @@ -283,6 +284,12 @@ "fieldtype": "Select", "label": "Action If Quality Inspection Is Rejected", "options": "Stop\nWarn" + }, + { + "description": "The percentage you are allowed to transfer more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed transfer 110 units.", + "fieldname": "mr_qty_allowance", + "fieldtype": "Float", + "label": "Over Transfer Allowance" } ], "icon": "icon-cog", @@ -290,7 +297,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-07-10 16:17:42.159829", + "modified": "2021-06-28 17:02:26.683002", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", @@ -310,4 +317,4 @@ "sort_field": "modified", "sort_order": "ASC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/uom_conversion_detail/uom_conversion_detail.py b/erpnext/stock/doctype/uom_conversion_detail/uom_conversion_detail.py index 67fe20bd379..fdead205670 100644 --- a/erpnext/stock/doctype/uom_conversion_detail/uom_conversion_detail.py +++ b/erpnext/stock/doctype/uom_conversion_detail/uom_conversion_detail.py @@ -7,4 +7,4 @@ import frappe from frappe.model.document import Document class UOMConversionDetail(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.js b/erpnext/stock/doctype/warehouse/test_warehouse.js index 8ea280cc59e..850da1ee45f 100644 --- a/erpnext/stock/doctype/warehouse/test_warehouse.js +++ b/erpnext/stock/doctype/warehouse/test_warehouse.js @@ -16,4 +16,4 @@ QUnit.test("test: warehouse", function (assert) { () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py index e3981c913e1..6e429a22552 100644 --- a/erpnext/stock/doctype/warehouse/test_warehouse.py +++ b/erpnext/stock/doctype/warehouse/test_warehouse.py @@ -180,4 +180,4 @@ def get_group_stock_account(company, company_abbr=None): if not company_abbr: company_abbr = frappe.get_cached_value("Company", company, 'abbr') group_stock_account = "Current Assets - " + company_abbr - return group_stock_account \ No newline at end of file + return group_stock_account diff --git a/erpnext/stock/doctype/warehouse/warehouse.js b/erpnext/stock/doctype/warehouse/warehouse.js index 1f172504a7f..9243e1ed84f 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.js +++ b/erpnext/stock/doctype/warehouse/warehouse.js @@ -48,11 +48,11 @@ frappe.ui.form.on("Warehouse", { frm.add_custom_button(__('Non-Group to Group'), function() { convert_to_group_or_ledger(frm); }, 'fa fa-retweet', 'btn-default') } - + frm.toggle_enable(['is_group', 'company'], false); frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Warehouse'}; - + frm.fields_dict['parent_warehouse'].get_query = function(doc) { return { filters: { @@ -83,6 +83,6 @@ function convert_to_group_or_ledger(frm){ callback: function(){ frm.refresh(); } - + }) -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/warehouse/warehouse_tree.js b/erpnext/stock/doctype/warehouse/warehouse_tree.js index 407d7d1ccd5..e9e14c72466 100644 --- a/erpnext/stock/doctype/warehouse/warehouse_tree.js +++ b/erpnext/stock/doctype/warehouse/warehouse_tree.js @@ -24,4 +24,4 @@ frappe.treeview_settings['Warehouse'] = { + '').insertBefore(node.$ul); } } -} \ No newline at end of file +} diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index ca174a3f63c..a0fbcecc5de 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -74,9 +74,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru update_party_blanket_order(args, out) - if not doc or cint(doc.get('is_return')) == 0: - # get price list rate only if the invoice is not a credit or debit note - get_price_list_rate(args, item, out) + out.update(get_price_list_rate(args, item)) if args.customer and cint(args.is_pos): out.update(get_pos_profile_item_details(args.company, args, update_data=True)) @@ -288,6 +286,7 @@ def get_basic_details(args, item, overwrite_warehouse=True): "warehouse": warehouse, "income_account": get_default_income_account(args, item_defaults, item_group_defaults, brand_defaults), "expense_account": expense_account or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults) , + "discount_account": None or get_default_discount_account(args, item_defaults), "cost_center": get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults), 'has_serial_no': item.has_serial_no, 'has_batch_no': item.has_batch_no, @@ -313,8 +312,8 @@ def get_basic_details(args, item, overwrite_warehouse=True): "transaction_date": args.get("transaction_date"), "against_blanket_order": args.get("against_blanket_order"), "bom_no": item.get("default_bom"), - "weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"), - "weight_uom": args.get("weight_uom") or item.get("weight_uom") + "weight_per_unit": item.get("weight_per_unit"), + "weight_uom": item.get("weight_uom") }) if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): @@ -441,7 +440,7 @@ def get_item_tax_info(company, tax_category, item_codes, item_rates=None, item_t if item_tax_templates is None: item_tax_templates = {} - + if item_rates is None: item_rates = {} @@ -590,6 +589,10 @@ def get_default_expense_account(args, item, item_group, brand): or brand.get("expense_account") or args.expense_account) +def get_default_discount_account(args, item): + return (item.get("default_discount_account") + or args.discount_account) + def get_default_deferred_account(args, item, fieldname=None): if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): return (item.get(fieldname) @@ -639,7 +642,10 @@ def get_default_supplier(args, item, item_group, brand): or item_group.get("default_supplier") or brand.get("default_supplier")) -def get_price_list_rate(args, item_doc, out): +def get_price_list_rate(args, item_doc, out=None): + if out is None: + out = frappe._dict() + meta = frappe.get_meta(args.parenttype or args.doctype) if meta.get_field("currency") or args.get('currency'): @@ -652,17 +658,17 @@ def get_price_list_rate(args, item_doc, out): if meta.get_field("currency"): validate_conversion_rate(args, meta) - price_list_rate = get_price_list_rate_for(args, item_doc.name) or 0 + price_list_rate = get_price_list_rate_for(args, item_doc.name) # variant - if not price_list_rate and item_doc.variant_of: + if price_list_rate is None and item_doc.variant_of: price_list_rate = get_price_list_rate_for(args, item_doc.variant_of) # insert in database - if not price_list_rate: + if price_list_rate is None: if args.price_list and args.rate: insert_item_price(args) - return {} + return out out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \ / flt(args.conversion_rate) @@ -672,6 +678,8 @@ def get_price_list_rate(args, item_doc, out): out.update(get_last_purchase_details(item_doc.name, args.name, args.conversion_rate)) + return out + def insert_item_price(args): """Insert Item Price if Price List and Price List Rate are specified and currency is the same""" if frappe.db.get_value("Price List", args.price_list, "currency", cache=True) == args.currency \ @@ -807,10 +815,14 @@ def check_packing_list(price_list_rate_name, desired_qty, item_code): def validate_conversion_rate(args, meta): from erpnext.controllers.accounts_controller import validate_conversion_rate - if (not args.conversion_rate - and args.currency==frappe.get_cached_value('Company', args.company, "default_currency")): + company_currency = frappe.get_cached_value('Company', args.company, "default_currency") + if (not args.conversion_rate and args.currency==company_currency): args.conversion_rate = 1.0 + if (not args.ignore_conversion_rate and args.conversion_rate == 1 and args.currency!=company_currency): + args.conversion_rate = get_exchange_rate(args.currency, + company_currency, args.transaction_date, "for_buying") or 1.0 + # validate currency conversion rate validate_conversion_rate(args.currency, args.conversion_rate, meta.get_label("conversion_rate"), args.company) @@ -1070,9 +1082,8 @@ def apply_price_list(args, as_doc=False): } def apply_price_list_on_item(args): - item_details = frappe._dict() item_doc = frappe.get_doc("Item", args.item_code) - get_price_list_rate(args, item_doc, item_details) + item_details = get_price_list_rate(args, item_doc) item_details.update(get_pricing_rule_for_item(args, item_details.price_list_rate)) diff --git a/erpnext/stock/landed_taxes_and_charges_common.js b/erpnext/stock/landed_taxes_and_charges_common.js index f3f61963a88..ff8a69fb033 100644 --- a/erpnext/stock/landed_taxes_and_charges_common.js +++ b/erpnext/stock/landed_taxes_and_charges_common.js @@ -59,4 +59,3 @@ document_list.forEach((doctype) => { } }); }); - diff --git a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html index 90112c78a83..de7e38e7d3e 100644 --- a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html +++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html @@ -37,4 +37,4 @@ {% endif %} -{% endfor %} \ No newline at end of file +{% endfor %} diff --git a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js index b610e7dd587..47ae86b9e21 100644 --- a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js +++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js @@ -117,4 +117,4 @@ frappe.pages['warehouse-capacity-summary'].on_page_load = function(wrapper) { setup_click('Item'); setup_click('Warehouse'); }); -}; \ No newline at end of file +}; diff --git a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html index acaf180a903..7ac5e640302 100644 --- a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html +++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html @@ -16,4 +16,4 @@ % Occupied - \ No newline at end of file + diff --git a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py index 7354eee4130..29689b1a912 100644 --- a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py +++ b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py @@ -24,7 +24,7 @@ def execute(filters=None): data.append([item, item_map[item]["item_name"], item_map[item]["description"], wh, batch, frappe.db.get_value('Batch', batch, 'expiry_date'), qty_dict.expiry_status ]) - + return columns, data @@ -70,7 +70,7 @@ def get_item_warehouse_batch_map(filters, float_precision): "expires_on": None, "expiry_status": None})) qty_dict = iwb_map[d.item_code][d.warehouse][d.batch_no] - + expiry_date_unicode = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') qty_dict.expires_on = expiry_date_unicode diff --git a/erpnext/stock/report/cogs_by_item_group/__init__.py b/erpnext/stock/report/cogs_by_item_group/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js new file mode 100644 index 00000000000..d7c50a66979 --- /dev/null +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js @@ -0,0 +1,31 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + + +frappe.query_reports["COGS By Item Group"] = { + filters: [ + { + label: __("Company"), + fieldname: "company", + fieldtype: "Link", + options: "Company", + mandatory: true, + default: frappe.defaults.get_user_default("Company"), + }, + { + label: __("From Date"), + fieldname: "from_date", + fieldtype: "Date", + mandatory: true, + default: frappe.datetime.year_start(), + }, + { + label: __("To Date"), + fieldname: "to_date", + fieldtype: "Date", + mandatory: true, + default: frappe.datetime.get_today(), + }, + ] +}; diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json new file mode 100644 index 00000000000..a14adf8a453 --- /dev/null +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-06-02 18:59:19.830928", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-06-02 18:59:55.470621", + "modified_by": "Administrator", + "module": "Stock", + "name": "COGS By Item Group", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "GL Entry", + "report_name": "COGS By Item Group", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Auditor" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py new file mode 100644 index 00000000000..da593a40d68 --- /dev/null +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py @@ -0,0 +1,188 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from collections import OrderedDict +import datetime +from typing import Dict, List, Tuple, Union + +import frappe +from frappe import _ +from frappe.utils import date_diff + +from erpnext.accounts.report.general_ledger.general_ledger import get_gl_entries + + +Filters = frappe._dict +Row = frappe._dict +Data = List[Row] +Columns = List[Dict[str, str]] +DateTime = Union[datetime.date, datetime.datetime] +FilteredEntries = List[Dict[str, Union[str, float, DateTime, None]]] +ItemGroupsDict = Dict[Tuple[int, int], Dict[str, Union[str, int]]] +SVDList = List[frappe._dict] + + +def execute(filters: Filters) -> Tuple[Columns, Data]: + update_filters_with_account(filters) + validate_filters(filters) + columns = get_columns() + data = get_data(filters) + return columns, data + + +def update_filters_with_account(filters: Filters) -> None: + account = frappe.get_value("Company", filters.get("company"), "default_expense_account") + filters.update(dict(account=account)) + + +def validate_filters(filters: Filters) -> None: + if filters.from_date > filters.to_date: + frappe.throw(_("From Date must be before To Date")) + + +def get_columns() -> Columns: + return [ + { + 'label': 'Item Group', + 'fieldname': 'item_group', + 'fieldtype': 'Data', + 'width': '200' + }, + { + 'label': 'COGS Debit', + 'fieldname': 'cogs_debit', + 'fieldtype': 'Currency', + 'width': '200' + } + ] + + +def get_data(filters: Filters) -> Data: + filtered_entries = get_filtered_entries(filters) + svd_list = get_stock_value_difference_list(filtered_entries) + leveled_dict = get_leveled_dict() + + assign_self_values(leveled_dict, svd_list) + assign_agg_values(leveled_dict) + + data = [] + for item in leveled_dict.items(): + i = item[1] + if i['agg_value'] == 0: + continue + data.append(get_row(i['name'], i['agg_value'], i['is_group'], i['level'])) + if i['self_value'] < i['agg_value'] and i['self_value'] > 0: + data.append(get_row(i['name'], i['self_value'], 0, i['level'] + 1)) + return data + + +def get_filtered_entries(filters: Filters) -> FilteredEntries: + gl_entries = get_gl_entries(filters, []) + filtered_entries = [] + for entry in gl_entries: + posting_date = entry.get('posting_date') + from_date = filters.get('from_date') + if date_diff(from_date, posting_date) > 0: + continue + filtered_entries.append(entry) + return filtered_entries + + +def get_stock_value_difference_list(filtered_entries: FilteredEntries) -> SVDList: + voucher_nos = [fe.get('voucher_no') for fe in filtered_entries] + svd_list = frappe.get_list( + 'Stock Ledger Entry', fields=['item_code','stock_value_difference'], + filters=[('voucher_no', 'in', voucher_nos)] + ) + assign_item_groups_to_svd_list(svd_list) + return svd_list + + +def get_leveled_dict() -> OrderedDict: + item_groups_dict = get_item_groups_dict() + lr_list = sorted(item_groups_dict, key=lambda x : int(x[0])) + leveled_dict = OrderedDict() + current_level = 0 + nesting_r = [] + for l, r in lr_list: + while current_level > 0 and nesting_r[-1] < l: + nesting_r.pop() + current_level -= 1 + + leveled_dict[(l,r)] = { + 'level' : current_level, + 'name' : item_groups_dict[(l,r)]['name'], + 'is_group' : item_groups_dict[(l,r)]['is_group'] + } + + if int(r) - int(l) > 1: + current_level += 1 + nesting_r.append(r) + + update_leveled_dict(leveled_dict) + return leveled_dict + + +def assign_self_values(leveled_dict: OrderedDict, svd_list: SVDList) -> None: + key_dict = {v['name']:k for k, v in leveled_dict.items()} + for item in svd_list: + key = key_dict[item.get("item_group")] + leveled_dict[key]['self_value'] += -item.get("stock_value_difference") + + +def assign_agg_values(leveled_dict: OrderedDict) -> None: + keys = list(leveled_dict.keys())[::-1] + prev_level = leveled_dict[keys[-1]]['level'] + accu = [0] + for k in keys[:-1]: + curr_level = leveled_dict[k]['level'] + if curr_level == prev_level: + accu[-1] += leveled_dict[k]['self_value'] + leveled_dict[k]['agg_value'] = leveled_dict[k]['self_value'] + + elif curr_level > prev_level: + accu.append(leveled_dict[k]['self_value']) + leveled_dict[k]['agg_value'] = accu[-1] + + elif curr_level < prev_level: + accu[-1] += leveled_dict[k]['self_value'] + leveled_dict[k]['agg_value'] = accu[-1] + + prev_level = curr_level + + # root node + rk = keys[-1] + leveled_dict[rk]['agg_value'] = sum(accu) + leveled_dict[rk]['self_value'] + + +def get_row(name:str, value:float, is_bold:int, indent:int) -> Row: + item_group = name + if is_bold: + item_group = frappe.bold(item_group) + return frappe._dict(item_group=item_group, cogs_debit=value, indent=indent) + + +def assign_item_groups_to_svd_list(svd_list: SVDList) -> None: + ig_map = get_item_groups_map(svd_list) + for item in svd_list: + item.item_group = ig_map[item.get("item_code")] + + +def get_item_groups_map(svd_list: SVDList) -> Dict[str, str]: + item_codes = set(i['item_code'] for i in svd_list) + ig_list = frappe.get_list( + 'Item', fields=['item_code','item_group'], + filters=[('item_code', 'in', item_codes)] + ) + return {i['item_code']:i['item_group'] for i in ig_list} + + +def get_item_groups_dict() -> ItemGroupsDict: + item_groups_list = frappe.get_all("Item Group", fields=("name", "is_group", "lft", "rgt")) + return {(i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']} + for i in item_groups_list} + + +def update_leveled_dict(leveled_dict: OrderedDict) -> None: + for k in leveled_dict: + leveled_dict[k].update({'self_value':0, 'agg_value':0}) diff --git a/erpnext/stock/report/delayed_item_report/delayed_item_report.py b/erpnext/stock/report/delayed_item_report/delayed_item_report.py index 4fc4027200d..61306662c0d 100644 --- a/erpnext/stock/report/delayed_item_report/delayed_item_report.py +++ b/erpnext/stock/report/delayed_item_report/delayed_item_report.py @@ -174,4 +174,4 @@ class DelayedItemReport(object): "fieldname": "po_no", "fieldtype": "Data", "width": 100 - }] \ No newline at end of file + }] diff --git a/erpnext/stock/report/delayed_order_report/delayed_order_report.py b/erpnext/stock/report/delayed_order_report/delayed_order_report.py index 79dc5d88219..d9151606884 100644 --- a/erpnext/stock/report/delayed_order_report/delayed_order_report.py +++ b/erpnext/stock/report/delayed_order_report/delayed_order_report.py @@ -87,4 +87,4 @@ class DelayedOrderReport(DelayedItemReport): "fieldname": "po_no", "fieldtype": "Data", "width": 110 - }] \ No newline at end of file + }] diff --git a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.js b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.js index ade004cde42..8a04565c197 100644 --- a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.js +++ b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.js @@ -6,4 +6,3 @@ frappe.require("assets/erpnext/js/sales_trends_filters.js", function() { filters: erpnext.get_sales_trends_filters() } }); - diff --git a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py index 446d3049b71..77fd2ff2447 100644 --- a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py +++ b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py @@ -47,4 +47,4 @@ def get_chart_data(data, filters): ] }, "type" : "bar" - } \ No newline at end of file + } diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py index cf174c93682..00125e71a9a 100644 --- a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py +++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py @@ -108,4 +108,4 @@ def get_columns(): 'fieldtype': 'Float', 'fieldname': 'differnce', 'width': 110 - }] \ No newline at end of file + }] diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py index e54cf4c66c7..b3b7594ffd7 100644 --- a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py +++ b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py @@ -145,4 +145,4 @@ def get_columns(): 'fieldtype': 'Currency', 'fieldname': 'valuation_rate', 'width': 110 - }] \ No newline at end of file + }] diff --git a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py index a7243878eb8..c8f60a15d64 100644 --- a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py +++ b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py @@ -138,4 +138,4 @@ def get_columns(filters): "fieldtype": "Currency", "width": "150" } - ] \ No newline at end of file + ] diff --git a/erpnext/stock/report/item_price_stock/item_price_stock.js b/erpnext/stock/report/item_price_stock/item_price_stock.js index 0bbc61b9dbf..7af1dab6a0b 100644 --- a/erpnext/stock/report/item_price_stock/item_price_stock.js +++ b/erpnext/stock/report/item_price_stock/item_price_stock.js @@ -11,4 +11,4 @@ frappe.query_reports["Item Price Stock"] = { "options": "Item" } ] -} \ No newline at end of file +} diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.py b/erpnext/stock/report/item_shortage_report/item_shortage_report.py index 086d833bbc4..c67eed7e926 100644 --- a/erpnext/stock/report/item_shortage_report/item_shortage_report.py +++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.py @@ -158,5 +158,3 @@ def get_columns(): ] return columns - - diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.js b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.js index c0535bf0efa..173aad6d5a9 100644 --- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.js +++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.js @@ -29,4 +29,4 @@ frappe.query_reports["Itemwise Recommended Reorder Level"] = { "options": "Brand" } ] -} \ No newline at end of file +} diff --git a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.js b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.js index d16485e8cc6..695efacb694 100644 --- a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.js +++ b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.js @@ -6,4 +6,3 @@ frappe.require("assets/erpnext/js/purchase_trends_filters.js", function() { filters: erpnext.get_purchase_trends_filters() } }); - diff --git a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py index 8227f1548c1..0d96ea6aa8e 100644 --- a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py +++ b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py @@ -48,4 +48,4 @@ def get_chart_data(data, filters): }, "type" : "bar", "colors":["#5e64ff"] - } \ No newline at end of file + } diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py index c3339fd341e..cc3aa3522d8 100644 --- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py @@ -50,4 +50,3 @@ def get_columns(filters): def get_data(filters): return get_stock_ledger_entries(filters, '<=', order="asc") or [] - diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.js b/erpnext/stock/report/stock_ageing/stock_ageing.js index 8495142ba5b..b22788f7a29 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.js +++ b/erpnext/stock/report/stock_ageing/stock_ageing.js @@ -64,4 +64,4 @@ frappe.query_reports["Stock Ageing"] = { "default": 0 } ] -} \ No newline at end of file +} diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.js b/erpnext/stock/report/stock_analytics/stock_analytics.js index 6b384e28611..78afe6d2642 100644 --- a/erpnext/stock/report/stock_analytics/stock_analytics.js +++ b/erpnext/stock/report/stock_analytics/stock_analytics.js @@ -36,12 +36,26 @@ frappe.query_reports["Stock Analytics"] = { options:"Brand", default: "", }, + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1, + }, { fieldname: "warehouse", label: __("Warehouse"), fieldtype: "Link", - options:"Warehouse", + options: "Warehouse", default: "", + get_query: function() { + const company = frappe.query_report.get_filter_value('company'); + return { + filters: { 'company': company } + } + } }, { fieldname: "from_date", diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.py b/erpnext/stock/report/stock_analytics/stock_analytics.py index 0cc8ca48aac..a1e1e7fce7c 100644 --- a/erpnext/stock/report/stock_analytics/stock_analytics.py +++ b/erpnext/stock/report/stock_analytics/stock_analytics.py @@ -1,14 +1,15 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +import datetime -from __future__ import unicode_literals import frappe from frappe import _, scrub -from frappe.utils import getdate, flt +from frappe.utils import getdate, get_quarter_start, get_first_day_of_week +from frappe.utils import get_first_day as get_first_day_of_month + from erpnext.stock.report.stock_balance.stock_balance import (get_items, get_stock_ledger_entries, get_item_details) from erpnext.accounts.utils import get_fiscal_year from erpnext.stock.utils import is_reposting_item_valuation_in_progress -from six import iteritems def execute(filters=None): is_reposting_item_valuation_in_progress() @@ -71,7 +72,8 @@ def get_columns(filters): def get_period_date_ranges(filters): from dateutil.relativedelta import relativedelta - from_date, to_date = getdate(filters.from_date), getdate(filters.to_date) + from_date = round_down_to_nearest_frequency(filters.from_date, filters.range) + to_date = getdate(filters.to_date) increment = { "Monthly": 1, @@ -97,6 +99,31 @@ def get_period_date_ranges(filters): return periodic_daterange + +def round_down_to_nearest_frequency(date: str, frequency: str) -> datetime.datetime: + """Rounds down the date to nearest frequency unit. + example: + + >>> round_down_to_nearest_frequency("2021-02-21", "Monthly") + datetime.datetime(2021, 2, 1) + + >>> round_down_to_nearest_frequency("2021-08-21", "Yearly") + datetime.datetime(2021, 1, 1) + """ + + def _get_first_day_of_fiscal_year(date): + fiscal_year = get_fiscal_year(date) + return fiscal_year and fiscal_year[1] or date + + round_down_function = { + "Monthly": get_first_day_of_month, + "Quarterly": get_quarter_start, + "Weekly": get_first_day_of_week, + "Yearly": _get_first_day_of_fiscal_year, + }.get(frequency, getdate) + return round_down_function(date) + + def get_period(posting_date, filters): months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] @@ -114,14 +141,42 @@ def get_period(posting_date, filters): def get_periodic_data(entry, filters): + """Structured as: + Item 1 + - Balance (updated and carried forward): + - Warehouse A : bal_qty/value + - Warehouse B : bal_qty/value + - Jun 2021 (sum of warehouse quantities used in report) + - Warehouse A : bal_qty/value + - Warehouse B : bal_qty/value + - Jul 2021 (sum of warehouse quantities used in report) + - Warehouse A : bal_qty/value + - Warehouse B : bal_qty/value + Item 2 + - Balance (updated and carried forward): + - Warehouse A : bal_qty/value + - Warehouse B : bal_qty/value + - Jun 2021 (sum of warehouse quantities used in report) + - Warehouse A : bal_qty/value + - Warehouse B : bal_qty/value + - Jul 2021 (sum of warehouse quantities used in report) + - Warehouse A : bal_qty/value + - Warehouse B : bal_qty/value + """ periodic_data = {} for d in entry: period = get_period(d.posting_date, filters) bal_qty = 0 + # if period against item does not exist yet, instantiate it + # insert existing balance dict against period, and add/subtract to it + if periodic_data.get(d.item_code) and not periodic_data.get(d.item_code).get(period): + previous_balance = periodic_data[d.item_code]['balance'].copy() + periodic_data[d.item_code][period] = previous_balance + if d.voucher_type == "Stock Reconciliation": - if periodic_data.get(d.item_code): - bal_qty = periodic_data[d.item_code]["balance"] + if periodic_data.get(d.item_code) and periodic_data.get(d.item_code).get('balance').get(d.warehouse): + bal_qty = periodic_data[d.item_code]['balance'][d.warehouse] qty_diff = d.qty_after_transaction - bal_qty else: @@ -132,12 +187,12 @@ def get_periodic_data(entry, filters): else: value = d.stock_value_difference - periodic_data.setdefault(d.item_code, {}).setdefault(period, 0.0) - periodic_data.setdefault(d.item_code, {}).setdefault("balance", 0.0) - - periodic_data[d.item_code]["balance"] += value - periodic_data[d.item_code][period] = periodic_data[d.item_code]["balance"] + # period-warehouse wise balance + periodic_data.setdefault(d.item_code, {}).setdefault('balance', {}).setdefault(d.warehouse, 0.0) + periodic_data.setdefault(d.item_code, {}).setdefault(period, {}).setdefault(d.warehouse, 0.0) + periodic_data[d.item_code]['balance'][d.warehouse] += value + periodic_data[d.item_code][period][d.warehouse] = periodic_data[d.item_code]['balance'][d.warehouse] return periodic_data @@ -149,7 +204,7 @@ def get_data(filters): periodic_data = get_periodic_data(sle, filters) ranges = get_period_date_ranges(filters) - for dummy, item_data in iteritems(item_details): + for dummy, item_data in item_details.items(): row = { "name": item_data.name, "item_name": item_data.item_name, @@ -160,7 +215,8 @@ def get_data(filters): total = 0 for dummy, end_date in ranges: period = get_period(end_date, filters) - amount = flt(periodic_data.get(item_data.name, {}).get(period)) + period_data = periodic_data.get(item_data.name, {}).get(period) + amount = sum(period_data.values()) if period_data else 0 row[scrub(period)] = amount total += amount row["total"] = total @@ -179,7 +235,3 @@ def get_chart_data(columns): chart["type"] = "line" return chart - - - - diff --git a/erpnext/stock/report/stock_analytics/test_stock_analytics.py b/erpnext/stock/report/stock_analytics/test_stock_analytics.py new file mode 100644 index 00000000000..00e268b4e0e --- /dev/null +++ b/erpnext/stock/report/stock_analytics/test_stock_analytics.py @@ -0,0 +1,35 @@ +import datetime +import unittest + +from frappe import _dict +from erpnext.accounts.utils import get_fiscal_year + +from erpnext.stock.report.stock_analytics.stock_analytics import get_period_date_ranges + + +class TestStockAnalyticsReport(unittest.TestCase): + def test_get_period_date_ranges(self): + + filters = _dict(range="Monthly", from_date="2020-12-28", to_date="2021-02-06") + + ranges = get_period_date_ranges(filters) + + expected_ranges = [ + [datetime.date(2020, 12, 1), datetime.date(2020, 12, 31)], + [datetime.date(2021, 1, 1), datetime.date(2021, 1, 31)], + [datetime.date(2021, 2, 1), datetime.date(2021, 2, 6)], + ] + + self.assertEqual(ranges, expected_ranges) + + def test_get_period_date_ranges_yearly(self): + + filters = _dict(range="Yearly", from_date="2021-01-28", to_date="2021-02-06") + + ranges = get_period_date_ranges(filters) + first_date = get_fiscal_year("2021-01-28")[1] + expected_ranges = [ + [first_date, datetime.date(2021, 2, 6)], + ] + + self.assertEqual(ranges, expected_ranges) diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py index 14d543b1740..7e0c0e8ab35 100644 --- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py +++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py @@ -22,6 +22,7 @@ def get_data(report_filters): data = [] filters = { + "is_cancelled": 0, "company": report_filters.company, "posting_date": ("<=", report_filters.as_on_date) } @@ -34,7 +35,7 @@ def get_data(report_filters): key = (d.voucher_type, d.voucher_no) gl_data = voucher_wise_gl_data.get(key) or {} d.account_value = gl_data.get("account_value", 0) - d.difference_value = (d.stock_value - d.account_value) + d.difference_value = abs(d.stock_value - d.account_value) if abs(d.difference_value) > 0.1: data.append(d) @@ -127,4 +128,4 @@ def get_columns(filters): "fieldtype": "Currency", "width": "120" } - ] \ No newline at end of file + ] diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index b6a80631892..fc3d719a780 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -16,8 +16,6 @@ def execute(filters=None): is_reposting_item_valuation_in_progress() if not filters: filters = {} - validate_filters(filters) - from_date = filters.get('from_date') to_date = filters.get('to_date') @@ -237,12 +235,15 @@ def filter_items_with_no_transactions(iwb_map, float_precision): return iwb_map def get_items(filters): + "Get items based on item code, item group or brand." conditions = [] if filters.get("item_code"): conditions.append("item.name=%(item_code)s") else: if filters.get("item_group"): conditions.append(get_item_group_condition(filters.get("item_group"))) + if filters.get("brand"): # used in stock analytics report + conditions.append("item.brand=%(brand)s") items = [] if conditions: @@ -295,12 +296,6 @@ def get_item_reorder_details(items): return dict((d.parent + d.warehouse, d) for d in item_reorder_details) -def validate_filters(filters): - if not (filters.get("item_code") or filters.get("warehouse")): - sle_count = flt(frappe.db.sql("""select count(name) from `tabStock Ledger Entry`""")[0][0]) - if sle_count > 500000: - frappe.throw(_("Please set filter based on Item or Warehouse due to a large amount of entries.")) - def get_variants_attributes(): '''Return all item variant attributes.''' return [i.name for i in frappe.get_all('Item Attribute')] diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py index 808d2791709..7956f2e8648 100644 --- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py +++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py @@ -32,7 +32,7 @@ def execute(filters=None): if filters.brand and filters.brand != item.brand: continue - + elif filters.item_group and filters.item_group != item.item_group: continue diff --git a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py index 78e95df9898..fa19eeba58b 100644 --- a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py +++ b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py @@ -58,14 +58,14 @@ def get_data(warehouse): serial_item_list = frappe.get_all("Item", filters={ 'has_serial_no': True, }, fields=['item_code', 'item_name']) - + status_list = ['Active', 'Expired'] data = [] for item in serial_item_list: - total_serial_no = frappe.db.count("Serial No", + total_serial_no = frappe.db.count("Serial No", filters={"item_code": item.item_code, "status": ("in", status_list), "warehouse": warehouse}) - actual_qty = frappe.db.get_value('Bin', fieldname=['actual_qty'], + actual_qty = frappe.db.get_value('Bin', fieldname=['actual_qty'], filters={"warehouse": warehouse, "item_code": item.item_code}) # frappe.db.get_value returns null if no record exist. @@ -84,4 +84,4 @@ def get_data(warehouse): data.append(row) - return data \ No newline at end of file + return data diff --git a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.js b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.js index cdc9895917c..5b006470756 100644 --- a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.js +++ b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.js @@ -25,4 +25,4 @@ frappe.query_reports["Supplier-Wise Sales Analytics"] = { "default": frappe.datetime.month_end() }, ] -} \ No newline at end of file +} diff --git a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py index 5873a7a3008..4108a575542 100644 --- a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py +++ b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py @@ -69,7 +69,7 @@ def get_consumed_details(filters): i.stock_uom, sle.actual_qty, sle.stock_value_difference, sle.voucher_no, sle.voucher_type from `tabStock Ledger Entry` sle, `tabItem` i - where sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1): + where sle.is_cancelled = 0 and sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1): consumed_details.setdefault(d.item_code, []).append(d) return consumed_details diff --git a/erpnext/stock/report/total_stock_summary/total_stock_summary.js b/erpnext/stock/report/total_stock_summary/total_stock_summary.js index 264642856da..90648f1b249 100644 --- a/erpnext/stock/report/total_stock_summary/total_stock_summary.js +++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.js @@ -38,4 +38,4 @@ frappe.query_reports["Total Stock Summary"] = { "reqd": 1 }, ] -} \ No newline at end of file +} diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index c15d1eda7dc..f762cc7b890 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -127,30 +127,24 @@ def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False): sle.submit() return sle -def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negative_stock=None, via_landed_cost_voucher=False): +def repost_future_sle(args=None, doc=None, voucher_type=None, voucher_no=None, allow_negative_stock=None, via_landed_cost_voucher=False): if not args and voucher_type and voucher_no: - args = get_args_for_voucher(voucher_type, voucher_no) + args = get_items_to_be_repost(voucher_type, voucher_no, doc) - distinct_item_warehouses = {} - for i, d in enumerate(args): - distinct_item_warehouses.setdefault((d.item_code, d.warehouse), frappe._dict({ - "reposting_status": False, - "sle": d, - "args_idx": i - })) + distinct_item_warehouses = get_distinct_item_warehouse(args, doc) - i = 0 + i = get_current_index(doc) or 0 while i < len(args): obj = update_entries_after({ - "item_code": args[i].item_code, - "warehouse": args[i].warehouse, - "posting_date": args[i].posting_date, - "posting_time": args[i].posting_time, + "item_code": args[i].get('item_code'), + "warehouse": args[i].get('warehouse'), + "posting_date": args[i].get('posting_date'), + "posting_time": args[i].get('posting_time'), "creation": args[i].get("creation"), "distinct_item_warehouses": distinct_item_warehouses }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) - distinct_item_warehouses[(args[i].item_code, args[i].warehouse)].reposting_status = True + distinct_item_warehouses[(args[i].get('item_code'), args[i].get('warehouse'))].reposting_status = True if obj.new_items_found: for item_wh, data in iteritems(distinct_item_warehouses): @@ -159,11 +153,35 @@ def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negat args.append(data.sle) elif data.sle_changed and not data.reposting_status: args[data.args_idx] = data.sle - + data.sle_changed = False i += 1 -def get_args_for_voucher(voucher_type, voucher_no): + if doc and i % 2 == 0: + update_args_in_repost_item_valuation(doc, i, args, distinct_item_warehouses) + + if doc and args: + update_args_in_repost_item_valuation(doc, i, args, distinct_item_warehouses) + +def update_args_in_repost_item_valuation(doc, index, args, distinct_item_warehouses): + frappe.db.set_value(doc.doctype, doc.name, { + 'items_to_be_repost': json.dumps(args, default=str), + 'distinct_item_and_warehouse': json.dumps({str(k): v for k,v in distinct_item_warehouses.items()}, default=str), + 'current_index': index + }) + + frappe.db.commit() + + frappe.publish_realtime('item_reposting_progress', { + 'name': doc.name, + 'items_to_be_repost': json.dumps(args, default=str), + 'current_index': index + }) + +def get_items_to_be_repost(voucher_type, voucher_no, doc=None): + if doc and doc.items_to_be_repost: + return json.loads(doc.items_to_be_repost) or [] + return frappe.db.get_all("Stock Ledger Entry", filters={"voucher_type": voucher_type, "voucher_no": voucher_no}, fields=["item_code", "warehouse", "posting_date", "posting_time", "creation"], @@ -171,6 +189,25 @@ def get_args_for_voucher(voucher_type, voucher_no): group_by="item_code, warehouse" ) +def get_distinct_item_warehouse(args=None, doc=None): + distinct_item_warehouses = {} + if doc and doc.distinct_item_and_warehouse: + distinct_item_warehouses = json.loads(doc.distinct_item_and_warehouse) + distinct_item_warehouses = {frappe.safe_eval(k): frappe._dict(v) for k, v in distinct_item_warehouses.items()} + else: + for i, d in enumerate(args): + distinct_item_warehouses.setdefault((d.item_code, d.warehouse), frappe._dict({ + "reposting_status": False, + "sle": d, + "args_idx": i + })) + + return distinct_item_warehouses + +def get_current_index(doc=None): + if doc and doc.current_index: + return doc.current_index + class update_entries_after(object): """ update valution rate and qty after transaction @@ -234,15 +271,13 @@ class update_entries_after(object): } """ - self.data.setdefault(args.warehouse, frappe._dict()) - warehouse_dict = self.data[args.warehouse] previous_sle = get_previous_sle_of_current_voucher(args) - warehouse_dict.previous_sle = previous_sle - for key in ("qty_after_transaction", "valuation_rate", "stock_value"): - setattr(warehouse_dict, key, flt(previous_sle.get(key))) - - warehouse_dict.update({ + self.data[args.warehouse] = frappe._dict({ + "previous_sle": previous_sle, + "qty_after_transaction": flt(previous_sle.qty_after_transaction), + "valuation_rate": flt(previous_sle.valuation_rate), + "stock_value": flt(previous_sle.stock_value), "prev_stock_value": previous_sle.stock_value or 0.0, "stock_queue": json.loads(previous_sle.stock_queue or "[]"), "stock_value_difference": 0.0 diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 8a6a3a3e4a0..9f6d0a8addd 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -224,7 +224,7 @@ def get_avg_purchase_rate(serial_nos): def get_valuation_method(item_code): """get valuation method from item or default""" - val_method = frappe.db.get_value('Item', item_code, 'valuation_method') + val_method = frappe.db.get_value('Item', item_code, 'valuation_method', cache=True) if not val_method: val_method = frappe.db.get_value("Stock Settings", None, "valuation_method") or "FIFO" return val_method @@ -275,17 +275,17 @@ def get_valid_serial_nos(sr_nos, qty=0, item_code=''): return valid_serial_nos def validate_warehouse_company(warehouse, company): - warehouse_company = frappe.db.get_value("Warehouse", warehouse, "company") + warehouse_company = frappe.db.get_value("Warehouse", warehouse, "company", cache=True) if warehouse_company and warehouse_company != company: frappe.throw(_("Warehouse {0} does not belong to company {1}").format(warehouse, company), InvalidWarehouseCompany) def is_group_warehouse(warehouse): - if frappe.db.get_value("Warehouse", warehouse, "is_group"): + if frappe.db.get_value("Warehouse", warehouse, "is_group", cache=True): frappe.throw(_("Group node warehouse is not allowed to select for transactions")) def validate_disabled_warehouse(warehouse): - if frappe.db.get_value("Warehouse", warehouse, "disabled"): + if frappe.db.get_value("Warehouse", warehouse, "disabled", cache=True): frappe.throw(_("Disabled Warehouse {0} cannot be used for this transaction.").format(get_link_to_form('Warehouse', warehouse))) def update_included_uom_in_report(columns, result, include_uom, conversion_factors): @@ -314,13 +314,16 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto for row_idx, row in enumerate(result): data = row.items() if is_dict_obj else enumerate(row) for key, value in data: - if key not in convertible_columns or not conversion_factors[row_idx-1]: + if key not in convertible_columns: continue + # If no conversion factor for the UOM, defaults to 1 + if not conversion_factors[row_idx]: + conversion_factors[row_idx] = 1 if convertible_columns.get(key) == 'rate': - new_value = flt(value) * conversion_factors[row_idx-1] + new_value = flt(value) * conversion_factors[row_idx] else: - new_value = flt(value) / conversion_factors[row_idx-1] + new_value = flt(value) / conversion_factors[row_idx] if not is_dict_obj: row.insert(key+1, new_value) @@ -386,4 +389,4 @@ def is_reposting_item_valuation_in_progress(): reposting_in_progress = frappe.db.exists("Repost Item Valuation", {'docstatus': 1, 'status': ['in', ['Queued','In Progress']]}) if reposting_in_progress: - frappe.msgprint(_("Item valuation reposting in progress. Report might show incorrect item valuation."), alert=1) \ No newline at end of file + frappe.msgprint(_("Item valuation reposting in progress. Report might show incorrect item valuation."), alert=1) diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 9c69deb6a48..074f1aca0e2 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -223,6 +223,10 @@ class Issue(Document): return replicated_issue.name + def reset_issue_metrics(self): + self.db_set("resolution_time", None) + self.db_set("user_resolution_time", None) + def before_insert(self): if frappe.db.get_single_value("Support Settings", "track_service_level_agreement"): if frappe.flags.in_test: @@ -231,8 +235,7 @@ class Issue(Document): self.set_response_and_resolution_time() def set_response_and_resolution_time(self, priority=None, service_level_agreement=None): - service_level_agreement = get_active_service_level_agreement_for(priority=priority, - customer=self.customer, service_level_agreement=service_level_agreement) + service_level_agreement = get_active_service_level_agreement_for(self) if not service_level_agreement: if frappe.db.get_value("Issue", self.name, "service_level_agreement"): @@ -243,7 +246,8 @@ class Issue(Document): frappe.throw(_("This Service Level Agreement is specific to Customer {0}").format(service_level_agreement.customer)) self.service_level_agreement = service_level_agreement.name - self.priority = service_level_agreement.default_priority if not priority else priority + if not self.priority: + self.priority = service_level_agreement.default_priority priority = get_priority(self) diff --git a/erpnext/support/doctype/issue_priority/issue_priority.py b/erpnext/support/doctype/issue_priority/issue_priority.py index 7c8925ebc30..514b6cc26ba 100644 --- a/erpnext/support/doctype/issue_priority/issue_priority.py +++ b/erpnext/support/doctype/issue_priority/issue_priority.py @@ -8,4 +8,4 @@ from frappe import _ from frappe.model.document import Document class IssuePriority(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/support/doctype/issue_priority/test_issue_priority.py b/erpnext/support/doctype/issue_priority/test_issue_priority.py index a7b55f8a74c..618c93ea9d8 100644 --- a/erpnext/support/doctype/issue_priority/test_issue_priority.py +++ b/erpnext/support/doctype/issue_priority/test_issue_priority.py @@ -25,4 +25,4 @@ def insert_priority(name): frappe.get_doc({ "doctype": "Issue Priority", "name": name - }).insert(ignore_permissions=True) \ No newline at end of file + }).insert(ignore_permissions=True) diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json index 939c1999828..1678f04def8 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json @@ -18,6 +18,10 @@ "entity_type", "column_break_10", "entity", + "filters_section", + "condition", + "column_break_15", + "condition_description", "agreement_details_section", "start_date", "active", @@ -171,10 +175,30 @@ "fieldtype": "Table", "label": "Pause SLA On", "options": "Pause SLA On Status" + }, + { + "fieldname": "filters_section", + "fieldtype": "Section Break", + "label": "Assignment Condition" + }, + { + "fieldname": "column_break_15", + "fieldtype": "Column Break" + }, + { + "fieldname": "condition", + "fieldtype": "Code", + "label": "Condition", + "options": "Python" + }, + { + "fieldname": "condition_description", + "fieldtype": "HTML", + "options": "

          Condition Examples:

          \n
          doc.status==\"Open\"
          doc.due_date==nowdate()
          doc.total > 40000\n
          " } ], "links": [], - "modified": "2020-06-10 12:30:15.050785", + "modified": "2021-07-27 11:16:45.596579", "modified_by": "Administrator", "module": "Support", "name": "Service Level Agreement", @@ -208,4 +232,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py index 70c469663b7..11812426d67 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -3,10 +3,12 @@ # For license information, please see license.txt from __future__ import unicode_literals + import frappe from frappe.model.document import Document from frappe import _ -from frappe.utils import getdate, get_weekdays, get_link_to_form +from frappe.utils import getdate, get_weekdays, get_link_to_form, nowdate +from frappe.utils.safe_exec import get_safe_globals class ServiceLevelAgreement(Document): @@ -14,6 +16,7 @@ class ServiceLevelAgreement(Document): self.validate_doc() self.check_priorities() self.check_support_and_resolution() + self.validate_condition() def check_priorities(self): default_priority = [] @@ -92,6 +95,14 @@ class ServiceLevelAgreement(Document): if frappe.db.exists("Service Level Agreement", {"entity_type": self.entity_type, "entity": self.entity, "name": ["!=", self.name]}): frappe.throw(_("Service Level Agreement with Entity Type {0} and Entity {1} already exists.").format(self.entity_type, self.entity)) + def validate_condition(self): + temp_doc = frappe.new_doc('Issue') + if self.condition: + try: + frappe.safe_eval(self.condition, None, get_context(temp_doc)) + except Exception: + frappe.throw(_("The Condition '{0}' is invalid").format(self.condition)) + def get_service_level_agreement_priority(self, priority): priority = frappe.get_doc("Service Level Priority", {"priority": priority, "parent": self.name}) @@ -112,7 +123,7 @@ def check_agreement_status(): if doc.end_date and getdate(doc.end_date) < getdate(frappe.utils.getdate()): frappe.db.set_value("Service Level Agreement", service_level_agreement.name, "active", 0) -def get_active_service_level_agreement_for(priority, customer=None, service_level_agreement=None): +def get_active_service_level_agreement_for(doc): if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"): return @@ -121,23 +132,42 @@ def get_active_service_level_agreement_for(priority, customer=None, service_leve ["Service Level Agreement", "enable", "=", 1] ] - if priority: - filters.append(["Service Level Priority", "priority", "=", priority]) + if doc.get('priority'): + filters.append(["Service Level Priority", "priority", "=", doc.get('priority')]) + customer = doc.get('customer') or_filters = [ ["Service Level Agreement", "entity", "in", [customer, get_customer_group(customer), get_customer_territory(customer)]] ] + + service_level_agreement = doc.get('service_level_agreement') if service_level_agreement: or_filters = [ - ["Service Level Agreement", "name", "=", service_level_agreement], + ["Service Level Agreement", "name", "=", doc.get('service_level_agreement')], ] - or_filters.append(["Service Level Agreement", "default_service_level_agreement", "=", 1]) + default_sla_filter = filters + [["Service Level Agreement", "default_service_level_agreement", "=", 1]] + default_sla = frappe.get_all("Service Level Agreement", filters=default_sla_filter, + fields=["name", "default_priority", "condition"]) - agreement = frappe.get_list("Service Level Agreement", filters=filters, or_filters=or_filters, - fields=["name", "default_priority"]) + filters += [["Service Level Agreement", "default_service_level_agreement", "=", 0]] + agreements = frappe.get_all("Service Level Agreement", filters=filters, or_filters=or_filters, + fields=["name", "default_priority", "condition"]) - return agreement[0] if agreement else None + # check if the current document on which SLA is to be applied fulfills all the conditions + filtered_agreements = [] + for agreement in agreements: + condition = agreement.get('condition') + if not condition or (condition and frappe.safe_eval(condition, None, get_context(doc))): + filtered_agreements.append(agreement) + + # if any default sla + filtered_agreements += default_sla + + return filtered_agreements[0] if filtered_agreements else None + +def get_context(doc): + return {"doc": doc.as_dict(), "nowdate": nowdate, "frappe": frappe._dict(utils=get_safe_globals().get("frappe").get("utils"))} def get_customer_group(customer): if customer: diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement_dashboard.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement_dashboard.py index f2bd6813965..7e7a405d6e7 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement_dashboard.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Issue'] } ] - } \ No newline at end of file + } diff --git a/erpnext/support/doctype/warranty_claim/warranty_claim.js b/erpnext/support/doctype/warranty_claim/warranty_claim.js index 79f46758d12..d2ee52ad5cc 100644 --- a/erpnext/support/doctype/warranty_claim/warranty_claim.js +++ b/erpnext/support/doctype/warranty_claim/warranty_claim.js @@ -93,4 +93,4 @@ cur_frm.fields_dict['item_code'].get_query = function(doc, cdt, cdn) { ] } } -}; \ No newline at end of file +}; diff --git a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py index 922da2b33de..69bf2730d35 100644 --- a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py +++ b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py @@ -32,4 +32,4 @@ def execute(filters=None): ORDER BY creation_date desc ''', (filters.from_date, filters.to_date)) - return columns, data \ No newline at end of file + return columns, data diff --git a/erpnext/support/report/issue_analytics/issue_analytics.py b/erpnext/support/report/issue_analytics/issue_analytics.py index 3fdb10ddf38..54fce0b3592 100644 --- a/erpnext/support/report/issue_analytics/issue_analytics.py +++ b/erpnext/support/report/issue_analytics/issue_analytics.py @@ -218,4 +218,4 @@ class IssueAnalytics(object): 'datasets': [] }, 'type': 'line' - } \ No newline at end of file + } diff --git a/erpnext/support/report/issue_analytics/test_issue_analytics.py b/erpnext/support/report/issue_analytics/test_issue_analytics.py index 77483198ecc..a9d961a4592 100644 --- a/erpnext/support/report/issue_analytics/test_issue_analytics.py +++ b/erpnext/support/report/issue_analytics/test_issue_analytics.py @@ -22,7 +22,7 @@ class TestIssueAnalytics(unittest.TestCase): if current_month_date.year != last_month_date.year: self.current_month += '_' + str(current_month_date.year) self.last_month += '_' + str(last_month_date.year) - + def test_issue_analytics(self): create_service_level_agreements_for_issues() create_issue_types() @@ -211,4 +211,4 @@ def create_records(): "assign_to": ["test@example.com", "test1@example.com"], "doctype": "Issue", "name": issue.name - }) \ No newline at end of file + }) diff --git a/erpnext/support/report/issue_summary/issue_summary.py b/erpnext/support/report/issue_summary/issue_summary.py index bba25b8bed6..7c4af39f104 100644 --- a/erpnext/support/report/issue_summary/issue_summary.py +++ b/erpnext/support/report/issue_summary/issue_summary.py @@ -362,4 +362,3 @@ class IssueSummary(object): 'datatype': 'Int', } ] - diff --git a/erpnext/support/web_form/issues/issues.js b/erpnext/support/web_form/issues/issues.js index 699703c5792..ffc5e984253 100644 --- a/erpnext/support/web_form/issues/issues.js +++ b/erpnext/support/web_form/issues/issues.js @@ -1,3 +1,3 @@ frappe.ready(function() { // bind events here -}) \ No newline at end of file +}) diff --git a/erpnext/telephony/doctype/call_log/call_log.py b/erpnext/telephony/doctype/call_log/call_log.py index 4d553df08b8..6f8e4116956 100644 --- a/erpnext/telephony/doctype/call_log/call_log.py +++ b/erpnext/telephony/doctype/call_log/call_log.py @@ -142,7 +142,7 @@ def link_existing_conversations(doc, state): for log in logs: call_log = frappe.get_doc('Call Log', log) call_log.add_link(link_type=doc.doctype, link_name=doc.name) - call_log.save() + call_log.save(ignore_permissions=True) frappe.db.commit() except Exception: frappe.log_error(title=_('Error during caller information update')) @@ -173,4 +173,3 @@ def get_linked_call_logs(doctype, docname): }) return timeline_contents - diff --git a/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.js b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.js index 1bcc8461323..b80acdb3760 100644 --- a/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.js +++ b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.js @@ -99,4 +99,3 @@ frappe.ui.form.on('Incoming Call Settings', { validate_call_schedule(frm.doc.call_handling_schedule); } }); - diff --git a/erpnext/templates/emails/birthday_reminder.html b/erpnext/templates/emails/birthday_reminder.html index 12cdf1ec600..1f57b4969c0 100644 --- a/erpnext/templates/emails/birthday_reminder.html +++ b/erpnext/templates/emails/birthday_reminder.html @@ -22,4 +22,4 @@ {{ reminder_text }}

          {{ message }}

          - \ No newline at end of file + diff --git a/erpnext/templates/emails/daily_project_summary.html b/erpnext/templates/emails/daily_project_summary.html index 8b60830db62..5ccc6101665 100644 --- a/erpnext/templates/emails/daily_project_summary.html +++ b/erpnext/templates/emails/daily_project_summary.html @@ -43,4 +43,4 @@
          -{% endfor %} \ No newline at end of file +{% endfor %} diff --git a/erpnext/templates/emails/daily_work_summary.html b/erpnext/templates/emails/daily_work_summary.html index a22e09cb8de..1764e8f7038 100644 --- a/erpnext/templates/emails/daily_work_summary.html +++ b/erpnext/templates/emails/daily_work_summary.html @@ -52,4 +52,4 @@ -{% endif %} \ No newline at end of file +{% endif %} diff --git a/erpnext/templates/emails/request_for_quotation.html b/erpnext/templates/emails/request_for_quotation.html index 812939a5538..3283987fab0 100644 --- a/erpnext/templates/emails/request_for_quotation.html +++ b/erpnext/templates/emails/request_for_quotation.html @@ -21,4 +21,4 @@

          -{% endif %} \ No newline at end of file +{% endif %} diff --git a/erpnext/templates/emails/training_event.html b/erpnext/templates/emails/training_event.html index 51c232d8e87..8a2414a3c92 100644 --- a/erpnext/templates/emails/training_event.html +++ b/erpnext/templates/emails/training_event.html @@ -11,7 +11,7 @@

          {{_("Update Response")}}

          {% if not self_study %}

          {{_("Please update your status for this training event")}}:

          -
          +
          {% else %}

          {{_("Please confirm once you have completed your training")}}:

          diff --git a/erpnext/templates/generators/item/item.html b/erpnext/templates/generators/item/item.html index 135982d7090..663ea79f4ec 100644 --- a/erpnext/templates/generators/item/item.html +++ b/erpnext/templates/generators/item/item.html @@ -33,4 +33,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/templates/generators/item/item_inquiry.js b/erpnext/templates/generators/item/item_inquiry.js index e7db3a368df..4724b681196 100644 --- a/erpnext/templates/generators/item/item_inquiry.js +++ b/erpnext/templates/generators/item/item_inquiry.js @@ -74,4 +74,4 @@ frappe.ready(() => { d.show(); }); -}); \ No newline at end of file +}); diff --git a/erpnext/templates/generators/item/item_specifications.html b/erpnext/templates/generators/item/item_specifications.html index 469a45fd7d4..d4dfa8e591a 100644 --- a/erpnext/templates/generators/item/item_specifications.html +++ b/erpnext/templates/generators/item/item_specifications.html @@ -11,4 +11,4 @@ -{%- endif %} \ No newline at end of file +{%- endif %} diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html index 393c3a43afb..b5f18ba66d1 100644 --- a/erpnext/templates/generators/item_group.html +++ b/erpnext/templates/generators/item_group.html @@ -9,7 +9,7 @@ {% endblock %} {% block page_content %} -
          +
          {% if slideshow %} {{ web_block( @@ -127,15 +127,36 @@
          -
          -
          +
          +
          +
          +
          {% if frappe.form_dict.start|int > 0 %} - + {% endif %} {% if items|length >= page_length %} - + {% endif %}
          -{% endblock %} \ No newline at end of file + + +{% endblock %} diff --git a/erpnext/templates/generators/job_opening.html b/erpnext/templates/generators/job_opening.html index c562db3c25a..135fb3643d3 100644 --- a/erpnext/templates/generators/job_opening.html +++ b/erpnext/templates/generators/job_opening.html @@ -14,17 +14,17 @@
          {{ description }}
          {% endif %} -{%- if publish_salary_range -%} +{%- if publish_salary_range -%}
          {{_("Salary range per month")}}: {{ frappe.format_value(frappe.utils.flt(lower_range), currency=currency) }} - {{ frappe.format_value(frappe.utils.flt(upper_range), currency=currency) }}
          {% endif %}

          {%- if job_application_route -%} - {{ _("Apply Now") }} {% else %} - {{ _("Apply Now") }} {% endif %} diff --git a/erpnext/templates/generators/student_admission.html b/erpnext/templates/generators/student_admission.html index 8b153448eea..8cc58a0a1f2 100644 --- a/erpnext/templates/generators/student_admission.html +++ b/erpnext/templates/generators/student_admission.html @@ -14,7 +14,7 @@ {%- if introduction -%}

          {{ introduction }}
          -{% endif %} +{% endif %} {%- if doc.enable_admission_application -%}

          diff --git a/erpnext/templates/includes/cart/address_picker_card.html b/erpnext/templates/includes/cart/address_picker_card.html index 2334ea2955d..646210e65f1 100644 --- a/erpnext/templates/includes/cart/address_picker_card.html +++ b/erpnext/templates/includes/cart/address_picker_card.html @@ -9,4 +9,4 @@

          {{ _('Edit') }}
          -
          \ No newline at end of file + diff --git a/erpnext/templates/includes/cart/cart_address_picker.html b/erpnext/templates/includes/cart/cart_address_picker.html index 72cc5f51423..66a50ecc9f3 100644 --- a/erpnext/templates/includes/cart/cart_address_picker.html +++ b/erpnext/templates/includes/cart/cart_address_picker.html @@ -1,4 +1,3 @@
          {{ _("Shipping Address") }}
          - diff --git a/erpnext/templates/includes/cart/cart_items_dropdown.html b/erpnext/templates/includes/cart/cart_items_dropdown.html index b2ba4312d6c..5d107fc0d06 100644 --- a/erpnext/templates/includes/cart/cart_items_dropdown.html +++ b/erpnext/templates/includes/cart/cart_items_dropdown.html @@ -9,4 +9,4 @@ {{ d.get_formatted("amount") }} -{% endfor %} \ No newline at end of file +{% endfor %} diff --git a/erpnext/templates/includes/course/macros.html b/erpnext/templates/includes/course/macros.html index c80dca4bcc9..334b5ea200a 100644 --- a/erpnext/templates/includes/course/macros.html +++ b/erpnext/templates/includes/course/macros.html @@ -1 +1 @@ -{% macro back_link(doc) %}&back-to=/courses?course={{ doc.name }}&back-to-title={{ doc.course_name }}{% endmacro %} \ No newline at end of file +{% macro back_link(doc) %}&back-to=/courses?course={{ doc.name }}&back-to-title={{ doc.course_name }}{% endmacro %} diff --git a/erpnext/templates/includes/itemised_tax_breakup.html b/erpnext/templates/includes/itemised_tax_breakup.html index c2f13539cdf..5652bb1dddd 100644 --- a/erpnext/templates/includes/itemised_tax_breakup.html +++ b/erpnext/templates/includes/itemised_tax_breakup.html @@ -43,4 +43,4 @@ {% endfor %} - \ No newline at end of file + diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html index c44bfb53848..be0d47f3715 100644 --- a/erpnext/templates/includes/macros.html +++ b/erpnext/templates/includes/macros.html @@ -120,4 +120,4 @@ {% endif %} -{%- endmacro -%} \ No newline at end of file +{%- endmacro -%} diff --git a/erpnext/templates/includes/navbar/navbar_items.html b/erpnext/templates/includes/navbar/navbar_items.html index 133d99e5eb9..291220629c9 100644 --- a/erpnext/templates/includes/navbar/navbar_items.html +++ b/erpnext/templates/includes/navbar/navbar_items.html @@ -9,4 +9,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/templates/includes/order/order_macros.html b/erpnext/templates/includes/order/order_macros.html index da4fb8c0460..7b3c9a41318 100644 --- a/erpnext/templates/includes/order/order_macros.html +++ b/erpnext/templates/includes/order/order_macros.html @@ -40,4 +40,4 @@ -{% endmacro %} \ No newline at end of file +{% endmacro %} diff --git a/erpnext/templates/includes/projects.css b/erpnext/templates/includes/projects.css index 5a717fc6699..5d9fc50385e 100644 --- a/erpnext/templates/includes/projects.css +++ b/erpnext/templates/includes/projects.css @@ -86,4 +86,4 @@ .progress-hg{ margin-bottom: 30!important; height:2px; -} \ No newline at end of file +} diff --git a/erpnext/templates/includes/projects/project_search_box.html b/erpnext/templates/includes/projects/project_search_box.html index 6f53bae2e80..d7466873dda 100644 --- a/erpnext/templates/includes/projects/project_search_box.html +++ b/erpnext/templates/includes/projects/project_search_box.html @@ -27,4 +27,4 @@ frappe.ready(function() { }); $(".form-search").on("submit", function() { return false; }); }); - \ No newline at end of file + diff --git a/erpnext/templates/includes/salary_slip_log.html b/erpnext/templates/includes/salary_slip_log.html index 107df51dd8b..d36ee6e23bb 100644 --- a/erpnext/templates/includes/salary_slip_log.html +++ b/erpnext/templates/includes/salary_slip_log.html @@ -16,4 +16,4 @@ {% endfor %} - \ No newline at end of file + diff --git a/erpnext/templates/includes/topic/topic_row.html b/erpnext/templates/includes/topic/topic_row.html index 3401bd39371..38d46b7fe00 100644 --- a/erpnext/templates/includes/topic/topic_row.html +++ b/erpnext/templates/includes/topic/topic_row.html @@ -1,4 +1,4 @@ -
          + \ No newline at end of file +
          diff --git a/erpnext/templates/pages/cart_terms.html b/erpnext/templates/pages/cart_terms.html index 521c583cb60..6d84fb86a78 100644 --- a/erpnext/templates/pages/cart_terms.html +++ b/erpnext/templates/pages/cart_terms.html @@ -1,2 +1,2 @@ -
          {{doc.terms}}
          \ No newline at end of file +
          {{doc.terms}}
          diff --git a/erpnext/templates/pages/courses.html b/erpnext/templates/pages/courses.html index 42e7f3e70b7..6592f7a2e5c 100644 --- a/erpnext/templates/pages/courses.html +++ b/erpnext/templates/pages/courses.html @@ -8,4 +8,4 @@

          {{ intro }}

          -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/templates/pages/courses.py b/erpnext/templates/pages/courses.py index c80d8e7d229..92c38f6fcae 100644 --- a/erpnext/templates/pages/courses.py +++ b/erpnext/templates/pages/courses.py @@ -17,4 +17,3 @@ def get_context(context): context.doc = course context.sidebar_title = sidebar_title context.intro = course.course_intro - diff --git a/erpnext/templates/pages/home.css b/erpnext/templates/pages/home.css index cf5476635bd..785d8059ba0 100644 --- a/erpnext/templates/pages/home.css +++ b/erpnext/templates/pages/home.css @@ -6,4 +6,4 @@ padding: 10rem 0; } {% endif %} -/* csslint ignore:end */ \ No newline at end of file +/* csslint ignore:end */ diff --git a/erpnext/templates/pages/home.html b/erpnext/templates/pages/home.html index 2ef9c105347..9a61eabaf8c 100644 --- a/erpnext/templates/pages/home.html +++ b/erpnext/templates/pages/home.html @@ -72,4 +72,4 @@ {{ render_homepage_section(section) }} {% endfor %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/templates/pages/integrations/gocardless_checkout.html b/erpnext/templates/pages/integrations/gocardless_checkout.html index 7193d755a1e..6072db49ea9 100644 --- a/erpnext/templates/pages/integrations/gocardless_checkout.html +++ b/erpnext/templates/pages/integrations/gocardless_checkout.html @@ -13,4 +13,4 @@ {{ _("Loading Payment System") }}

          -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/templates/pages/integrations/gocardless_checkout.py b/erpnext/templates/pages/integrations/gocardless_checkout.py index 96a0f42a05c..bdef79cfbed 100644 --- a/erpnext/templates/pages/integrations/gocardless_checkout.py +++ b/erpnext/templates/pages/integrations/gocardless_checkout.py @@ -74,4 +74,4 @@ def check_mandate(data, reference_doctype, reference_docname): except Exception as e: frappe.log_error(e, "GoCardless Payment Error") - return {"redirect_to": '/integrations/payment-failed'} \ No newline at end of file + return {"redirect_to": '/integrations/payment-failed'} diff --git a/erpnext/templates/pages/integrations/gocardless_confirmation.html b/erpnext/templates/pages/integrations/gocardless_confirmation.html index 6ba154a06c7..d961c6344af 100644 --- a/erpnext/templates/pages/integrations/gocardless_confirmation.html +++ b/erpnext/templates/pages/integrations/gocardless_confirmation.html @@ -13,4 +13,4 @@ {{ _("Payment Confirmation") }}

          -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/templates/pages/integrations/gocardless_confirmation.py b/erpnext/templates/pages/integrations/gocardless_confirmation.py index cfaa1a15cfc..0b72e9f8b60 100644 --- a/erpnext/templates/pages/integrations/gocardless_confirmation.py +++ b/erpnext/templates/pages/integrations/gocardless_confirmation.py @@ -86,4 +86,4 @@ def create_mandate(data): }).insert(ignore_permissions=True) except Exception: - frappe.log_error(frappe.get_traceback()) \ No newline at end of file + frappe.log_error(frappe.get_traceback()) diff --git a/erpnext/templates/pages/material_request_info.html b/erpnext/templates/pages/material_request_info.html index 0c2772e4d82..151d029ee47 100644 --- a/erpnext/templates/pages/material_request_info.html +++ b/erpnext/templates/pages/material_request_info.html @@ -71,4 +71,4 @@ {% endfor %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/templates/pages/material_request_info.py b/erpnext/templates/pages/material_request_info.py index 28e541a5d92..e29860ddd67 100644 --- a/erpnext/templates/pages/material_request_info.py +++ b/erpnext/templates/pages/material_request_info.py @@ -19,7 +19,7 @@ def get_context(context): if not frappe.has_website_permission(context.doc): frappe.throw(_("Not Permitted"), frappe.PermissionError) - + default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=frappe.form_dict.doctype), "value") if default_print_format: context.print_format = default_print_format @@ -45,5 +45,5 @@ def get_more_items_info(items, material_request): item.delivered_qty = flt(frappe.db.sql("""select sum(transfer_qty) from `tabStock Entry Detail` where material_request = %s and item_code = %s and docstatus = 1""", - (material_request, item.item_code))[0][0]) - return items \ No newline at end of file + (material_request, item.item_code))[0][0]) + return items diff --git a/erpnext/templates/pages/non_profit/join-chapter.html b/erpnext/templates/pages/non_profit/join-chapter.html index 89a7d2aace8..4923efc4e8c 100644 --- a/erpnext/templates/pages/non_profit/join-chapter.html +++ b/erpnext/templates/pages/non_profit/join-chapter.html @@ -56,4 +56,4 @@ {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/templates/pages/non_profit/leave-chapter.html b/erpnext/templates/pages/non_profit/leave-chapter.html index bc4242f9196..fd7658b3b1e 100644 --- a/erpnext/templates/pages/non_profit/leave-chapter.html +++ b/erpnext/templates/pages/non_profit/leave-chapter.html @@ -39,4 +39,4 @@ }); }) -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py index 34985d94ea7..816a25963f5 100644 --- a/erpnext/templates/pages/order.py +++ b/erpnext/templates/pages/order.py @@ -32,9 +32,9 @@ def get_context(context): if not frappe.has_website_permission(context.doc): frappe.throw(_("Not Permitted"), frappe.PermissionError) - + # check for the loyalty program of the customer - customer_loyalty_program = frappe.db.get_value("Customer", context.doc.customer, "loyalty_program") + customer_loyalty_program = frappe.db.get_value("Customer", context.doc.customer, "loyalty_program") if customer_loyalty_program: from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points loyalty_program_details = get_loyalty_program_details_with_points(context.doc.customer, customer_loyalty_program) diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py index d0d72f073a9..9ab76deff73 100644 --- a/erpnext/templates/pages/product_search.py +++ b/erpnext/templates/pages/product_search.py @@ -47,4 +47,3 @@ def get_product_list(search=None, start=0, limit=12): set_product_info_for_website(item) return [get_item_for_list_in_html(r) for r in data] - diff --git a/erpnext/templates/pages/projects.js b/erpnext/templates/pages/projects.js index 262167fc0b9..bd6bcea7ca0 100644 --- a/erpnext/templates/pages/projects.js +++ b/erpnext/templates/pages/projects.js @@ -117,4 +117,4 @@ frappe.ready(function() { }) return false; } -}); \ No newline at end of file +}); diff --git a/erpnext/templates/pages/task_info.html b/erpnext/templates/pages/task_info.html index 6cd6a7e51af..fe4d304a398 100644 --- a/erpnext/templates/pages/task_info.html +++ b/erpnext/templates/pages/task_info.html @@ -147,4 +147,4 @@ }); -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/templates/pages/task_info.py b/erpnext/templates/pages/task_info.py index b832b88048b..260e2788cd0 100644 --- a/erpnext/templates/pages/task_info.py +++ b/erpnext/templates/pages/task_info.py @@ -7,8 +7,8 @@ def get_context(context): context.no_cache = 1 task = frappe.get_doc('Task', frappe.form_dict.task) - + context.comments = frappe.get_all('Communication', filters={'reference_name': task.name, 'comment_type': 'comment'}, fields=['subject', 'sender_full_name', 'communication_date']) - - context.doc = task \ No newline at end of file + + context.doc = task diff --git a/erpnext/templates/pages/timelog_info.html b/erpnext/templates/pages/timelog_info.html index 22ea3e45d38..be13826444c 100644 --- a/erpnext/templates/pages/timelog_info.html +++ b/erpnext/templates/pages/timelog_info.html @@ -45,4 +45,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/templates/pages/timelog_info.py b/erpnext/templates/pages/timelog_info.py index 7a3361c2ef5..ee86483fa29 100644 --- a/erpnext/templates/pages/timelog_info.py +++ b/erpnext/templates/pages/timelog_info.py @@ -7,5 +7,5 @@ def get_context(context): context.no_cache = 1 timelog = frappe.get_doc('Time Log', frappe.form_dict.timelog) - - context.doc = timelog \ No newline at end of file + + context.doc = timelog diff --git a/erpnext/templates/print_formats/includes/item_table_qty.html b/erpnext/templates/print_formats/includes/item_table_qty.html index 8e68f1cc638..aaa949192ca 100644 --- a/erpnext/templates/print_formats/includes/item_table_qty.html +++ b/erpnext/templates/print_formats/includes/item_table_qty.html @@ -12,4 +12,3 @@ {%- endif %} {{ doc.get_formatted("qty", doc) }} {%- endif %} - diff --git a/erpnext/tests/test_regional.py b/erpnext/tests/test_regional.py index 282fc6454b7..5b3f45a1af7 100644 --- a/erpnext/tests/test_regional.py +++ b/erpnext/tests/test_regional.py @@ -14,4 +14,4 @@ class TestInit(unittest.TestCase): self.assertEqual(test_method(), 'original') frappe.flags.country = 'France' - self.assertEqual(test_method(), 'overridden') \ No newline at end of file + self.assertEqual(test_method(), 'overridden') diff --git a/erpnext/tests/test_subcontracting.py b/erpnext/tests/test_subcontracting.py index 8b0ce0957d4..f55137bc9cf 100644 --- a/erpnext/tests/test_subcontracting.py +++ b/erpnext/tests/test_subcontracting.py @@ -874,4 +874,4 @@ def make_bom_for_subcontracted_items(): def set_backflush_based_on(based_on): frappe.db.set_value('Buying Settings', None, - 'backflush_raw_materials_of_subcontract_based_on', based_on) \ No newline at end of file + 'backflush_raw_materials_of_subcontract_based_on', based_on) diff --git a/erpnext/tests/ui/setup_wizard.js b/erpnext/tests/ui/setup_wizard.js index aeb8d2a1167..ccff785ec94 100644 --- a/erpnext/tests/ui/setup_wizard.js +++ b/erpnext/tests/ui/setup_wizard.js @@ -44,4 +44,4 @@ module.exports = { after: browser => { browser.end(); }, -}; \ No newline at end of file +}; diff --git a/erpnext/tests/ui_test_helpers.py b/erpnext/tests/ui_test_helpers.py new file mode 100644 index 00000000000..902fd64d686 --- /dev/null +++ b/erpnext/tests/ui_test_helpers.py @@ -0,0 +1,59 @@ +import frappe +from frappe.utils import getdate + +@frappe.whitelist() +def create_employee_records(): + create_company() + create_missing_designation() + + emp1 = create_employee('Test Employee 1', 'CEO') + emp2 = create_employee('Test Employee 2', 'CTO') + emp3 = create_employee('Test Employee 3', 'Head of Marketing and Sales', emp1) + emp4 = create_employee('Test Employee 4', 'Project Manager', emp2) + emp5 = create_employee('Test Employee 5', 'Engineer', emp2) + emp6 = create_employee('Test Employee 6', 'Analyst', emp3) + emp7 = create_employee('Test Employee 7', 'Software Developer', emp4) + + employees = [emp1, emp2, emp3, emp4, emp5, emp6, emp7] + return employees + +@frappe.whitelist() +def get_employee_records(): + return frappe.db.get_list('Employee', filters={ + 'company': 'Test Org Chart' + }, pluck='name', order_by='name') + +def create_company(): + company = frappe.db.exists('Company', 'Test Org Chart') + if not company: + company = frappe.get_doc({ + 'doctype': 'Company', + 'company_name': 'Test Org Chart', + 'country': 'India', + 'default_currency': 'INR' + }).insert().name + + return company + +def create_employee(first_name, designation, reports_to=None): + employee = frappe.db.exists('Employee', {'first_name': first_name, 'designation': designation}) + if not employee: + employee = frappe.get_doc({ + 'doctype': 'Employee', + 'first_name': first_name, + 'company': 'Test Org Chart', + 'gender': 'Female', + 'date_of_birth': getdate('08-12-1998'), + 'date_of_joining': getdate('01-01-2021'), + 'designation': designation, + 'reports_to': reports_to + }).insert().name + + return employee + +def create_missing_designation(): + if not frappe.db.exists('Designation', 'CTO'): + frappe.get_doc({ + 'doctype': 'Designation', + 'designation_name': 'CTO' + }).insert() diff --git a/erpnext/utilities/activation.py b/erpnext/utilities/activation.py index 50c4b255ce1..0f9f2f886de 100644 --- a/erpnext/utilities/activation.py +++ b/erpnext/utilities/activation.py @@ -13,33 +13,33 @@ def get_level(): min_count = 0 doctypes = { "Asset": 5, - "BOM": 3, - "Customer": 5, + "BOM": 3, + "Customer": 5, "Delivery Note": 5, - "Employee": 3, - "Instructor": 5, + "Employee": 3, + "Instructor": 5, "Issue": 5, - "Item": 5, - "Journal Entry": 3, + "Item": 5, + "Journal Entry": 3, "Lead": 3, "Leave Application": 5, "Material Request": 5, - "Opportunity": 5, - "Payment Entry": 2, + "Opportunity": 5, + "Payment Entry": 2, "Project": 5, - "Purchase Order": 2, + "Purchase Order": 2, "Purchase Invoice": 5, "Purchase Receipt": 5, "Quotation": 3, "Salary Slip": 5, "Salary Structure": 5, - "Sales Order": 2, - "Sales Invoice": 2, + "Sales Order": 2, + "Sales Invoice": 2, "Stock Entry": 3, - "Student": 5, + "Student": 5, "Supplier": 5, "Task": 5, - "User": 5, + "User": 5, "Work Order": 5 } diff --git a/erpnext/utilities/bot.py b/erpnext/utilities/bot.py index b2e74da9215..485b0b3383f 100644 --- a/erpnext/utilities/bot.py +++ b/erpnext/utilities/bot.py @@ -36,4 +36,4 @@ class FindItemBot(BotParser): return "\n\n".join(out) else: - return _("Did not find any item called {0}").format(item) \ No newline at end of file + return _("Did not find any item called {0}").format(item) diff --git a/erpnext/utilities/doctype/rename_tool/rename_tool.py b/erpnext/utilities/doctype/rename_tool/rename_tool.py index 0f8a7a385c1..5e3ac1a4c92 100644 --- a/erpnext/utilities/doctype/rename_tool/rename_tool.py +++ b/erpnext/utilities/doctype/rename_tool/rename_tool.py @@ -29,4 +29,3 @@ def upload(select_doctype=None, rows=None): rows = read_csv_content_from_attached_file(frappe.get_doc("Rename Tool", "Rename Tool")) return bulk_rename(select_doctype, rows=rows) - diff --git a/erpnext/utilities/doctype/video/video_list.js b/erpnext/utilities/doctype/video/video_list.js index 8273a4a781f..6f78f6ee127 100644 --- a/erpnext/utilities/doctype/video/video_list.js +++ b/erpnext/utilities/doctype/video/video_list.js @@ -4,4 +4,4 @@ frappe.listview_settings["Video"] = { frappe.set_route("Form","Video Settings", "Video Settings"); }); } -} \ No newline at end of file +} diff --git a/erpnext/utilities/doctype/video_settings/video_settings.py b/erpnext/utilities/doctype/video_settings/video_settings.py index 36fb54f0150..db021b473a4 100644 --- a/erpnext/utilities/doctype/video_settings/video_settings.py +++ b/erpnext/utilities/doctype/video_settings/video_settings.py @@ -19,4 +19,4 @@ class VideoSettings(Document): except Exception: title = _("Failed to Authenticate the API key.") frappe.log_error(title + "\n\n" + frappe.get_traceback(), title=title) - frappe.throw(title + " Please check the error logs.", title=_("Invalid Credentials")) \ No newline at end of file + frappe.throw(title + " Please check the error logs.", title=_("Invalid Credentials")) diff --git a/erpnext/utilities/hierarchy_chart.py b/erpnext/utilities/hierarchy_chart.py new file mode 100644 index 00000000000..384d84194bb --- /dev/null +++ b/erpnext/utilities/hierarchy_chart.py @@ -0,0 +1,29 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ + +@frappe.whitelist() +def get_all_nodes(parent, parent_name, method, company): + '''Recursively gets all data from nodes''' + method = frappe.get_attr(method) + + if method not in frappe.whitelisted: + frappe.throw(_('Not Permitted'), frappe.PermissionError) + + data = method(parent, company) + result = [dict(parent=parent, parent_name=parent_name, data=data)] + + nodes_to_expand = [{'id': d.get('id'), 'name': d.get('name')} for d in data if d.get('expandable')] + + while nodes_to_expand: + parent = nodes_to_expand.pop(0) + data = method(parent.get('id'), company) + result.append(dict(parent=parent.get('id'), parent_name=parent.get('name'), data=data)) + for d in data: + if d.get('expandable'): + nodes_to_expand.append({'id': d.get('id'), 'name': d.get('name')}) + + return result diff --git a/erpnext/utilities/report/youtube_interactions/youtube_interactions.py b/erpnext/utilities/report/youtube_interactions/youtube_interactions.py index 3516a35097a..29a489ddcc7 100644 --- a/erpnext/utilities/report/youtube_interactions/youtube_interactions.py +++ b/erpnext/utilities/report/youtube_interactions/youtube_interactions.py @@ -110,4 +110,4 @@ def get_chart_summary_data(data): "datatype": "Float", } ] - return chart_data, summary \ No newline at end of file + return chart_data, summary diff --git a/erpnext/utilities/web_form/addresses/addresses.js b/erpnext/utilities/web_form/addresses/addresses.js index 699703c5792..ffc5e984253 100644 --- a/erpnext/utilities/web_form/addresses/addresses.js +++ b/erpnext/utilities/web_form/addresses/addresses.js @@ -1,3 +1,3 @@ frappe.ready(function() { // bind events here -}) \ No newline at end of file +}) diff --git a/erpnext/www/all-products/index.html b/erpnext/www/all-products/index.html index 92c76ad8790..7c18ecc41fe 100644 --- a/erpnext/www/all-products/index.html +++ b/erpnext/www/all-products/index.html @@ -164,4 +164,4 @@ }); -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js index 0721056816b..1c641b59ad1 100644 --- a/erpnext/www/all-products/index.js +++ b/erpnext/www/all-products/index.js @@ -124,6 +124,10 @@ $(() => { attribute_filters: if_key_exists(attribute_filters) }; + const item_group = $(".item-group-content").data('item-group'); + if (item_group) { + Object.assign(field_filters, { item_group }); + } return new Promise((resolve, reject) => { frappe.call('erpnext.portal.product_configurator.utils.get_products_html_for_website', args) .then(r => { diff --git a/erpnext/www/all-products/item_row.html b/erpnext/www/all-products/item_row.html index 20fc9a4878c..a7e994c1e3f 100644 --- a/erpnext/www/all-products/item_row.html +++ b/erpnext/www/all-products/item_row.html @@ -4,4 +4,3 @@ item.item_name or item.name, item.website_image or item.image, item.route, item.website_description or item.description, item.formatted_price, item.item_group ) }} - diff --git a/erpnext/www/all-products/not_found.html b/erpnext/www/all-products/not_found.html index e1986b44154..91989a9ef48 100644 --- a/erpnext/www/all-products/not_found.html +++ b/erpnext/www/all-products/not_found.html @@ -1 +1 @@ -
          {{ _('No products found') }}
          \ No newline at end of file +
          {{ _('No products found') }}
          diff --git a/erpnext/www/book_appointment/index.css b/erpnext/www/book_appointment/index.css index 6c49fde739e..277610876f7 100644 --- a/erpnext/www/book_appointment/index.css +++ b/erpnext/www/book_appointment/index.css @@ -12,7 +12,7 @@ @media (max-width: 768px) { #submit-button-area { display: grid; - grid-template-areas: + grid-template-areas: "submit" "back"; } diff --git a/erpnext/www/book_appointment/index.html b/erpnext/www/book_appointment/index.html index f242f43ad54..207175f89dc 100644 --- a/erpnext/www/book_appointment/index.html +++ b/erpnext/www/book_appointment/index.html @@ -63,4 +63,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/www/book_appointment/index.py b/erpnext/www/book_appointment/index.py index ccfa97bc62f..d805c275bbc 100644 --- a/erpnext/www/book_appointment/index.py +++ b/erpnext/www/book_appointment/index.py @@ -148,4 +148,4 @@ def _deltatime_to_datetime(date, deltatime): def _datetime_to_deltatime(date_time): midnight = datetime.datetime.combine(date_time.date(), datetime.time.min) - return (date_time-midnight) \ No newline at end of file + return (date_time-midnight) diff --git a/erpnext/www/book_appointment/verify/index.html b/erpnext/www/book_appointment/verify/index.html index ebb65b1f24e..9bcd3d202e2 100644 --- a/erpnext/www/book_appointment/verify/index.html +++ b/erpnext/www/book_appointment/verify/index.html @@ -3,7 +3,7 @@ {% block title %} {{ _("Verify Email") }} {% endblock%} - + {% block page_content %} {% if success==True %} @@ -15,4 +15,4 @@ Verification failed please check the link {% endif %} -{% endblock%} \ No newline at end of file +{% endblock%} diff --git a/erpnext/www/book_appointment/verify/index.py b/erpnext/www/book_appointment/verify/index.py index d4478ae34a8..bd766c0ea8a 100644 --- a/erpnext/www/book_appointment/verify/index.py +++ b/erpnext/www/book_appointment/verify/index.py @@ -17,4 +17,4 @@ def get_context(context): return context else: context.success = False - return context \ No newline at end of file + return context diff --git a/erpnext/www/lms/content.py b/erpnext/www/lms/content.py index 0c048453625..05cbb16d3cb 100644 --- a/erpnext/www/lms/content.py +++ b/erpnext/www/lms/content.py @@ -65,4 +65,4 @@ def allowed_content_access(program, content, content_type): and `tabTopic Content`.parent = `tabCourse Topic`.topic and `tabProgram Course`.parent = %(program)s""", {'program': program}) - return (content, content_type) in contents_of_program \ No newline at end of file + return (content, content_type) in contents_of_program diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html index 0d70ed5cefd..c07b9402b10 100644 --- a/erpnext/www/lms/course.html +++ b/erpnext/www/lms/course.html @@ -103,4 +103,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/www/lms/index.py b/erpnext/www/lms/index.py index 26f59a2395e..c14b94326b5 100644 --- a/erpnext/www/lms/index.py +++ b/erpnext/www/lms/index.py @@ -13,4 +13,4 @@ def get_context(context): def get_featured_programs(): - return utils.get_portal_programs() or [] \ No newline at end of file + return utils.get_portal_programs() or [] diff --git a/erpnext/www/lms/macros/card.html b/erpnext/www/lms/macros/card.html index dc8fc5c72c7..3cbdec61aa0 100644 --- a/erpnext/www/lms/macros/card.html +++ b/erpnext/www/lms/macros/card.html @@ -31,4 +31,4 @@
          -{% endmacro %} \ No newline at end of file +{% endmacro %} diff --git a/erpnext/www/lms/macros/hero.html b/erpnext/www/lms/macros/hero.html index 94f239eb8ed..e72bfc8175b 100644 --- a/erpnext/www/lms/macros/hero.html +++ b/erpnext/www/lms/macros/hero.html @@ -52,4 +52,4 @@ } {% endblock %} -{% endmacro %} \ No newline at end of file +{% endmacro %} diff --git a/erpnext/www/lms/profile.py b/erpnext/www/lms/profile.py index 4788ea6e70b..7e338e38f13 100644 --- a/erpnext/www/lms/profile.py +++ b/erpnext/www/lms/profile.py @@ -23,4 +23,4 @@ def get_program_progress(student): completion = utils.get_program_completion(program) student_progress.append({'program': program.program_name, 'name': program.name, 'progress':progress, 'completion': completion}) - return student_progress \ No newline at end of file + return student_progress diff --git a/erpnext/www/lms/program.html b/erpnext/www/lms/program.html index 7ad618630a4..30528c667dd 100644 --- a/erpnext/www/lms/program.html +++ b/erpnext/www/lms/program.html @@ -84,4 +84,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/www/lms/program.py b/erpnext/www/lms/program.py index 104d3fa315a..a4f588ccf18 100644 --- a/erpnext/www/lms/program.py +++ b/erpnext/www/lms/program.py @@ -26,4 +26,4 @@ def get_program(program_name): def get_course_progress(courses, program): progress = {course.name: utils.get_course_progress(course, program) for course in courses} - return progress or {} \ No newline at end of file + return progress or {} diff --git a/erpnext/www/lms/topic.html b/erpnext/www/lms/topic.html index cd24616cd45..dc69599112a 100644 --- a/erpnext/www/lms/topic.html +++ b/erpnext/www/lms/topic.html @@ -55,4 +55,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/www/lms/topic.py b/erpnext/www/lms/topic.py index 8abbc72e918..993828090ce 100644 --- a/erpnext/www/lms/topic.py +++ b/erpnext/www/lms/topic.py @@ -42,4 +42,4 @@ def get_contents(topic, course, program): result = None progress.append({'content': content, 'content_type': content.doctype, 'completed': status, 'score': score, 'result': result}) - return progress \ No newline at end of file + return progress diff --git a/erpnext/www/support/index.html b/erpnext/www/support/index.html index 12b4c2c0819..3c19198cc16 100644 --- a/erpnext/www/support/index.html +++ b/erpnext/www/support/index.html @@ -96,6 +96,6 @@ .search-container { margin-top: 1.2rem; max-width: 500px; - } + } {%- endblock -%} diff --git a/erpnext/www/support/index.py b/erpnext/www/support/index.py index 5d267430c16..70090c7805d 100644 --- a/erpnext/www/support/index.py +++ b/erpnext/www/support/index.py @@ -8,7 +8,7 @@ def get_context(context): context.greeting_title = setting.greeting_title context.greeting_subtitle = setting.greeting_subtitle - + # Support content favorite_articles = get_favorite_articles_by_page_view() if len(favorite_articles) < 6: @@ -16,15 +16,15 @@ def get_context(context): if favorite_articles: for article in favorite_articles: name_list.append(article.name) - for record in (frappe.get_all("Help Article", - fields=["title", "content", "route", "category"], - filters={"name": ['not in', tuple(name_list)], "published": 1}, + for record in (frappe.get_all("Help Article", + fields=["title", "content", "route", "category"], + filters={"name": ['not in', tuple(name_list)], "published": 1}, order_by="creation desc", limit=(6-len(favorite_articles)))): favorite_articles.append(record) - + context.favorite_article_list = get_favorite_articles(favorite_articles) context.help_article_list = get_help_article_list() - + def get_favorite_articles_by_page_view(): return frappe.db.sql( """ @@ -34,13 +34,13 @@ def get_favorite_articles_by_page_view(): t1.content as content, t1.route as route, t1.category as category, - count(t1.route) as count - FROM `tabHelp Article` AS t1 + count(t1.route) as count + FROM `tabHelp Article` AS t1 INNER JOIN - `tabWeb Page View` AS t2 - ON t1.route = t2.path + `tabWeb Page View` AS t2 + ON t1.route = t2.path WHERE t1.published = 1 - GROUP BY route + GROUP BY route ORDER BY count DESC LIMIT 6; """, as_dict=True) @@ -71,4 +71,4 @@ def get_help_article_list(): 'articles': help_articles, } help_article_list.append(help_aricles_per_caetgory) - return help_article_list \ No newline at end of file + return help_article_list diff --git a/package.json b/package.json index c9ee7a622c4..5bc1e56a210 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "snyk": "^1.518.0" }, "dependencies": { - "onscan.js": "^1.5.2" + "onscan.js": "^1.5.2", + "html2canvas": "^1.1.4" }, "scripts": { "snyk-protect": "snyk protect", diff --git a/yarn.lock b/yarn.lock index 0a2ac1affc8..cc01d89344a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -688,6 +688,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-arraybuffer@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz#4b944fac0191aa5907afe2d8c999ccc57ce80f45" + integrity sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ== + base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -997,6 +1002,13 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== +css-line-break@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-1.1.1.tgz#d5e9bdd297840099eb0503c7310fd34927a026ef" + integrity sha512-1feNVaM4Fyzdj4mKPIQNL2n70MmuYzAXZ1aytlROFX1JsOo070OsugwGjj7nl6jnDJWHDM8zRZswkmeYVWZJQA== + dependencies: + base64-arraybuffer "^0.2.0" + debug@^3.1.0, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -1472,6 +1484,13 @@ hosted-git-info@^3.0.4, hosted-git-info@^3.0.7: dependencies: lru-cache "^6.0.0" +html2canvas@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.1.4.tgz#53ae91cd26e9e9e623c56533cccb2e3f57c8124c" + integrity sha512-uHgQDwrXsRmFdnlOVFvHin9R7mdjjZvoBoXxicPR+NnucngkaLa5zIDW9fzMkiip0jSffyTyWedE8iVogYOeWg== + dependencies: + css-line-break "1.1.1" + http-cache-semantics@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"