diff --git a/.github/workflows/initiate_release.yml b/.github/workflows/initiate_release.yml index f57d46b354d..51cc911f0a0 100644 --- a/.github/workflows/initiate_release.yml +++ b/.github/workflows/initiate_release.yml @@ -8,8 +8,8 @@ permissions: on: schedule: - # 9:30 UTC => 3 PM IST Tuesday - - cron: "30 9 * * 2" + # 9:30 UTC => 3 PM IST + - cron: "30 9 * * 1,4" workflow_dispatch: jobs: @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - version: ["14", "15", "16"] + version: ["15", "16"] steps: - uses: octokit/request-action@v2.x diff --git a/.github/workflows/run-indinvidual-tests.yml b/.github/workflows/run-individual-tests.yml similarity index 82% rename from .github/workflows/run-indinvidual-tests.yml rename to .github/workflows/run-individual-tests.yml index 3e4b8371f8a..a70a2394757 100644 --- a/.github/workflows/run-indinvidual-tests.yml +++ b/.github/workflows/run-individual-tests.yml @@ -4,8 +4,8 @@ on: workflow_dispatch: concurrency: - group: server-individual-tests-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }} - cancel-in-progress: false + group: server-individual-tests-lightmode-develop + cancel-in-progress: true permissions: contents: read @@ -21,7 +21,7 @@ jobs: - id: set-matrix run: | # Use grep and find to get the list of test files - matrix=$(find . -path '*/doctype/*/test_*.py' | xargs grep -l 'def test_' | awk '{ + matrix=$(find . -path '*/test_*.py' | xargs grep -l 'def test_' | sort | awk '{ # Remove ./ prefix, file extension, and replace / with . gsub(/^\.\//, "", $0) gsub(/\.py$/, "", $0) @@ -58,6 +58,7 @@ jobs: strategy: fail-fast: false matrix: ${{fromJson(needs.discover.outputs.matrix)}} + max-parallel: 14 name: Test @@ -130,4 +131,13 @@ jobs: FRAPPE_BRANCH: ${{ github.event.inputs.branch }} - name: Run Tests - run: 'cd ~/frappe-bench/ && bench --site test_site run-tests --app erpnext --module ${{ matrix.test }}' + run: | + site_name=$(echo "${{matrix.test}}" | sed -e 's/.*\.\(test_.*$\)/\1/') + echo "$site_name" + mkdir ~/frappe-bench/sites/$site_name + cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config_mariadb.json" ~/frappe-bench/sites/$site_name/site_config.json + cd ~/frappe-bench/ + bench --site $site_name reinstall --yes + bench --site $site_name set-config allow_tests true + bench --site $site_name run-tests --module ${{ matrix.test }} --lightmode + diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml index 2bfe5e7065d..b8e88d1b5f3 100644 --- a/.github/workflows/server-tests-mariadb.yml +++ b/.github/workflows/server-tests-mariadb.yml @@ -129,7 +129,7 @@ jobs: FRAPPE_BRANCH: ${{ github.event.client_payload.sha || github.event.inputs.branch }} - name: Run Tests - run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --total-builds ${{ strategy.job-total }} --build-number ${{ matrix.container }} --with-coverage' + run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --lightmode --app erpnext --total-builds ${{ strategy.job-total }} --build-number ${{ matrix.container }} --with-coverage' env: TYPE: server diff --git a/CODEOWNERS b/CODEOWNERS index 328ea4cf713..804230ac8d0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -7,17 +7,17 @@ erpnext/accounts/ @ruthra-kumar erpnext/assets/ @khushi8112 erpnext/regional @ruthra-kumar erpnext/selling @ruthra-kumar -erpnext/support/ @ruthra-kumar erpnext/buying/ @rohitwaghchaure @mihir-kandoi -erpnext/maintenance/ @rohitwaghchaure +erpnext/maintenance/ @rohitwaghchaure @mihir-kandoi erpnext/manufacturing/ @rohitwaghchaure @mihir-kandoi -erpnext/quality_management/ @rohitwaghchaure +erpnext/quality_management/ @rohitwaghchaure @mihir-kandoi erpnext/stock/ @rohitwaghchaure @mihir-kandoi -erpnext/subcontracting @mihir-kandoi +erpnext/subcontracting/ @mihir-kandoi +erpnext/projects/ @nishkagosalia erpnext/controllers/ @ruthra-kumar @rohitwaghchaure @mihir-kandoi -erpnext/patches/ @ruthra-kumar +erpnext/patches/ @ruthra-kumar @rohitwaghchaure @mihir-kandoi -.github/ @ruthra-kumar +.github/ @ruthra-kumar @mihir-kandoi pyproject.toml @ruthra-kumar diff --git a/README.md b/README.md index 622c004e3ad..d44739f4839 100644 --- a/README.md +++ b/README.md @@ -75,25 +75,48 @@ It takes care of installation, setup, upgrades, monitoring, maintenance and supp - ### Self-Hosted #### Docker -Prerequisites: docker, docker-compose, git. Refer [Docker Documentation](https://docs.docker.com) for more details on Docker setup. +See [Frappe Docker Documentation](https://github.com/frappe/frappe_docker) for full documentation & FAQ on docker setup -Run following commands: +#### Prerequisites -``` +- [Docker](https://docs.docker.com/get-docker/) +- [Docker Compose v2](https://docs.docker.com/compose/) +- [git](https://docs.github.com/en/get-started/getting-started-with-git/set-up-git) + +> For Docker basics and best practices refer to Docker's [documentation](https://docs.docker.com) + +#### Demo setup + +The fastest way to try ERPNext is to play in an already set up sandbox, in your browser, click the button below: + + + Try in PWD + + +### Try on your environment + +> **⚠️ Disposable demo only** +> +> **This setup is intended for quick evaluation. Expect to throw the environment away.** You will not be able to install custom apps to this setup. For production deployments, custom configurations, and detailed explanations, see the full documentation. + +First clone the repo: + +```sh git clone https://github.com/frappe/frappe_docker cd frappe_docker -docker compose -f pwd.yml up -d ``` -After a couple of minutes, site should be accessible on your localhost port: 8080. Use below default login credentials to access the site. -- Username: Administrator -- Password: admin +Then run: -See [Frappe Docker](https://github.com/frappe/frappe_docker?tab=readme-ov-file#to-run-on-arm64-architecture-follow-this-instructions) for ARM based docker setup. +```sh +docker compose -f pwd.yml up -d +``` +Wait for a couple of minutes for ERPNext site to be created or check `create-site` container logs before opening browser on port `8080`. (username: `Administrator`, password: `admin`) + +See [Frappe Docker](https://github.com/frappe/frappe_docker/blob/main/docs/01-getting-started/03-arm64.md) for ARM based docker setup ## Development Setup 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 09846114ea2..f96f7af260a 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 @@ -17,6 +17,15 @@ frappe.dashboards.chart_sources["Account Balance Timeline"] = { fieldtype: "Link", options: "Account", reqd: 1, + default: locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"], + get_query: () => { + return { + filters: { + account_type: "Bank", + is_group: 0, + }, + }; + }, }, ], }; diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py index 261e68ca015..0a49280e5ae 100644 --- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py +++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py @@ -4,7 +4,7 @@ import frappe from frappe import _ -from frappe.utils import add_to_date, formatdate, get_link_to_form, getdate, nowdate +from frappe.utils import add_to_date, formatdate, getdate, nowdate from frappe.utils.dashboard import cache_source from frappe.utils.dateutils import get_from_date_from_timespan, get_period_ending from frappe.utils.nestedset import get_descendants_of @@ -37,21 +37,14 @@ def get( filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json) account = filters.get("account") - filters.get("company") + company = filters.get("company") - if not account and chart_name: - frappe.throw( - _("Account is not set for the dashboard chart {0}").format( - get_link_to_form("Dashboard Chart", chart_name) - ) - ) - - if not frappe.db.exists("Account", account) and chart_name: - frappe.throw( - _("Account {0} does not exists in the dashboard chart {1}").format( - account, get_link_to_form("Dashboard Chart", chart_name) - ) - ) + if not company and not account: + frappe.throw(_("Company and account filters not set!")) + if not company: + frappe.throw(_("Company filter not set!")) + if not account: + frappe.throw(_("Account filter not set!")) if not to_date: to_date = nowdate() diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 7a8ed4dff43..c3518077410 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -523,7 +523,8 @@ def make_gl_entries( if gl_entries: try: make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True) - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() except Exception as e: if frappe.in_test: doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}") @@ -605,7 +606,8 @@ def book_revenue_via_journal_entry( if submit: journal_entry.submit() - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() except Exception: frappe.db.rollback() doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}") diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index 315b41560ce..5ff4e4a47e2 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -52,60 +52,55 @@ frappe.treeview_settings["Account"] = { ], root_label: "Accounts", get_tree_nodes: "erpnext.accounts.utils.get_children", - on_get_node: function (nodes, deep = false) { - if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return; + on_node_render: function (node, deep) { + const render_balances = () => { + for (let account of cur_tree.account_balance_data) { + const node = cur_tree.nodes && cur_tree.nodes[account.value]; + if (!node || node.is_root) continue; - let accounts = []; - if (deep) { - // in case of `get_all_nodes` - accounts = nodes.reduce((acc, node) => [...acc, ...node.data], []); - } else { - accounts = nodes; - } + // show Dr if positive since balance is calculated as debit - credit else show Cr + const balance = account.balance_in_account_currency || account.balance; + const dr_or_cr = balance > 0 ? __("Dr") : __("Cr"); + const format = (value, currency) => format_currency(Math.abs(value), currency); - frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => { - if (value) { - const get_balances = frappe.call({ - method: "erpnext.accounts.utils.get_account_balances", - args: { - accounts: accounts, - company: cur_tree.args.company, - include_default_fb_balances: true, - }, - }); - - get_balances.then((r) => { - if (!r.message || r.message.length == 0) return; - - for (let account of r.message) { - const node = cur_tree.nodes && cur_tree.nodes[account.value]; - if (!node || node.is_root) continue; - - // show Dr if positive since balance is calculated as debit - credit else show Cr - const balance = account.balance_in_account_currency || account.balance; - const dr_or_cr = balance > 0 ? __("Dr") : __("Cr"); - const format = (value, currency) => format_currency(Math.abs(value), currency); - - if (account.balance !== undefined) { - node.parent && node.parent.find(".balance-area").remove(); - $( - '' + - (account.balance_in_account_currency - ? format( - account.balance_in_account_currency, - account.account_currency - ) + " / " - : "") + - format(account.balance, account.company_currency) + - " " + - dr_or_cr + - "" - ).insertBefore(node.$ul); - } - } - }); + if (account.balance !== undefined) { + node.parent && node.parent.find(".balance-area").remove(); + $( + '' + + (account.account_currency != account.company_currency + ? format(account.balance_in_account_currency, account.account_currency) + + " / " + : "") + + format(account.balance, account.company_currency) + + " " + + dr_or_cr + + "" + ).insertBefore(node.$ul); + } } - }); + }; + + if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return; + if (!cur_tree.account_balance_data) { + frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => { + if (value) { + frappe.call({ + method: "erpnext.accounts.utils.get_account_balances_coa", + args: { + company: cur_tree.args.company, + include_default_fb_balances: true, + }, + callback: function (r) { + if (!r.message || r.message.length === 0) return; + cur_tree.account_balance_data = r.message || []; + render_balances(); + }, + }); + } + }); + } else { + render_balances(); + } }, add_tree_node: "erpnext.accounts.utils.add_ac", menu_items: [ diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json index 1cb95962709..496cf4599b3 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json @@ -6,64 +6,83 @@ "Current Assets": { "Accounts Receivable": { "Debtors": { - "account_type": "Receivable" + "account_type": "Receivable", + "account_category": "Trade Receivables" } }, "Bank Accounts": { "account_type": "Bank", - "is_group": 1 + "is_group": 1, + "account_category": "Cash and Cash Equivalents" }, "Cash In Hand": { "Cash": { - "account_type": "Cash" + "account_type": "Cash", + "account_category": "Cash and Cash Equivalents" }, - "account_type": "Cash" + "account_type": "Cash", + "account_category": "Cash and Cash Equivalents" }, "Loans and Advances (Assets)": { - "is_group": 1 + "is_group": 1, + "account_category": "Other Receivables" }, "Securities and Deposits": { - "Earnest Money": {} + "Earnest Money": { + "account_category": "Other Current Assets" + } }, "Stock Assets": { "Stock In Hand": { - "account_type": "Stock" + "account_type": "Stock", + "account_category": "Stock Assets" }, - "account_type": "Stock" + "account_type": "Stock", + "account_category": "Stock Assets" }, "Tax Assets": { - "is_group": 1 + "is_group": 1, + "account_category": "Other Current Assets" } }, "Fixed Assets": { "Capital Equipment": { - "account_type": "Fixed Asset" + "account_type": "Fixed Asset", + "account_category": "Tangible Assets" }, "Electronic Equipment": { - "account_type": "Fixed Asset" + "account_type": "Fixed Asset", + "account_category": "Tangible Assets" }, "Furniture and Fixtures": { - "account_type": "Fixed Asset" + "account_type": "Fixed Asset", + "account_category": "Tangible Assets" }, "Office Equipment": { - "account_type": "Fixed Asset" + "account_type": "Fixed Asset", + "account_category": "Tangible Assets" }, "Plants and Machineries": { - "account_type": "Fixed Asset" + "account_type": "Fixed Asset", + "account_category": "Tangible Assets" }, "Buildings": { - "account_type": "Fixed Asset" + "account_type": "Fixed Asset", + "account_category": "Tangible Assets" }, "Accumulated Depreciations": { - "account_type": "Accumulated Depreciation" + "account_type": "Accumulated Depreciation", + "account_category": "Tangible Assets" } }, "Investments": { - "is_group": 1 + "is_group": 1, + "account_category": "Long-term Investments" }, "Temporary Accounts": { "Temporary Opening": { - "account_type": "Temporary" + "account_type": "Temporary", + "account_category": "Other Non-current Assets" } }, "root_type": "Asset" @@ -72,55 +91,103 @@ "Direct Expenses": { "Stock Expenses": { "Cost of Goods Sold": { - "account_type": "Cost of Goods Sold" + "account_type": "Cost of Goods Sold", + "account_category": "Cost of Goods Sold" }, "Expenses Included In Valuation": { - "account_type": "Expenses Included In Valuation" + "account_type": "Expenses Included In Valuation", + "account_category": "Other Direct Costs" }, "Stock Adjustment": { - "account_type": "Stock Adjustment" + "account_type": "Stock Adjustment", + "account_category": "Other Direct Costs" } } }, "Indirect Expenses": { - "Administrative Expenses": {}, - "Commission on Sales": {}, + "Administrative Expenses": { + "account_category": "Operating Expenses" + }, + "Commission on Sales": { + "account_category": "Operating Expenses" + }, "Depreciation": { - "account_type": "Depreciation" + "account_type": "Depreciation", + "account_category": "Operating Expenses" + }, + "Entertainment Expenses": { + "account_category": "Operating Expenses" }, - "Entertainment Expenses": {}, "Freight and Forwarding Charges": { - "account_type": "Chargeable" + "account_type": "Chargeable", + "account_category": "Operating Expenses" + }, + "Legal Expenses": { + "account_category": "Operating Expenses" + }, + "Marketing Expenses": { + "account_type": "Chargeable", + "account_category": "Operating Expenses" + }, + "Miscellaneous Expenses": { + "account_type": "Chargeable", + "account_category": "Operating Expenses" + }, + "Office Maintenance Expenses": { + "account_category": "Operating Expenses" + }, + "Office Rent": { + "account_category": "Operating Expenses" + }, + "Postal Expenses": { + "account_category": "Operating Expenses" + }, + "Print and Stationery": { + "account_category": "Operating Expenses" }, - "Legal Expenses": {}, - "Marketing Expenses": {}, - "Miscellaneous Expenses": {}, - "Office Maintenance Expenses": {}, - "Office Rent": {}, - "Postal Expenses": {}, - "Print and Stationery": {}, "Rounded Off": { - "account_type": "Round Off" + "account_type": "Round Off", + "account_category": "Operating Expenses" }, - "Salary": {}, - "Sales Expenses": {}, - "Telephone Expenses": {}, - "Travel Expenses": {}, - "Utility Expenses": {}, - "Write Off": {}, - "Exchange Gain/Loss": {}, - "Gain/Loss on Asset Disposal": {}, - "Impairment": {} + "Salary": { + "account_category": "Operating Expenses" + }, + "Sales Expenses": { + "account_category": "Operating Expenses" + }, + "Telephone Expenses": { + "account_category": "Operating Expenses" + }, + "Travel Expenses": { + "account_category": "Operating Expenses" + }, + "Utility Expenses": { + "account_category": "Operating Expenses" + }, + "Write Off": { + "account_category": "Operating Expenses" + }, + "Exchange Gain/Loss": { + "account_category": "Operating Expenses" + }, + "Gain/Loss on Asset Disposal": { + "account_category": "Other Operating Income" + }, + "Impairment": { + "account_category": "Operating Expenses" + } }, "root_type": "Expense" }, "Income": { "Direct Income": { "Sales": { - "account_type": "Income Account" + "account_type": "Income Account", + "account_category": "Revenue from Operations" }, "Service": { - "account_type": "Income Account" + "account_type": "Income Account", + "account_category": "Revenue from Operations" }, "account_type": "Income Account" }, @@ -132,31 +199,51 @@ }, "Source of Funds (Liabilities)": { "Capital Account": { - "Reserves and Surplus": {}, - "Shareholders Funds": {}, - "Revaluation Surplus": {} + "Reserves and Surplus": { + "account_category": "Reserves and Surplus" + }, + "Shareholders Funds": { + "account_category": "Share Capital" + }, + "Revaluation Surplus": { + "account_category": "Reserves and Surplus" + } }, "Current Liabilities": { "Accounts Payable": { "Creditors": { - "account_type": "Payable" + "account_type": "Payable", + "account_category": "Trade Payables" }, - "Payroll Payable": {} + "Payroll Payable": { + "account_category": "Other Payables" + } }, "Stock Liabilities": { "Stock Received But Not Billed": { - "account_type": "Stock Received But Not Billed" + "account_type": "Stock Received But Not Billed", + "account_category": "Trade Payables" } }, "Duties and Taxes": { "TDS": { - "account_type": "Tax" - } + "account_type": "Tax", + "account_category": "Current Tax Liabilities" + }, + "account_type": "Tax", + "is_group": 1, + "account_category": "Current Tax Liabilities" }, "Loans (Liabilities)": { - "Secured Loans": {}, - "Unsecured Loans": {}, - "Bank Overdraft Account": {} + "Secured Loans": { + "account_category": "Long-term Borrowings" + }, + "Unsecured Loans": { + "account_category": "Long-term Borrowings" + }, + "Bank Overdraft Account": { + "account_category": "Short-term Borrowings" + } } }, "root_type": "Liability" diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index 80ccc549d75..3629102ea5a 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -2,7 +2,6 @@ # License: GNU General Public License v3. See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import nowdate from erpnext.accounts.doctype.account.account import ( @@ -11,11 +10,10 @@ from erpnext.accounts.doctype.account.account import ( update_account_number, ) from erpnext.stock import get_company_default_inventory_account, get_warehouse_account - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Company"] +from erpnext.tests.utils import ERPNextTestSuite -class TestAccount(IntegrationTestCase): +class TestAccount(ERPNextTestSuite): def test_rename_account(self): if not frappe.db.exists("Account", "1210 - Debtors - _TC"): acc = frappe.new_doc("Account") diff --git a/erpnext/accounts/doctype/account/test_records.json b/erpnext/accounts/doctype/account/test_records.json deleted file mode 100644 index d15f7ad4c0a..00000000000 --- a/erpnext/accounts/doctype/account/test_records.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "doctype": "Account", - "name": "_Test Account 1" - } -] diff --git a/erpnext/accounts/doctype/account_category/test_account_category.py b/erpnext/accounts/doctype/account_category/test_account_category.py index ea3c2c7783c..e695b11bcb5 100644 --- a/erpnext/accounts/doctype/account_category/test_account_category.py +++ b/erpnext/accounts/doctype/account_category/test_account_category.py @@ -2,19 +2,3 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase - -# On IntegrationTestCase, the doctype test records and all -# link-field test record dependencies are recursively loaded -# Use these module variables to add/remove to/from that list -EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] -IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] - - -class IntegrationTestAccountCategory(IntegrationTestCase): - """ - Integration tests for AccountCategory. - Use this class for testing interactions between multiple components. - """ - - pass diff --git a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.js b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.js index e35591474b1..d5d109a324f 100644 --- a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.js +++ b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.js @@ -1,8 +1,8 @@ // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -// frappe.ui.form.on("Account Closing Balance", { -// refresh(frm) { - -// }, -// }); +frappe.ui.form.on("Account Closing Balance", { + refresh(frm) { + frm.page.btn_secondary.hide(); + }, +}); diff --git a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.json b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.json index b980a8378a9..c17a79642dd 100644 --- a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.json +++ b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.json @@ -5,6 +5,7 @@ "doctype": "DocType", "document_type": "Document", "engine": "InnoDB", + "is_submittable": 1, "field_order": [ "closing_date", "account", diff --git a/erpnext/accounts/doctype/account_closing_balance/test_account_closing_balance.py b/erpnext/accounts/doctype/account_closing_balance/test_account_closing_balance.py index 53f4e5d15af..a39bd00579e 100644 --- a/erpnext/accounts/doctype/account_closing_balance/test_account_closing_balance.py +++ b/erpnext/accounts/doctype/account_closing_balance/test_account_closing_balance.py @@ -2,8 +2,9 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase + +from erpnext.tests.utils import ERPNextTestSuite -class TestAccountClosingBalance(IntegrationTestCase): +class TestAccountClosingBalance(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py index f14e7d2b21d..1721ebcac68 100644 --- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py @@ -2,18 +2,13 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Cost Center", "Location", "Warehouse", "Department"] +from erpnext.tests.utils import ERPNextTestSuite -class TestAccountingDimension(IntegrationTestCase): - def setUp(self): - create_dimension() - +class TestAccountingDimension(ERPNextTestSuite): def test_dimension_against_sales_invoice(self): si = create_sales_invoice(do_not_save=1) @@ -78,68 +73,3 @@ class TestAccountingDimension(IntegrationTestCase): si.save() self.assertRaises(frappe.ValidationError, si.submit) - - def tearDown(self): - disable_dimension() - frappe.flags.accounting_dimensions_details = None - frappe.flags.dimension_filter_map = None - - -def create_dimension(): - frappe.set_user("Administrator") - - if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}): - dimension = frappe.get_doc( - { - "doctype": "Accounting Dimension", - "document_type": "Department", - } - ) - dimension.append( - "dimension_defaults", - { - "company": "_Test Company", - "reference_document": "Department", - "default_dimension": "_Test Department - _TC", - }, - ) - dimension.insert() - dimension.save() - else: - dimension = frappe.get_doc("Accounting Dimension", "Department") - dimension.disabled = 0 - dimension.save() - - if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}): - dimension1 = frappe.get_doc( - { - "doctype": "Accounting Dimension", - "document_type": "Location", - } - ) - - dimension1.append( - "dimension_defaults", - { - "company": "_Test Company", - "reference_document": "Location", - "default_dimension": "Block 1", - }, - ) - - dimension1.insert() - dimension1.save() - else: - dimension1 = frappe.get_doc("Accounting Dimension", "Location") - dimension1.disabled = 0 - dimension1.save() - - -def disable_dimension(): - dimension1 = frappe.get_doc("Accounting Dimension", "Department") - dimension1.disabled = 1 - dimension1.save() - - dimension2 = frappe.get_doc("Accounting Dimension", "Location") - dimension2.disabled = 1 - dimension2.save() diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py index bff288cf4d8..d528587b998 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py +++ b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py @@ -1,23 +1,15 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest - import frappe -from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import ( - create_dimension, - disable_dimension, -) from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Location", "Cost Center", "Department"] +from erpnext.tests.utils import ERPNextTestSuite -class TestAccountingDimensionFilter(unittest.TestCase): +class TestAccountingDimensionFilter(ERPNextTestSuite): def setUp(self): - create_dimension() create_accounting_dimension_filter() self.invoice_list = [] @@ -44,17 +36,6 @@ class TestAccountingDimensionFilter(unittest.TestCase): self.assertRaises(MandatoryAccountDimensionError, si.submit) self.invoice_list.append(si) - def tearDown(self): - disable_dimension_filter() - disable_dimension() - frappe.flags.accounting_dimensions_details = None - frappe.flags.dimension_filter_map = None - - for si in self.invoice_list: - si.load_from_db() - if si.docstatus == 1: - si.cancel() - def create_accounting_dimension_filter(): if not frappe.db.get_value("Accounting Dimension Filter", {"accounting_dimension": "Cost Center"}): diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.json b/erpnext/accounts/doctype/accounting_period/accounting_period.json index 2fb69f6b97c..10c6b72ec88 100644 --- a/erpnext/accounts/doctype/accounting_period/accounting_period.json +++ b/erpnext/accounts/doctype/accounting_period/accounting_period.json @@ -20,7 +20,6 @@ { "fieldname": "period_name", "fieldtype": "Data", - "in_list_view": 1, "label": "Period Name", "reqd": 1, "unique": 1 @@ -79,7 +78,7 @@ } ], "links": [], - "modified": "2025-12-01 16:53:44.631299", + "modified": "2026-03-09 17:15:33.577217", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting Period", diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py index d8ad730a960..dccc5f8e0f7 100644 --- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py @@ -2,7 +2,6 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import add_months, nowdate from erpnext.accounts.doctype.accounting_period.accounting_period import ( @@ -10,11 +9,10 @@ from erpnext.accounts.doctype.accounting_period.accounting_period import ( OverlapError, ) from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Item"] +from erpnext.tests.utils import ERPNextTestSuite -class TestAccountingPeriod(IntegrationTestCase): +class TestAccountingPeriod(ERPNextTestSuite): def test_overlap(self): ap1 = create_accounting_period( start_date="2018-04-01", end_date="2018-06-30", company="Wind Power LLC" @@ -89,10 +87,6 @@ class TestAccountingPeriod(IntegrationTestCase): doc.submit() # Should not raise self.assertEqual(doc.docstatus, 1) - def tearDown(self): - for d in frappe.get_all("Accounting Period"): - frappe.delete_doc("Accounting Period", d.name) - def create_accounting_period(**args): args = frappe._dict(args) diff --git a/erpnext/accounts/doctype/accounts_settings/test_accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/test_accounts_settings.py index ba7bbe91386..d41e13ffe43 100644 --- a/erpnext/accounts/doctype/accounts_settings/test_accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/test_accounts_settings.py @@ -1,15 +1,9 @@ import frappe -from frappe.tests import IntegrationTestCase + +from erpnext.tests.utils import ERPNextTestSuite -class TestAccountsSettings(IntegrationTestCase): - def tearDown(self): - # Just in case `save` method succeeds, we need to take things back to default so that other tests - # don't break - cur_settings = frappe.get_doc("Accounts Settings", "Accounts Settings") - cur_settings.allow_stale = 1 - cur_settings.save() - +class TestAccountsSettings(ERPNextTestSuite): def test_stale_days(self): cur_settings = frappe.get_doc("Accounts Settings", "Accounts Settings") cur_settings.allow_stale = 0 diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.js b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.js index 24b82fa8025..bf9abdbc21c 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.js +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.js @@ -3,6 +3,7 @@ frappe.ui.form.on("Advance Payment Ledger Entry", { refresh(frm) { + frm.page.btn_secondary.hide(); frm.set_currency_labels(["amount"], frm.doc.currency); frm.set_currency_labels(["base_amount"], erpnext.get_currency(frm.doc.company)); }, diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json index 1bf909117a8..2783cb1bda4 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json @@ -4,6 +4,7 @@ "creation": "2024-10-16 16:57:12.085072", "doctype": "DocType", "engine": "InnoDB", + "is_submittable": 1, "field_order": [ "company", "voucher_type", diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py b/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py index f824e6893e4..53047b61718 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py @@ -2,7 +2,6 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import nowdate, today from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry @@ -10,14 +9,13 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order -# On IntegrationTestCase, the doctype test records and all +# On ERPNextTestSuite, the doctype test records and all # link-field test record depdendencies are recursively loaded # Use these module variables to add/remove to/from that list -EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] -IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +from erpnext.tests.utils import ERPNextTestSuite -class TestAdvancePaymentLedgerEntry(AccountsTestMixin, IntegrationTestCase): +class TestAdvancePaymentLedgerEntry(AccountsTestMixin, ERPNextTestSuite): """ Integration tests for AdvancePaymentLedgerEntry. Use this class for testing interactions between multiple components. @@ -30,9 +28,6 @@ class TestAdvancePaymentLedgerEntry(AccountsTestMixin, IntegrationTestCase): self.create_item() self.clear_old_entries() - def tearDown(self): - frappe.db.rollback() - def create_sales_order(self, qty=1, rate=100, currency="INR", do_not_submit=False): """ Helper method diff --git a/erpnext/accounts/doctype/bank/test_bank.py b/erpnext/accounts/doctype/bank/test_bank.py index 6aa57089ddd..c17b2437cb8 100644 --- a/erpnext/accounts/doctype/bank/test_bank.py +++ b/erpnext/accounts/doctype/bank/test_bank.py @@ -1,8 +1,8 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestBank(IntegrationTestCase): +class TestBank(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/bank_account/test_bank_account.py b/erpnext/accounts/doctype/bank_account/test_bank_account.py index 70cb8afc267..a1f785e58b0 100644 --- a/erpnext/accounts/doctype/bank_account/test_bank_account.py +++ b/erpnext/accounts/doctype/bank_account/test_bank_account.py @@ -1,8 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestBankAccount(IntegrationTestCase): +class TestBankAccount(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.py b/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.py index a8ab617ff41..4b4aae093a3 100644 --- a/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.py +++ b/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.py @@ -1,8 +1,8 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestBankAccountSubtype(IntegrationTestCase): +class TestBankAccountSubtype(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/bank_account_type/test_bank_account_type.py b/erpnext/accounts/doctype/bank_account_type/test_bank_account_type.py index ad7647a41fa..dd92be43268 100644 --- a/erpnext/accounts/doctype/bank_account_type/test_bank_account_type.py +++ b/erpnext/accounts/doctype/bank_account_type/test_bank_account_type.py @@ -2,8 +2,8 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestBankAccountType(IntegrationTestCase): +class TestBankAccountType(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py index 5a8b7e36243..f7451830e1d 100644 --- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py +++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py @@ -5,7 +5,9 @@ import frappe from frappe import _, msgprint from frappe.model.document import Document +from frappe.query_builder import Case from frappe.query_builder.custom import ConstantColumn +from frappe.query_builder.functions import Coalesce, Sum from frappe.utils import cint, flt, fmt_money, getdate from pypika import Order @@ -182,65 +184,162 @@ def get_payment_entries_for_bank_clearance( ): entries = [] - condition = "" - pe_condition = "" + journal_entry = frappe.qb.DocType("Journal Entry") + journal_entry_account = frappe.qb.DocType("Journal Entry Account") + + journal_entry_query = ( + frappe.qb.from_(journal_entry_account) + .inner_join(journal_entry) + .on(journal_entry_account.parent == journal_entry.name) + .select( + ConstantColumn("Journal Entry").as_("payment_document"), + journal_entry.name.as_("payment_entry"), + journal_entry.cheque_no.as_("cheque_number"), + journal_entry.cheque_date, + Sum(journal_entry_account.debit_in_account_currency).as_("debit"), + Sum(journal_entry_account.credit_in_account_currency).as_("credit"), + journal_entry.posting_date, + journal_entry_account.against_account, + journal_entry.clearance_date, + journal_entry_account.account_currency, + ) + .where( + (journal_entry_account.account == account) + & (journal_entry.docstatus == 1) + & (journal_entry.posting_date >= from_date) + & (journal_entry.posting_date <= to_date) + & (journal_entry.is_opening == "No") + ) + ) + if not include_reconciled_entries: - condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')" - pe_condition = "and (pe.clearance_date IS NULL or pe.clearance_date='0000-00-00')" + journal_entry_query = journal_entry_query.where( + (journal_entry.clearance_date.isnull()) | (journal_entry.clearance_date == "0000-00-00") + ) - journal_entries = frappe.db.sql( - f""" - select - "Journal Entry" as payment_document, t1.name as payment_entry, - t1.cheque_no as cheque_number, t1.cheque_date, - sum(t2.debit_in_account_currency) as debit, sum(t2.credit_in_account_currency) as credit, - t1.posting_date, t2.against_account, t1.clearance_date, t2.account_currency - from - `tabJournal Entry` t1, `tabJournal Entry Account` t2 - where - t2.parent = t1.name and t2.account = %(account)s and t1.docstatus=1 - and t1.posting_date >= %(from)s and t1.posting_date <= %(to)s - and ifnull(t1.is_opening, 'No') = 'No' {condition} - group by t2.account, t1.name - order by t1.posting_date ASC, t1.name DESC - """, - {"account": account, "from": from_date, "to": to_date}, - as_dict=1, + journal_entries = ( + journal_entry_query.groupby(journal_entry_account.account, journal_entry.name) + .orderby(journal_entry.posting_date) + .orderby(journal_entry.name, order=Order.desc) + ).run(as_dict=True) + + pe = frappe.qb.DocType("Payment Entry") + company = frappe.qb.DocType("Company") + payment_entry_query = ( + frappe.qb.from_(pe) + .join(company) + .on(pe.company == company.name) + .select( + ConstantColumn("Payment Entry").as_("payment_document"), + pe.name.as_("payment_entry"), + pe.reference_no.as_("cheque_number"), + pe.reference_date.as_("cheque_date"), + ( + Case() + .when( + pe.paid_from == account, + ( + pe.paid_amount + + ( + Case() + .when( + (pe.payment_type == "Pay") + & (company.default_currency == pe.paid_from_account_currency), + pe.base_total_taxes_and_charges, + ) + .else_(pe.total_taxes_and_charges) + ) + ), + ) + .else_(0) + ).as_("credit"), + ( + Case() + .when(pe.paid_from == account, 0) + .else_( + pe.received_amount + + ( + Case() + .when( + company.default_currency == pe.paid_to_account_currency, + pe.base_total_taxes_and_charges, + ) + .else_(pe.total_taxes_and_charges) + ) + ) + ).as_("debit"), + pe.posting_date, + Coalesce(pe.party, Case().when(pe.paid_from == account, pe.paid_to).else_(pe.paid_from)).as_( + "against_account" + ), + pe.clearance_date, + ( + Case() + .when(pe.paid_to == account, pe.paid_to_account_currency) + .else_(pe.paid_from_account_currency) + ).as_("account_currency"), + ) + .where( + ((pe.paid_from == account) | (pe.paid_to == account)) + & (pe.docstatus == 1) + & (pe.posting_date >= from_date) + & (pe.posting_date <= to_date) + ) ) - payment_entries = frappe.db.sql( - f""" - select - "Payment Entry" as payment_document, pe.name as payment_entry, - pe.reference_no as cheque_number, pe.reference_date as cheque_date, - if(pe.paid_from=%(account)s, pe.paid_amount + if(pe.payment_type = 'Pay' and c.default_currency = pe.paid_from_account_currency, pe.base_total_taxes_and_charges, pe.total_taxes_and_charges) , 0) as credit, - if(pe.paid_from=%(account)s, 0, pe.received_amount + pe.total_taxes_and_charges) as debit, - pe.posting_date, ifnull(pe.party,if(pe.paid_from=%(account)s,pe.paid_to,pe.paid_from)) as against_account, pe.clearance_date, - if(pe.paid_to=%(account)s, pe.paid_to_account_currency, pe.paid_from_account_currency) as account_currency - from `tabPayment Entry` as pe - join `tabCompany` c on c.name = pe.company - where - (pe.paid_from=%(account)s or pe.paid_to=%(account)s) and pe.docstatus=1 - and pe.posting_date >= %(from)s and pe.posting_date <= %(to)s - {pe_condition} - order by - pe.posting_date ASC, pe.name DESC - """, - { - "account": account, - "from": from_date, - "to": to_date, - }, - as_dict=1, + if not include_reconciled_entries: + payment_entry_query = payment_entry_query.where( + (pe.clearance_date.isnull()) | (pe.clearance_date == "0000-00-00") + ) + + payment_entries = (payment_entry_query.orderby(pe.posting_date).orderby(pe.name, order=Order.desc)).run( + as_dict=True ) - pos_sales_invoices, pos_purchase_invoices = [], [] + acc = frappe.qb.DocType("Account") + + pi = frappe.qb.DocType("Purchase Invoice") + + paid_purchase_invoices_query = ( + frappe.qb.from_(pi) + .inner_join(acc) + .on(pi.cash_bank_account == acc.name) + .select( + ConstantColumn("Purchase Invoice").as_("payment_document"), + pi.name.as_("payment_entry"), + pi.paid_amount.as_("credit"), + pi.posting_date, + pi.supplier.as_("against_account"), + pi.bill_no.as_("cheque_number"), + pi.clearance_date, + acc.account_currency, + ConstantColumn(0).as_("debit"), + ) + .where( + (pi.docstatus == 1) + & (pi.is_paid == 1) + & (pi.cash_bank_account == account) + & (pi.posting_date >= from_date) + & (pi.posting_date <= to_date) + ) + ) + + if not include_reconciled_entries: + paid_purchase_invoices_query = paid_purchase_invoices_query.where( + (pi.clearance_date.isnull()) | (pi.clearance_date == "0000-00-00") + ) + + paid_purchase_invoices = ( + paid_purchase_invoices_query.orderby(pi.posting_date).orderby(pi.name, order=Order.desc) + ).run(as_dict=True) + + pos_sales_invoices = [] + if include_pos_transactions: si_payment = frappe.qb.DocType("Sales Invoice Payment") si = frappe.qb.DocType("Sales Invoice") - acc = frappe.qb.DocType("Account") - pos_sales_invoices = ( + pos_sales_invoices_query = ( frappe.qb.from_(si_payment) .inner_join(si) .on(si_payment.parent == si.name) @@ -263,38 +362,22 @@ def get_payment_entries_for_bank_clearance( & (si.posting_date >= from_date) & (si.posting_date <= to_date) ) - .orderby(si.posting_date) - .orderby(si.name, order=Order.desc) - ).run(as_dict=True) + ) - pi = frappe.qb.DocType("Purchase Invoice") + if not include_reconciled_entries: + pos_sales_invoices_query = pos_sales_invoices_query.where( + (si_payment.clearance_date.isnull()) | (si_payment.clearance_date == "0000-00-00") + ) - pos_purchase_invoices = ( - frappe.qb.from_(pi) - .inner_join(acc) - .on(pi.cash_bank_account == acc.name) - .select( - ConstantColumn("Purchase Invoice").as_("payment_document"), - pi.name.as_("payment_entry"), - pi.paid_amount.as_("credit"), - pi.posting_date, - pi.supplier.as_("against_account"), - pi.clearance_date, - acc.account_currency, - ConstantColumn(0).as_("debit"), - ) - .where( - (pi.docstatus == 1) - & (pi.cash_bank_account == account) - & (pi.posting_date >= from_date) - & (pi.posting_date <= to_date) - ) - .orderby(pi.posting_date) - .orderby(pi.name, order=Order.desc) + pos_sales_invoices = ( + pos_sales_invoices_query.orderby(si.posting_date).orderby(si.name, order=Order.desc) ).run(as_dict=True) entries = ( - list(payment_entries) + list(journal_entries) + list(pos_sales_invoices) + list(pos_purchase_invoices) + list(payment_entries) + + list(journal_entries) + + list(pos_sales_invoices) + + list(paid_purchase_invoices) ) return entries diff --git a/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py index 3416a4e78a4..1190ffac9f3 100644 --- a/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py +++ b/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py @@ -2,7 +2,6 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import add_months, getdate from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center @@ -14,13 +13,12 @@ from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse -from erpnext.tests.utils import if_lending_app_installed, if_lending_app_not_installed +from erpnext.tests.utils import ERPNextTestSuite, if_lending_app_installed, if_lending_app_not_installed -class TestBankClearance(IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() +class TestBankClearance(ERPNextTestSuite): + def setUp(self): + frappe.clear_cache() create_warehouse( warehouse_name="_Test Warehouse", properties={"parent_warehouse": "All Warehouses - _TC"}, diff --git a/erpnext/accounts/doctype/bank_guarantee/test_bank_guarantee.py b/erpnext/accounts/doctype/bank_guarantee/test_bank_guarantee.py index 7aee2ccb6ff..c5ad4d20940 100644 --- a/erpnext/accounts/doctype/bank_guarantee/test_bank_guarantee.py +++ b/erpnext/accounts/doctype/bank_guarantee/test_bank_guarantee.py @@ -1,8 +1,8 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestBankGuarantee(IntegrationTestCase): +class TestBankGuarantee(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py index 6f8ab21ccf7..59c3573b1b3 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -59,7 +59,7 @@ def get_bank_transactions( filters.append(["date", "<=", to_date]) if from_date: filters.append(["date", ">=", from_date]) - transactions = frappe.get_all( + transactions = frappe.get_list( "Bank Transaction", fields=[ "date", @@ -84,6 +84,7 @@ def get_bank_transactions( @frappe.whitelist() def get_account_balance(bank_account: str, till_date: str | date, company: str): # returns account balance till the specified date + frappe.has_permission("Bank Account", "read", bank_account, throw=True) account = frappe.db.get_value("Bank Account", bank_account, "account") filters = frappe._dict( { diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py index 53586b333b4..5354aa0c4dd 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py @@ -4,7 +4,6 @@ import frappe from frappe import qb -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, today from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import ( @@ -13,9 +12,10 @@ from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool ) from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.tests.utils import ERPNextTestSuite -class TestBankReconciliationTool(AccountsTestMixin, IntegrationTestCase): +class TestBankReconciliationTool(AccountsTestMixin, ERPNextTestSuite): def setUp(self): self.create_company() self.create_customer() @@ -24,9 +24,6 @@ class TestBankReconciliationTool(AccountsTestMixin, IntegrationTestCase): qb.from_(bank_dt).delete().where(bank_dt.name == "HDFC").run() self.create_bank_account() - def tearDown(self): - frappe.db.rollback() - def create_bank_account(self): bank = frappe.get_doc( { @@ -43,6 +40,7 @@ class TestBankReconciliationTool(AccountsTestMixin, IntegrationTestCase): "bank": bank.name, "is_company_account": True, "account": self.bank, # account from Chart of Accounts + "company": self.company, } ) .insert() diff --git a/erpnext/accounts/doctype/bank_statement_import/test_bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/test_bank_statement_import.py index 3e1cee9115e..2ae00059c83 100644 --- a/erpnext/accounts/doctype/bank_statement_import/test_bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/test_bank_statement_import.py @@ -1,15 +1,14 @@ # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -import unittest - from erpnext.accounts.doctype.bank_statement_import.bank_statement_import import ( is_mt940_format, preprocess_mt940_content, ) +from erpnext.tests.utils import ERPNextTestSuite -class TestBankStatementImport(unittest.TestCase): +class TestBankStatementImport(ERPNextTestSuite): """Unit tests for Bank Statement Import functions""" def test_preprocess_mt940_content_with_long_statement_number(self): diff --git a/erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py b/erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py index da2c28a148c..bb1e436c5f7 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py +++ b/erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py @@ -2,27 +2,20 @@ # License: GNU General Public License v3. See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import nowdate from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account +from erpnext.tests.utils import ERPNextTestSuite IBAN_1 = "DE02000000003716541159" IBAN_2 = "DE02500105170137075030" -class TestAutoMatchParty(IntegrationTestCase): - @classmethod - def setUpClass(cls): +class TestAutoMatchParty(ERPNextTestSuite): + def setUp(self): create_bank_account() frappe.db.set_single_value("Accounts Settings", "enable_party_matching", 1) frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 1) - return super().setUpClass() - - @classmethod - def tearDownClass(cls): - frappe.db.set_single_value("Accounts Settings", "enable_party_matching", 0) - frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 0) def test_match_by_account_number(self): create_supplier_for_match(account_no=IBAN_1[11:]) diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index e04c67e54d4..c69d255c51a 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -6,7 +6,6 @@ import json import frappe from frappe import utils from frappe.model.docstatus import DocStatus -from frappe.tests import IntegrationTestCase from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import ( get_linked_payments, @@ -19,12 +18,10 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_paymen from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice -from erpnext.tests.utils import if_lending_app_installed - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Item", "Cost Center"] +from erpnext.tests.utils import ERPNextTestSuite, if_lending_app_installed -class TestBankTransaction(IntegrationTestCase): +class TestBankTransaction(ERPNextTestSuite): def setUp(self): make_pos_profile() diff --git a/erpnext/accounts/doctype/bisect_accounting_statements/test_bisect_accounting_statements.py b/erpnext/accounts/doctype/bisect_accounting_statements/test_bisect_accounting_statements.py index fd1793b2560..55e4811a87f 100644 --- a/erpnext/accounts/doctype/bisect_accounting_statements/test_bisect_accounting_statements.py +++ b/erpnext/accounts/doctype/bisect_accounting_statements/test_bisect_accounting_statements.py @@ -2,8 +2,10 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase -class TestBisectAccountingStatements(IntegrationTestCase): +from erpnext.tests.utils import ERPNextTestSuite + + +class TestBisectAccountingStatements(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/bisect_nodes/test_bisect_nodes.py b/erpnext/accounts/doctype/bisect_nodes/test_bisect_nodes.py index 1f37009d8e2..082b2a28f1f 100644 --- a/erpnext/accounts/doctype/bisect_nodes/test_bisect_nodes.py +++ b/erpnext/accounts/doctype/bisect_nodes/test_bisect_nodes.py @@ -2,8 +2,10 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase -class TestBisectNodes(IntegrationTestCase): +from erpnext.tests.utils import ERPNextTestSuite + + +class TestBisectNodes(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py index 866f9cb322d..d3dc7476c98 100644 --- a/erpnext/accounts/doctype/budget/test_budget.py +++ b/erpnext/accounts/doctype/budget/test_budget.py @@ -17,12 +17,6 @@ from erpnext.tests.utils import ERPNextTestSuite class TestBudget(ERPNextTestSuite): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.make_monthly_distribution() - cls.make_projects() - def setUp(self): frappe.db.set_single_value("Accounts Settings", "use_legacy_budget_controller", False) self.company = "_Test Company" diff --git a/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.py b/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.py index 2852caeca1c..7a38d8a9a93 100644 --- a/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.py +++ b/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.py @@ -1,8 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestCashierClosing(IntegrationTestCase): +class TestCashierClosing(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.py index fab0808e1d1..f1248393aca 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.py @@ -1,8 +1,8 @@ # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestChartofAccountsImporter(IntegrationTestCase): +class TestChartofAccountsImporter(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/cheque_print_template/test_cheque_print_template.py b/erpnext/accounts/doctype/cheque_print_template/test_cheque_print_template.py index c46a1c73aff..23e95efe6bd 100644 --- a/erpnext/accounts/doctype/cheque_print_template/test_cheque_print_template.py +++ b/erpnext/accounts/doctype/cheque_print_template/test_cheque_print_template.py @@ -1,8 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestChequePrintTemplate(IntegrationTestCase): +class TestChequePrintTemplate(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/cost_center/test_cost_center.py b/erpnext/accounts/doctype/cost_center/test_cost_center.py index 87b4acaefcf..3c3a60d4885 100644 --- a/erpnext/accounts/doctype/cost_center/test_cost_center.py +++ b/erpnext/accounts/doctype/cost_center/test_cost_center.py @@ -2,10 +2,11 @@ # License: GNU General Public License v3. See license.txt import frappe -from frappe.tests import IntegrationTestCase + +from erpnext.tests.utils import ERPNextTestSuite -class TestCostCenter(IntegrationTestCase): +class TestCostCenter(ERPNextTestSuite): def test_cost_center_creation_against_child_node(self): cost_center = frappe.get_doc( { diff --git a/erpnext/accounts/doctype/cost_center/test_records.json b/erpnext/accounts/doctype/cost_center/test_records.json deleted file mode 100644 index 941a85b382d..00000000000 --- a/erpnext/accounts/doctype/cost_center/test_records.json +++ /dev/null @@ -1,23 +0,0 @@ -[ - { - "company": "_Test Company", - "cost_center_name": "_Test Cost Center", - "doctype": "Cost Center", - "is_group": 0, - "parent_cost_center": "_Test Company - _TC" - }, - { - "company": "_Test Company", - "cost_center_name": "_Test Cost Center 2", - "doctype": "Cost Center", - "is_group": 0, - "parent_cost_center": "_Test Company - _TC" - }, - { - "company": "_Test Company", - "cost_center_name": "_Test Write Off Cost Center", - "doctype": "Cost Center", - "is_group": 0, - "parent_cost_center": "_Test Company - _TC" - } -] diff --git a/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py index 449730bc8c3..dac04501e0f 100644 --- a/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py +++ b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py @@ -3,7 +3,6 @@ import frappe from frappe.query_builder.functions import Sum -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, today from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center @@ -15,9 +14,10 @@ from erpnext.accounts.doctype.cost_center_allocation.cost_center_allocation impo WrongPercentageAllocation, ) from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry +from erpnext.tests.utils import ERPNextTestSuite -class TestCostCenterAllocation(IntegrationTestCase): +class TestCostCenterAllocation(ERPNextTestSuite): def setUp(self): cost_centers = [ "Main Cost Center 1", @@ -190,7 +190,7 @@ class TestCostCenterAllocation(IntegrationTestCase): coa2.cancel() jv.cancel() - @IntegrationTestCase.change_settings("System Settings", {"rounding_method": "Commercial Rounding"}) + @ERPNextTestSuite.change_settings("System Settings", {"rounding_method": "Commercial Rounding"}) def test_debit_credit_on_cost_center_allocation_for_commercial_rounding(self): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice diff --git a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py index d8e134b8502..ba4e7c0b191 100644 --- a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py +++ b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py @@ -2,11 +2,9 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Item"] +from erpnext.tests.utils import ERPNextTestSuite def test_create_test_data(): @@ -87,6 +85,7 @@ def test_create_test_data(): "partner_name": "_Test Coupon Partner", "commission_rate": 2, "referral_code": "COPART", + "territory": "All Territories", } ) sales_partner.insert() @@ -109,13 +108,10 @@ def test_create_test_data(): coupon_code.insert() -class TestCouponCode(IntegrationTestCase): +class TestCouponCode(ERPNextTestSuite): def setUp(self): test_create_test_data() - def tearDown(self): - frappe.set_user("Administrator") - def test_sales_order_with_coupon_code(self): frappe.db.set_value("Coupon Code", "SAVE30", "used", 0) diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json index ae3dbd59d69..9f0852bb686 100644 --- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json @@ -101,11 +101,11 @@ "label": "Use HTTP Protocol" } ], - "hide_toolbar": 1, + "hide_toolbar": 0, "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-01-02 18:19:02.873815", + "modified": "2026-03-16 13:28:21.075743", "modified_by": "Administrator", "module": "Accounts", "name": "Currency Exchange Settings", diff --git a/erpnext/accounts/doctype/currency_exchange_settings/test_currency_exchange_settings.py b/erpnext/accounts/doctype/currency_exchange_settings/test_currency_exchange_settings.py index 202e674c552..8d91dfe6a63 100644 --- a/erpnext/accounts/doctype/currency_exchange_settings/test_currency_exchange_settings.py +++ b/erpnext/accounts/doctype/currency_exchange_settings/test_currency_exchange_settings.py @@ -2,8 +2,8 @@ # For license information, please see license.txt # import frappe -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestCurrencyExchangeSettings(IntegrationTestCase): +class TestCurrencyExchangeSettings(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index 4ba44a2a3c9..6eaf1d8798e 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -4,37 +4,20 @@ import json import frappe from frappe.model import mapper -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, nowdate, today from erpnext import get_default_cost_center from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry -from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import ( - unlink_payment_on_cancel_of_invoice, -) from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( create_dunning as create_dunning_from_sales_invoice, ) from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import ( create_sales_invoice_against_cost_center, ) - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Company", "Cost Center"] +from erpnext.tests.utils import ERPNextTestSuite -class TestDunning(IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - create_dunning_type("First Notice", fee=0.0, interest=0.0, is_default=1) - create_dunning_type("Second Notice", fee=10.0, interest=10.0, is_default=0) - unlink_payment_on_cancel_of_invoice() - - @classmethod - def tearDownClass(cls): - unlink_payment_on_cancel_of_invoice(0) - super().tearDownClass() - +class TestDunning(ERPNextTestSuite): def test_dunning_without_fees(self): dunning = create_dunning(overdue_days=20) diff --git a/erpnext/accounts/doctype/dunning_type/test_dunning_type.py b/erpnext/accounts/doctype/dunning_type/test_dunning_type.py index ea176fbcf47..1e58e56570b 100644 --- a/erpnext/accounts/doctype/dunning_type/test_dunning_type.py +++ b/erpnext/accounts/doctype/dunning_type/test_dunning_type.py @@ -2,8 +2,8 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestDunningType(IntegrationTestCase): +class TestDunningType(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/dunning_type/test_records.json b/erpnext/accounts/doctype/dunning_type/test_records.json deleted file mode 100644 index 7f28aab873c..00000000000 --- a/erpnext/accounts/doctype/dunning_type/test_records.json +++ /dev/null @@ -1,36 +0,0 @@ -[ - { - "doctype": "Dunning Type", - "dunning_type": "_Test First Notice", - "company": "_Test Company", - "is_default": 1, - "dunning_fee": 0.0, - "rate_of_interest": 0.0, - "dunning_letter_text": [ - { - "language": "en", - "body_text": "We have still not received payment for our invoice", - "closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees." - } - ], - "income_account": "Sales - _TC", - "cost_center": "_Test Cost Center - _TC" - }, - { - "doctype": "Dunning Type", - "dunning_type": "_Test Second Notice", - "company": "_Test Company", - "is_default": 0, - "dunning_fee": 10.0, - "rate_of_interest": 10.0, - "dunning_letter_text": [ - { - "language": "en", - "body_text": "We have still not received payment for our invoice", - "closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees." - } - ], - "income_account": "Sales - _TC", - "cost_center": "_Test Cost Center - _TC" - } -] diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py index 96079284dd9..1310a8b482b 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py @@ -5,15 +5,15 @@ import frappe from frappe.query_builder import functions from frappe.query_builder.utils import DocType -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, flt, today from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.tests.utils import ERPNextTestSuite -class TestExchangeRateRevaluation(AccountsTestMixin, IntegrationTestCase): +class TestExchangeRateRevaluation(AccountsTestMixin, ERPNextTestSuite): def setUp(self): self.create_company() self.create_usd_receivable_account() @@ -22,14 +22,13 @@ class TestExchangeRateRevaluation(AccountsTestMixin, IntegrationTestCase): self.clear_old_entries() self.set_system_and_company_settings() - def tearDown(self): - frappe.db.rollback() - def set_system_and_company_settings(self): # set number and currency precision system_settings = frappe.get_doc("System Settings") system_settings.float_precision = 2 system_settings.currency_precision = 2 + system_settings.language = "en" + system_settings.time_zone = "Asia/Kolkata" system_settings.save() # Using Exchange Gain/Loss account for unrealized as well. @@ -37,7 +36,7 @@ class TestExchangeRateRevaluation(AccountsTestMixin, IntegrationTestCase): company_doc.unrealized_exchange_gain_loss_account = company_doc.exchange_gain_loss_account company_doc.save() - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0}, ) @@ -91,7 +90,7 @@ class TestExchangeRateRevaluation(AccountsTestMixin, IntegrationTestCase): )[0] self.assertEqual(acc_balance.balance, 8500.0) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0}, ) @@ -164,7 +163,7 @@ class TestExchangeRateRevaluation(AccountsTestMixin, IntegrationTestCase): self.assertEqual(acc_balance.balance, 0.0) self.assertEqual(acc_balance.balance_in_account_currency, 0.0) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0}, ) @@ -259,7 +258,7 @@ class TestExchangeRateRevaluation(AccountsTestMixin, IntegrationTestCase): self.assertEqual(flt(acc_balance.balance, precision), 0.0) self.assertEqual(flt(acc_balance.balance_in_account_currency, precision), 0.0) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0}, ) diff --git a/erpnext/accounts/doctype/finance_book/test_finance_book.py b/erpnext/accounts/doctype/finance_book/test_finance_book.py index f69ff2425a5..d9d6c0e44ab 100644 --- a/erpnext/accounts/doctype/finance_book/test_finance_book.py +++ b/erpnext/accounts/doctype/finance_book/test_finance_book.py @@ -2,12 +2,12 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry +from erpnext.tests.utils import ERPNextTestSuite -class TestFinanceBook(IntegrationTestCase): +class TestFinanceBook(ERPNextTestSuite): def test_finance_book(self): finance_book = create_finance_book() diff --git a/erpnext/accounts/doctype/financial_report_template/test_financial_report_engine.py b/erpnext/accounts/doctype/financial_report_template/test_financial_report_engine.py index e65e3f185a0..48bd1a5e48a 100644 --- a/erpnext/accounts/doctype/financial_report_template/test_financial_report_engine.py +++ b/erpnext/accounts/doctype/financial_report_template/test_financial_report_engine.py @@ -16,12 +16,6 @@ from erpnext.accounts.doctype.financial_report_template.test_financial_report_te from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry from erpnext.accounts.utils import get_currency_precision, get_fiscal_year -# On IntegrationTestCase, the doctype test records and all -# link-field test record dependencies are recursively loaded -# Use these module variables to add/remove to/from that list -EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] -IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] - class TestDependencyResolver(FinancialReportTemplateTestCase): """Test cases for DependencyResolver class""" diff --git a/erpnext/accounts/doctype/financial_report_template/test_financial_report_template.py b/erpnext/accounts/doctype/financial_report_template/test_financial_report_template.py index ef5404bd478..9372c503d12 100644 --- a/erpnext/accounts/doctype/financial_report_template/test_financial_report_template.py +++ b/erpnext/accounts/doctype/financial_report_template/test_financial_report_template.py @@ -2,29 +2,16 @@ # For license information, please see license.txt import frappe -from frappe.tests import IntegrationTestCase -from frappe.tests.utils import make_test_records -# On IntegrationTestCase, the doctype test records and all -# link-field test record dependencies are recursively loaded -# Use these module variables to add/remove to/from that list -EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] -IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +from erpnext.tests.utils import ERPNextTestSuite -class TestFinancialReportTemplate(IntegrationTestCase): - pass - - -class FinancialReportTemplateTestCase(IntegrationTestCase): +class FinancialReportTemplateTestCase(ERPNextTestSuite): """Utility class with common setup and helper methods for all test classes""" - @classmethod - def setUpClass(cls): + def setUp(self): """Set up test data""" - make_test_records("Company") - make_test_records("Fiscal Year") - cls.create_test_template() + self.create_test_template() @classmethod def create_test_template(cls): diff --git a/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py index db08e80a0d1..26358399cde 100644 --- a/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py +++ b/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py @@ -2,13 +2,12 @@ # License: GNU General Public License v3. See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import now_datetime -IGNORE_TEST_RECORD_DEPENDENCIES = ["Company"] +from erpnext.tests.utils import ERPNextTestSuite -class TestFiscalYear(IntegrationTestCase): +class TestFiscalYear(ERPNextTestSuite): def test_extra_year(self): if frappe.db.exists("Fiscal Year", "_Test Fiscal Year 2000"): frappe.delete_doc("Fiscal Year", "_Test Fiscal Year 2000") diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index b3afb906c6b..a8815a7d19a 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -5,6 +5,7 @@ "doctype": "DocType", "document_type": "Document", "engine": "InnoDB", + "is_submittable": 1, "field_order": [ "dates_section", "posting_date", diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 3abfa176622..452164c728c 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -489,4 +489,5 @@ def rename_temporarily_named_docs(doctype): for hook in frappe.get_hooks(hook_type): frappe.call(hook, newname=newname, oldname=oldname) - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() diff --git a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py index 7f204edd4ea..6caec70dc00 100644 --- a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py @@ -3,13 +3,13 @@ import frappe from frappe.model.naming import parse_naming_series -from frappe.tests import IntegrationTestCase from erpnext.accounts.doctype.gl_entry.gl_entry import rename_gle_sle_docs from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry +from erpnext.tests.utils import ERPNextTestSuite -class TestGLEntry(IntegrationTestCase): +class TestGLEntry(ERPNextTestSuite): def test_round_off_entry(self): frappe.db.set_value("Company", "_Test Company", "round_off_account", "_Test Write Off - _TC") frappe.db.set_value("Company", "_Test Company", "round_off_cost_center", "_Test Cost Center - _TC") diff --git a/erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py index a852ee3c8a3..7d92fb1e117 100644 --- a/erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py +++ b/erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py @@ -2,16 +2,16 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, flt, nowdate from erpnext.accounts.doctype.account.test_account import create_account from erpnext.accounts.doctype.journal_entry.journal_entry import get_payment_entry_against_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries +from erpnext.tests.utils import ERPNextTestSuite -class TestInvoiceDiscounting(IntegrationTestCase): +class TestInvoiceDiscounting(ERPNextTestSuite): def setUp(self): self.ar_credit = create_account( account_name="_Test Accounts Receivable Credit", diff --git a/erpnext/accounts/doctype/item_tax_template/test_item_tax_template.py b/erpnext/accounts/doctype/item_tax_template/test_item_tax_template.py index 144d5661550..f180c324a6d 100644 --- a/erpnext/accounts/doctype/item_tax_template/test_item_tax_template.py +++ b/erpnext/accounts/doctype/item_tax_template/test_item_tax_template.py @@ -1,8 +1,8 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestItemTaxTemplate(IntegrationTestCase): +class TestItemTaxTemplate(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/item_tax_template/test_records.json b/erpnext/accounts/doctype/item_tax_template/test_records.json deleted file mode 100644 index 4d9537d4b89..00000000000 --- a/erpnext/accounts/doctype/item_tax_template/test_records.json +++ /dev/null @@ -1,79 +0,0 @@ -[ - { - "doctype": "Item Tax Template", - "title": "_Test Account Excise Duty @ 10", - "company": "_Test Company", - "taxes": [ - { - "doctype": "Item Tax Template Detail", - "parentfield": "taxes", - "tax_rate": 10, - "tax_type": "_Test Account Excise Duty - _TC" - } - ] - }, - { - "doctype": "Item Tax Template", - "title": "_Test Account Excise Duty @ 12", - "company": "_Test Company", - "taxes": [ - { - "doctype": "Item Tax Template Detail", - "parentfield": "taxes", - "tax_rate": 12, - "tax_type": "_Test Account Excise Duty - _TC" - } - ] - }, - { - "doctype": "Item Tax Template", - "title": "_Test Account Excise Duty @ 15", - "company": "_Test Company", - "taxes": [ - { - "doctype": "Item Tax Template Detail", - "parentfield": "taxes", - "tax_rate": 15, - "tax_type": "_Test Account Excise Duty - _TC" - } - ] - }, - { - "doctype": "Item Tax Template", - "title": "_Test Account Excise Duty @ 20", - "company": "_Test Company", - "taxes": [ - { - "doctype": "Item Tax Template Detail", - "parentfield": "taxes", - "tax_rate": 20, - "tax_type": "_Test Account Excise Duty - _TC" - } - ] - }, - { - "doctype": "Item Tax Template", - "title": "_Test Item Tax Template 1", - "company": "_Test Company", - "taxes": [ - { - "doctype": "Item Tax Template Detail", - "parentfield": "taxes", - "tax_rate": 5, - "tax_type": "_Test Account Excise Duty - _TC" - }, - { - "doctype": "Item Tax Template Detail", - "parentfield": "taxes", - "tax_rate": 10, - "tax_type": "_Test Account Education Cess - _TC" - }, - { - "doctype": "Item Tax Template Detail", - "parentfield": "taxes", - "tax_rate": 15, - "tax_type": "_Test Account S&H Education Cess - _TC" - } - ] - } -] diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index 45e9c4eab85..0856f41bb09 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -140,6 +140,7 @@ { "fieldname": "company", "fieldtype": "Link", + "in_list_view": 1, "in_standard_filter": 1, "label": "Company", "oldfieldname": "company", @@ -181,7 +182,6 @@ "fieldname": "cheque_no", "fieldtype": "Data", "in_global_search": 1, - "in_list_view": 1, "label": "Reference Number", "mandatory_depends_on": "eval:doc.voucher_type == \"Bank Entry\"", "no_copy": 1, @@ -665,7 +665,7 @@ "table_fieldname": "payment_entries" } ], - "modified": "2026-02-16 16:06:10.468482", + "modified": "2026-03-09 17:15:26.569327", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 4f6e8b7507e..d2eb3b23dda 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -294,6 +294,8 @@ class JournalEntry(AccountsController): # References for this Journal are removed on the `on_cancel` event in accounts_controller super().on_cancel() + + from_doc_events = getattr(self, "ignore_linked_doctypes", ()) self.ignore_linked_doctypes = ( "GL Entry", "Stock Ledger Entry", @@ -307,6 +309,10 @@ class JournalEntry(AccountsController): "Advance Payment Ledger Entry", "Tax Withholding Entry", ) + + if from_doc_events and from_doc_events != self.ignore_linked_doctypes: + self.ignore_linked_doctypes = self.ignore_linked_doctypes + from_doc_events + self.make_gl_entries(1) JournalTaxWithholding(self).on_cancel() self.unlink_advance_entry_reference() diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 1a048a237bf..53d6013e1e2 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -2,19 +2,20 @@ # License: GNU General Public License v3. See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import flt, nowdate from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.accounts.doctype.journal_entry.journal_entry import StockAccountInvalidTransaction from erpnext.exceptions import InvalidAccountCurrency from erpnext.selling.doctype.customer.test_customer import make_customer, set_credit_limit +from erpnext.tests.utils import ERPNextTestSuite -class TestJournalEntry(IntegrationTestCase): - @IntegrationTestCase.change_settings( - "Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1} - ) +class TestJournalEntry(ERPNextTestSuite): + def setUp(self): + self.load_test_records("Journal Entry") + + @ERPNextTestSuite.change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) def test_journal_entry_with_against_jv(self): jv_invoice = frappe.copy_doc(self.globalTestRecords["Journal Entry"][2]) base_jv = frappe.copy_doc(self.globalTestRecords["Journal Entry"][0]) @@ -149,7 +150,6 @@ class TestJournalEntry(IntegrationTestCase): if account_bal == stock_bal: self.assertRaises(StockAccountInvalidTransaction, jv.save) - frappe.db.rollback() else: jv.submit() jv.cancel() diff --git a/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py b/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py index d1eb1acec84..616327e8493 100644 --- a/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py +++ b/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py @@ -2,8 +2,8 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestJournalEntryTemplate(IntegrationTestCase): +class TestJournalEntryTemplate(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/ledger_health/test_ledger_health.py b/erpnext/accounts/doctype/ledger_health/test_ledger_health.py index 0eec86ccc0f..84fd3925ded 100644 --- a/erpnext/accounts/doctype/ledger_health/test_ledger_health.py +++ b/erpnext/accounts/doctype/ledger_health/test_ledger_health.py @@ -3,23 +3,20 @@ import frappe from frappe import qb -from frappe.tests import IntegrationTestCase from frappe.utils import nowdate from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.accounts.utils import run_ledger_health_checks +from erpnext.tests.utils import ERPNextTestSuite -class TestLedgerHealth(AccountsTestMixin, IntegrationTestCase): +class TestLedgerHealth(AccountsTestMixin, ERPNextTestSuite): def setUp(self): self.create_company() self.create_customer() self.configure_monitoring_tool() self.clear_old_entries() - def tearDown(self): - frappe.db.rollback() - def configure_monitoring_tool(self): monitor_settings = frappe.get_doc("Ledger Health Monitor") monitor_settings.enable_health_monitor = True diff --git a/erpnext/accounts/doctype/ledger_health_monitor/test_ledger_health_monitor.py b/erpnext/accounts/doctype/ledger_health_monitor/test_ledger_health_monitor.py index bc2193f10ce..191a06510b9 100644 --- a/erpnext/accounts/doctype/ledger_health_monitor/test_ledger_health_monitor.py +++ b/erpnext/accounts/doctype/ledger_health_monitor/test_ledger_health_monitor.py @@ -2,8 +2,10 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase -class TestLedgerHealthMonitor(IntegrationTestCase): +from erpnext.tests.utils import ERPNextTestSuite + + +class TestLedgerHealthMonitor(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py index d65c9f6780e..a219e21526d 100644 --- a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py +++ b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py @@ -71,14 +71,16 @@ def start_merge(docname): ledger_merge.account, ) row.db_set("merged", 1) - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() successful_merges += 1 frappe.publish_realtime( "ledger_merge_progress", {"ledger_merge": ledger_merge.name, "current": successful_merges, "total": total}, ) except Exception: - frappe.db.rollback() + if not frappe.in_test: + frappe.db.rollback() ledger_merge.log_error("Ledger merge failed") finally: if successful_merges == total: diff --git a/erpnext/accounts/doctype/ledger_merge/test_ledger_merge.py b/erpnext/accounts/doctype/ledger_merge/test_ledger_merge.py index c9fb000b539..ee636f16405 100644 --- a/erpnext/accounts/doctype/ledger_merge/test_ledger_merge.py +++ b/erpnext/accounts/doctype/ledger_merge/test_ledger_merge.py @@ -2,12 +2,12 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from erpnext.accounts.doctype.ledger_merge.ledger_merge import start_merge +from erpnext.tests.utils import ERPNextTestSuite -class TestLedgerMerge(IntegrationTestCase): +class TestLedgerMerge(ERPNextTestSuite): def test_merge_success(self): if not frappe.db.exists("Account", "Indirect Expenses - _TC"): acc = frappe.new_doc("Account") @@ -100,16 +100,3 @@ class TestLedgerMerge(IntegrationTestCase): self.assertFalse(frappe.db.exists("Account", "Indirect Test Income - _TC")) self.assertTrue(frappe.db.exists("Account", "Administrative Test Income - _TC")) - - def tearDown(self): - for entry in frappe.db.get_all("Ledger Merge"): - frappe.delete_doc("Ledger Merge", entry.name) - - test_accounts = [ - "Indirect Test Expenses - _TC", - "Administrative Test Expenses - _TC", - "Indirect Test Income - _TC", - "Administrative Test Income - _TC", - ] - for account in test_accounts: - frappe.delete_doc_if_exists("Account", account) diff --git a/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.py b/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.py index 6d6579d6ed4..40a15d29909 100644 --- a/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.py +++ b/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.py @@ -2,31 +2,20 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import today -from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.tests.utils import ERPNextTestSuite -class TestLoyaltyPointEntry(IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() +class TestLoyaltyPointEntry(ERPNextTestSuite): + def setUp(self): # Create test records - create_records() - cls.loyalty_program_name = "Test Single Loyalty" - cls.customer_name = "Test Loyalty Customer" - customer = frappe.get_doc("Customer", cls.customer_name) - customer.db_set("loyalty_program", cls.loyalty_program_name) - - @classmethod - def tearDownClass(cls): - # Delete all Loyalty Point Entries - frappe.db.sql("DELETE FROM `tabLoyalty Point Entry` WHERE customer = %s", cls.customer_name) - frappe.db.sql("DELETE FROM `tabSales Invoice` WHERE customer = %s", cls.customer_name) - frappe.db.commit() - # cls.customer.delete() + self.loyalty_program_name = "Test Single Loyalty" + self.customer_name = "Test Loyalty Customer" + customer = frappe.get_doc("Customer", self.customer_name) + customer.loyalty_program = self.loyalty_program_name + customer.save() def create_test_invoice(self, redeem=None): if redeem: @@ -68,9 +57,10 @@ class TestLoyaltyPointEntry(IntegrationTestCase): self.assertEqual(entry.discretionary_reason, "Customer Appreciation") def test_redeem_loyalty_points(self): - self.create_test_invoice(redeem=10) + self.create_test_invoice() + self.create_test_invoice(redeem=7) doc = frappe.get_last_doc("Loyalty Point Entry") - self.assertEqual(doc.loyalty_points, -10) + self.assertEqual(doc.loyalty_points, -7) # Check balance balance = frappe.db.sql( @@ -82,4 +72,4 @@ class TestLoyaltyPointEntry(IntegrationTestCase): (self.customer_name,), )[0][0] - self.assertEqual(balance, 75) # 85 added, 10 redeemed + self.assertEqual(balance, 3) # 10 added, 7 redeemed diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py index 604e95df6b8..28731e4db51 100644 --- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py +++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py @@ -3,7 +3,6 @@ import unittest import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import cint, flt, getdate, today from erpnext.accounts.doctype.loyalty_program.loyalty_program import ( @@ -11,15 +10,10 @@ from erpnext.accounts.doctype.loyalty_program.loyalty_program import ( get_loyalty_program_details_with_points, ) from erpnext.accounts.party import get_dashboard_info +from erpnext.tests.utils import ERPNextTestSuite -class TestLoyaltyProgram(IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - # create relevant item, customer, loyalty program, etc - create_records() - +class TestLoyaltyProgram(ERPNextTestSuite): def test_loyalty_points_earned_single_tier(self): frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty") # create a new sales invoice @@ -323,96 +317,3 @@ def create_sales_invoice_record(qty=1): ], } ) - - -def create_records(): - # create a new loyalty Account - if not frappe.db.exists("Account", "Loyalty - _TC"): - frappe.get_doc( - { - "doctype": "Account", - "account_name": "Loyalty", - "parent_account": "Direct Expenses - _TC", - "company": "_Test Company", - "is_group": 0, - "account_type": "Expense Account", - } - ).insert() - - # create a new loyalty program Single tier - if not frappe.db.exists("Loyalty Program", "Test Single Loyalty"): - frappe.get_doc( - { - "doctype": "Loyalty Program", - "loyalty_program_name": "Test Single Loyalty", - "auto_opt_in": 1, - "from_date": today(), - "loyalty_program_type": "Single Tier Program", - "conversion_factor": 1, - "expiry_duration": 10, - "company": "_Test Company", - "cost_center": "Main - _TC", - "expense_account": "Loyalty - _TC", - "collection_rules": [{"tier_name": "Bronce", "collection_factor": 1000, "min_spent": 0}], - } - ).insert() - - # create a new customer - if not frappe.db.exists("Customer", "Test Loyalty Customer"): - frappe.get_doc( - { - "customer_group": "_Test Customer Group", - "customer_name": "Test Loyalty Customer", - "customer_type": "Individual", - "doctype": "Customer", - "territory": "_Test Territory", - } - ).insert() - - # create a new loyalty program Multiple tier - if not frappe.db.exists("Loyalty Program", "Test Multiple Loyalty"): - frappe.get_doc( - { - "doctype": "Loyalty Program", - "loyalty_program_name": "Test Multiple Loyalty", - "auto_opt_in": 1, - "from_date": today(), - "loyalty_program_type": "Multiple Tier Program", - "conversion_factor": 1, - "expiry_duration": 10, - "company": "_Test Company", - "cost_center": "Main - _TC", - "expense_account": "Loyalty - _TC", - "collection_rules": [ - {"tier_name": "Bronze", "collection_factor": 1000, "min_spent": 0}, - {"tier_name": "Silver", "collection_factor": 1000, "min_spent": 10000}, - {"tier_name": "Gold", "collection_factor": 1000, "min_spent": 19000}, - ], - } - ).insert() - - # create an item - if not frappe.db.exists("Item", "Loyal Item"): - frappe.get_doc( - { - "doctype": "Item", - "item_code": "Loyal Item", - "item_name": "Loyal Item", - "item_group": "All Item Groups", - "company": "_Test Company", - "is_stock_item": 1, - "opening_stock": 100, - "valuation_rate": 10000, - } - ).insert() - - # create item price - if not frappe.db.exists("Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}): - frappe.get_doc( - { - "doctype": "Item Price", - "price_list": "Standard Selling", - "item_code": "Loyal Item", - "price_list_rate": 10000, - } - ).insert() diff --git a/erpnext/accounts/doctype/mode_of_payment/test_mode_of_payment.py b/erpnext/accounts/doctype/mode_of_payment/test_mode_of_payment.py index 6099a7ec70a..679bbb53386 100644 --- a/erpnext/accounts/doctype/mode_of_payment/test_mode_of_payment.py +++ b/erpnext/accounts/doctype/mode_of_payment/test_mode_of_payment.py @@ -2,10 +2,11 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase + +from erpnext.tests.utils import ERPNextTestSuite -class TestModeofPayment(IntegrationTestCase): +class TestModeofPayment(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/monthly_distribution/test_monthly_distribution.py b/erpnext/accounts/doctype/monthly_distribution/test_monthly_distribution.py index 9d6f2578639..29d148b4e92 100644 --- a/erpnext/accounts/doctype/monthly_distribution/test_monthly_distribution.py +++ b/erpnext/accounts/doctype/monthly_distribution/test_monthly_distribution.py @@ -1,8 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestMonthlyDistribution(IntegrationTestCase): +class TestMonthlyDistribution(ERPNextTestSuite): pass 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 4938e6690e5..466b38126d7 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 @@ -50,6 +50,7 @@ frappe.ui.form.on("Opening Invoice Creation Tool", { refresh: function (frm) { frm.disable_save(); + frm.trigger("create_missing_party"); !frm.doc.import_in_progress && frm.trigger("make_dashboard"); frm.page.set_primary_action(__("Create Invoices"), () => { let btn_primary = frm.page.btn_primary.get(0); @@ -123,7 +124,8 @@ frappe.ui.form.on("Opening Invoice Creation Tool", { invoice_type: function (frm) { $.each(frm.doc.invoices, (idx, row) => { row.party_type = frm.doc.invoice_type == "Sales" ? "Customer" : "Supplier"; - row.party = ""; + frappe.model.set_value(row.doctype, row.name, "party", ""); + frappe.model.set_value(row.doctype, row.name, "party_name", ""); }); frm.refresh_fields(); }, @@ -162,9 +164,35 @@ frappe.ui.form.on("Opening Invoice Creation Tool", { row.party_type = frm.doc.invoice_type == "Sales" ? "Customer" : "Supplier"; }); }, + + create_missing_party: function (frm) { + if (frm.doc.create_missing_party) { + frm.fields_dict["invoices"].grid.update_docfield_property("party", "reqd", 0); + frm.fields_dict["invoices"].grid.update_docfield_property("party_name", "read_only", 0); + } else { + frm.fields_dict["invoices"].grid.update_docfield_property("party", "reqd", 1); + frm.fields_dict["invoices"].grid.update_docfield_property("party_name", "read_only", 1); + } + frm.refresh_field("invoices"); + }, }); frappe.ui.form.on("Opening Invoice Creation Tool Item", { + party: function (frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (!row.party) { + frappe.model.set_value(cdt, cdn, "party_name", ""); + return; + } + + let party_type = frm.doc.invoice_type == "Sales" ? "Customer" : "Supplier"; + let name_field = party_type === "Customer" ? "customer_name" : "supplier_name"; + + frappe.db.get_value(party_type, row.party, name_field, (r) => { + frappe.model.set_value(cdt, cdn, "party_name", r?.[name_field] || ""); + }); + }, + invoices_add: (frm) => { frm.trigger("update_invoice_table"); }, diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json index deeee72c18a..8d1c3e87ba1 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json @@ -8,9 +8,9 @@ "engine": "InnoDB", "field_order": [ "company", - "create_missing_party", "column_break_3", "invoice_type", + "create_missing_party", "accounting_dimensions_section", "cost_center", "dimension_col_break", @@ -29,7 +29,7 @@ }, { "default": "0", - "description": "Create missing customer or supplier.", + "description": "If party does not exist, create it using the Party Name field.", "fieldname": "create_missing_party", "fieldtype": "Check", "label": "Create Missing Party" @@ -65,10 +65,10 @@ "options": "Cost Center" }, { - "fieldname": "project", - "fieldtype": "Link", - "label": "Project", - "options": "Project" + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" }, { "collapsible": 1, @@ -84,7 +84,7 @@ "hide_toolbar": 1, "issingle": 1, "links": [], - "modified": "2024-03-27 13:10:06.564397", + "modified": "2026-03-23 00:32:15.600086", "modified_by": "Administrator", "module": "Accounts", "name": "Opening Invoice Creation Tool", @@ -101,8 +101,9 @@ } ], "quick_entry": 1, + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ 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 4b5c8bcfeb8..1744e9928f9 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 @@ -32,6 +32,7 @@ class OpeningInvoiceCreationTool(Document): create_missing_party: DF.Check invoice_type: DF.Literal["Sales", "Purchase"] invoices: DF.Table[OpeningInvoiceCreationToolItem] + project: DF.Link | None # end: auto-generated types def onload(self): @@ -102,10 +103,20 @@ class OpeningInvoiceCreationTool(Document): row.due_date = row.due_date or nowdate() def validate_mandatory_invoice_fields(self, row): - if not frappe.db.exists(row.party_type, row.party): - if self.create_missing_party: - self.add_party(row.party_type, row.party) - else: + if self.create_missing_party: + if not row.party and not row.party_name: + frappe.throw(_("Row #{}: Either Party ID or Party Name is required").format(row.idx)) + + if not row.party and row.party_name: + row.party = self.add_party(row.party_type, row.party_name) + + if row.party and not frappe.db.exists(row.party_type, row.party): + row.party = self.add_party(row.party_type, row.party) + + else: + if not row.party: + frappe.throw(_("Row #{}: Party ID is required").format(row.idx)) + if not frappe.db.exists(row.party_type, row.party): frappe.throw( _("Row #{}: {} {} does not exist.").format( row.idx, frappe.bold(row.party_type), frappe.bold(row.party) @@ -113,7 +124,7 @@ class OpeningInvoiceCreationTool(Document): ) mandatory_error_msg = _("Row #{0}: {1} is required to create the Opening {2} Invoices") - for d in ("Party", "Outstanding Amount", "Temporary Opening Account"): + for d in ("Outstanding Amount", "Temporary Opening Account"): if not row.get(scrub(d)): frappe.throw(mandatory_error_msg.format(row.idx, d, self.invoice_type)) @@ -159,6 +170,7 @@ class OpeningInvoiceCreationTool(Document): party_doc.flags.ignore_mandatory = True party_doc.save(ignore_permissions=True) + return party_doc.name def get_invoice_dict(self, row=None): def get_item_dict(): @@ -262,7 +274,8 @@ def start_import(invoices): doc.flags.ignore_mandatory = True doc.insert(set_name=invoice_number) doc.submit() - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() names.append(doc.name) except Exception: errors += 1 diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool_dashboard.html b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool_dashboard.html index afbcfa5602a..43be52717c3 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool_dashboard.html +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool_dashboard.html @@ -1,5 +1,5 @@ {% $.each(data, (company, summary) => { %} -
{{ company }}
+
{{ company }}
@@ -23,7 +23,7 @@ - + {% endif %} {% }); %} diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py index 730d5db8ae6..c01ada6d317 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py @@ -2,27 +2,14 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase -from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import ( - create_dimension, - disable_dimension, -) from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import ( get_temporary_opening_account, ) - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Customer", "Supplier", "Accounting Dimension"] +from erpnext.tests.utils import ERPNextTestSuite -class TestOpeningInvoiceCreationTool(IntegrationTestCase): - @classmethod - def setUpClass(cls): - if not frappe.db.exists("Company", "_Test Opening Invoice Company"): - make_company() - create_dimension() - return super().setUpClass() - +class TestOpeningInvoiceCreationTool(ERPNextTestSuite): def make_invoices( self, invoice_type="Sales", @@ -149,9 +136,6 @@ class TestOpeningInvoiceCreationTool(IntegrationTestCase): } self.check_expected_values(invoices, expected_value, invoice_type="Sales") - def tearDown(self): - disable_dimension() - def get_opening_invoice_creation_dict(**args): party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier" @@ -190,19 +174,6 @@ def get_opening_invoice_creation_dict(**args): return invoice_dict -def make_company(): - if frappe.db.exists("Company", "_Test Opening Invoice Company"): - return frappe.get_doc("Company", "_Test Opening Invoice Company") - - company = frappe.new_doc("Company") - company.company_name = "_Test Opening Invoice Company" - company.abbr = "_TOIC" - company.default_currency = "INR" - company.country = "Pakistan" - company.insert() - return company - - def make_customer(customer=None): customer_name = customer or "Opening Customer" customer = frappe.get_doc( diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json index 29daab42439..74ce2a6fb67 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json @@ -8,6 +8,7 @@ "invoice_number", "party_type", "party", + "party_name", "temporary_opening_account", "column_break_3", "posting_date", @@ -35,9 +36,9 @@ "fieldname": "party", "fieldtype": "Dynamic Link", "in_list_view": 1, - "label": "Party", - "options": "party_type", - "reqd": 1 + "label": "Party ID", + "mandatory_depends_on": "eval: !parent.create_missing_party", + "options": "party_type" }, { "fieldname": "temporary_opening_account", @@ -118,11 +119,17 @@ "fieldname": "supplier_invoice_date", "fieldtype": "Date", "label": "Supplier Invoice Date" + }, + { + "fieldname": "party_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Party Name" } ], "istable": 1, "links": [], - "modified": "2025-12-01 16:18:07.997594", + "modified": "2026-03-20 02:11:42.023575", "modified_by": "Administrator", "module": "Accounts", "name": "Opening Invoice Creation Tool Item", diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.py b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.py index 1ea025322b4..38e97672c96 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.py @@ -22,7 +22,8 @@ class OpeningInvoiceCreationToolItem(Document): parent: DF.Data parentfield: DF.Data parenttype: DF.Data - party: DF.DynamicLink + party: DF.DynamicLink | None + party_name: DF.Data | None party_type: DF.Link | None posting_date: DF.Date | None qty: DF.Data | None diff --git a/erpnext/accounts/doctype/party_link/test_party_link.py b/erpnext/accounts/doctype/party_link/test_party_link.py index a796de526a5..4f488b19456 100644 --- a/erpnext/accounts/doctype/party_link/test_party_link.py +++ b/erpnext/accounts/doctype/party_link/test_party_link.py @@ -2,8 +2,8 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestPartyLink(IntegrationTestCase): +class TestPartyLink(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 1adf6a5866e..afa508781d8 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -138,6 +138,7 @@ "fieldname": "posting_date", "fieldtype": "Date", "in_list_view": 1, + "in_standard_filter": 1, "label": "Posting Date", "reqd": 1 }, @@ -160,7 +161,6 @@ { "fieldname": "mode_of_payment", "fieldtype": "Link", - "in_list_view": 1, "label": "Mode of Payment", "options": "Mode of Payment" }, @@ -228,6 +228,7 @@ "fieldname": "paid_from", "fieldtype": "Link", "in_global_search": 1, + "in_list_view": 1, "label": "Account Paid From", "options": "Account", "print_hide": 1, @@ -252,6 +253,7 @@ "fieldname": "paid_to", "fieldtype": "Link", "in_global_search": 1, + "in_list_view": 1, "label": "Account Paid To", "options": "Account", "print_hide": 1, @@ -414,6 +416,7 @@ "depends_on": "eval:(doc.paid_from && doc.paid_to)", "fieldname": "reference_no", "fieldtype": "Data", + "in_standard_filter": 1, "label": "Cheque/Reference No", "mandatory_depends_on": "eval:(doc.paid_from_account_type == 'Bank' || doc.paid_to_account_type == 'Bank')" }, @@ -792,7 +795,7 @@ "table_fieldname": "payment_entries" } ], - "modified": "2026-02-03 16:08:49.800381", + "modified": "2026-03-09 17:15:30.453920", "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 31d6a1a7f5a..1d693c15078 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -2378,9 +2378,7 @@ def get_outstanding_reference_documents(args: str | dict, validate: bool = False vouchers=args.get("vouchers") or None, ) - outstanding_invoices = split_invoices_based_on_payment_terms( - outstanding_invoices, args.get("company") - ) + outstanding_invoices = split_refdocs_based_on_payment_terms(outstanding_invoices, args.get("company")) for d in outstanding_invoices: d["exchange_rate"] = 1 @@ -2418,6 +2416,8 @@ def get_outstanding_reference_documents(args: str | dict, validate: bool = False filters=args, ) + orders_to_be_billed = split_refdocs_based_on_payment_terms(orders_to_be_billed, args.get("company")) + data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed if not data: @@ -2440,13 +2440,13 @@ def get_outstanding_reference_documents(args: str | dict, validate: bool = False return data -def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list: +def split_refdocs_based_on_payment_terms(refdocs, company) -> list: """Split a list of invoices based on their payment terms.""" - exc_rates = get_currency_data(outstanding_invoices, company) + exc_rates = get_currency_data(refdocs, company) - outstanding_invoices_after_split = [] - for entry in outstanding_invoices: - if entry.voucher_type in ["Sales Invoice", "Purchase Invoice"]: + outstanding_refdoc_after_split = [] + for entry in refdocs: + if entry.voucher_type in ["Sales Invoice", "Purchase Invoice", "Sales Order", "Purchase Order"]: if payment_term_template := frappe.db.get_value( entry.voucher_type, entry.voucher_no, "payment_terms_template" ): @@ -2461,25 +2461,25 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list ), alert=True, ) - outstanding_invoices_after_split += split_rows + outstanding_refdoc_after_split += split_rows continue # If not an invoice or no payment terms template, add as it is - outstanding_invoices_after_split.append(entry) + outstanding_refdoc_after_split.append(entry) - return outstanding_invoices_after_split + return outstanding_refdoc_after_split -def get_currency_data(outstanding_invoices: list, company: str | None = None) -> dict: +def get_currency_data(outstanding_refdocs: list, company: str | None = None) -> dict: """Get currency and conversion data for a list of invoices.""" exc_rates = frappe._dict() company_currency = frappe.db.get_value("Company", company, "default_currency") if company else None - for doctype in ["Sales Invoice", "Purchase Invoice"]: - invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype] + for doctype in ["Sales Invoice", "Purchase Invoice", "Sales Order", "Purchase Order"]: + refdoc = [x.voucher_no for x in outstanding_refdocs if x.voucher_type == doctype] for x in frappe.db.get_all( doctype, - filters={"name": ["in", invoices]}, + filters={"name": ["in", refdoc]}, fields=["name", "currency", "conversion_rate", "party_account_currency"], ): exc_rates[x.name] = frappe._dict( @@ -2558,14 +2558,9 @@ def get_orders_to_be_billed( if not voucher_type: return [] - # Add cost center condition - doc = frappe.get_doc({"doctype": voucher_type}) - condition = "" - if doc and hasattr(doc, "cost_center") and doc.cost_center: - condition = " and cost_center='%s'" % cost_center - # dynamic dimension filters - active_dimensions = get_dimensions()[0] + condition = "" + active_dimensions = get_dimensions(True)[0] for dim in active_dimensions: if filters.get(dim.fieldname): condition += f" and {dim.fieldname}='{filters.get(dim.fieldname)}'" diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index f6c240c0714..9d2890f5e79 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -4,7 +4,6 @@ import frappe from frappe import qb -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, flt, nowdate from erpnext.accounts.doctype.account.test_account import create_account @@ -24,14 +23,10 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import ( ) from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.setup.doctype.employee.test_employee import make_employee - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Item", "Currency Exchange"] +from erpnext.tests.utils import ERPNextTestSuite -class TestPaymentEntry(IntegrationTestCase): - def tearDown(self): - frappe.db.rollback() - +class TestPaymentEntry(ERPNextTestSuite): def get_journals_for(self, voucher_type: str, voucher_no: str) -> list: journals = [] if voucher_type and voucher_no: @@ -427,7 +422,7 @@ class TestPaymentEntry(IntegrationTestCase): self.assertEqual(si.payment_schedule[0].outstanding, 0) self.assertEqual(si.payment_schedule[0].discounted_amount, 50) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", { "allow_multi_currency_invoices_against_single_party_account": 1, @@ -645,6 +640,7 @@ class TestPaymentEntry(IntegrationTestCase): def test_payment_entry_retrieves_last_exchange_rate(self): from erpnext.setup.doctype.currency_exchange.test_currency_exchange import save_new_records + self.load_test_records("Currency Exchange") save_new_records(self.globalTestRecords["Currency Exchange"]) pe = frappe.new_doc("Payment Entry") @@ -982,6 +978,7 @@ class TestPaymentEntry(IntegrationTestCase): def test_gl_of_multi_currency_payment_transaction(self): from erpnext.setup.doctype.currency_exchange.test_currency_exchange import save_new_records + self.load_test_records("Currency Exchange") save_new_records(self.globalTestRecords["Currency Exchange"]) paid_from = create_account( parent_account="Current Liabilities - _TC", @@ -1158,7 +1155,7 @@ class TestPaymentEntry(IntegrationTestCase): } self.assertDictEqual(ref_details, expected_response) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", { "unlink_payment_on_cancellation_of_invoice": 1, @@ -1253,7 +1250,7 @@ class TestPaymentEntry(IntegrationTestCase): si3.cancel() si3.delete() - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", { "unlink_payment_on_cancellation_of_invoice": 1, @@ -1949,7 +1946,7 @@ class TestPaymentEntry(IntegrationTestCase): # 'Is Opening' should always be 'No' for normal advance payments self.assertEqual(gl_with_opening_set, []) - @IntegrationTestCase.change_settings("Accounts Settings", {"delete_linked_ledger_entries": 1}) + @ERPNextTestSuite.change_settings("Accounts Settings", {"delete_linked_ledger_entries": 1}) def test_delete_linked_exchange_gain_loss_journal(self): from erpnext.accounts.doctype.account.test_account import create_account from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( @@ -2022,6 +2019,92 @@ class TestPaymentEntry(IntegrationTestCase): self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, pe.doctype, pe.name) self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, "Journal Entry", jv[0]) + def test_outstanding_orders_split_by_payment_terms(self): + create_payment_terms_template() + + so = make_sales_order(do_not_save=1, qty=1, rate=200) + so.payment_terms_template = "Test Receivable Template" + so.save().submit() + + args = { + "posting_date": nowdate(), + "company": so.company, + "party_type": "Customer", + "payment_type": "Receive", + "party": so.customer, + "party_account": "Debtors - _TC", + "get_orders_to_be_billed": True, + } + + references = get_outstanding_reference_documents(args) + + self.assertEqual(len(references), 2) + self.assertEqual(references[0].voucher_no, so.name) + self.assertEqual(references[1].voucher_no, so.name) + self.assertEqual(references[0].payment_term, "Basic Amount Receivable") + self.assertEqual(references[1].payment_term, "Tax Receivable") + + def test_outstanding_orders_no_split_when_allocate_disabled(self): + create_payment_terms_template() + + template = frappe.get_doc("Payment Terms Template", "Test Receivable Template") + template.allocate_payment_based_on_payment_terms = 0 + template.save() + + so = make_sales_order(do_not_save=1, qty=1, rate=200) + so.payment_terms_template = "Test Receivable Template" + so.save().submit() + + args = { + "posting_date": nowdate(), + "company": so.company, + "party_type": "Customer", + "payment_type": "Receive", + "party": so.customer, + "party_account": "Debtors - _TC", + "get_orders_to_be_billed": True, + } + + references = get_outstanding_reference_documents(args) + + self.assertEqual(len(references), 1) + self.assertIsNone(references[0].payment_term) + + template.allocate_payment_based_on_payment_terms = 1 + template.save() + + def test_outstanding_multicurrency_sales_order_split(self): + create_payment_terms_template() + + so = make_sales_order( + customer="_Test Customer USD", + currency="USD", + qty=1, + rate=100, + do_not_submit=True, + ) + so.payment_terms_template = "Test Receivable Template" + so.conversion_rate = 50 + so.save().submit() + + args = { + "posting_date": nowdate(), + "company": so.company, + "party_type": "Customer", + "payment_type": "Receive", + "party": so.customer, + "party_account": "Debtors - _TC", + "get_orders_to_be_billed": True, + } + + references = get_outstanding_reference_documents(args) + + # Should split without throwing currency errors + self.assertEqual(len(references), 2) + for ref in references: + self.assertEqual(ref.voucher_no, so.name) + self.assertIsNotNone(ref.payment_term) + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") diff --git a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json index 59a01bc84ef..8d4e55c970a 100644 --- a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json +++ b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json @@ -22,6 +22,7 @@ "reqd": 1 }, { + "allow_on_submit": 1, "fieldname": "cost_center", "fieldtype": "Link", "in_list_view": 1, @@ -59,7 +60,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-08-13 06:52:46.130142", + "modified": "2026-03-11 14:26:11.312950", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Deduction", diff --git a/erpnext/accounts/doctype/payment_gateway_account/test_payment_gateway_account.py b/erpnext/accounts/doctype/payment_gateway_account/test_payment_gateway_account.py index 655bd2d7d99..fe70292144f 100644 --- a/erpnext/accounts/doctype/payment_gateway_account/test_payment_gateway_account.py +++ b/erpnext/accounts/doctype/payment_gateway_account/test_payment_gateway_account.py @@ -1,10 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase - -IGNORE_TEST_RECORD_DEPENDENCIES = ["Payment Gateway"] +from erpnext.tests.utils import ERPNextTestSuite -class TestPaymentGatewayAccount(IntegrationTestCase): +class TestPaymentGatewayAccount(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.js b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.js index 07fe83177ba..1d54968144b 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.js +++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.js @@ -2,6 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on("Payment Ledger Entry", { - // refresh: function(frm) { - // } + refresh(frm) { + frm.page.btn_secondary.hide(); + }, }); diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json index fcc3a15206a..de4bc9d98ef 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json +++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json @@ -5,6 +5,7 @@ "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", + "is_submittable": 1, "field_order": [ "posting_date", "company", diff --git a/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py index 1de103764a4..c2528040e98 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py +++ b/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py @@ -3,7 +3,6 @@ import frappe from frappe import qb -from frappe.tests import IntegrationTestCase from frappe.utils import nowdate from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry @@ -11,9 +10,10 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_pay from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.stock.doctype.item.test_item import create_item +from erpnext.tests.utils import ERPNextTestSuite -class TestPaymentLedgerEntry(IntegrationTestCase): +class TestPaymentLedgerEntry(ERPNextTestSuite): def setUp(self): self.ple = qb.DocType("Payment Ledger Entry") self.create_company() @@ -21,9 +21,6 @@ class TestPaymentLedgerEntry(IntegrationTestCase): self.create_customer() self.clear_old_entries() - def tearDown(self): - frappe.db.rollback() - def create_company(self): company_name = "_Test Payment Ledger" company = None @@ -445,7 +442,7 @@ class TestPaymentLedgerEntry(IntegrationTestCase): self.assertEqual(pl_entries_for_crnote[0], expected_values[0]) self.assertEqual(pl_entries_for_crnote[1], expected_values[1]) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1}, ) @@ -474,7 +471,7 @@ class TestPaymentLedgerEntry(IntegrationTestCase): si.delete() self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, si.doctype, si.name) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1}, ) @@ -507,7 +504,7 @@ class TestPaymentLedgerEntry(IntegrationTestCase): si.delete() self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, si.doctype, si.name) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", { "unlink_payment_on_cancellation_of_invoice": 1, diff --git a/erpnext/accounts/doctype/payment_order/test_payment_order.py b/erpnext/accounts/doctype/payment_order/test_payment_order.py index f12491fdf15..b11500cfd20 100644 --- a/erpnext/accounts/doctype/payment_order/test_payment_order.py +++ b/erpnext/accounts/doctype/payment_order/test_payment_order.py @@ -3,7 +3,6 @@ import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import getdate from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import ( @@ -15,9 +14,10 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import ( make_payment_order, ) from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice +from erpnext.tests.utils import ERPNextTestSuite -class TestPaymentOrder(IntegrationTestCase): +class TestPaymentOrder(ERPNextTestSuite): def setUp(self): # generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error uniq_identifier = frappe.generate_hash(length=10) @@ -26,9 +26,6 @@ class TestPaymentOrder(IntegrationTestCase): gl_account=self.gl_account, bank_account_name="Checking Account " + uniq_identifier ) - def tearDown(self): - frappe.db.rollback() - def test_payment_order_creation_against_payment_entry(self): purchase_invoice = make_purchase_invoice() payment_entry = get_payment_entry( diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 3682e7c63a9..1bbf228d73a 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -4,7 +4,6 @@ import frappe from frappe import qb -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, add_years, flt, getdate, nowdate, today from frappe.utils.data import getdate as convert_to_date @@ -17,11 +16,10 @@ from erpnext.accounts.party import get_party_account from erpnext.accounts.utils import get_fiscal_year from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.stock.doctype.item.test_item import create_item - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Item"] +from erpnext.tests.utils import ERPNextTestSuite -class TestPaymentReconciliation(IntegrationTestCase): +class TestPaymentReconciliation(ERPNextTestSuite): def setUp(self): self.create_company() self.create_item() @@ -30,9 +28,6 @@ class TestPaymentReconciliation(IntegrationTestCase): self.create_cost_center() self.clear_old_entries() - def tearDown(self): - frappe.db.rollback() - def create_company(self): company = None if frappe.db.exists("Company", "_Test Payment Reconciliation"): @@ -1235,7 +1230,7 @@ class TestPaymentReconciliation(IntegrationTestCase): payment_vouchers = [x.get("reference_name") for x in pr.get("payments")] self.assertCountEqual(payment_vouchers, [je2.name, pe2.name]) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", { "allow_multi_currency_invoices_against_single_party_account": 1, diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 1ba9cf1675d..4f6e4a8f037 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -750,7 +750,8 @@ def make_payment_request(**args): pr.submit() if args.order_type == "Shopping Cart": - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() frappe.local.response["type"] = "redirect" frappe.local.response["location"] = pr.get_payment_url() @@ -955,6 +956,7 @@ def resend_payment_email(docname: str): @frappe.whitelist() def make_payment_entry(docname: str): doc = frappe.get_doc("Payment Request", docname) + doc.check_permission("read") return doc.create_payment_entry(submit=False).as_dict() diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index c59846d69b7..ae9c8d21976 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -6,7 +6,6 @@ import re from unittest.mock import patch import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, nowdate from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry @@ -17,8 +16,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.setup.utils import get_exchange_rate - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Currency Exchange", "Journal Entry", "Contact", "Address"] +from erpnext.tests.utils import ERPNextTestSuite PAYMENT_URL = "https://example.com/payment" @@ -63,7 +61,7 @@ payment_method = [ ] -class TestPaymentRequest(IntegrationTestCase): +class TestPaymentRequest(ERPNextTestSuite): def setUp(self): for payment_gateway in payment_gateways: if not frappe.db.get_value("Payment Gateway", payment_gateway["gateway"], "name"): @@ -100,9 +98,6 @@ class TestPaymentRequest(IntegrationTestCase): self._get_payment_gateway_controller = _get_payment_gateway_controller.start() self.addCleanup(_get_payment_gateway_controller.stop) - def tearDown(self): - frappe.db.rollback() - def test_payment_request_linkings(self): so_inr = make_sales_order(currency="INR", do_not_save=True) so_inr.disable_rounded_total = 1 @@ -503,7 +498,7 @@ class TestPaymentRequest(IntegrationTestCase): return_doc=1, ) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1} ) def test_multiple_payment_if_partially_paid_for_multi_currency(self): @@ -621,7 +616,7 @@ class TestPaymentRequest(IntegrationTestCase): self.assertEqual(pr.outstanding_amount, 0) self.assertEqual(pr.grand_total, 20000) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1} ) def test_single_payment_with_payment_term_for_multi_currency(self): diff --git a/erpnext/accounts/doctype/payment_term/test_payment_term.py b/erpnext/accounts/doctype/payment_term/test_payment_term.py index 78c4a672476..f170c789256 100644 --- a/erpnext/accounts/doctype/payment_term/test_payment_term.py +++ b/erpnext/accounts/doctype/payment_term/test_payment_term.py @@ -1,8 +1,8 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestPaymentTerm(IntegrationTestCase): +class TestPaymentTerm(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/payment_term/test_records.json b/erpnext/accounts/doctype/payment_term/test_records.json deleted file mode 100644 index ef6e0693b3c..00000000000 --- a/erpnext/accounts/doctype/payment_term/test_records.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "doctype":"Payment Term", - "due_date_based_on":"Day(s) after invoice date", - "payment_term_name":"_Test N30", - "description":"_Test Net 30 Days", - "invoice_portion":50, - "credit_days":30 - }, - { - "doctype":"Payment Term", - "due_date_based_on":"Day(s) after invoice date", - "payment_term_name":"_Test COD", - "description":"_Test Cash on Delivery", - "invoice_portion":50, - "credit_days":0 - }, - { - "doctype":"Payment Term", - "due_date_based_on":"Month(s) after the end of the invoice month", - "payment_term_name":"_Test EONM", - "description":"_Test End of Next Month", - "invoice_portion":100, - "credit_months":1 - }, - { - "doctype":"Payment Term", - "due_date_based_on":"Day(s) after invoice date", - "payment_term_name":"_Test N30 1", - "description":"_Test Net 30 Days", - "invoice_portion":100, - "credit_days":30 - } -] \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.py b/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.py index c8a43991a44..9fc91b6f2de 100644 --- a/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.py +++ b/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.py @@ -2,13 +2,11 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase + +from erpnext.tests.utils import ERPNextTestSuite -class TestPaymentTermsTemplate(IntegrationTestCase): - def tearDown(self): - frappe.delete_doc("Payment Terms Template", "_Test Payment Terms Template For Test", force=1) - +class TestPaymentTermsTemplate(ERPNextTestSuite): def test_create_template(self): template = frappe.get_doc( { diff --git a/erpnext/accounts/doctype/payment_terms_template/test_records.json b/erpnext/accounts/doctype/payment_terms_template/test_records.json deleted file mode 100644 index fea0b35c112..00000000000 --- a/erpnext/accounts/doctype/payment_terms_template/test_records.json +++ /dev/null @@ -1,60 +0,0 @@ -[ - { - "doctype":"Payment Terms Template", - "terms":[ - { - "doctype":"Payment Terms Template Detail", - "due_date_based_on":"Day(s) after invoice date", - "idx":1, - "description":"Cash on Delivery", - "invoice_portion":50, - "credit_days":0, - "credit_months":0, - "payment_term":"_Test COD" - }, - { - "doctype":"Payment Terms Template Detail", - "due_date_based_on":"Day(s) after invoice date", - "idx":2, - "description":"Net 30 Days ", - "invoice_portion":50, - "credit_days":30, - "credit_months":0, - "payment_term":"_Test N30" - } - ], - "template_name":"_Test Payment Term Template" - }, - { - "doctype":"Payment Terms Template", - "terms":[ - { - "doctype":"Payment Terms Template Detail", - "due_date_based_on":"Month(s) after the end of the invoice month", - "idx":1, - "description":"_Test End of Next Months", - "invoice_portion":100, - "credit_days":0, - "credit_months":1, - "payment_term":"_Test EONM" - } - ], - "template_name":"_Test Payment Term Template 1" - }, - { - "doctype":"Payment Terms Template", - "terms":[ - { - "doctype":"Payment Terms Template Detail", - "due_date_based_on":"Day(s) after invoice date", - "idx":1, - "description":"_Test Net Within 30 days", - "invoice_portion":100, - "credit_days":30, - "credit_months":0, - "payment_term":"_Test N30 1" - } - ], - "template_name":"_Test Payment Term Template 3" - } -] \ No newline at end of file diff --git a/erpnext/accounts/doctype/pegged_currencies/test_pegged_currencies.py b/erpnext/accounts/doctype/pegged_currencies/test_pegged_currencies.py index 32bb3f34fd5..e695b11bcb5 100644 --- a/erpnext/accounts/doctype/pegged_currencies/test_pegged_currencies.py +++ b/erpnext/accounts/doctype/pegged_currencies/test_pegged_currencies.py @@ -2,28 +2,3 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase, UnitTestCase - -# On IntegrationTestCase, the doctype test records and all -# link-field test record dependencies are recursively loaded -# Use these module variables to add/remove to/from that list -EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] -IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] - - -class UnitTestPeggedCurrencies(UnitTestCase): - """ - Unit tests for PeggedCurrencies. - Use this class for testing individual functions and methods. - """ - - pass - - -class IntegrationTestPeggedCurrencies(IntegrationTestCase): - """ - Integration tests for PeggedCurrencies. - Use this class for testing interactions between multiple components. - """ - - pass 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 9bb8e7bff80..7b6d8e5c317 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 @@ -2,16 +2,16 @@ # License: GNU General Public License v3. See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import today from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.utils import get_fiscal_year +from erpnext.tests.utils import ERPNextTestSuite -class TestPeriodClosingVoucher(IntegrationTestCase): +class TestPeriodClosingVoucher(ERPNextTestSuite): def setUp(self): super().setUp() frappe.db.set_single_value("Accounts Settings", "use_legacy_controller_for_pcv", 1) @@ -390,6 +390,3 @@ def create_cost_center(cc_name): ) costcenter.insert(ignore_if_duplicate=True) return costcenter.name - - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Customer", "Cost Center"] diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py index d771a08cf06..7f546a4c7bb 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py @@ -2,12 +2,7 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase -from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import ( - create_dimension, - disable_dimension, -) from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import ( make_closing_entry_from_opening, ) @@ -21,26 +16,14 @@ from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle get_batch_from_bundle, ) from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry +from erpnext.tests.utils import ERPNextTestSuite -class TestPOSClosingEntry(IntegrationTestCase): - @classmethod - def setUpClass(cls): - frappe.db.sql("delete from `tabPOS Opening Entry`") - cls.enterClassContext(cls.change_settings("POS Settings", {"invoice_type": "POS Invoice"})) - - @classmethod - def tearDownClass(cls): - frappe.db.sql("delete from `tabPOS Opening Entry`") - +class TestPOSClosingEntry(ERPNextTestSuite): def setUp(self): - # Make stock available for POS Sales - frappe.db.sql("delete from `tabPOS Opening Entry`") + init_user_and_profile() make_stock_entry(target="_Test Warehouse - _TC", qty=2, basic_rate=100) - - def tearDown(self): - frappe.set_user("Administrator") - frappe.db.sql("delete from `tabPOS Profile`") + frappe.db.set_single_value("POS Settings", "invoice_type", "POS Invoice") def test_pos_closing_entry(self): test_user, pos_profile = init_user_and_profile() @@ -57,6 +40,7 @@ class TestPOSClosingEntry(IntegrationTestCase): pos_inv2.submit() pcv_doc = make_closing_entry_from_opening(opening_entry) + pcv_doc.flags.in_test = True payment = pcv_doc.payment_reconciliation[0] self.assertEqual(payment.mode_of_payment, "Cash") @@ -65,6 +49,7 @@ class TestPOSClosingEntry(IntegrationTestCase): if d.mode_of_payment == "Cash": d.closing_amount = 6700 + pcv_doc.flags.in_test = True pcv_doc.submit() self.assertEqual(pcv_doc.total_quantity, 2) @@ -83,6 +68,7 @@ class TestPOSClosingEntry(IntegrationTestCase): pos_inv.submit() pcv_doc = make_closing_entry_from_opening(opening_entry) + pcv_doc.flags.in_test = True pcv_doc.submit() self.assertTrue(pcv_doc.name) @@ -115,6 +101,7 @@ class TestPOSClosingEntry(IntegrationTestCase): pos_return.submit() pcv_doc = make_closing_entry_from_opening(opening_entry) + pcv_doc.flags.in_test = True pcv_doc.submit() opening_entry = create_opening_entry(pos_profile, test_user.name) @@ -144,6 +131,7 @@ class TestPOSClosingEntry(IntegrationTestCase): if d.mode_of_payment == "Cash": d.closing_amount = 6700 + pcv_doc.flags.in_test = True pcv_doc.submit() pos_inv1.load_from_db() @@ -169,7 +157,6 @@ class TestPOSClosingEntry(IntegrationTestCase): test case to check whether we can create POS Closing Entry without mandatory accounting dimension """ - create_dimension() location = frappe.get_doc("Accounting Dimension", "Location") location.dimension_defaults[0].mandatory_for_bs = True location.save() @@ -197,6 +184,7 @@ class TestPOSClosingEntry(IntegrationTestCase): pcv_doc = make_closing_entry_from_opening(opening_entry) # will assert coz the new mandatory accounting dimension bank is not set in POS Profile + pcv_doc.flags.in_test = True self.assertRaises(frappe.ValidationError, pcv_doc.submit) accounting_dimension_department = frappe.get_doc( @@ -204,7 +192,6 @@ class TestPOSClosingEntry(IntegrationTestCase): ) accounting_dimension_department.mandatory_for_bs = 0 accounting_dimension_department.save() - disable_dimension() def test_merging_into_sales_invoice_for_batched_item(self): frappe.flags.print_message = False @@ -264,6 +251,7 @@ class TestPOSClosingEntry(IntegrationTestCase): self.assertEqual(batch_qty_with_pos, 0.0) pcv_doc = make_closing_entry_from_opening(opening_entry) + pcv_doc.flags.in_test = True pcv_doc.submit() piv_merge = frappe.db.get_value("POS Invoice Merge Log", {"pos_closing_entry": pcv_doc.name}, "name") @@ -287,6 +275,7 @@ class TestPOSClosingEntry(IntegrationTestCase): frappe.flags.print_message = True pcv_doc.reload() + pcv_doc.flags.in_test = True pcv_doc.cancel() batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code) @@ -301,7 +290,7 @@ class TestPOSClosingEntry(IntegrationTestCase): batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code) self.assertEqual(batch_qty_with_pos, 10.0) - @IntegrationTestCase.change_settings("POS Settings", {"invoice_type": "Sales Invoice"}) + @ERPNextTestSuite.change_settings("POS Settings", {"invoice_type": "Sales Invoice"}) def test_closing_entries_with_sales_invoice(self): test_user, pos_profile = init_user_and_profile() opening_entry = create_opening_entry(pos_profile, test_user.name) @@ -329,6 +318,7 @@ class TestPOSClosingEntry(IntegrationTestCase): if d.mode_of_payment == "Cash": d.closing_amount = 1500 + pcv_doc.flags.in_test = True pcv_doc.submit() self.assertEqual(pcv_doc.total_quantity, 15) @@ -472,7 +462,7 @@ def init_user_and_profile(**args): user = "test@example.com" test_user = frappe.get_doc("User", user) - roles = ("Accounts Manager", "Accounts User", "Sales Manager") + roles = ("Accounts Manager", "Accounts User", "Sales Manager", "Stock User", "Item Manager") test_user.add_roles(*roles) frappe.set_user(user) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 53c1d75db4e..c9b35a27c29 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -867,11 +867,16 @@ class POSInvoice(SalesInvoice): idx = self.payments[-1].idx if self.payments else -1 + self.reload() + self.flags.ignore_validate_update_after_submit = True + for d in payments: idx += 1 payment = create_payments_on_invoice(self, idx, frappe._dict(d)) paid_amount += flt(payment.amount) - payment.submit() + self.append("payments", payment) + + self.save() paid_amount = flt(flt(paid_amount), self.precision("paid_amount")) base_paid_amount = flt(flt(paid_amount * self.conversion_rate), self.precision("base_paid_amount")) diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 9750e4c7f83..7857f8b4cad 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -4,7 +4,6 @@ import copy import frappe from frappe import _ -from frappe.tests import IntegrationTestCase from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import ( set_default_account_for_mode_of_payment, @@ -13,45 +12,40 @@ from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile from erpnext.accounts.doctype.sales_invoice.sales_invoice import PartialPaymentValidationError from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice -from erpnext.stock.doctype.item.test_item import make_item -from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import ( get_batch_from_bundle, get_serial_nos_from_bundle, make_serial_batch_bundle, ) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.tests.utils import ERPNextTestSuite -class TestPOSInvoice(IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0)) - cls.enterClassContext(cls.change_settings("POS Settings", invoice_type="POS Invoice")) - make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100) - frappe.db.sql("delete from `tabTax Rule`") - +class POSInvoiceTestMixin(ERPNextTestSuite): + def setUp(self): from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile - from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry - cls.test_user, cls.pos_profile = init_user_and_profile() - cls.opening_entry = create_opening_entry(cls.pos_profile, cls.test_user.name) - mode_of_payment = frappe.get_doc("Mode of Payment", "Bank Draft") - set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank - _TC") + self.load_test_records("Stock Entry") + self.test_user, self.pos_profile = init_user_and_profile() - @classmethod - def tearDownClass(cls): - frappe.db.sql("delete from `tabPOS Invoice`") - opening_entry_doc = frappe.get_doc("POS Opening Entry", cls.opening_entry.name) - opening_entry_doc.cancel() - - def tearDown(self): if frappe.session.user != "Administrator": frappe.set_user("Administrator") - if frappe.db.get_single_value("Selling Settings", "validate_selling_price"): - frappe.db.set_single_value("Selling Settings", "validate_selling_price", 0) + frappe.db.set_single_value("Selling Settings", "validate_selling_price", 0) + frappe.db.set_single_value("POS Settings", "invoice_type", "POS Invoice") + make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100) + frappe.db.sql("delete from `tabTax Rule`") + + mode_of_payment = frappe.get_doc("Mode of Payment", "Bank Draft") + set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank - _TC") + + +class TestPOSInvoice(POSInvoiceTestMixin): + def setUp(self): + super().setUp() + from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry + + self.opening_entry = create_opening_entry(self.pos_profile, self.test_user.name) def test_timestamp_change(self): w = create_pos_invoice(do_not_save=1) @@ -641,9 +635,7 @@ class TestPOSInvoice(IntegrationTestCase): from erpnext.accounts.doctype.loyalty_program.loyalty_program import ( get_loyalty_program_details_with_points, ) - from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records - create_records() frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty") before_lp_details = get_loyalty_program_details_with_points( "Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty" @@ -680,6 +672,7 @@ class TestPOSInvoice(IntegrationTestCase): get_loyalty_program_details_with_points, ) + frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty") # add 10 loyalty points pos_inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000, do_not_save=1) pos_inv.append( @@ -709,134 +702,6 @@ class TestPOSInvoice(IntegrationTestCase): ) self.assertEqual(after_redeem_lp_details.loyalty_points, 9) - def test_merging_into_sales_invoice_with_discount(self): - from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import ( - init_user_and_profile, - ) - from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import ( - consolidate_pos_invoices, - ) - - frappe.db.sql("delete from `tabPOS Invoice`") - test_user, pos_profile = init_user_and_profile() - pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1) - pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 270}) - pos_inv.save() - pos_inv.submit() - - pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) - pos_inv2.append("payments", {"mode_of_payment": "Cash", "amount": 3200}) - pos_inv2.save() - pos_inv2.submit() - - consolidate_pos_invoices() - - pos_inv.load_from_db() - rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total") - self.assertEqual(rounded_total, 3470) - - def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self): - from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import ( - init_user_and_profile, - ) - from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import ( - consolidate_pos_invoices, - ) - - frappe.db.sql("delete from `tabPOS Invoice`") - test_user, pos_profile = init_user_and_profile() - pos_inv = create_pos_invoice(rate=300, do_not_submit=1) - pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 300}) - pos_inv.append( - "taxes", - { - "charge_type": "On Net Total", - "account_head": "_Test Account Service Tax - _TC", - "cost_center": "_Test Cost Center - _TC", - "description": "Service Tax", - "rate": 14, - "included_in_print_rate": 1, - }, - ) - pos_inv.save() - pos_inv.submit() - - pos_inv2 = create_pos_invoice(rate=300, qty=2, do_not_submit=1) - pos_inv2.additional_discount_percentage = 10 - pos_inv2.append("payments", {"mode_of_payment": "Cash", "amount": 540}) - pos_inv2.append( - "taxes", - { - "charge_type": "On Net Total", - "account_head": "_Test Account Service Tax - _TC", - "cost_center": "_Test Cost Center - _TC", - "description": "Service Tax", - "rate": 14, - "included_in_print_rate": 1, - }, - ) - pos_inv2.save() - pos_inv2.submit() - - consolidate_pos_invoices() - - pos_inv.load_from_db() - rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total") - self.assertEqual(rounded_total, 840) - - def test_merging_with_validate_selling_price(self): - from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import ( - init_user_and_profile, - ) - from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import ( - consolidate_pos_invoices, - ) - - if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"): - frappe.db.set_single_value("Selling Settings", "validate_selling_price", 1) - - item = "Test Selling Price Validation" - make_item(item, {"is_stock_item": 1}) - make_purchase_receipt(item_code=item, warehouse="_Test Warehouse - _TC", qty=1, rate=300) - frappe.db.sql("delete from `tabPOS Invoice`") - test_user, pos_profile = init_user_and_profile() - pos_inv = create_pos_invoice(item=item, rate=300, do_not_submit=1) - pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 300}) - pos_inv.append( - "taxes", - { - "charge_type": "On Net Total", - "account_head": "_Test Account Service Tax - _TC", - "cost_center": "_Test Cost Center - _TC", - "description": "Service Tax", - "rate": 14, - "included_in_print_rate": 1, - }, - ) - self.assertRaises(frappe.ValidationError, pos_inv.submit) - - pos_inv2 = create_pos_invoice(item=item, rate=400, do_not_submit=1) - pos_inv2.append("payments", {"mode_of_payment": "Cash", "amount": 400}) - pos_inv2.append( - "taxes", - { - "charge_type": "On Net Total", - "account_head": "_Test Account Service Tax - _TC", - "cost_center": "_Test Cost Center - _TC", - "description": "Service Tax", - "rate": 14, - "included_in_print_rate": 1, - }, - ) - pos_inv2.save() - pos_inv2.submit() - - consolidate_pos_invoices() - - pos_inv2.load_from_db() - rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total") - self.assertEqual(rounded_total, 400) - def test_pos_batch_reservation(self): from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( get_auto_batch_nos, @@ -1003,40 +868,39 @@ class TestPOSInvoice(IntegrationTestCase): def test_ignore_pricing_rule(self): from erpnext.accounts.doctype.pricing_rule.test_pricing_rule import make_pricing_rule - item_price = frappe.get_doc( - { - "doctype": "Item Price", - "item_code": "_Test Item", - "price_list": "_Test Price List", - "price_list_rate": "450", - } - ) - item_price.insert() + if not frappe.db.exists( + "Item Price", + {"item_code": "_Test Item", "price_list": "_Test Price List", "price_list_rate": "450"}, + ): + item_price = frappe.get_doc( + { + "doctype": "Item Price", + "item_code": "_Test Item", + "price_list": "_Test Price List", + "price_list_rate": "450", + } + ) + item_price.insert() + pr = make_pricing_rule(selling=1, priority=5, discount_percentage=10) pr.save() - try: - pos_inv = create_pos_invoice(qty=1, do_not_submit=1) - pos_inv.items[0].rate = 300 - pos_inv.save() - self.assertEqual(pos_inv.items[0].discount_percentage, 10) - # rate shouldn't change - self.assertEqual(pos_inv.items[0].rate, 405) + pos_inv = create_pos_invoice(qty=1, do_not_submit=1) + pos_inv.items[0].rate = 300 + pos_inv.save() + self.assertEqual(pos_inv.items[0].discount_percentage, 10) + # rate shouldn't change + self.assertEqual(pos_inv.items[0].rate, 405) - pos_inv.ignore_pricing_rule = 1 - pos_inv.save() - self.assertEqual(pos_inv.ignore_pricing_rule, 1) - # rate should reset since pricing rules are ignored - self.assertEqual(pos_inv.items[0].rate, 450) + pos_inv.ignore_pricing_rule = 1 + pos_inv.save() + self.assertEqual(pos_inv.ignore_pricing_rule, 1) + # rate should reset since pricing rules are ignored + self.assertEqual(pos_inv.items[0].rate, 450) - pos_inv.items[0].rate = 300 - pos_inv.save() - self.assertEqual(pos_inv.items[0].rate, 300) - - finally: - item_price.delete() - pos_inv.delete() - pr.delete() + pos_inv.items[0].rate = 300 + pos_inv.save() + self.assertEqual(pos_inv.items[0].rate, 300) def test_delivered_serial_no_case(self): from erpnext.accounts.doctype.pos_invoice_merge_log.test_pos_invoice_merge_log import ( @@ -1045,32 +909,26 @@ class TestPOSInvoice(IntegrationTestCase): from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item - frappe.db.savepoint("before_test_delivered_serial_no_case") - try: - se = make_serialized_item(self) - serial_no = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0] + se = make_serialized_item(self) + serial_no = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0] - dn = create_delivery_note(item_code="_Test Serialized Item With Series", serial_no=[serial_no]) - delivered_serial_no = get_serial_nos_from_bundle(dn.get("items")[0].serial_and_batch_bundle)[0] + dn = create_delivery_note(item_code="_Test Serialized Item With Series", serial_no=[serial_no]) + delivered_serial_no = get_serial_nos_from_bundle(dn.get("items")[0].serial_and_batch_bundle)[0] - self.assertEqual(serial_no, delivered_serial_no) + self.assertEqual(serial_no, delivered_serial_no) - init_user_and_profile() + init_user_and_profile() - pos_inv = create_pos_invoice( - item_code="_Test Serialized Item With Series", - serial_no=[serial_no], - qty=1, - rate=100, - do_not_submit=True, - ignore_sabb_validation=True, - ) + pos_inv = create_pos_invoice( + item_code="_Test Serialized Item With Series", + serial_no=[serial_no], + qty=1, + rate=100, + do_not_submit=True, + ignore_sabb_validation=True, + ) - self.assertRaises(frappe.ValidationError, pos_inv.submit) - - finally: - frappe.db.rollback(save_point="before_test_delivered_serial_no_case") - frappe.set_user("Administrator") + self.assertRaises(frappe.ValidationError, pos_inv.submit) def test_bundle_stock_availability_validation(self): from erpnext.accounts.doctype.pos_invoice.pos_invoice import ProductBundleStockValidationError diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice_merge.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice_merge.py new file mode 100644 index 00000000000..a944ad71897 --- /dev/null +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice_merge.py @@ -0,0 +1,163 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import frappe + +from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import POSInvoiceTestMixin, create_pos_invoice +from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt + + +class TestPOSInvoiceMerging(POSInvoiceTestMixin): + def clear_pos_data(self): + frappe.db.sql("delete from `tabPOS Opening Entry`;") + frappe.db.sql("delete from `tabPOS Closing Entry`;") + frappe.db.sql("delete from `tabPOS Invoice`;") + + def setUp(self): + self.clear_pos_data() + super().setUp() + + from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry + + self.opening_entry = create_opening_entry(self.pos_profile, self.test_user.name) + + def test_merging_into_sales_invoice_with_discount(self): + from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import ( + make_closing_entry_from_opening, + ) + from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import ( + init_user_and_profile, + ) + from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import ( + consolidate_pos_invoices, + ) + + frappe.db.sql("delete from `tabPOS Invoice`") + test_user, pos_profile = init_user_and_profile() + pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1) + pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 270}) + pos_inv.save() + pos_inv.submit() + + pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) + pos_inv2.append("payments", {"mode_of_payment": "Cash", "amount": 3200}) + pos_inv2.save() + pos_inv2.submit() + + closing_entry = make_closing_entry_from_opening(self.opening_entry) + consolidate_pos_invoices(closing_entry=closing_entry) # does DB commit + + pos_inv.load_from_db() + rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total") + self.assertEqual(rounded_total, 3470) + + def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self): + from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import ( + make_closing_entry_from_opening, + ) + from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import ( + init_user_and_profile, + ) + from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import ( + consolidate_pos_invoices, + ) + + frappe.db.sql("delete from `tabPOS Invoice`") + test_user, pos_profile = init_user_and_profile() + pos_inv = create_pos_invoice(rate=300, do_not_submit=1) + pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 300}) + pos_inv.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": "_Test Account Service Tax - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Service Tax", + "rate": 14, + "included_in_print_rate": 1, + }, + ) + pos_inv.save() + pos_inv.submit() + + pos_inv2 = create_pos_invoice(rate=300, qty=2, do_not_submit=1) + pos_inv2.additional_discount_percentage = 10 + pos_inv2.append("payments", {"mode_of_payment": "Cash", "amount": 540}) + pos_inv2.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": "_Test Account Service Tax - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Service Tax", + "rate": 14, + "included_in_print_rate": 1, + }, + ) + pos_inv2.save() + pos_inv2.submit() + + self.closing_entry = make_closing_entry_from_opening(self.opening_entry) + consolidate_pos_invoices(closing_entry=self.closing_entry) # does DB commit + + pos_inv.load_from_db() + rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total") + self.assertEqual(rounded_total, 840) + + def test_merging_with_validate_selling_price(self): + from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import ( + make_closing_entry_from_opening, + ) + from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import ( + init_user_and_profile, + ) + from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import ( + consolidate_pos_invoices, + ) + + if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"): + frappe.db.set_single_value("Selling Settings", "validate_selling_price", 1) + + item = "Test Selling Price Validation" + make_item(item, {"is_stock_item": 1}) + make_purchase_receipt(item_code=item, warehouse="_Test Warehouse - _TC", qty=1, rate=300) + frappe.db.sql("delete from `tabPOS Invoice`") + test_user, pos_profile = init_user_and_profile() + pos_inv = create_pos_invoice(item=item, rate=300, do_not_submit=1) + pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 300}) + pos_inv.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": "_Test Account Service Tax - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Service Tax", + "rate": 14, + "included_in_print_rate": 1, + }, + ) + self.assertRaises(frappe.ValidationError, pos_inv.submit) + + pos_inv2 = create_pos_invoice(item=item, rate=400, do_not_submit=1) + pos_inv2.append("payments", {"mode_of_payment": "Cash", "amount": 400}) + pos_inv2.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": "_Test Account Service Tax - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Service Tax", + "rate": 14, + "included_in_print_rate": 1, + }, + ) + pos_inv2.save() + pos_inv2.submit() + + self.closing_entry = make_closing_entry_from_opening(self.opening_entry) + consolidate_pos_invoices(closing_entry=self.closing_entry) # does DB commit + + pos_inv2.load_from_db() + rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total") + self.assertEqual(rounded_total, 400) 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 0874955ec47..e41548b89f6 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 @@ -283,7 +283,7 @@ class POSInvoiceMergeLog(Document): base_rounding_adjustment += doc.base_rounding_adjustment base_rounded_total += doc.base_rounded_total - for d in doc.get("item_wise_tax_details"): + for d in doc.get("item_wise_tax_details") or []: row = frappe._dict( item=old_new_item_map[d.item_row], tax=old_new_tax_map[d.tax_row], @@ -572,7 +572,7 @@ def split_invoices(invoices): def create_merge_logs(invoice_by_customer, closing_entry=None): - try: + def merge_and_close(): for customer, invoices_acc_dim in invoice_by_customer.items(): for invoices in invoices_acc_dim.values(): for _invoices in split_invoices(invoices): @@ -594,25 +594,30 @@ def create_merge_logs(invoice_by_customer, closing_entry=None): closing_entry.db_set("error_message", "") closing_entry.update_opening_entry() - except Exception as e: - frappe.db.rollback() - message_log = frappe.message_log.pop() if frappe.message_log else str(e) - error_message = get_error_message(message_log) + if frappe.in_test: + merge_and_close() + else: + try: + merge_and_close() + except Exception as e: + frappe.db.rollback() + message_log = frappe.message_log.pop() if frappe.message_log else str(e) + error_message = get_error_message(message_log) - if closing_entry: - closing_entry.set_status(update=True, status="Failed") - if isinstance(error_message, list): - error_message = json.dumps(error_message) - closing_entry.db_set("error_message", error_message) - raise + if closing_entry: + closing_entry.set_status(update=True, status="Failed") + if isinstance(error_message, list): + error_message = json.dumps(error_message) + closing_entry.db_set("error_message", error_message) + raise - finally: - frappe.db.commit() - frappe.publish_realtime("closing_process_complete", user=frappe.session.user) + finally: + frappe.db.commit() + frappe.publish_realtime("closing_process_complete", user=frappe.session.user) def cancel_merge_logs(merge_logs, closing_entry=None): - try: + def merge_cancel_and_close(): for log in merge_logs: merge_log = frappe.get_doc("POS Invoice Merge Log", log) if merge_log.docstatus == 2: @@ -626,19 +631,24 @@ def cancel_merge_logs(merge_logs, closing_entry=None): closing_entry.db_set("error_message", "") closing_entry.update_opening_entry(for_cancel=True) - except Exception as e: - frappe.db.rollback() - message_log = frappe.message_log.pop() if frappe.message_log else str(e) - error_message = get_error_message(message_log) + if frappe.flags.in_test: + merge_cancel_and_close() + else: + try: + merge_cancel_and_close() + except Exception as e: + frappe.db.rollback() + message_log = frappe.message_log.pop() if frappe.message_log else str(e) + error_message = get_error_message(message_log) - if closing_entry: - closing_entry.set_status(update=True, status="Submitted") - closing_entry.db_set("error_message", error_message) - raise + if closing_entry: + closing_entry.set_status(update=True, status="Submitted") + closing_entry.db_set("error_message", error_message) + raise - finally: - frappe.db.commit() - frappe.publish_realtime("closing_process_complete", user=frappe.session.user) + finally: + frappe.db.commit() + frappe.publish_realtime("closing_process_complete", user=frappe.session.user) def enqueue_job(job, **kwargs): 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 39fd8142073..5c6d03d7adb 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 @@ -2,7 +2,6 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import ( set_default_account_for_mode_of_payment, @@ -18,30 +17,25 @@ from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle get_serial_nos_from_bundle, ) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.tests.utils import ERPNextTestSuite -class TestPOSInvoiceMergeLog(IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - frappe.db.sql("delete from `tabPOS Opening Entry`") - cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0)) - cls.enterClassContext(cls.change_settings("POS Settings", invoice_type="POS Invoice")) - mode_of_payment = frappe.get_doc("Mode of Payment", "Bank Draft") - set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank - _TC") - +class TestPOSInvoiceMergeLog(ERPNextTestSuite): def setUp(self): - frappe.db.sql("delete from `tabPOS Invoice`") + mode_of_payment = frappe.get_doc("Mode of Payment", "Bank Draft") + self.test_user, self.pos_profile = init_user_and_profile() + self.opening_entry = create_opening_entry(self.pos_profile, self.test_user.name) - def tearDown(self): - frappe.set_user("Administrator") - frappe.db.sql("delete from `tabPOS Profile`") - frappe.db.sql("delete from `tabPOS Invoice`") + set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank - _TC") + frappe.db.set_single_value("POS Settings", "invoice_type", "POS Invoice") + frappe.db.set_single_value("Selling Settings", "validate_selling_price", 0) + + def make_closing_entry(self): + closing_entry = make_closing_entry_from_opening(self.opening_entry) + closing_entry.insert().submit() + return closing_entry def test_consolidated_invoice_creation(self): - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) - pos_inv = create_pos_invoice(rate=300, do_not_submit=1) pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300}) pos_inv.save() @@ -57,9 +51,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): pos_inv3.save() pos_inv3.submit() - closing_entry = make_closing_entry_from_opening(opening_entry) - closing_entry.insert() - closing_entry.submit() + self.make_closing_entry() pos_inv.load_from_db() self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) @@ -70,9 +62,6 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice) def test_consolidated_credit_note_creation(self): - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) - pos_inv = create_pos_invoice(rate=300, do_not_submit=1) pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300}) pos_inv.save() @@ -97,9 +86,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): pos_inv_cn.paid_amount = -300 pos_inv_cn.submit() - closing_entry = make_closing_entry_from_opening(opening_entry) - closing_entry.insert() - closing_entry.submit() + self.make_closing_entry() pos_inv.load_from_db() self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) @@ -117,9 +104,6 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): self.assertEqual(consolidated_credit_note.payments[1].amount, -200) def test_consolidated_invoice_item_taxes(self): - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) - inv = create_pos_invoice(qty=1, rate=100, do_not_save=True) inv.append( @@ -156,9 +140,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): inv2.save() inv2.submit() - closing_entry = make_closing_entry_from_opening(opening_entry) - closing_entry.insert() - closing_entry.submit() + self.make_closing_entry() inv.load_from_db() @@ -206,9 +188,6 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): qty=10, ) - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) - inv = create_pos_invoice(qty=3, rate=10000, do_not_save=True) inv.append( "taxes", @@ -243,9 +222,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): inv2.insert() inv2.submit() - closing_entry = make_closing_entry_from_opening(opening_entry) - closing_entry.insert() - closing_entry.submit() + self.make_closing_entry() inv.load_from_db() consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice) @@ -263,9 +240,6 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): qty=10, ) - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) - inv = create_pos_invoice(qty=6, rate=10000, do_not_save=True) inv.append( "taxes", @@ -305,16 +279,14 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): inv3.insert() inv3.submit() - closing_entry = make_closing_entry_from_opening(opening_entry) - closing_entry.insert() - closing_entry.submit() + self.make_closing_entry() inv.load_from_db() consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice) self.assertNotEqual(consolidated_invoice.outstanding_amount, 800) self.assertEqual(consolidated_invoice.status, "Paid") - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "System Settings", {"number_format": "#,###.###", "currency_precision": 3, "float_precision": 3} ) def test_consolidation_round_off_error_3(self): @@ -324,8 +296,6 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): rate=8000, qty=10, ) - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) item_rates = [69, 59, 29] for _i in [1, 2]: @@ -362,9 +332,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): inv.save() inv.submit() - closing_entry = make_closing_entry_from_opening(opening_entry) - closing_entry.insert() - closing_entry.submit() + self.make_closing_entry() inv.load_from_db() consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice) @@ -382,9 +350,6 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): qty=10, ) - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) - inv = create_pos_invoice(qty=1, rate=69.5, do_not_save=True) inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 70}) inv.insert() @@ -395,9 +360,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): inv2.insert() inv2.submit() - closing_entry = make_closing_entry_from_opening(opening_entry) - closing_entry.insert() - closing_entry.submit() + self.make_closing_entry() inv.load_from_db() consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice) @@ -414,14 +377,12 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): The second and third POS Invoice should be consolidated with a single Merge Log """ + self.load_test_records("Stock Entry") from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item se = make_serialized_item(self) serial_no = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0] - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) - pos_inv = create_pos_invoice( item_code="_Test Serialized Item With Series", serial_no=[serial_no], @@ -448,9 +409,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): pos_inv2.save() pos_inv2.submit() - closing_entry = make_closing_entry_from_opening(opening_entry) - closing_entry.insert() - closing_entry.submit() + self.make_closing_entry() pos_inv.load_from_db() pos_inv2.load_from_db() @@ -469,9 +428,6 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): create_cost_center(cost_center_name="_Test POS Cost Center 1", is_group=0) create_cost_center(cost_center_name="_Test POS Cost Center 2", is_group=0) - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) - pos_inv = create_pos_invoice(rate=300, do_not_submit=1) pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300}) pos_inv.cost_center = "_Test POS Cost Center 1 - _TC" @@ -490,9 +446,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): pos_inv3.save() pos_inv3.submit() - closing_entry = make_closing_entry_from_opening(opening_entry) - closing_entry.insert() - closing_entry.submit() + self.make_closing_entry() pos_inv.load_from_db() self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) @@ -511,17 +465,12 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase): """ Test if the company is fetched from POS Closing Entry """ - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) - pos_inv = create_pos_invoice(rate=300, do_not_submit=1) pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300}) pos_inv.save() pos_inv.submit() - closing_entry = make_closing_entry_from_opening(opening_entry) - closing_entry.insert() - closing_entry.submit() + closing_entry = self.make_closing_entry() self.assertTrue(frappe.db.exists("POS Invoice Merge Log", {"pos_closing_entry": closing_entry.name})) 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 0810814f479..e23e139b0fa 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 @@ -3,35 +3,21 @@ import frappe from frappe.core.doctype.user_permission.test_user_permission import create_user -from frappe.tests import IntegrationTestCase from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry +from erpnext.tests.utils import ERPNextTestSuite -class TestPOSOpeningEntry(IntegrationTestCase): - @classmethod - def setUpClass(cls): - frappe.db.sql("delete from `tabPOS Opening Entry`") - cls.enterClassContext(cls.change_settings("POS Settings", {"invoice_type": "POS Invoice"})) - - @classmethod - def tearDownClass(cls): - frappe.db.sql("delete from `tabPOS Opening Entry`") - +class TestPOSOpeningEntry(ERPNextTestSuite): def setUp(self): - # Make stock available for POS Sales - frappe.db.sql("delete from `tabPOS Opening Entry`") + frappe.db.set_single_value("POS Settings", "invoice_type", "POS Invoice") make_stock_entry(target="_Test Warehouse - _TC", qty=2, basic_rate=100) from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile self.init_user_and_profile = init_user_and_profile - def tearDown(self): - frappe.set_user("Administrator") - frappe.db.sql("delete from `tabPOS Profile`") - def test_pos_opening_entry(self): test_user, pos_profile = self.init_user_and_profile() opening_entry = create_opening_entry(pos_profile, test_user.name) @@ -60,7 +46,9 @@ class TestPOSOpeningEntry(IntegrationTestCase): self.assertEqual(opening_entry_1.status, "Open") self.assertEqual(opening_entry_1.user, test_user.name) - cashier_user = create_user("test_cashier@example.com", "Accounts Manager", "Sales Manager") + cashier_user = create_user( + "test_cashier@example.com", "Accounts Manager", "Sales Manager", "Stock User", "System Manager" + ) frappe.set_user(cashier_user.name) pos_profile2 = make_pos_profile(name="_Test POS Profile 2") diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py index 23ef5678e0d..7c28504d5e8 100644 --- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py @@ -2,19 +2,18 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import cint from erpnext.accounts.doctype.pos_profile.pos_profile import ( get_child_nodes, ) from erpnext.stock.get_item_details import get_pos_profile - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Item"] +from erpnext.tests.utils import ERPNextTestSuite -class TestPOSProfile(IntegrationTestCase): +class TestPOSProfile(ERPNextTestSuite): def test_pos_profile(self): + frappe.set_user("Administrator") make_pos_profile() pos_profile = get_pos_profile("_Test Company") or {} @@ -36,8 +35,6 @@ class TestPOSProfile(IntegrationTestCase): self.assertEqual(len(items), products_count[0][0]) self.assertEqual(len(customers), customers_count[0][0]) - frappe.db.sql("delete from `tabPOS Profile`") - def test_disabled_pos_profile_creation(self): make_pos_profile(name="_Test POS Profile 001", disabled=1) diff --git a/erpnext/accounts/doctype/pos_profile_user/test_pos_profile_user.py b/erpnext/accounts/doctype/pos_profile_user/test_pos_profile_user.py index 27afe146f0a..6223478e3e8 100644 --- a/erpnext/accounts/doctype/pos_profile_user/test_pos_profile_user.py +++ b/erpnext/accounts/doctype/pos_profile_user/test_pos_profile_user.py @@ -1,8 +1,8 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestPOSProfileUser(IntegrationTestCase): +class TestPOSProfileUser(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.json b/erpnext/accounts/doctype/pos_settings/pos_settings.json index 7afc19423d8..8e9adc56f14 100644 --- a/erpnext/accounts/doctype/pos_settings/pos_settings.json +++ b/erpnext/accounts/doctype/pos_settings/pos_settings.json @@ -50,10 +50,10 @@ "options": "1" } ], - "hide_toolbar": 1, + "hide_toolbar": 0, "issingle": 1, "links": [], - "modified": "2026-01-09 17:30:41.476806", + "modified": "2026-03-16 13:28:19.677217", "modified_by": "Administrator", "module": "Accounts", "name": "POS Settings", diff --git a/erpnext/accounts/doctype/pos_settings/test_pos_settings.py b/erpnext/accounts/doctype/pos_settings/test_pos_settings.py index a45af6e1930..261e31fdd97 100644 --- a/erpnext/accounts/doctype/pos_settings/test_pos_settings.py +++ b/erpnext/accounts/doctype/pos_settings/test_pos_settings.py @@ -1,8 +1,8 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestPOSSettings(IntegrationTestCase): +class TestPOSSettings(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 7ebdfed9d8d..effd87c8a70 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -3,7 +3,6 @@ import frappe -from frappe.tests import IntegrationTestCase from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice @@ -11,17 +10,15 @@ from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.get_item_details import get_item_details +from erpnext.tests.utils import ERPNextTestSuite -class TestPricingRule(IntegrationTestCase): +class TestPricingRule(ERPNextTestSuite): def setUp(self): delete_existing_pricing_rules() setup_pricing_rule_data() self.enterClassContext(self.change_settings("Selling Settings", validate_selling_price=0)) - def tearDown(self): - delete_existing_pricing_rules() - def test_pricing_rule_for_discount(self): from frappe import MandatoryError @@ -414,6 +411,7 @@ class TestPricingRule(IntegrationTestCase): self.assertEqual(item.discount_amount, 110) self.assertEqual(item.rate, 990) + @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": 1}) def test_pricing_rule_for_product_discount_on_same_item(self): frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule") test_record = { @@ -1189,6 +1187,7 @@ class TestPricingRule(IntegrationTestCase): si.delete() rule.delete() + @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": 1}) def test_pricing_rule_for_product_free_item_rounded_qty_and_recursion(self): frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule") test_record = { @@ -1234,6 +1233,7 @@ class TestPricingRule(IntegrationTestCase): so.save() self.assertEqual(len(so.items), 1) + @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": 1}) def test_pricing_rule_for_product_free_item_round_free_qty(self): frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule") test_record = { @@ -1520,9 +1520,6 @@ class TestPricingRule(IntegrationTestCase): pi.cancel() -EXTRA_TEST_RECORD_DEPENDENCIES = ["UTM Campaign"] - - def make_pricing_rule(**args): args = frappe._dict(args) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 763ea1bf7ea..6c2cff54cb3 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -242,8 +242,10 @@ def get_other_conditions(conditions, values, args): if group_condition: conditions += " and " + group_condition - date = args.get("transaction_date") or frappe.get_value( - args.get("doctype"), args.get("name"), "posting_date", ignore=True + date = ( + args.get("transaction_date") + or args.get("posting_date") + or frappe.get_value(args.get("doctype"), args.get("name"), "posting_date", ignore=True) ) if date: conditions += """ and %(transaction_date)s between ifnull(`tabPricing Rule`.valid_from, '2000-01-01') 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 08f3b06e58c..95be179b66d 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 @@ -2,7 +2,6 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from erpnext.accounts.doctype.account.test_account import create_account from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import ( @@ -10,9 +9,10 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import ( create_sales_invoice, ) from erpnext.stock.doctype.item.test_item import create_item +from erpnext.tests.utils import ERPNextTestSuite -class TestProcessDeferredAccounting(IntegrationTestCase): +class TestProcessDeferredAccounting(ERPNextTestSuite): def test_creation_of_ledger_entry_on_submit(self): """test creation of gl entries on submission of document""" change_acc_settings(acc_frozen_till_date="2023-05-31", book_deferred_entries_based_on="Months") @@ -52,6 +52,7 @@ class TestProcessDeferredAccounting(IntegrationTestCase): start_date="2023-05-01", end_date="2023-06-30", type="Income", + company="_Test Company", ) process_deferred_accounting.insert() @@ -82,6 +83,7 @@ class TestProcessDeferredAccounting(IntegrationTestCase): start_date="2019-01-01", end_date="2019-01-31", type="Income", + company="_Test Company", ) pda.submit() pda.cancel() diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/test_process_payment_reconciliation.py b/erpnext/accounts/doctype/process_payment_reconciliation/test_process_payment_reconciliation.py index d54ff6f902e..eff49ecadc5 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation/test_process_payment_reconciliation.py +++ b/erpnext/accounts/doctype/process_payment_reconciliation/test_process_payment_reconciliation.py @@ -2,8 +2,10 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase -class TestProcessPaymentReconciliation(IntegrationTestCase): +from erpnext.tests.utils import ERPNextTestSuite + + +class TestProcessPaymentReconciliation(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log/test_process_payment_reconciliation_log.py b/erpnext/accounts/doctype/process_payment_reconciliation_log/test_process_payment_reconciliation_log.py index 66a4dca21e1..26c4b5a4a1c 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation_log/test_process_payment_reconciliation_log.py +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log/test_process_payment_reconciliation_log.py @@ -2,8 +2,10 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase -class TestProcessPaymentReconciliationLog(IntegrationTestCase): +from erpnext.tests.utils import ERPNextTestSuite + + +class TestProcessPaymentReconciliationLog(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/test_process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/test_process_period_closing_voucher.py index 17ce2c13b09..e695b11bcb5 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/test_process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/test_process_period_closing_voucher.py @@ -2,19 +2,3 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase - -# On IntegrationTestCase, the doctype test records and all -# link-field test record dependencies are recursively loaded -# Use these module variables to add/remove to/from that list -EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] -IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] - - -class IntegrationTestProcessPeriodClosingVoucher(IntegrationTestCase): - """ - Integration tests for ProcessPeriodClosingVoucher. - Use this class for testing interactions between multiple components. - """ - - pass 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 3994538059c..52d05d856cd 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 @@ -465,6 +465,8 @@ def get_customer_emails(customer_name: str, primary_mandatory: str | int, billin when Is Billing Contact checked and Primary email- email with Is Primary checked""" + frappe.has_permission("Customer", "read", customer_name, throw=True) + billing_email = frappe.db.sql( """ SELECT @@ -508,6 +510,7 @@ def get_customer_emails(customer_name: str, primary_mandatory: str | int, billin @frappe.whitelist() def download_statements(document_name: str): doc = frappe.get_doc("Process Statement Of Accounts", document_name) + doc.check_permission("read") report = get_report_pdf(doc) if report: frappe.local.response.filename = doc.name + ".pdf" @@ -563,10 +566,10 @@ def send_emails(document_name: str, from_scheduler: bool = False, posting_date: new_from_date = add_months(new_to_date, -1 * doc.filter_duration) doc.add_comment("Comment", "Emails sent on: " + frappe.utils.format_datetime(frappe.utils.now())) if doc.report == "General Ledger": - doc.db_set("to_date", new_to_date, commit=True) - doc.db_set("from_date", new_from_date, commit=True) + frappe.db.set_value(doc.doctype, doc.name, "to_date", new_to_date) + frappe.db.set_value(doc.doctype, doc.name, "from_date", new_from_date) else: - doc.db_set("posting_date", new_to_date, commit=True) + frappe.db.set_value(doc.doctype, doc.name, "posting_date", new_to_date) return True else: return False diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py index 2d599fee1af..c16933c7836 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py @@ -3,7 +3,6 @@ import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, getdate, today from erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts import ( @@ -12,26 +11,16 @@ from erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of ) from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.tests.utils import ERPNextTestSuite -class TestProcessStatementOfAccounts(AccountsTestMixin, IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() +class TestProcessStatementOfAccounts(AccountsTestMixin, ERPNextTestSuite): + def setUp(self): + frappe.db.set_single_value("Selling Settings", "validate_selling_price", 0) letterhead = frappe.get_doc("Letter Head", "Company Letterhead - Grey") letterhead.is_default = 0 letterhead.save() - cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0)) - @classmethod - def tearDownClass(cls): - super().tearDownClass() - letterhead = frappe.get_doc("Letter Head", "Company Letterhead - Grey") - letterhead.is_default = 1 - letterhead.save() - frappe.db.commit() # nosemgrep - - def setUp(self): self.create_company() self.create_customer() self.create_customer(customer_name="Other Customer") @@ -101,9 +90,6 @@ class TestProcessStatementOfAccounts(AccountsTestMixin, IntegrationTestCase): for age_range in expected_ageing: self.assertEqual(expected_ageing[age_range], ageing.get(age_range)) - def tearDown(self): - frappe.db.rollback() - def create_process_soa(**args): args = frappe._dict(args) diff --git a/erpnext/accounts/doctype/process_subscription/test_process_subscription.py b/erpnext/accounts/doctype/process_subscription/test_process_subscription.py index 5ec80641192..8c7604b8f5c 100644 --- a/erpnext/accounts/doctype/process_subscription/test_process_subscription.py +++ b/erpnext/accounts/doctype/process_subscription/test_process_subscription.py @@ -2,8 +2,10 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase -class TestProcessSubscription(IntegrationTestCase): +from erpnext.tests.utils import ERPNextTestSuite + + +class TestProcessSubscription(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py index 996f361fd3f..e33995996c6 100644 --- a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py @@ -2,13 +2,13 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from erpnext.accounts.doctype.promotional_scheme.promotional_scheme import TransactionExists from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order +from erpnext.tests.utils import ERPNextTestSuite -class TestPromotionalScheme(IntegrationTestCase): +class TestPromotionalScheme(ERPNextTestSuite): def setUp(self): if frappe.db.exists("Promotional Scheme", "_Test Scheme"): frappe.delete_doc("Promotional Scheme", "_Test Scheme") diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 3926e27e519..d2021e0f9a4 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -266,6 +266,7 @@ { "fieldname": "due_date", "fieldtype": "Date", + "in_list_view": 1, "label": "Due Date", "oldfieldname": "due_date", "oldfieldtype": "Date" @@ -319,7 +320,8 @@ "fieldname": "posting_date", "fieldtype": "Date", "in_list_view": 1, - "label": "Date", + "in_standard_filter": 1, + "label": "Posting Date", "oldfieldname": "posting_date", "oldfieldtype": "Date", "print_hide": 1, @@ -389,7 +391,7 @@ }, { "collapsible": 1, - "collapsible_depends_on": "bill_no", + "collapsible_depends_on": "posting_date", "fieldname": "supplier_invoice_details", "fieldtype": "Section Break", "label": "Supplier Invoice" @@ -397,6 +399,8 @@ { "fieldname": "bill_no", "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Supplier Invoice No", "oldfieldname": "bill_no", "oldfieldtype": "Data", @@ -1689,7 +1693,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2026-02-23 13:23:57.269770", + "modified": "2026-03-17 20:44:00.221219", "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 2134578efac..b30a1db034d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -618,12 +618,13 @@ class PurchaseInvoice(BuyingController): frappe.db.set_value(self.doctype, self.name, "against_expense_account", self.against_expense_account) def po_required(self): - if frappe.db.get_single_value("Buying Settings", "po_required") == "Yes": - if frappe.get_value( + if ( + frappe.db.get_single_value("Buying Settings", "po_required") == "Yes" + and not self.is_internal_transfer() + and not frappe.get_value( "Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_order" - ): - return - + ) + ): for d in self.get("items"): if not d.purchase_order: msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code)) @@ -734,9 +735,10 @@ class PurchaseInvoice(BuyingController): for item in self.get("items"): if item.purchase_receipt: frappe.throw( - _("Stock cannot be updated against Purchase Receipt {0}").format( - item.purchase_receipt - ) + _( + "Stock cannot be updated for Purchase Invoice {0} because a Purchase Receipt {1} has already been created for this transaction. Please disable the 'Update Stock' checkbox in the Purchase Invoice and save the invoice." + ).format(self.name, item.purchase_receipt), + title=_("Stock Update Not Allowed"), ) def validate_for_repost(self): diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index cb771bdc35a..09febdfd915 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -3,7 +3,6 @@ import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, cint, flt, getdate, nowdate, today import erpnext @@ -37,24 +36,15 @@ from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle ) from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction from erpnext.stock.tests.test_utils import StockTestMixin - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"] -IGNORE_TEST_RECORD_DEPENDENCIES = ["Serial No"] +from erpnext.tests.utils import ERPNextTestSuite -class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): - @classmethod - def setUpClass(cls): - super().setUpClass() +class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): + def setUp(self): unlink_payment_on_cancel_of_invoice() frappe.db.set_single_value("Buying Settings", "allow_multiple_items", 1) - - @classmethod - def tearDownClass(cls): - unlink_payment_on_cancel_of_invoice(0) - - def tearDown(self): - frappe.db.rollback() + self.load_test_records("Purchase Invoice") + self.load_test_records("Journal Entry") def test_purchase_invoice_qty(self): pi = make_purchase_invoice(qty=0, do_not_save=True) @@ -352,6 +342,9 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): self.assertEqual(expected_values[gle.account][1], gle.debit) self.assertEqual(expected_values[gle.account][2], gle.credit) + @ERPNextTestSuite.change_settings( + "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1} + ) def test_purchase_invoice_with_exchange_rate_difference(self): from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( make_purchase_invoice as create_purchase_invoice, @@ -375,7 +368,7 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): # fetching the latest GL Entry with exchange gain and loss account account amount = frappe.db.get_value( - "GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "debit" + "GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "credit" ) discrepancy_caused_by_exchange_rate_diff = abs( pi.items[0].base_net_amount - pr.items[0].base_net_amount @@ -421,14 +414,14 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): # fetching the latest GL Entry with exchange gain and loss account account amount = frappe.db.get_value( - "GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "debit" + "GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "credit" ) discrepancy_caused_by_exchange_rate_diff = abs( pi.items[1].base_net_amount - pr.items[1].base_net_amount ) - self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount) + self.assertEqual(flt(discrepancy_caused_by_exchange_rate_diff, 2), amount) def test_purchase_invoice_change_naming_series(self): pi = frappe.copy_doc(self.globalTestRecords["Purchase Invoice"][1]) @@ -509,9 +502,7 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): self.assertEqual(tax.tax_amount, expected_values[i][1]) self.assertEqual(tax.total, expected_values[i][2]) - @IntegrationTestCase.change_settings( - "Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1} - ) + @ERPNextTestSuite.change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) def test_purchase_invoice_with_advance(self): jv = frappe.copy_doc(self.globalTestRecords["Journal Entry"][1]) jv.insert() @@ -562,9 +553,7 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): ) ) - @IntegrationTestCase.change_settings( - "Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1} - ) + @ERPNextTestSuite.change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) def test_invoice_with_advance_and_multi_payment_terms(self): jv = frappe.copy_doc(self.globalTestRecords["Journal Entry"][1]) jv.insert() @@ -1289,9 +1278,7 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): acc_settings.submit_journal_entriessubmit_journal_entries = 0 acc_settings.save() - @IntegrationTestCase.change_settings( - "Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1} - ) + @ERPNextTestSuite.change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) def test_gain_loss_with_advance_entry(self): unlink_enabled = frappe.db.get_single_value( "Accounts Settings", "unlink_payment_on_cancellation_of_invoice" @@ -1492,9 +1479,7 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): ) frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account) - @IntegrationTestCase.change_settings( - "Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1} - ) + @ERPNextTestSuite.change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) def test_purchase_invoice_advance_taxes(self): from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry @@ -2160,7 +2145,7 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): rate = flt(sle.stock_value_difference) / flt(sle.actual_qty) self.assertAlmostEqual(rate, 500) - @IntegrationTestCase.change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1}) + @ERPNextTestSuite.change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1}) def test_payment_allocation_for_payment_terms(self): from erpnext.buying.doctype.purchase_order.test_purchase_order import ( create_pr_against_po, @@ -2204,11 +2189,6 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): def test_offsetting_entries_for_accounting_dimensions(self): from erpnext.accounts.doctype.account.test_account import create_account - from erpnext.accounts.report.trial_balance.test_trial_balance import ( - clear_dimension_defaults, - create_accounting_dimension, - disable_dimension, - ) create_account( account_name="Offsetting", @@ -2216,7 +2196,16 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): parent_account="Temporary Accounts - _TC", ) - create_accounting_dimension(company="_Test Company", offsetting_account="Offsetting - _TC") + dim = frappe.get_doc("Accounting Dimension", "Branch") + dim.append( + "dimension_defaults", + { + "company": "_Test Company", + "reference_document": "Branch", + "offsetting_account": "Offsetting - _TC", + }, + ) + dim.save() branch1 = frappe.new_doc("Branch") branch1.branch = "Location 1" @@ -2253,8 +2242,6 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): voucher_type="Purchase Invoice", additional_columns=["branch"], ) - clear_dimension_defaults("Branch") - disable_dimension() def test_repost_accounting_entries(self): # update repost settings @@ -2287,6 +2274,7 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): def test_create_purchase_invoice_without_mandatory(self): pi = frappe.new_doc("Purchase Invoice") + pi.company = "_Test Company" pi.flags.ignore_mandatory = True pi.insert(ignore_permissions=True) @@ -2295,7 +2283,7 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): pi.delete() - @IntegrationTestCase.change_settings("Buying Settings", {"supplier_group": None}) + @ERPNextTestSuite.change_settings("Buying Settings", {"supplier_group": None}) def test_purchase_invoice_without_supplier_group(self): # Create a Supplier test_supplier_name = "_Test Supplier Without Supplier Group" @@ -2415,6 +2403,7 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): "doctype": "Serial No", "item_code": serial_item, "serial_no": serial_no, + "company": "_Test Company", } ).insert() @@ -2467,11 +2456,6 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): self.assertEqual(row.rejected_serial_no, serial_nos[2]) def test_make_pr_and_pi_from_po(self): - from erpnext.assets.doctype.asset.test_asset import create_asset_category - - if not frappe.db.exists("Asset Category", "Computers"): - create_asset_category() - item = create_item( item_code="_Test_Item", is_stock_item=0, is_fixed_asset=1, asset_category="Computers" ) @@ -2642,7 +2626,7 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Buying Settings", {"maintain_same_rate": 0, "set_landed_cost_based_on_purchase_invoice_rate": 1} ) def test_pr_status_rate_adjusted_from_pi(self): diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.py b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.py index 30bb017871b..4e34ec416b6 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.py +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.py @@ -1,8 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestPurchaseTaxesandChargesTemplate(IntegrationTestCase): +class TestPurchaseTaxesandChargesTemplate(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index cde007d14f6..b60c13fc8b8 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -4,7 +4,6 @@ import frappe from frappe import qb from frappe.query_builder.functions import Sum -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, nowdate, today from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry @@ -14,23 +13,17 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.accounts.utils import get_fiscal_year from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries, make_purchase_receipt +from erpnext.tests.utils import ERPNextTestSuite -class TestRepostAccountingLedger(AccountsTestMixin, IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0)) - +class TestRepostAccountingLedger(AccountsTestMixin, ERPNextTestSuite): def setUp(self): self.create_company() self.create_customer() self.create_item() + frappe.db.set_single_value("Selling Settings", "validate_selling_price", 0) update_repost_settings() - def tearDown(self): - frappe.db.rollback() - def test_01_basic_functions(self): si = create_sales_invoice( item=self.item, @@ -121,7 +114,7 @@ class TestRepostAccountingLedger(AccountsTestMixin, IntegrationTestCase): ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) self.assertRaises(frappe.ValidationError, ral.save) - @IntegrationTestCase.change_settings("Accounts Settings", {"delete_linked_ledger_entries": 1}) + @ERPNextTestSuite.change_settings("Accounts Settings", {"delete_linked_ledger_entries": 1}) def test_04_pcv_validation(self): # Clear old GL entries so PCV can be submitted. gl = frappe.qb.DocType("GL Entry") diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json index ba8188c1440..808986bba23 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json +++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json @@ -15,11 +15,11 @@ } ], "grid_page_length": 50, - "hide_toolbar": 1, + "hide_toolbar": 0, "in_create": 1, "issingle": 1, "links": [], - "modified": "2026-01-02 18:19:08.888368", + "modified": "2026-03-16 13:28:21.312607", "modified_by": "Administrator", "module": "Accounts", "name": "Repost Accounting Ledger Settings", diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/test_repost_accounting_ledger_settings.py b/erpnext/accounts/doctype/repost_accounting_ledger_settings/test_repost_accounting_ledger_settings.py index 2dd4c50ba7e..6a8698e96af 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger_settings/test_repost_accounting_ledger_settings.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/test_repost_accounting_ledger_settings.py @@ -2,8 +2,10 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase -class TestRepostAccountingLedgerSettings(IntegrationTestCase): +from erpnext.tests.utils import ERPNextTestSuite + + +class TestRepostAccountingLedgerSettings(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/repost_payment_ledger/test_repost_payment_ledger.py b/erpnext/accounts/doctype/repost_payment_ledger/test_repost_payment_ledger.py index a09ba71e27c..8c2b8946121 100644 --- a/erpnext/accounts/doctype/repost_payment_ledger/test_repost_payment_ledger.py +++ b/erpnext/accounts/doctype/repost_payment_ledger/test_repost_payment_ledger.py @@ -2,8 +2,10 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase -class TestRepostPaymentLedger(IntegrationTestCase): +from erpnext.tests.utils import ERPNextTestSuite + + +class TestRepostPaymentLedger(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index f97e641c757..a76568eab2e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -381,7 +381,9 @@ "fieldtype": "Date", "hide_days": 1, "hide_seconds": 1, - "label": "Date", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Posting Date", "no_copy": 1, "oldfieldname": "posting_date", "oldfieldtype": "Date", @@ -415,6 +417,7 @@ "fieldtype": "Date", "hide_days": 1, "hide_seconds": 1, + "in_list_view": 1, "label": "Payment Due Date", "no_copy": 1, "oldfieldname": "due_date", @@ -1639,6 +1642,7 @@ "fieldtype": "Select", "hide_days": 1, "hide_seconds": 1, + "in_list_view": 1, "in_standard_filter": 1, "label": "Status", "length": 30, @@ -2330,7 +2334,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2026-02-28 17:58:56.453076", + "modified": "2026-03-09 17:15:30.931929", "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 7a21a0bcf69..a386e778154 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -920,11 +920,9 @@ class SalesInvoice(SellingController): if self.pos_profile: pos = frappe.get_doc("POS Profile", self.pos_profile) - if not self.get("payments") and not for_validate: - update_multi_mode_option(self, pos) - if pos: if not for_validate: + update_multi_mode_option(self, pos) self.tax_category = pos.get("tax_category") if not for_validate and not self.customer: @@ -2780,7 +2778,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): "doctype": target_doctype, "postprocess": update_details, "set_target_warehouse": "set_from_warehouse", - "field_no_map": ["taxes_and_charges", "set_warehouse", "shipping_address"], + "field_no_map": ["taxes_and_charges", "set_warehouse", "shipping_address", "cost_center"], }, doctype + " Item": item_field_map, }, @@ -3009,6 +3007,8 @@ def update_multi_mode_option(doc, pos_profile): payment.account = payment_mode.default_account payment.type = payment_mode.type + mop_refetched = bool(doc.payments) and not doc.is_created_using_pos + doc.set("payments", []) invalid_modes = [] mode_of_payments = [d.mode_of_payment for d in pos_profile.get("payments")] @@ -3030,6 +3030,12 @@ def update_multi_mode_option(doc, pos_profile): msg = _("Please set default Cash or Bank account in Mode of Payments {}") frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account")) + if mop_refetched: + frappe.toast( + _("Payment methods refreshed. Please review before proceeding."), + indicator="orange", + ) + def get_all_mode_of_payments(doc): return frappe.db.sql( diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json index 814c6757d38..9aee69cd053 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_records.json +++ b/erpnext/accounts/doctype/sales_invoice/test_records.json @@ -383,5 +383,390 @@ "price_list_currency": "INR", "selling_price_list": "_Test Price List", "territory": "_Test Territory" + }, + { + "company": "_Test Company", + "conversion_rate": 1.0, + "currency": "INR", + "cost_center": "_Test Cost Center - _TC", + "customer": "_Test Customer", + "customer_name": "_Test Customer", + "debit_to": "Debtors - _TC", + "doctype": "Sales Invoice", + "items": [ + { + "amount": 500.0, + "base_amount": 500.0, + "base_rate": 500.0, + "cost_center": "_Test Cost Center - _TC", + "description": "138-CMS Shoe", + "doctype": "Sales Invoice Item", + "income_account": "Sales - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "item_code": "138-CMS Shoe", + "item_name": "138-CMS Shoe", + "parentfield": "items", + "qty": 1.0, + "rate": 500.0, + "uom": "_Test UOM", + "conversion_factor": 1, + "stock_uom": "_Test UOM" + } + ], + "base_grand_total": 561.8, + "grand_total": 561.8, + "is_pos": 0, + "naming_series": "T-SINV-", + "base_net_total": 500.0, + "taxes": [ + { + "account_head": "_Test Account VAT - _TC", + "charge_type": "On Net Total", + "description": "VAT", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "cost_center": "_Test Cost Center - _TC", + "rate": 6 + }, + { + "account_head": "_Test Account Service Tax - _TC", + "charge_type": "On Net Total", + "description": "Service Tax", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "cost_center": "_Test Cost Center - _TC", + "rate": 6.36 + } + ], + "plc_conversion_rate": 1.0, + "price_list_currency": "INR", + "selling_price_list": "_Test Price List", + "territory": "_Test Territory" + }, + { + "company": "_Test Company", + "conversion_rate": 1.0, + "currency": "INR", + "customer": "_Test Customer", + "customer_name": "_Test Customer", + "debit_to": "Debtors - _TC", + "doctype": "Sales Invoice", + "cost_center": "_Test Cost Center - _TC", + "items": [ + { + "amount": 500.0, + "base_amount": 500.0, + "base_rate": 500.0, + "cost_center": "_Test Cost Center - _TC", + "description": "_Test Item", + "doctype": "Sales Invoice Item", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "income_account": "Sales - _TC", + "item_code": "_Test Item", + "item_name": "_Test Item", + "parentfield": "items", + "price_list_rate": 500.0, + "qty": 1.0, + "uom": "_Test UOM", + "conversion_factor": 1, + "stock_uom": "_Test UOM" + } + ], + "base_grand_total": 630.0, + "grand_total": 630.0, + "is_pos": 0, + "naming_series": "T-SINV-", + "base_net_total": 500.0, + "taxes": [ + { + "account_head": "_Test Account VAT - _TC", + "charge_type": "On Net Total", + "description": "VAT", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "cost_center": "_Test Cost Center - _TC", + "rate": 16 + }, + { + "account_head": "_Test Account Service Tax - _TC", + "charge_type": "On Net Total", + "description": "Service Tax", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "cost_center": "_Test Cost Center - _TC", + "rate": 10 + } + ], + "plc_conversion_rate": 1.0, + "price_list_currency": "INR", + "selling_price_list": "_Test Price List", + "territory": "_Test Territory" + }, + { + "company": "_Test Company", + "conversion_rate": 1.0, + "currency": "INR", + "customer": "_Test Customer", + "customer_name": "_Test Customer", + "debit_to": "Debtors - _TC", + "doctype": "Sales Invoice", + "cost_center": "_Test Cost Center - _TC", + "items": [ + { + "cost_center": "_Test Cost Center - _TC", + "doctype": "Sales Invoice Item", + "income_account": "Sales - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "item_code": "_Test Item Home Desktop 100", + "item_name": "_Test Item Home Desktop 100", + "item_tax_template": "_Test Account Excise Duty @ 10 - _TC", + "parentfield": "items", + "price_list_rate": 50, + "qty": 10, + "rate": 50, + "uom": "_Test UOM 1", + "conversion_factor": 1, + "stock_uom": "_Test UOM 1" + }, + { + "cost_center": "_Test Cost Center - _TC", + "doctype": "Sales Invoice Item", + "income_account": "Sales - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "item_code": "_Test Item Home Desktop 200", + "item_name": "_Test Item Home Desktop 200", + "parentfield": "items", + "price_list_rate": 150, + "qty": 5, + "uom": "_Test UOM", + "conversion_factor": 1, + "rate": 150, + "stock_uom": "_Test UOM" + } + ], + "grand_total": 0, + "is_pos": 0, + "naming_series": "T-SINV-", + "taxes": [ + { + "account_head": "_Test Account Shipping Charges - _TC", + "charge_type": "Actual", + "cost_center": "_Test Cost Center - _TC", + "description": "Shipping Charges", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "tax_amount": 100 + }, + { + "account_head": "_Test Account Customs Duty - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Customs Duty", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "rate": 10 + }, + { + "account_head": "_Test Account Excise Duty - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Excise Duty", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "rate": 12 + }, + { + "account_head": "_Test Account Education Cess - _TC", + "charge_type": "On Previous Row Amount", + "cost_center": "_Test Cost Center - _TC", + "description": "Education Cess", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "rate": 2, + "row_id": 3 + }, + { + "account_head": "_Test Account S&H Education Cess - _TC", + "charge_type": "On Previous Row Amount", + "cost_center": "_Test Cost Center - _TC", + "description": "S&H Education Cess", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "rate": 1, + "row_id": 3 + }, + { + "account_head": "_Test Account CST - _TC", + "charge_type": "On Previous Row Total", + "cost_center": "_Test Cost Center - _TC", + "description": "CST", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "rate": 2, + "row_id": 5 + }, + { + "account_head": "_Test Account VAT - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "VAT", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "rate": 12.5 + }, + { + "account_head": "_Test Account Discount - _TC", + "charge_type": "On Previous Row Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Discount", + "doctype": "Sales Taxes and Charges", + "parentfield": "taxes", + "rate": -10, + "row_id": 7 + } + ], + "plc_conversion_rate": 1.0, + "price_list_currency": "INR", + "selling_price_list": "_Test Price List", + "territory": "_Test Territory" + }, + { + "company": "_Test Company", + "conversion_rate": 1.0, + "currency": "INR", + "customer": "_Test Customer", + "customer_name": "_Test Customer", + "debit_to": "Debtors - _TC", + "doctype": "Sales Invoice", + "cost_center": "_Test Cost Center - _TC", + "items": [ + { + "cost_center": "_Test Cost Center - _TC", + "doctype": "Sales Invoice Item", + "income_account": "Sales - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "item_code": "_Test Item Home Desktop 100", + "item_name": "_Test Item Home Desktop 100", + "item_tax_template": "_Test Account Excise Duty @ 10 - _TC", + "parentfield": "items", + "price_list_rate": 62.5, + "qty": 10, + "uom": "_Test UOM 1", + "conversion_factor": 1, + "stock_uom": "_Test UOM 1" + }, + { + "cost_center": "_Test Cost Center - _TC", + "doctype": "Sales Invoice Item", + "income_account": "Sales - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "item_code": "_Test Item Home Desktop 200", + "item_name": "_Test Item Home Desktop 200", + "parentfield": "items", + "price_list_rate": 190.66, + "qty": 5, + "uom": "_Test UOM", + "conversion_factor": 1, + "stock_uom": "_Test UOM" + } + ], + "grand_total": 0, + "is_pos": 0, + "naming_series": "T-SINV-", + "taxes": [ + { + "account_head": "_Test Account Excise Duty - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Excise Duty", + "doctype": "Sales Taxes and Charges", + "idx": 1, + "included_in_print_rate": 1, + "parentfield": "taxes", + "rate": 12 + }, + { + "account_head": "_Test Account Education Cess - _TC", + "charge_type": "On Previous Row Amount", + "cost_center": "_Test Cost Center - _TC", + "description": "Education Cess", + "doctype": "Sales Taxes and Charges", + "idx": 2, + "included_in_print_rate": 1, + "parentfield": "taxes", + "rate": 2, + "row_id": 1 + }, + { + "account_head": "_Test Account S&H Education Cess - _TC", + "charge_type": "On Previous Row Amount", + "cost_center": "_Test Cost Center - _TC", + "description": "S&H Education Cess", + "doctype": "Sales Taxes and Charges", + "idx": 3, + "included_in_print_rate": 1, + "parentfield": "taxes", + "rate": 1, + "row_id": 1 + }, + { + "account_head": "_Test Account CST - _TC", + "charge_type": "On Previous Row Total", + "cost_center": "_Test Cost Center - _TC", + "description": "CST", + "doctype": "Sales Taxes and Charges", + "idx": 4, + "included_in_print_rate": 1, + "parentfield": "taxes", + "rate": 2, + "row_id": 3 + }, + { + "account_head": "_Test Account VAT - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "VAT", + "doctype": "Sales Taxes and Charges", + "idx": 5, + "included_in_print_rate": 1, + "parentfield": "taxes", + "rate": 12.5 + }, + { + "account_head": "_Test Account Customs Duty - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Customs Duty", + "doctype": "Sales Taxes and Charges", + "idx": 6, + "parentfield": "taxes", + "rate": 10 + }, + { + "account_head": "_Test Account Shipping Charges - _TC", + "charge_type": "Actual", + "cost_center": "_Test Cost Center - _TC", + "description": "Shipping Charges", + "doctype": "Sales Taxes and Charges", + "idx": 7, + "parentfield": "taxes", + "tax_amount": 100 + }, + { + "account_head": "_Test Account Discount - _TC", + "charge_type": "On Previous Row Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Discount", + "doctype": "Sales Taxes and Charges", + "idx": 8, + "parentfield": "taxes", + "rate": -10, + "row_id": 7 + } + ], + "plc_conversion_rate": 1.0, + "price_list_currency": "INR", + "selling_price_list": "_Test Price List", + "territory": "_Test Territory" } ] diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index c5e2e61feb4..bdb786bf533 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -7,7 +7,6 @@ import json import frappe from frappe import qb from frappe.model.dynamic_links import get_dynamic_link_map -from frappe.tests import IntegrationTestCase, change_settings from frappe.utils import add_days, cint, flt, format_date, getdate, nowdate, today import erpnext @@ -23,7 +22,7 @@ from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import ( from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction from erpnext.accounts.utils import PaymentEntryUnlinkError from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries -from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data +from erpnext.assets.doctype.asset.test_asset import create_asset from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( get_depr_schedule, ) @@ -53,6 +52,11 @@ from erpnext.tests.utils import ERPNextTestSuite class TestSalesInvoice(ERPNextTestSuite): def setUp(self): + self.load_test_records("Journal Entry") + self.load_test_records("Stock Entry") + self.load_test_records("Sales Invoice") + unlink_payment_on_cancel_of_invoice() + from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}]) @@ -65,8 +69,9 @@ class TestSalesInvoice(ERPNextTestSuite): ) for company in frappe.get_all("Company", pluck="name"): frappe.db.set_value("Company", company, "accounts_frozen_till_date", None) + frappe.db.set_single_value("Selling Settings", "validate_selling_price", 0) - @change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"maintain_same_internal_transaction_rate": 1, "maintain_same_rate_action": "Stop"}, ) @@ -86,9 +91,6 @@ class TestSalesInvoice(ERPNextTestSuite): pi.submit() self.assertIn("Rate must be same", str(e.exception)) - def tearDown(self): - frappe.db.rollback() - def make(self): w = frappe.copy_doc(self.globalTestRecords["Sales Invoice"][0]) w.is_pos = 0 @@ -96,18 +98,6 @@ class TestSalesInvoice(ERPNextTestSuite): w.submit() return w - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0)) - cls.make_employees() - cls.make_sales_person() - unlink_payment_on_cancel_of_invoice() - - @classmethod - def tearDownClass(self): - unlink_payment_on_cancel_of_invoice(0) - def test_sales_invoice_qty(self): si = create_sales_invoice(qty=0, do_not_save=True) with self.assertRaises(InvalidQtyError): @@ -229,9 +219,7 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertRaises(frappe.LinkExistsError, si.cancel) unlink_payment_on_cancel_of_invoice() - @IntegrationTestCase.change_settings( - "Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1} - ) + @ERPNextTestSuite.change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) def test_payment_entry_unlink_against_standalone_credit_note(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry @@ -273,6 +261,9 @@ class TestSalesInvoice(ERPNextTestSuite): si1.load_from_db() self.assertRaises(PaymentEntryUnlinkError, si1.cancel) + @ERPNextTestSuite.change_settings( + "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1} + ) def test_sales_invoice_calculation_export_currency(self): si = frappe.copy_doc(self.globalTestRecords["Sales Invoice"][2]) si.currency = "USD" @@ -536,6 +527,7 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertTrue(gle) + @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": True}) def test_tax_calculation_with_multiple_items(self): si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True) item_row = si.get("items")[0] @@ -564,6 +556,7 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertEqual(si.grand_total, 5474.0) + @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": True}) def test_tax_calculation_with_item_tax_template(self): si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True) item_row = si.get("items")[0] @@ -630,6 +623,7 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertEqual(si.rounding_adjustment, 0.43) self.assertEqual(si.rounded_total, 5676.0) + @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": True}) def test_tax_calculation_with_multiple_items_and_discount(self): si = create_sales_invoice(qty=1, rate=75, do_not_save=True) item_row = si.get("items")[0] @@ -754,6 +748,9 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertEqual(si.base_grand_total, 1622.97) self.assertEqual(si.grand_total, 1622.97) + @ERPNextTestSuite.change_settings( + "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1} + ) def test_sales_invoice_calculation_export_currency_with_tax_inclusive_price(self): # prepare si = frappe.copy_doc(self.globalTestRecords["Sales Invoice"][3]) @@ -844,7 +841,7 @@ class TestSalesInvoice(ERPNextTestSuite): w = self.make() self.assertEqual(w.outstanding_amount, w.base_rounded_total) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"add_taxes_from_item_tax_template": 0, "add_taxes_from_taxes_and_charges_template": 0}, ) @@ -1401,9 +1398,7 @@ class TestSalesInvoice(ERPNextTestSuite): dn.submit() return dn - @IntegrationTestCase.change_settings( - "Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1} - ) + @ERPNextTestSuite.change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) def test_sales_invoice_with_advance(self): jv = frappe.copy_doc(self.globalTestRecords["Journal Entry"][0]) jv.insert() @@ -1624,7 +1619,6 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertRaises(frappe.ValidationError, cr_note.save) def test_gle_made_when_asset_is_returned(self): - create_asset_data() asset = create_asset(item_code="Macbook Pro") si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000) @@ -2131,17 +2125,21 @@ class TestSalesInvoice(ERPNextTestSuite): si.insert() return si + @ERPNextTestSuite.change_settings("Selling Settings", {"sales_update_frequency": "Each Transaction"}) def test_company_monthly_sales(self): - existing_current_month_sales = frappe.get_cached_value( - "Company", "_Test Company", "total_monthly_sales" - ) + from erpnext.setup.doctype.company.company import update_company_current_month_sales + + company = "_Test Company" + + update_company_current_month_sales(company) + existing_current_month_sales = frappe.db.get_value("Company", company, "total_monthly_sales") si = create_sales_invoice() - current_month_sales = frappe.get_cached_value("Company", "_Test Company", "total_monthly_sales") + current_month_sales = frappe.db.get_value("Company", company, "total_monthly_sales") self.assertEqual(current_month_sales, existing_current_month_sales + si.base_grand_total) si.cancel() - current_month_sales = frappe.get_cached_value("Company", "_Test Company", "total_monthly_sales") + current_month_sales = frappe.db.get_value("Company", company, "total_monthly_sales") self.assertEqual(current_month_sales, existing_current_month_sales) def test_rounding_adjustment(self): @@ -2188,6 +2186,7 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertEqual(expected_values[gle.account][1], gle.debit) self.assertEqual(expected_values[gle.account][2], gle.credit) + @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": True}) def test_rounding_adjustment_2(self): si = create_sales_invoice(rate=400, do_not_save=True) for rate in [400.25, 600.30, 100.65]: @@ -2245,14 +2244,8 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertEqual(expected_account_values[0], gle.debit) self.assertEqual(expected_account_values[1], gle.credit) + @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": True}) def test_rounding_adjustment_3(self): - from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension - - # Dimension creates custom field, which does an implicit DB commit as it is a DDL command - # Ensure dimension don't have any mandatory fields - create_dimension() - - # rollback from tearDown() happens till here si = create_sales_invoice(do_not_save=True) si.items = [] for d in [(1122, 2), (1122.01, 1), (1122.01, 1)]: @@ -2484,7 +2477,7 @@ class TestSalesInvoice(ERPNextTestSuite): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"book_deferred_entries_based_on": "Days", "book_deferred_entries_via_journal_entry": 0}, ) @@ -2540,7 +2533,7 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertRaises(frappe.ValidationError, si.save) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"book_deferred_entries_based_on": "Months", "book_deferred_entries_via_journal_entry": 0}, ) @@ -3005,7 +2998,7 @@ class TestSalesInvoice(ERPNextTestSuite): si.submit() - @IntegrationTestCase.change_settings("Selling Settings", {"enable_discount_accounting": 1}) + @ERPNextTestSuite.change_settings("Selling Settings", {"enable_discount_accounting": 1}) def test_sales_invoice_with_discount_accounting_enabled(self): discount_account = create_account( account_name="Discount Account", @@ -3022,7 +3015,7 @@ class TestSalesInvoice(ERPNextTestSuite): check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1)) - @IntegrationTestCase.change_settings("Selling Settings", {"enable_discount_accounting": 1}) + @ERPNextTestSuite.change_settings("Selling Settings", {"enable_discount_accounting": 1}) def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self): from erpnext.accounts.doctype.repost_accounting_ledger.test_repost_accounting_ledger import ( update_repost_settings, @@ -3113,7 +3106,6 @@ class TestSalesInvoice(ERPNextTestSuite): Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on its date of sale. """ - create_asset_data() asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, submit=1) post_depreciation_entries(getdate("2021-09-30")) @@ -3139,7 +3131,6 @@ class TestSalesInvoice(ERPNextTestSuite): Tests if an Asset set to depreciate yearly on Dec 31, that gets sold on Dec 31 after two years, created an additional depreciation entry on its date of sale. """ - create_asset_data() asset = create_asset( item_code="Macbook Pro", calculate_depreciation=1, @@ -3460,7 +3451,8 @@ class TestSalesInvoice(ERPNextTestSuite): si.submit() frappe.db.set_value("Company", "_Test Company", "accounts_frozen_till_date", None) - @IntegrationTestCase.change_settings("Accounts Settings", {"over_billing_allowance": 0}) + @ERPNextTestSuite.change_settings("Accounts Settings", {"over_billing_allowance": 0}) + @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": 1}) def test_over_billing_case_against_delivery_note(self): """ Test a case where duplicating the item with qty = 1 in the invoice @@ -3486,7 +3478,7 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertTrue("cannot overbill" in str(err.exception).lower()) dn.cancel() - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", { "book_deferred_entries_via_journal_entry": 1, @@ -3604,9 +3596,7 @@ class TestSalesInvoice(ERPNextTestSuite): account.disabled = 0 account.save() - @IntegrationTestCase.change_settings( - "Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1} - ) + @ERPNextTestSuite.change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) def test_gain_loss_with_advance_entry(self): from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry @@ -3769,7 +3759,7 @@ class TestSalesInvoice(ERPNextTestSuite): set_advance_flag(company="_Test Company", flag=0, default_account="") - @IntegrationTestCase.change_settings("Selling Settings", {"customer_group": None, "territory": None}) + @ERPNextTestSuite.change_settings("Selling Settings", {"customer_group": None, "territory": None}) def test_sales_invoice_without_customer_group_and_territory(self): # create a customer if not frappe.db.exists("Customer", "_Test Simple Customer"): @@ -3787,7 +3777,7 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertEqual(si.customer_group, None) self.assertEqual(si.territory, None) - @IntegrationTestCase.change_settings("Selling Settings", {"allow_negative_rates_for_items": 0}) + @ERPNextTestSuite.change_settings("Selling Settings", {"allow_negative_rates_for_items": 0}) def test_sales_return_negative_rate(self): si = create_sales_invoice(is_return=1, qty=-2, rate=-10, do_not_save=True) self.assertRaises(frappe.ValidationError, si.save) @@ -3884,19 +3874,18 @@ class TestSalesInvoice(ERPNextTestSuite): def test_loyalty_points_redemption_with_shopping_cart(self): from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import ( - create_records, create_sales_invoice_record, ) from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order # Set up loyalty program - create_records() - frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty") + loyalty_customer = frappe.get_doc("Customer", {"customer_name": "Test Loyalty Customer"}).name + frappe.db.set_value("Customer", loyalty_customer, "loyalty_program", "Test Single Loyalty") create_sales_invoice_record(10).insert().submit() # Create a sales order - so = make_sales_order(qty=10, do_not_save=True, customer="Test Loyalty Customer") + so = make_sales_order(qty=10, do_not_save=True, customer=loyalty_customer) so.name = "_T-Sales Order LP-0001" so.order_type = "Shopping Cart" so.loyalty_points = 50 @@ -4174,12 +4163,14 @@ class TestSalesInvoice(ERPNextTestSuite): si.save() return si + @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": True}) def test_rounding_validation_for_opening_with_inclusive_tax(self): si = self._create_opening_invoice_with_inclusive_tax() # 'Round Off for Opening' not set in Company master # Ledger level validation must be thrown self.assertRaises(frappe.ValidationError, si.submit) + @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": True}) def test_ledger_entries_on_opening_invoice_with_rounding_loss_by_inclusive_tax(self): si = self._create_opening_invoice_with_inclusive_tax() # 'Round Off for Opening' is set in Company master @@ -4201,7 +4192,7 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertEqual(len(actual), 4) self.assertEqual(expected, actual) - @IntegrationTestCase.change_settings("Accounts Settings", {"enable_common_party_accounting": True}) + @ERPNextTestSuite.change_settings("Accounts Settings", {"enable_common_party_accounting": True}) def test_common_party_with_foreign_currency_jv(self): from erpnext.accounts.doctype.account.test_account import create_account from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( @@ -4283,7 +4274,7 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertTrue(jv) self.assertEqual(jv[0], si.grand_total) - @IntegrationTestCase.change_settings("Accounts Settings", {"enable_common_party_accounting": True}) + @ERPNextTestSuite.change_settings("Accounts Settings", {"enable_common_party_accounting": True}) def test_common_party_with_different_currency_in_debtor_and_creditor(self): from erpnext.accounts.doctype.account.test_account import create_account from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( @@ -4392,6 +4383,7 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertTrue(all([x == "Credit Note" for x in gl_entries])) + @ERPNextTestSuite.change_settings("Selling Settings", {"sales_update_frequency": "Each Transaction"}) def test_total_billed_amount(self): si = create_sales_invoice(do_not_submit=True) @@ -4407,6 +4399,8 @@ class TestSalesInvoice(ERPNextTestSuite): doc = frappe.get_doc("Project", project.name) self.assertEqual(doc.total_billed_amount, si.grand_total) + @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": True}) + @ERPNextTestSuite.change_settings("Selling Settings", {"sales_update_frequency": "Each Transaction"}) def test_total_billed_amount_with_different_projects(self): # This test case is for checking the scenario where project is set at document level and for **some** child items only, not all from copy import copy @@ -4739,7 +4733,7 @@ class TestSalesInvoice(ERPNextTestSuite): doc.db_set("do_not_use_batchwise_valuation", original_value) - @change_settings("Selling Settings", {"set_zero_rate_for_expired_batch": True}) + @ERPNextTestSuite.change_settings("Selling Settings", {"set_zero_rate_for_expired_batch": True}) def test_zero_valuation_for_standalone_credit_note_with_expired_batch(self): item_code = "_Test Item for Expiry Batch Zero Valuation" make_item_for_si( @@ -4799,6 +4793,33 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertEqual(stock_ledger_entry.incoming_rate, 0.0) + def test_inter_company_transaction_cost_center(self): + si = create_sales_invoice( + company="Wind Power LLC", + customer="_Test Internal Customer", + debit_to="Debtors - WP", + warehouse="Stores - WP", + income_account="Sales - WP", + expense_account="Cost of Goods Sold - WP", + parent_cost_center="Main - WP", + cost_center="Main - WP", + currency="USD", + do_not_save=1, + ) + + si.selling_price_list = "_Test Price List Rest of the World" + si.submit() + + cost_center = frappe.db.get_value("Company", "_Test Company 1", "cost_center") + frappe.db.set_value("Company", "_Test Company 1", "cost_center", None) + + target_doc = make_inter_company_transaction("Sales Invoice", si.name) + + self.assertEqual(target_doc.cost_center, None) + self.assertEqual(target_doc.items[0].cost_center, None) + + frappe.db.set_value("Company", "_Test Company 1", "cost_center", cost_center) + def make_item_for_si(item_code, properties=None): from erpnext.stock.doctype.item.test_item import make_item @@ -4983,9 +5004,6 @@ def create_sales_invoice_against_cost_center(**args): return si -EXTRA_TEST_RECORD_DEPENDENCIES = ["Journal Entry", "Contact", "Address"] - - def get_outstanding_amount(against_voucher_type, against_voucher, account, party, party_type): bal = flt( frappe.db.sql( 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 deleted file mode 100644 index 74db08d5b86..00000000000 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_records.json +++ /dev/null @@ -1,209 +0,0 @@ -[ - { - "company": "_Test Company", - "doctype": "Sales Taxes and Charges Template", - "taxes": [ - { - "account_head": "_Test Account VAT - _TC", - "charge_type": "On Net Total", - "description": "VAT", - "doctype": "Sales Taxes and Charges", - "cost_center": "Main - _TC", - "parentfield": "taxes", - "rate": 6 - }, - { - "account_head": "_Test Account Service Tax - _TC", - "charge_type": "On Net Total", - "description": "Service Tax", - "doctype": "Sales Taxes and Charges", - "cost_center": "Main - _TC", - "parentfield": "taxes", - "rate": 6.36 - } - ], - "title": "_Test Sales Taxes and Charges Template" - }, - { - "company": "_Test Company", - "doctype": "Sales Taxes and Charges Template", - "taxes": [ - { - "account_head": "_Test Account Shipping Charges - _TC", - "charge_type": "Actual", - "cost_center": "_Test Cost Center - _TC", - "description": "Shipping Charges", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", - "tax_amount": 100 - }, - { - "account_head": "_Test Account Customs Duty - _TC", - "charge_type": "On Net Total", - "cost_center": "_Test Cost Center - _TC", - "description": "Customs Duty", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", - "rate": 10 - }, - { - "account_head": "_Test Account Excise Duty - _TC", - "charge_type": "On Net Total", - "cost_center": "_Test Cost Center - _TC", - "description": "Excise Duty", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", - "rate": 12 - }, - { - "account_head": "_Test Account Education Cess - _TC", - "charge_type": "On Previous Row Amount", - "cost_center": "_Test Cost Center - _TC", - "description": "Education Cess", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", - "rate": 2, - "row_id": 3 - }, - { - "account_head": "_Test Account S&H Education Cess - _TC", - "charge_type": "On Previous Row Amount", - "cost_center": "_Test Cost Center - _TC", - "description": "S&H Education Cess", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", - "rate": 1, - "row_id": 3 - }, - { - "account_head": "_Test Account CST - _TC", - "charge_type": "On Previous Row Total", - "cost_center": "_Test Cost Center - _TC", - "description": "CST", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", - "rate": 2, - "row_id": 5 - }, - { - "account_head": "_Test Account VAT - _TC", - "charge_type": "On Net Total", - "cost_center": "_Test Cost Center - _TC", - "description": "VAT", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", - "rate": 12.5 - }, - { - "account_head": "_Test Account Discount - _TC", - "charge_type": "On Previous Row Total", - "cost_center": "_Test Cost Center - _TC", - "description": "Discount", - "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", - "rate": -10, - "row_id": 7 - } - ], - "title": "_Test India Tax Master" - }, - { - "company": "_Test Company", - "doctype": "Sales Taxes and Charges Template", - "taxes": [ - { - "account_head": "_Test Account VAT - _TC", - "charge_type": "On Net Total", - "description": "VAT", - "doctype": "Sales Taxes and Charges", - "cost_center": "Main - _TC", - "parentfield": "taxes", - "rate": 12 - }, - { - "account_head": "_Test Account Service Tax - _TC", - "charge_type": "On Net Total", - "description": "Service Tax", - "doctype": "Sales Taxes and Charges", - "cost_center": "Main - _TC", - "parentfield": "taxes", - "rate": 4 - } - ], - "title": "_Test Sales Taxes and Charges Template - Rest of the World" - }, - { - "company": "_Test Company", - "doctype": "Sales Taxes and Charges Template", - "taxes": [ - { - "account_head": "_Test Account VAT - _TC", - "charge_type": "On Net Total", - "description": "VAT", - "doctype": "Sales Taxes and Charges", - "cost_center": "Main - _TC", - "parentfield": "taxes", - "rate": 12 - }, - { - "account_head": "_Test Account Service Tax - _TC", - "charge_type": "On Net Total", - "description": "Service Tax", - "doctype": "Sales Taxes and Charges", - "cost_center": "Main - _TC", - "parentfield": "taxes", - "rate": 4 - } - ], - "title": "_Test Sales Taxes and Charges Template 1" - }, - { - "company": "_Test Company", - "doctype": "Sales Taxes and Charges Template", - "taxes": [ - { - "account_head": "_Test Account VAT - _TC", - "charge_type": "On Net Total", - "description": "VAT", - "doctype": "Sales Taxes and Charges", - "cost_center": "Main - _TC", - "parentfield": "taxes", - "rate": 12 - }, - { - "account_head": "_Test Account Service Tax - _TC", - "charge_type": "On Net Total", - "description": "Service Tax", - "doctype": "Sales Taxes and Charges", - "cost_center": "Main - _TC", - "parentfield": "taxes", - "rate": 4 - } - ], - "title": "_Test Sales Taxes and Charges Template 2" - }, - { - "doctype" : "Sales Taxes and Charges Template", - "title": "_Test Tax 1", - "company": "_Test Company", - "taxes":[{ - "charge_type": "Actual", - "account_head": "Sales Expenses - _TC", - "cost_center": "Main - _TC", - "description": "Test Shopping cart taxes with Tax Rule", - "tax_amount": 1000 - }] - }, - { - "doctype" : "Sales Taxes and Charges Template", - "title": "_Test Tax 2", - "company": "_Test Company", - "taxes":[{ - "charge_type": "Actual", - "account_head": "Sales Expenses - _TC", - "cost_center": "Main - _TC", - "description": "Test Shopping cart taxes with Tax Rule", - "tax_amount": 200 - }] - } -] diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_sales_taxes_and_charges_template.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_sales_taxes_and_charges_template.py index e7454829e98..f780fef5c0e 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_sales_taxes_and_charges_template.py +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_sales_taxes_and_charges_template.py @@ -1,8 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestSalesTaxesandChargesTemplate(IntegrationTestCase): +class TestSalesTaxesandChargesTemplate(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/share_transfer/test_share_transfer.py b/erpnext/accounts/doctype/share_transfer/test_share_transfer.py index 9cd05bdbe37..e1d56167476 100644 --- a/erpnext/accounts/doctype/share_transfer/test_share_transfer.py +++ b/erpnext/accounts/doctype/share_transfer/test_share_transfer.py @@ -2,14 +2,12 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from erpnext.accounts.doctype.share_transfer.share_transfer import ShareDontExists - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Share Type", "Shareholder"] +from erpnext.tests.utils import ERPNextTestSuite -class TestShareTransfer(IntegrationTestCase): +class TestShareTransfer(ERPNextTestSuite): def setUp(self): frappe.db.sql("delete from `tabShare Transfer`") frappe.db.sql("delete from `tabShare Balance`") diff --git a/erpnext/accounts/doctype/share_type/test_records.json b/erpnext/accounts/doctype/share_type/test_records.json deleted file mode 100644 index 88cb8c056fe..00000000000 --- a/erpnext/accounts/doctype/share_type/test_records.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "doctype": "Share Type", - "title": "Class A" - }, - { - "doctype": "Share Type", - "title": "Class B" - } -] \ No newline at end of file diff --git a/erpnext/accounts/doctype/share_type/test_share_type.py b/erpnext/accounts/doctype/share_type/test_share_type.py index c43eb206c69..3e067c16166 100644 --- a/erpnext/accounts/doctype/share_type/test_share_type.py +++ b/erpnext/accounts/doctype/share_type/test_share_type.py @@ -1,8 +1,8 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestShareType(IntegrationTestCase): +class TestShareType(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/shareholder/test_records.json b/erpnext/accounts/doctype/shareholder/test_records.json deleted file mode 100644 index 39b72d47770..00000000000 --- a/erpnext/accounts/doctype/shareholder/test_records.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "doctype": "Shareholder", - "naming_series": "SH-", - "title": "Iron Man", - "company": "_Test Company" - }, - { - "doctype": "Shareholder", - "naming_series": "SH-", - "title": "Thor", - "company": "_Test Company" - }, - { - "doctype": "Shareholder", - "naming_series": "SH-", - "title": "Hulk", - "company": "_Test Company" - } -] \ No newline at end of file diff --git a/erpnext/accounts/doctype/shareholder/test_shareholder.py b/erpnext/accounts/doctype/shareholder/test_shareholder.py index 00c321dcb69..d2cc440d60d 100644 --- a/erpnext/accounts/doctype/shareholder/test_shareholder.py +++ b/erpnext/accounts/doctype/shareholder/test_shareholder.py @@ -1,8 +1,8 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestShareholder(IntegrationTestCase): +class TestShareholder(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py index d9b7b0a408f..3ec11364afa 100644 --- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py +++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py @@ -152,7 +152,9 @@ class ShippingRule(Document): frappe.throw(_("Shipping rule only applicable for Buying")) shipping_charge["doctype"] = "Purchase Taxes and Charges" - shipping_charge["category"] = "Valuation and Total" + shipping_charge["category"] = ( + "Valuation and Total" if doc.get_stock_items() or doc.get_asset_items() else "Total" + ) shipping_charge["add_deduct_tax"] = "Add" existing_shipping_charge = doc.get("taxes", filters=shipping_charge) diff --git a/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py index 57099aae6a3..40e66756f7e 100644 --- a/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py +++ b/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py @@ -2,16 +2,19 @@ # License: GNU General Public License v3. See license.txt import frappe -from frappe.tests import IntegrationTestCase from erpnext.accounts.doctype.shipping_rule.shipping_rule import ( FromGreaterThanToError, ManyBlankToValuesError, OverlappingConditionError, ) +from erpnext.tests.utils import ERPNextTestSuite -class TestShippingRule(IntegrationTestCase): +class TestShippingRule(ERPNextTestSuite): + def setUp(self): + self.load_test_records("Shipping Rule") + def test_from_greater_than_to(self): shipping_rule = frappe.copy_doc(self.globalTestRecords["Shipping Rule"][0]) shipping_rule.name = self.globalTestRecords["Shipping Rule"][0].get("name") @@ -85,5 +88,5 @@ def create_shipping_rule(shipping_rule_type, shipping_rule_name): }, ) sr.insert(ignore_permissions=True) - sr.submit() + return sr diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 0b3da559e39..642f918c3b1 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -772,7 +772,8 @@ def process_all(subscription: list, posting_date: DateTimeLikeObject | None = No try: subscription = frappe.get_doc("Subscription", subscription_name) subscription.process(posting_date) - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() except frappe.ValidationError: frappe.db.rollback() subscription.log_error("Subscription failed") diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index b6b1cdc81e2..d90a589d5e6 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -3,7 +3,6 @@ import frappe -from frappe.tests import IntegrationTestCase from frappe.utils.data import ( add_days, add_months, @@ -18,20 +17,16 @@ from frappe.utils.data import ( from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.subscription.subscription import get_prorata_factor - -EXTRA_TEST_RECORD_DEPENDENCIES = ("UOM", "Item Group", "Item") +from erpnext.tests.utils import ERPNextTestSuite -class TestSubscription(IntegrationTestCase): +class TestSubscription(ERPNextTestSuite): def setUp(self): make_plans() create_parties() reset_settings() frappe.db.set_value("Company", "_Test Company", "accounts_frozen_till_date", None) - def tearDown(self): - frappe.db.rollback() - def test_create_subscription_with_trial_with_correct_period(self): subscription = create_subscription( trial_period_start=nowdate(), trial_period_end=add_months(nowdate(), 1) @@ -471,7 +466,7 @@ class TestSubscription(IntegrationTestCase): currency = frappe.db.get_value("Sales Invoice", subscription.invoices[0].name, "currency") self.assertEqual(currency, "USD") - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1}, ) diff --git a/erpnext/accounts/doctype/subscription_invoice/test_subscription_invoice.py b/erpnext/accounts/doctype/subscription_invoice/test_subscription_invoice.py index 347777b6945..15b4a017602 100644 --- a/erpnext/accounts/doctype/subscription_invoice/test_subscription_invoice.py +++ b/erpnext/accounts/doctype/subscription_invoice/test_subscription_invoice.py @@ -1,8 +1,8 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestSubscriptionInvoice(IntegrationTestCase): +class TestSubscriptionInvoice(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/subscription_plan/test_subscription_plan.py b/erpnext/accounts/doctype/subscription_plan/test_subscription_plan.py index 6c11af041c9..76328f9e4c3 100644 --- a/erpnext/accounts/doctype/subscription_plan/test_subscription_plan.py +++ b/erpnext/accounts/doctype/subscription_plan/test_subscription_plan.py @@ -1,8 +1,8 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestSubscriptionPlan(IntegrationTestCase): +class TestSubscriptionPlan(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/subscription_settings/subscription_settings.json b/erpnext/accounts/doctype/subscription_settings/subscription_settings.json index 326fee345dc..06112d51fd1 100644 --- a/erpnext/accounts/doctype/subscription_settings/subscription_settings.json +++ b/erpnext/accounts/doctype/subscription_settings/subscription_settings.json @@ -31,10 +31,10 @@ } ], "grid_page_length": 50, - "hide_toolbar": 1, + "hide_toolbar": 0, "issingle": 1, "links": [], - "modified": "2026-01-02 18:18:34.671062", + "modified": "2026-03-16 13:28:20.485964", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription Settings", diff --git a/erpnext/accounts/doctype/subscription_settings/test_subscription_settings.py b/erpnext/accounts/doctype/subscription_settings/test_subscription_settings.py index 36fd6b0f7a7..d6a4376d806 100644 --- a/erpnext/accounts/doctype/subscription_settings/test_subscription_settings.py +++ b/erpnext/accounts/doctype/subscription_settings/test_subscription_settings.py @@ -1,8 +1,8 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestSubscriptionSettings(IntegrationTestCase): +class TestSubscriptionSettings(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/tax_category/test_records.json b/erpnext/accounts/doctype/tax_category/test_records.json deleted file mode 100644 index 1bb7f5ed876..00000000000 --- a/erpnext/accounts/doctype/tax_category/test_records.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "doctype": "Tax Category", - "title": "_Test Tax Category 1" - }, - { - "doctype": "Tax Category", - "title": "_Test Tax Category 2" - }, - { - "doctype": "Tax Category", - "title": "_Test Tax Category 3" - } -] diff --git a/erpnext/accounts/doctype/tax_category/test_tax_category.py b/erpnext/accounts/doctype/tax_category/test_tax_category.py index 596682499d9..bd51ba7ba36 100644 --- a/erpnext/accounts/doctype/tax_category/test_tax_category.py +++ b/erpnext/accounts/doctype/tax_category/test_tax_category.py @@ -1,8 +1,8 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestTaxCategory(IntegrationTestCase): +class TestTaxCategory(ERPNextTestSuite): pass diff --git a/erpnext/accounts/doctype/tax_rule/test_records.json b/erpnext/accounts/doctype/tax_rule/test_records.json deleted file mode 100644 index e80b12f73ea..00000000000 --- a/erpnext/accounts/doctype/tax_rule/test_records.json +++ /dev/null @@ -1,28 +0,0 @@ -[ - { - "doctype": "Tax Rule", - "tax_type" : "Sales", - "sales_tax_template": "_Test Tax 1 - _TC", - "use_for_shopping_cart": 1, - "billing_city": "_Test City", - "billing_state": "Test State", - "billing_country": "India", - "shipping_city": "_Test City", - "shipping_country": "India", - "priority": 1, - "company": "_Test Company" - }, - { - "doctype": "Tax Rule", - "tax_type" : "Sales", - "sales_tax_template": "_Test Tax 2 - _TC", - "use_for_shopping_cart": 0, - "billing_city": "_Test City", - "billing_country": "India", - "shipping_city": "_Test City", - "shipping_state": "Test State", - "shipping_country": "India", - "priority": 2, - "company": "_Test Company" - } -] \ No newline at end of file diff --git a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py index 9b8e4feb83a..cd47d689c60 100644 --- a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py +++ b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py @@ -2,25 +2,16 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule, get_tax_template from erpnext.crm.doctype.opportunity.opportunity import make_quotation from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity +from erpnext.tests.utils import ERPNextTestSuite -class TestTaxRule(IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - frappe.db.set_single_value("Shopping Cart Settings", "enabled", 0) - - @classmethod - def tearDownClass(cls): - frappe.db.sql("delete from `tabTax Rule`") - +class TestTaxRule(ERPNextTestSuite): def setUp(self): - frappe.db.sql("delete from `tabTax Rule`") + frappe.db.set_single_value("Shopping Cart Settings", "enabled", 0) def test_conflict(self): tax_rule1 = make_tax_rule( 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 a9773ceae60..bd633c94dc9 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 @@ -5,27 +5,19 @@ import datetime import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, add_months, today from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.utils import get_fiscal_year from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Supplier Group", "Customer Group"] +from erpnext.tests.utils import ERPNextTestSuite -class TestTaxWithholdingCategory(IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() +class TestTaxWithholdingCategory(ERPNextTestSuite): + def setUp(self): # create relevant supplier, etc create_records() create_tax_withholding_category_records() - make_pan_no_field() - - def tearDown(self): - frappe.db.rollback() def validate_tax_withholding_entries(self, doctype, docname, expected_entries): """Validate tax withholding entries for a document""" @@ -1036,6 +1028,7 @@ class TestTaxWithholdingCategory(IntegrationTestCase): self.cleanup_invoices(invoices) + @ERPNextTestSuite.change_settings("Buying Settings", {"allow_multiple_items": 1}) def test_tds_calculation_on_net_total_partial_tds(self): self.setup_party_with_category("Supplier", "Test TDS Supplier4", "Cumulative Threshold TDS") invoices = [] @@ -2049,7 +2042,7 @@ class TestTaxWithholdingCategory(IntegrationTestCase): self.assertEqual(pi2.taxes, []) self.assertEqual(payment.taxes[0].tax_amount, 6000) - @IntegrationTestCase.change_settings("Accounts Settings", {"delete_linked_ledger_entries": 1}) + @ERPNextTestSuite.change_settings("Accounts Settings", {"delete_linked_ledger_entries": 1}) def test_tds_payment_entry_cancellation(self): """ Test payment entry cancellation clears withholding references from matched entries @@ -2227,7 +2220,7 @@ class TestTaxWithholdingCategory(IntegrationTestCase): self.validate_tax_withholding_entries("Purchase Invoice", pi1.name, expected_entries) self.cleanup_invoices(invoices) - @IntegrationTestCase.change_settings("Accounts Settings", {"delete_linked_ledger_entries": 1}) + @ERPNextTestSuite.change_settings("Accounts Settings", {"delete_linked_ledger_entries": 1}) def test_tds_purchase_invoice_cancellation(self): """ Test that after cancellation, new documents get automatically adjusted against remaining entries @@ -4004,18 +3997,3 @@ def create_lower_deduction_certificate( "certificate_limit": limit, } ).insert() - - -def make_pan_no_field(): - pan_field = { - "Supplier": [ - { - "fieldname": "pan", - "label": "PAN", - "fieldtype": "Data", - "translatable": 0, - } - ] - } - - create_custom_fields(pan_field, update=1) diff --git a/erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.py b/erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.py index 6aff1116935..fa0adfae5b8 100644 --- a/erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.py +++ b/erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.py @@ -197,6 +197,7 @@ class TaxWithholdingEntry(Document): new_entry = frappe.copy_doc(old_entry) new_entry.update(values_to_update) + new_entry.flags.skip_docstatus_validation = True new_entry.insert() docs_needing_reindex.add((old_entry.parenttype, old_entry.parent)) @@ -335,6 +336,7 @@ class TaxWithholdingEntry(Document): "withholding_date": None, } new_entry.update(values_to_update) + new_entry.flags.skip_docstatus_validation = True new_entry.insert() docs_needing_reindex.add((entry.parenttype, entry.parent)) diff --git a/erpnext/accounts/doctype/tax_withholding_entry/test_tax_withholding_entry.py b/erpnext/accounts/doctype/tax_withholding_entry/test_tax_withholding_entry.py index 8a09f46e079..3f52eb77ee5 100644 --- a/erpnext/accounts/doctype/tax_withholding_entry/test_tax_withholding_entry.py +++ b/erpnext/accounts/doctype/tax_withholding_entry/test_tax_withholding_entry.py @@ -1,17 +1,10 @@ # Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe -from frappe.tests import IntegrationTestCase - -# On IntegrationTestCase, the doctype test records and all -# link-field test record dependencies are recursively loaded -# Use these module variables to add/remove to/from that list -EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] -IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +from erpnext.tests.utils import ERPNextTestSuite -class IntegrationTestTaxWithholdingEntry(IntegrationTestCase): +class IntegrationTestTaxWithholdingEntry(ERPNextTestSuite): """ Integration tests for TaxWithholdingEntry. Use this class for testing interactions between multiple components. diff --git a/erpnext/accounts/doctype/tax_withholding_group/test_tax_withholding_group.py b/erpnext/accounts/doctype/tax_withholding_group/test_tax_withholding_group.py index 268febe3d7e..eacdf7f36fc 100644 --- a/erpnext/accounts/doctype/tax_withholding_group/test_tax_withholding_group.py +++ b/erpnext/accounts/doctype/tax_withholding_group/test_tax_withholding_group.py @@ -1,17 +1,10 @@ # Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe -from frappe.tests import IntegrationTestCase - -# On IntegrationTestCase, the doctype test records and all -# link-field test record dependencies are recursively loaded -# Use these module variables to add/remove to/from that list -EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] -IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +from erpnext.tests.utils import ERPNextTestSuite -class IntegrationTestTaxWithholdingGroup(IntegrationTestCase): +class IntegrationTestTaxWithholdingGroup(ERPNextTestSuite): """ Integration tests for TaxWithholdingGroup. Use this class for testing interactions between multiple components. diff --git a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py index acd30cc47b7..7f931b7556e 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py @@ -2,7 +2,6 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import today from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry @@ -12,9 +11,10 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order +from erpnext.tests.utils import ERPNextTestSuite -class TestUnreconcilePayment(AccountsTestMixin, IntegrationTestCase): +class TestUnreconcilePayment(AccountsTestMixin, ERPNextTestSuite): def setUp(self): self.create_company() self.create_customer() @@ -23,9 +23,6 @@ class TestUnreconcilePayment(AccountsTestMixin, IntegrationTestCase): self.create_item() self.clear_old_entries() - def tearDown(self): - frappe.db.rollback() - def create_sales_invoice(self, do_not_submit=False): si = create_sales_invoice( item=self.item, diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 97008d0b057..a04511f0c40 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -429,6 +429,8 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False): gle.flags.adv_adj = adv_adj gle.flags.update_outstanding = update_outstanding or "Yes" gle.flags.notify_update = False + if gle.is_cancelled: + gle.flags.ignore_links = True gle.submit() if ( diff --git a/erpnext/accounts/print_format/point_of_sale/point_of_sale.json b/erpnext/accounts/print_format/point_of_sale/point_of_sale.json deleted file mode 100644 index c0c50cb4e26..00000000000 --- a/erpnext/accounts/print_format/point_of_sale/point_of_sale.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "align_labels_right": 0, - "creation": "2016-05-05 17:16:18.564460", - "custom_format": 1, - "disabled": 0, - "doc_type": "Sales Invoice", - "docstatus": 0, - "doctype": "Print Format", - "font": "Default", - "html": "\n\n

\n\t{{ company }}
\n\t{{ __(\"POS No : \") }} {{ offline_pos_name }}
\n

\n

\n\t{{ __(\"Customer\") }}: {{ customer }}
\n

\n\n

\n\t{{ __(\"Date\") }}: {{ dateutil.global_date_format(posting_date) }}
\n

\n\n
\n
{{ format_currency(summary[doctype].outstanding_amount, summary.currency, 2) }}
\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{% for item in items %}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{% endfor %}\n\t\n
{{ __(\"Item\") }}{{ __(\"Qty\") }}{{ __(\"Amount\") }}
\n\t\t\t\t{{ item.item_name }}\n\t\t\t{{ format_number(item.qty, null,precision(\"difference\")) }}
@ {{ format_currency(item.rate, currency) }}
{{ format_currency(item.amount, currency) }}
\n\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{% for row in taxes %}\n\t\t{% if not row.included_in_print_rate %}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{% endif %}\n\t\t{% endfor %}\n\t\t{% if discount_amount %}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{% endif %}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n
\n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t
\n\t\t\t\t{{ row.description }}\n\t\t\t\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t
\n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t
\n\t\t\t\t{{ __(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t
\n\t\t\t\t{{ __(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t
\n\t\t\t\t{{ __(\"Qty Total\") }}\n\t\t\t\n\t\t\t\t{{ qty_total }}\n\t\t\t
\n\n\n
\n

{{ terms }}

\n

{{ __(\"Thank you, please visit again.\") }}

", - "idx": 0, - "line_breaks": 0, - "modified": "2019-09-05 17:20:30.726659", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Point of Sale", - "owner": "Administrator", - "print_format_builder": 0, - "print_format_type": "JS", - "raw_printing": 0, - "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 13b261da724..5edf078b31d 100644 --- a/erpnext/accounts/report/account_balance/test_account_balance.py +++ b/erpnext/accounts/report/account_balance/test_account_balance.py @@ -1,12 +1,12 @@ import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import getdate from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.account_balance.account_balance import execute +from erpnext.tests.utils import ERPNextTestSuite -class TestAccountBalance(IntegrationTestCase): +class TestAccountBalance(ERPNextTestSuite): def test_account_balance(self): frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 2'") frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'") diff --git a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py index 8da40ea3aa4..87a4b989661 100644 --- a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py +++ b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py @@ -1,13 +1,13 @@ import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, today from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.report.accounts_payable.accounts_payable import execute from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.tests.utils import ERPNextTestSuite -class TestAccountsPayable(AccountsTestMixin, IntegrationTestCase): +class TestAccountsPayable(AccountsTestMixin, ERPNextTestSuite): def setUp(self): self.create_company() self.create_customer() @@ -15,9 +15,6 @@ class TestAccountsPayable(AccountsTestMixin, IntegrationTestCase): self.create_supplier(currency="USD", supplier_name="Test Supplier2") self.create_usd_payable_account() - def tearDown(self): - frappe.db.rollback() - def test_accounts_payable_for_foreign_currency_supplier(self): pi = self.create_purchase_invoice(do_not_submit=True) pi.currency = "USD" diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 907840e8ff4..9fa72f1716c 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -1,6 +1,5 @@ import frappe from frappe import qb -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, flt, getdate, today from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry @@ -8,9 +7,10 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order +from erpnext.tests.utils import ERPNextTestSuite -class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase): +class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite): def setUp(self): self.create_company() self.create_customer() @@ -18,9 +18,6 @@ class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase): self.create_usd_receivable_account() self.clear_old_entries() - def tearDown(self): - frappe.db.rollback() - def create_sales_invoice(self, no_payment_schedule=False, do_not_submit=False, **args): frappe.set_user("Administrator") si = create_sales_invoice( @@ -199,7 +196,7 @@ class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase): row = report[1] self.assertTrue(len(row) == 0) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1}, ) @@ -448,7 +445,7 @@ class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase): ], ) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0}, ) @@ -692,11 +689,9 @@ class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase): ) def test_sales_person(self): - sales_person = ( - frappe.get_doc({"doctype": "Sales Person", "sales_person_name": "John Clark", "enabled": True}) - .insert() - .submit() - ) + sales_person = frappe.get_doc( + {"doctype": "Sales Person", "sales_person_name": "John Clark", "enabled": True} + ).insert() si = self.create_sales_invoice(do_not_submit=True) si.append("sales_team", {"sales_person": sales_person.name, "allocated_percentage": 100}) si.save().submit() @@ -772,18 +767,14 @@ class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase): def test_party_account_filter(self): si1 = self.create_sales_invoice() - self.customer2 = ( - frappe.get_doc( - { - "doctype": "Customer", - "customer_name": "Jane Doe", - "type": "Individual", - "default_currency": "USD", - } - ) - .insert() - .submit() - ) + self.customer2 = frappe.get_doc( + { + "doctype": "Customer", + "customer_name": "Jane Doe", + "type": "Individual", + "default_currency": "USD", + } + ).insert() si2 = self.create_sales_invoice(do_not_submit=True) si2.posting_date = add_days(today(), -1) @@ -995,18 +986,14 @@ class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase): self.assertEqual(expected_data, report_output) def test_future_payments_on_foreign_currency(self): - self.customer2 = ( - frappe.get_doc( - { - "doctype": "Customer", - "customer_name": "Jane Doe", - "type": "Individual", - "default_currency": "USD", - } - ) - .insert() - .submit() - ) + self.customer2 = frappe.get_doc( + { + "doctype": "Customer", + "customer_name": "Jane Doe", + "type": "Individual", + "default_currency": "USD", + } + ).insert() si = self.create_sales_invoice(do_not_submit=True) si.posting_date = add_days(today(), -1) diff --git a/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py index d357c0d6273..96fa4ae8b64 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py @@ -1,14 +1,14 @@ import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import today from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.tests.utils import ERPNextTestSuite -class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase): +class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite): def setUp(self): self.maxDiff = None self.create_company() @@ -16,9 +16,6 @@ class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase): self.create_item() self.clear_old_entries() - def tearDown(self): - frappe.db.rollback() - def test_01_receivable_summary_output(self): """ Test for Invoices, Paid, Advance and Outstanding @@ -112,7 +109,7 @@ class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase): self.assertEqual(len(rpt_output), 1) self.assertDictEqual(rpt_output[0], expected_data) - @IntegrationTestCase.change_settings("Selling Settings", {"cust_master_name": "Naming Series"}) + @ERPNextTestSuite.change_settings("Selling Settings", {"cust_master_name": "Naming Series"}) def test_02_various_filters_and_output(self): filters = { "company": self.company, diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py index fad93e6fb70..212698765b1 100644 --- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py +++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py @@ -4,6 +4,7 @@ import frappe from frappe import _ +from frappe.query_builder.functions import IfNull, Sum from frappe.utils import add_days, flt, formatdate @@ -75,210 +76,333 @@ def get_group_by_asset_category_data(filters): def get_asset_categories_for_grouped_by_category(filters): - condition = "" - if filters.get("asset_category"): - condition += " and a.asset_category = %(asset_category)s" - if filters.get("finance_book"): - condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = a.name and ads.finance_book = %(finance_book)s)" + asset = frappe.qb.DocType("Asset") + asset_depreciation_schedule = frappe.qb.DocType("Asset Depreciation Schedule") + asset_capitalization_asset_item = frappe.qb.DocType("Asset Capitalization Asset Item") + asset_capitalization = frappe.qb.DocType("Asset Capitalization") - # nosemgrep - return frappe.db.sql( - f""" - SELECT a.asset_category, - ifnull(sum(case when a.purchase_date < %(from_date)s then - case when ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s then - a.net_purchase_amount - else - 0 - end - else - 0 - end), 0) as value_as_on_from_date, - ifnull(sum(case when a.purchase_date >= %(from_date)s then - a.net_purchase_amount - else - 0 - end), 0) as value_of_new_purchase, - ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 - and a.disposal_date >= %(from_date)s - and a.disposal_date <= %(to_date)s then - case when a.status = "Sold" then - a.net_purchase_amount - else - 0 - end - else - 0 - end), 0) as value_of_sold_asset, - ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 - and a.disposal_date >= %(from_date)s - and a.disposal_date <= %(to_date)s then - case when a.status = "Scrapped" then - a.net_purchase_amount - else - 0 - end - else - 0 - end), 0) as value_of_scrapped_asset, - ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 - and a.disposal_date >= %(from_date)s - and a.disposal_date <= %(to_date)s then - case when a.status = "Capitalized" then - a.net_purchase_amount - else - 0 - end - else - 0 - end), 0) as value_of_capitalized_asset - from `tabAsset` a - where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition} - and not exists( - select 1 from `tabAsset Capitalization Asset Item` acai join `tabAsset Capitalization` ac on acai.parent=ac.name - where acai.asset = a.name - and ac.posting_date < %(from_date)s - and ac.docstatus=1 - ) - group by a.asset_category - """, - { - "to_date": filters.to_date, - "from_date": filters.from_date, - "company": filters.company, - "asset_category": filters.get("asset_category"), - "finance_book": filters.get("finance_book"), - }, - as_dict=1, + disposal_in_period = ( + (IfNull(asset.disposal_date, 0) != 0) + & (asset.disposal_date >= filters.from_date) + & (asset.disposal_date <= filters.to_date) ) + value_as_on_from_date = IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (asset.purchase_date < filters.from_date) + & ((IfNull(asset.disposal_date, 0) == 0) | (asset.disposal_date >= filters.from_date)), + asset.net_purchase_amount, + ) + .else_(0) + ), + 0, + ).as_("value_as_on_from_date") + + value_of_new_purchase = IfNull( + Sum( + frappe.qb.terms.Case() + .when(asset.purchase_date >= filters.from_date, asset.net_purchase_amount) + .else_(0) + ), + 0, + ).as_("value_of_new_purchase") + + value_of_sold_asset = IfNull( + Sum( + frappe.qb.terms.Case() + .when(disposal_in_period & (asset.status == "Sold"), asset.net_purchase_amount) + .else_(0) + ), + 0, + ).as_("value_of_sold_asset") + + value_of_scrapped_asset = IfNull( + Sum( + frappe.qb.terms.Case() + .when(disposal_in_period & (asset.status == "Scrapped"), asset.net_purchase_amount) + .else_(0) + ), + 0, + ).as_("value_of_scrapped_asset") + + value_of_capitalized_asset = IfNull( + Sum( + frappe.qb.terms.Case() + .when(disposal_in_period & (asset.status == "Capitalized"), asset.net_purchase_amount) + .else_(0) + ), + 0, + ).as_("value_of_capitalized_asset") + + capitalized_before_from_date = ( + frappe.qb.from_(asset_capitalization_asset_item) + .join(asset_capitalization) + .on(asset_capitalization_asset_item.parent == asset_capitalization.name) + .select(asset_capitalization_asset_item.asset) + .where(asset_capitalization.posting_date < filters.from_date) + .where(asset_capitalization.docstatus == 1) + ) + + query = ( + frappe.qb.from_(asset) + .select( + asset.asset_category, + value_as_on_from_date, + value_of_new_purchase, + value_of_sold_asset, + value_of_scrapped_asset, + value_of_capitalized_asset, + ) + .where(asset.docstatus == 1) + .where(asset.company == filters.company) + .where(asset.purchase_date <= filters.to_date) + .where(asset.name.notin(capitalized_before_from_date)) + .groupby(asset.asset_category) + ) + + if filters.get("asset_category"): + query = query.where(asset.asset_category == filters.get("asset_category")) + + if filters.get("finance_book"): + assets_with_finance_book = ( + frappe.qb.from_(asset_depreciation_schedule) + .select(asset_depreciation_schedule.asset) + .where(asset_depreciation_schedule.finance_book == filters.get("finance_book")) + ) + query = query.where(asset.name.isin(assets_with_finance_book)) + + return query.run(as_dict=True) + def get_assets_for_grouped_by_category(filters): - condition = "" - if filters.get("asset_category"): - condition = f" and a.asset_category = '{filters.get('asset_category')}'" - finance_book_filter = "" - if filters.get("finance_book"): - finance_book_filter += " and ifnull(gle.finance_book, '')=%(finance_book)s" - condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = a.name and ads.finance_book = %(finance_book)s)" + asset = frappe.qb.DocType("Asset") + gl_entry = frappe.qb.DocType("GL Entry") + asset_category_account = frappe.qb.DocType("Asset Category Account") + company = frappe.qb.DocType("Company") + asset_depreciation_schedule = frappe.qb.DocType("Asset Depreciation Schedule") - # nosemgrep - return frappe.db.sql( - f""" - SELECT results.asset_category, - sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date, - sum(results.depreciation_eliminated_via_reversal) as depreciation_eliminated_via_reversal, - sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period, - sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period - from (SELECT a.asset_category, - ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then - gle.debit - else - 0 - end), 0) as accumulated_depreciation_as_on_from_date, - ifnull(sum(case when gle.posting_date <= %(to_date)s and ifnull(a.disposal_date, 0) = 0 then - gle.credit - else - 0 - end), 0) as depreciation_eliminated_via_reversal, - ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s - and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then - gle.debit - else - 0 - end), 0) as depreciation_eliminated_during_the_period, - ifnull(sum(case when gle.posting_date >= %(from_date)s and gle.posting_date <= %(to_date)s - and (ifnull(a.disposal_date, 0) = 0 or gle.posting_date <= a.disposal_date) then - gle.debit - else - 0 - end), 0) as depreciation_amount_during_the_period - from `tabGL Entry` gle - join `tabAsset` a on - gle.against_voucher = a.name - join `tabAsset Category Account` aca on - aca.parent = a.asset_category and aca.company_name = %(company)s - join `tabCompany` company on - company.name = %(company)s - where - a.docstatus=1 - and a.company=%(company)s - and a.purchase_date <= %(to_date)s - and gle.is_cancelled = 0 - and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account) - {condition} {finance_book_filter} - group by a.asset_category - union - SELECT a.asset_category, - ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date < %(from_date)s then - 0 - else - a.opening_accumulated_depreciation - end), 0) as accumulated_depreciation_as_on_from_date, - 0 as depreciation_eliminated_via_reversal, - ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then - a.opening_accumulated_depreciation - else - 0 - end), 0) as depreciation_eliminated_during_the_period, - 0 as depreciation_amount_during_the_period - from `tabAsset` a - where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition} - group by a.asset_category) as results - group by results.asset_category - """, - { - "to_date": filters.to_date, - "from_date": filters.from_date, - "company": filters.company, - "finance_book": filters.get("finance_book", ""), - }, - as_dict=1, + assets_with_finance_book = None + if filters.get("finance_book"): + assets_with_finance_book = ( + frappe.qb.from_(asset_depreciation_schedule) + .select(asset_depreciation_schedule.asset) + .where(asset_depreciation_schedule.finance_book == filters.get("finance_book")) + ) + + from_gl_entries_query = ( + frappe.qb.from_(gl_entry) + .join(asset) + .on(gl_entry.against_voucher == asset.name) + .join(asset_category_account) + .on( + (asset_category_account.parent == asset.asset_category) + & (asset_category_account.company_name == filters.company) + ) + .join(company) + .on(company.name == filters.company) + .select( + asset.asset_category, + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (gl_entry.posting_date < filters.from_date) + & ( + (IfNull(asset.disposal_date, 0) == 0) | (asset.disposal_date >= filters.from_date) + ), + gl_entry.debit, + ) + .else_(0) + ), + 0, + ).as_("accumulated_depreciation_as_on_from_date"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (gl_entry.posting_date <= filters.to_date) & (IfNull(asset.disposal_date, 0) == 0), + gl_entry.credit, + ) + .else_(0) + ), + 0, + ).as_("depreciation_eliminated_via_reversal"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (IfNull(asset.disposal_date, 0) != 0) + & (asset.disposal_date >= filters.from_date) + & (asset.disposal_date <= filters.to_date) + & (gl_entry.posting_date <= asset.disposal_date), + gl_entry.debit, + ) + .else_(0) + ), + 0, + ).as_("depreciation_eliminated_during_the_period"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (gl_entry.posting_date >= filters.from_date) + & (gl_entry.posting_date <= filters.to_date) + & ( + (IfNull(asset.disposal_date, 0) == 0) + | (gl_entry.posting_date <= asset.disposal_date) + ), + gl_entry.debit, + ) + .else_(0) + ), + 0, + ).as_("depreciation_amount_during_the_period"), + ) + .where(asset.docstatus == 1) + .where(asset.company == filters.company) + .where(asset.purchase_date <= filters.to_date) + .where(gl_entry.is_cancelled == 0) + .where( + gl_entry.account + == IfNull( + asset_category_account.depreciation_expense_account, + company.depreciation_expense_account, + ) + ) + .groupby(asset.asset_category) ) + from_opening_depreciation_query = ( + frappe.qb.from_(asset) + .select( + asset.asset_category, + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (IfNull(asset.disposal_date, 0) != 0) & (asset.disposal_date < filters.from_date), + 0, + ) + .else_(asset.opening_accumulated_depreciation) + ), + 0, + ).as_("accumulated_depreciation_as_on_from_date"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (asset.disposal_date >= filters.from_date) & (asset.disposal_date <= filters.to_date), + asset.opening_accumulated_depreciation, + ) + .else_(0) + ), + 0, + ).as_("depreciation_eliminated_during_the_period"), + ) + .where(asset.docstatus == 1) + .where(asset.company == filters.company) + .where(asset.purchase_date <= filters.to_date) + .groupby(asset.asset_category) + ) + + if filters.get("asset_category"): + from_gl_entries_query = from_gl_entries_query.where( + asset.asset_category == filters.get("asset_category") + ) + from_opening_depreciation_query = from_opening_depreciation_query.where( + asset.asset_category == filters.get("asset_category") + ) + + if assets_with_finance_book is not None: + from_gl_entries_query = from_gl_entries_query.where( + IfNull(gl_entry.finance_book, "") == filters.get("finance_book") + ).where(asset.name.isin(assets_with_finance_book)) + from_opening_depreciation_query = from_opening_depreciation_query.where( + asset.name.isin(assets_with_finance_book) + ) + + combined = {} + + for row in from_gl_entries_query.run(as_dict=True): + combined[row.asset_category] = { + "asset_category": row.asset_category, + "accumulated_depreciation_as_on_from_date": flt(row.accumulated_depreciation_as_on_from_date), + "depreciation_eliminated_via_reversal": flt(row.depreciation_eliminated_via_reversal), + "depreciation_eliminated_during_the_period": flt(row.depreciation_eliminated_during_the_period), + "depreciation_amount_during_the_period": flt(row.depreciation_amount_during_the_period), + } + + for row in from_opening_depreciation_query.run(as_dict=True): + if row.asset_category not in combined: + combined[row.asset_category] = { + "asset_category": row.asset_category, + "accumulated_depreciation_as_on_from_date": 0.0, + "depreciation_eliminated_via_reversal": 0.0, + "depreciation_eliminated_during_the_period": 0.0, + "depreciation_amount_during_the_period": 0.0, + } + + combined[row.asset_category]["accumulated_depreciation_as_on_from_date"] += flt( + row.accumulated_depreciation_as_on_from_date + ) + combined[row.asset_category]["depreciation_eliminated_during_the_period"] += flt( + row.depreciation_eliminated_during_the_period + ) + + return list(combined.values()) + def get_asset_value_adjustment_map_by_category(filters): - asset_value_adjustments = frappe.db.sql( - """ - SELECT - a.asset_category AS asset_category, - IFNULL( - SUM( - CASE - WHEN gle.posting_date < %(from_date)s - AND (a.disposal_date IS NULL OR a.disposal_date >= %(from_date)s) - THEN gle.debit - gle.credit - ELSE 0 - END - ), - 0) AS value_adjustment_before_from_date, - IFNULL( - SUM( - CASE - WHEN gle.posting_date <= %(to_date)s - AND (a.disposal_date IS NULL OR a.disposal_date >= %(to_date)s) - THEN gle.debit - gle.credit - ELSE 0 - END - ), - 0) AS value_adjustment_till_to_date + asset = frappe.qb.DocType("Asset") + gl_entry = frappe.qb.DocType("GL Entry") + asset_category_account = frappe.qb.DocType("Asset Category Account") - FROM `tabGL Entry` gle - JOIN `tabAsset` a ON gle.against_voucher = a.name - JOIN `tabAsset Category Account` aca - ON aca.parent = a.asset_category - AND aca.company_name = %(company)s - WHERE gle.is_cancelled = 0 - AND a.docstatus = 1 - AND a.company = %(company)s - AND a.purchase_date <= %(to_date)s - AND gle.account = aca.fixed_asset_account - AND gle.is_opening = 'No' - GROUP BY a.asset_category - """, - {"from_date": filters.from_date, "to_date": filters.to_date, "company": filters.company}, - as_dict=1, - ) + asset_value_adjustments = ( + frappe.qb.from_(gl_entry) + .join(asset) + .on(gl_entry.against_voucher == asset.name) + .join(asset_category_account) + .on( + (asset_category_account.parent == asset.asset_category) + & (asset_category_account.company_name == filters.company) + ) + .select( + asset.asset_category.as_("asset_category"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (gl_entry.posting_date < filters.from_date) + & (asset.disposal_date.isnull() | (asset.disposal_date >= filters.from_date)), + gl_entry.debit - gl_entry.credit, + ) + .else_(0) + ), + 0, + ).as_("value_adjustment_before_from_date"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (gl_entry.posting_date <= filters.to_date) + & (asset.disposal_date.isnull() | (asset.disposal_date >= filters.to_date)), + gl_entry.debit - gl_entry.credit, + ) + .else_(0) + ), + 0, + ).as_("value_adjustment_till_to_date"), + ) + .where(gl_entry.is_cancelled == 0) + .where(asset.docstatus == 1) + .where(asset.company == filters.company) + .where(asset.purchase_date <= filters.to_date) + .where(gl_entry.account == asset_category_account.fixed_asset_account) + .where(gl_entry.is_opening == "No") + .groupby(asset.asset_category) + ).run(as_dict=True) category_value_adjustment_map = {} @@ -346,210 +470,327 @@ def get_group_by_asset_data(filters): def get_asset_details_for_grouped_by_category(filters): - condition = "" - if filters.get("asset"): - condition += " and a.name = %(asset)s" - if filters.get("finance_book"): - condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = a.name and ads.finance_book = %(finance_book)s)" + asset = frappe.qb.DocType("Asset") + asset_depreciation_schedule = frappe.qb.DocType("Asset Depreciation Schedule") + asset_capitalization_asset_item = frappe.qb.DocType("Asset Capitalization Asset Item") + asset_capitalization = frappe.qb.DocType("Asset Capitalization") - # nosemgrep - return frappe.db.sql( - f""" - SELECT a.name, a.asset_name, - ifnull(sum(case when a.purchase_date < %(from_date)s then - case when ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s then - a.net_purchase_amount - else - 0 - end - else - 0 - end), 0) as value_as_on_from_date, - ifnull(sum(case when a.purchase_date >= %(from_date)s then - a.net_purchase_amount - else - 0 - end), 0) as value_of_new_purchase, - ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 - and a.disposal_date >= %(from_date)s - and a.disposal_date <= %(to_date)s then - case when a.status = "Sold" then - a.net_purchase_amount - else - 0 - end - else - 0 - end), 0) as value_of_sold_asset, - ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 - and a.disposal_date >= %(from_date)s - and a.disposal_date <= %(to_date)s then - case when a.status = "Scrapped" then - a.net_purchase_amount - else - 0 - end - else - 0 - end), 0) as value_of_scrapped_asset, - ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 - and a.disposal_date >= %(from_date)s - and a.disposal_date <= %(to_date)s then - case when a.status = "Capitalized" then - a.net_purchase_amount - else - 0 - end - else - 0 - end), 0) as value_of_capitalized_asset - from `tabAsset` a - where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition} - and not exists( - select 1 from `tabAsset Capitalization Asset Item` acai join `tabAsset Capitalization` ac on acai.parent=ac.name - where acai.asset = a.name - and ac.posting_date < %(from_date)s - and ac.docstatus=1 - ) - group by a.name - """, - { - "to_date": filters.to_date, - "from_date": filters.from_date, - "company": filters.company, - "asset": filters.get("asset"), - "finance_book": filters.get("finance_book"), - }, - as_dict=1, + disposal_in_period = ( + (IfNull(asset.disposal_date, 0) != 0) + & (asset.disposal_date >= filters.from_date) + & (asset.disposal_date <= filters.to_date) ) + capitalized_before_from_date = ( + frappe.qb.from_(asset_capitalization_asset_item) + .join(asset_capitalization) + .on(asset_capitalization_asset_item.parent == asset_capitalization.name) + .select(asset_capitalization_asset_item.asset) + .where(asset_capitalization.posting_date < filters.from_date) + .where(asset_capitalization.docstatus == 1) + ) + + query = ( + frappe.qb.from_(asset) + .select( + asset.name, + asset.asset_name, + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (asset.purchase_date < filters.from_date) + & ( + (IfNull(asset.disposal_date, 0) == 0) | (asset.disposal_date >= filters.from_date) + ), + asset.net_purchase_amount, + ) + .else_(0) + ), + 0, + ).as_("value_as_on_from_date"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when(asset.purchase_date >= filters.from_date, asset.net_purchase_amount) + .else_(0) + ), + 0, + ).as_("value_of_new_purchase"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when(disposal_in_period & (asset.status == "Sold"), asset.net_purchase_amount) + .else_(0) + ), + 0, + ).as_("value_of_sold_asset"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when(disposal_in_period & (asset.status == "Scrapped"), asset.net_purchase_amount) + .else_(0) + ), + 0, + ).as_("value_of_scrapped_asset"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + disposal_in_period & (asset.status == "Capitalized"), + asset.net_purchase_amount, + ) + .else_(0) + ), + 0, + ).as_("value_of_capitalized_asset"), + ) + .where(asset.docstatus == 1) + .where(asset.company == filters.company) + .where(asset.purchase_date <= filters.to_date) + .where(asset.name.notin(capitalized_before_from_date)) + .groupby(asset.name) + ) + + if filters.get("asset"): + query = query.where(asset.name == filters.get("asset")) + + if filters.get("finance_book"): + assets_with_finance_book = ( + frappe.qb.from_(asset_depreciation_schedule) + .select(asset_depreciation_schedule.asset) + .where(asset_depreciation_schedule.finance_book == filters.get("finance_book")) + ) + query = query.where(asset.name.isin(assets_with_finance_book)) + + return query.run(as_dict=True) + def get_assets_for_grouped_by_asset(filters): - condition = "" - if filters.get("asset"): - condition = f" and a.name = '{filters.get('asset')}'" - finance_book_filter = "" - if filters.get("finance_book"): - finance_book_filter += " and ifnull(gle.finance_book, '')=%(finance_book)s" - condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = a.name and ads.finance_book = %(finance_book)s)" + asset = frappe.qb.DocType("Asset") + gl_entry = frappe.qb.DocType("GL Entry") + asset_category_account = frappe.qb.DocType("Asset Category Account") + company = frappe.qb.DocType("Company") + asset_depreciation_schedule = frappe.qb.DocType("Asset Depreciation Schedule") - # nosemgrep - return frappe.db.sql( - f""" - SELECT results.name as asset, - sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date, - sum(results.depreciation_eliminated_via_reversal) as depreciation_eliminated_via_reversal, - sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period, - sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period - from (SELECT a.name as name, - ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then - gle.debit - else - 0 - end), 0) as accumulated_depreciation_as_on_from_date, - ifnull(sum(case when gle.posting_date <= %(to_date)s and ifnull(a.disposal_date, 0) = 0 then - gle.credit - else - 0 - end), 0) as depreciation_eliminated_via_reversal, - ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s - and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then - gle.debit - else - 0 - end), 0) as depreciation_eliminated_during_the_period, - ifnull(sum(case when gle.posting_date >= %(from_date)s and gle.posting_date <= %(to_date)s - and (ifnull(a.disposal_date, 0) = 0 or gle.posting_date <= a.disposal_date) then - gle.debit - else - 0 - end), 0) as depreciation_amount_during_the_period - from `tabGL Entry` gle - join `tabAsset` a on - gle.against_voucher = a.name - join `tabAsset Category Account` aca on - aca.parent = a.asset_category and aca.company_name = %(company)s - join `tabCompany` company on - company.name = %(company)s - where - a.docstatus=1 - and a.company=%(company)s - and a.purchase_date <= %(to_date)s - and gle.is_cancelled = 0 - and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account) - {finance_book_filter} {condition} - group by a.name - union - SELECT a.name as name, - ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date < %(from_date)s then - 0 - else - a.opening_accumulated_depreciation - end), 0) as accumulated_depreciation_as_on_from_date, - 0 as depreciation_as_on_from_date_credit, - ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then - a.opening_accumulated_depreciation - else - 0 - end), 0) as depreciation_eliminated_during_the_period, - 0 as depreciation_amount_during_the_period - from `tabAsset` a - where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition} - group by a.name) as results - group by results.name - """, - { - "to_date": filters.to_date, - "from_date": filters.from_date, - "company": filters.company, - "finance_book": filters.get("finance_book", ""), - }, - as_dict=1, + assets_with_finance_book = None + if filters.get("finance_book"): + assets_with_finance_book = ( + frappe.qb.from_(asset_depreciation_schedule) + .select(asset_depreciation_schedule.asset) + .where(asset_depreciation_schedule.finance_book == filters.get("finance_book")) + ) + + from_gl_entries_query = ( + frappe.qb.from_(gl_entry) + .join(asset) + .on(gl_entry.against_voucher == asset.name) + .join(asset_category_account) + .on( + (asset_category_account.parent == asset.asset_category) + & (asset_category_account.company_name == filters.company) + ) + .join(company) + .on(company.name == filters.company) + .select( + asset.name.as_("asset"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (gl_entry.posting_date < filters.from_date) + & ( + (IfNull(asset.disposal_date, 0) == 0) | (asset.disposal_date >= filters.from_date) + ), + gl_entry.debit, + ) + .else_(0) + ), + 0, + ).as_("accumulated_depreciation_as_on_from_date"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (gl_entry.posting_date <= filters.to_date) & (IfNull(asset.disposal_date, 0) == 0), + gl_entry.credit, + ) + .else_(0) + ), + 0, + ).as_("depreciation_eliminated_via_reversal"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (IfNull(asset.disposal_date, 0) != 0) + & (asset.disposal_date >= filters.from_date) + & (asset.disposal_date <= filters.to_date) + & (gl_entry.posting_date <= asset.disposal_date), + gl_entry.debit, + ) + .else_(0) + ), + 0, + ).as_("depreciation_eliminated_during_the_period"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (gl_entry.posting_date >= filters.from_date) + & (gl_entry.posting_date <= filters.to_date) + & ( + (IfNull(asset.disposal_date, 0) == 0) + | (gl_entry.posting_date <= asset.disposal_date) + ), + gl_entry.debit, + ) + .else_(0) + ), + 0, + ).as_("depreciation_amount_during_the_period"), + ) + .where(asset.docstatus == 1) + .where(asset.company == filters.company) + .where(asset.purchase_date <= filters.to_date) + .where(gl_entry.is_cancelled == 0) + .where( + gl_entry.account + == IfNull( + asset_category_account.depreciation_expense_account, + company.depreciation_expense_account, + ) + ) + .groupby(asset.name) ) + from_opening_depreciation_query = ( + frappe.qb.from_(asset) + .select( + asset.name.as_("asset"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (IfNull(asset.disposal_date, 0) != 0) & (asset.disposal_date < filters.from_date), + 0, + ) + .else_(asset.opening_accumulated_depreciation) + ), + 0, + ).as_("accumulated_depreciation_as_on_from_date"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (asset.disposal_date >= filters.from_date) & (asset.disposal_date <= filters.to_date), + asset.opening_accumulated_depreciation, + ) + .else_(0) + ), + 0, + ).as_("depreciation_eliminated_during_the_period"), + ) + .where(asset.docstatus == 1) + .where(asset.company == filters.company) + .where(asset.purchase_date <= filters.to_date) + .groupby(asset.name) + ) + + if filters.get("asset"): + from_gl_entries_query = from_gl_entries_query.where(asset.name == filters.get("asset")) + from_opening_depreciation_query = from_opening_depreciation_query.where( + asset.name == filters.get("asset") + ) + + if assets_with_finance_book is not None: + from_gl_entries_query = from_gl_entries_query.where( + IfNull(gl_entry.finance_book, "") == filters.get("finance_book") + ).where(asset.name.isin(assets_with_finance_book)) + from_opening_depreciation_query = from_opening_depreciation_query.where( + asset.name.isin(assets_with_finance_book) + ) + + combined = {} + + for row in from_gl_entries_query.run(as_dict=True): + combined[row.asset] = { + "asset": row.asset, + "accumulated_depreciation_as_on_from_date": flt(row.accumulated_depreciation_as_on_from_date), + "depreciation_eliminated_via_reversal": flt(row.depreciation_eliminated_via_reversal), + "depreciation_eliminated_during_the_period": flt(row.depreciation_eliminated_during_the_period), + "depreciation_amount_during_the_period": flt(row.depreciation_amount_during_the_period), + } + + for row in from_opening_depreciation_query.run(as_dict=True): + if row.asset not in combined: + combined[row.asset] = { + "asset": row.asset, + "accumulated_depreciation_as_on_from_date": 0.0, + "depreciation_eliminated_via_reversal": 0.0, + "depreciation_eliminated_during_the_period": 0.0, + "depreciation_amount_during_the_period": 0.0, + } + + combined[row.asset]["accumulated_depreciation_as_on_from_date"] += flt( + row.accumulated_depreciation_as_on_from_date + ) + combined[row.asset]["depreciation_eliminated_during_the_period"] += flt( + row.depreciation_eliminated_during_the_period + ) + + return list(combined.values()) + def get_asset_value_adjustment_map(filters): - asset_with_value_adjustments = frappe.db.sql( - """ - SELECT - a.name AS asset, - IFNULL( - SUM( - CASE - WHEN gle.posting_date < %(from_date)s - AND (a.disposal_date IS NULL OR a.disposal_date >= %(from_date)s) - THEN gle.debit - gle.credit - ELSE 0 - END - ), - 0) AS value_adjustment_before_from_date, - IFNULL( - SUM( - CASE - WHEN gle.posting_date <= %(to_date)s - AND (a.disposal_date IS NULL OR a.disposal_date >= %(to_date)s) - THEN gle.debit - gle.credit - ELSE 0 - END - ), - 0) AS value_adjustment_till_to_date + asset = frappe.qb.DocType("Asset") + gl_entry = frappe.qb.DocType("GL Entry") + asset_category_account = frappe.qb.DocType("Asset Category Account") - FROM `tabGL Entry` gle - JOIN `tabAsset` a ON gle.against_voucher = a.name - JOIN `tabAsset Category Account` aca - ON aca.parent = a.asset_category - AND aca.company_name = %(company)s - WHERE gle.is_cancelled = 0 - AND a.docstatus = 1 - AND a.company = %(company)s - AND a.purchase_date <= %(to_date)s - AND gle.account = aca.fixed_asset_account - AND gle.is_opening = 'No' - GROUP BY a.name - """, - {"from_date": filters.from_date, "to_date": filters.to_date, "company": filters.company}, - as_dict=1, - ) + asset_with_value_adjustments = ( + frappe.qb.from_(gl_entry) + .join(asset) + .on(gl_entry.against_voucher == asset.name) + .join(asset_category_account) + .on( + (asset_category_account.parent == asset.asset_category) + & (asset_category_account.company_name == filters.company) + ) + .select( + asset.name.as_("asset"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (gl_entry.posting_date < filters.from_date) + & (asset.disposal_date.isnull() | (asset.disposal_date >= filters.from_date)), + gl_entry.debit - gl_entry.credit, + ) + .else_(0) + ), + 0, + ).as_("value_adjustment_before_from_date"), + IfNull( + Sum( + frappe.qb.terms.Case() + .when( + (gl_entry.posting_date <= filters.to_date) + & (asset.disposal_date.isnull() | (asset.disposal_date >= filters.to_date)), + gl_entry.debit - gl_entry.credit, + ) + .else_(0) + ), + 0, + ).as_("value_adjustment_till_to_date"), + ) + .where(gl_entry.is_cancelled == 0) + .where(asset.docstatus == 1) + .where(asset.company == filters.company) + .where(asset.purchase_date <= filters.to_date) + .where(gl_entry.account == asset_category_account.fixed_asset_account) + .where(gl_entry.is_opening == "No") + .groupby(asset.name) + ).run(as_dict=True) asset_value_adjustment_map = {} diff --git a/erpnext/accounts/report/balance_sheet/test_balance_sheet.py b/erpnext/accounts/report/balance_sheet/test_balance_sheet.py index cf225ba39da..dce9d8ab0fa 100644 --- a/erpnext/accounts/report/balance_sheet/test_balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/test_balance_sheet.py @@ -2,18 +2,16 @@ # MIT License. See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils.data import today from erpnext.accounts.report.balance_sheet.balance_sheet import execute +from erpnext.tests.utils import ERPNextTestSuite COMPANY = "_Test Company 6" COMPANY_SHORT_NAME = "_TC6" -EXTRA_TEST_RECORD_DEPENDENCIES = ["Company"] - -class TestBalanceSheet(IntegrationTestCase): +class TestBalanceSheet(ERPNextTestSuite): def test_balance_sheet(self): frappe.db.sql(f"delete from `tabJournal Entry` where company='{COMPANY}'") frappe.db.sql(f"delete from `tabGL Entry` where company='{COMPANY}'") 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 ae675670446..5320de2b66c 100644 --- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py +++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py @@ -4,7 +4,10 @@ import frappe from frappe import _ -from frappe.utils import getdate, nowdate +from frappe.query_builder import Case +from frappe.query_builder.custom import ConstantColumn +from frappe.utils import getdate +from pypika import Order def execute(filters=None): @@ -48,17 +51,6 @@ def get_columns(): return columns -def get_conditions(filters): - conditions = "" - - if filters.get("from_date"): - conditions += " and posting_date>=%(from_date)s" - if filters.get("to_date"): - conditions += " and posting_date<=%(to_date)s" - - return conditions - - def get_entries(filters): entries = [] @@ -73,41 +65,90 @@ def get_entries(filters): return sorted( entries, - key=lambda k: k[2].strftime("%H%M%S") or getdate(nowdate()), + key=lambda k: getdate(k[2]), ) def get_entries_for_bank_clearance_summary(filters): entries = [] - conditions = get_conditions(filters) + je = frappe.qb.DocType("Journal Entry") + jea = frappe.qb.DocType("Journal Entry Account") - journal_entries = frappe.db.sql( - f"""SELECT - "Journal Entry", jv.name, jv.posting_date, jv.cheque_no, - jv.clearance_date, jvd.against_account, jvd.debit - jvd.credit - FROM - `tabJournal Entry Account` jvd, `tabJournal Entry` jv - WHERE - jvd.parent = jv.name and jv.docstatus=1 and jvd.account = %(account)s {conditions} - order by posting_date DESC, jv.name DESC""", - filters, - as_list=1, - ) + journal_entries = ( + frappe.qb.from_(jea) + .inner_join(je) + .on(jea.parent == je.name) + .select( + ConstantColumn("Journal Entry").as_("payment_document"), + je.name.as_("payment_entry"), + je.posting_date, + je.cheque_no, + je.clearance_date, + jea.against_account, + jea.debit_in_account_currency - jea.credit_in_account_currency, + ) + .where( + (jea.account == filters.account) + & (je.docstatus == 1) + & (je.posting_date >= filters.from_date) + & (je.posting_date <= filters.to_date) + & ((je.is_opening == "No") | (je.is_opening.isnull())) + ) + .orderby(je.posting_date, order=Order.desc) + .orderby(je.name, order=Order.desc) + ).run(as_list=True) - payment_entries = frappe.db.sql( - f"""SELECT - "Payment Entry", name, posting_date, reference_no, clearance_date, party, - if(paid_from=%(account)s, ((paid_amount * -1) - total_taxes_and_charges) , received_amount) - FROM - `tabPayment Entry` - WHERE - docstatus=1 and (paid_from = %(account)s or paid_to = %(account)s) {conditions} - order by posting_date DESC, name DESC""", - filters, - as_list=1, - ) + pe = frappe.qb.DocType("Payment Entry") + payment_entries = ( + frappe.qb.from_(pe) + .select( + ConstantColumn("Payment Entry").as_("payment_document"), + pe.name.as_("payment_entry"), + pe.posting_date, + pe.reference_no.as_("cheque_no"), + pe.clearance_date, + pe.party.as_("against_account"), + Case() + .when( + (pe.paid_from == filters.account), + ((pe.paid_amount * -1) - pe.total_taxes_and_charges), + ) + .else_(pe.received_amount), + ) + .where((pe.paid_from == filters.account) | (pe.paid_to == filters.account)) + .where( + (pe.docstatus == 1) + & (pe.posting_date >= filters.from_date) + & (pe.posting_date <= filters.to_date) + ) + .orderby(pe.posting_date, order=Order.desc) + .orderby(pe.name, order=Order.desc) + ).run(as_list=True) - entries = journal_entries + payment_entries + pi = frappe.qb.DocType("Purchase Invoice") + purchase_invoices = ( + frappe.qb.from_(pi) + .select( + ConstantColumn("Purchase Invoice").as_("payment_document"), + pi.name.as_("payment_entry"), + pi.posting_date, + pi.bill_no.as_("cheque_no"), + pi.clearance_date, + pi.supplier.as_("against_account"), + (pi.paid_amount * -1).as_("amount"), + ) + .where( + (pi.docstatus == 1) + & (pi.is_paid == 1) + & (pi.cash_bank_account == filters.account) + & (pi.posting_date >= filters.from_date) + & (pi.posting_date <= filters.to_date) + ) + .orderby(pi.posting_date, order=Order.desc) + .orderby(pi.name, order=Order.desc) + ).run(as_list=True) + + entries = journal_entries + payment_entries + purchase_invoices return entries diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py index bfc2f2d56ff..474e25c5474 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py @@ -4,7 +4,11 @@ import frappe from frappe import _ +from frappe.query_builder import Case +from frappe.query_builder.custom import ConstantColumn +from frappe.query_builder.functions import Coalesce, Sum from frappe.utils import flt, getdate +from pypika import Order from erpnext.accounts.utils import get_balance_on @@ -123,73 +127,143 @@ def get_entries_for_bank_reconciliation_statement(filters): payment_entries = get_payment_entries(filters) + purchase_invoices = get_purchase_invoices(filters) + pos_entries = [] if filters.include_pos_transactions: pos_entries = get_pos_entries(filters) - return list(journal_entries) + list(payment_entries) + list(pos_entries) + return list(journal_entries) + list(payment_entries) + list(pos_entries) + list(purchase_invoices) def get_journal_entries(filters): - return frappe.db.sql( - """ - select "Journal Entry" as payment_document, jv.posting_date, - jv.name as payment_entry, jvd.debit_in_account_currency as debit, - jvd.credit_in_account_currency as credit, jvd.against_account, - jv.cheque_no as reference_no, jv.cheque_date as ref_date, jv.clearance_date, jvd.account_currency - from - `tabJournal Entry Account` jvd, `tabJournal Entry` jv - where jvd.parent = jv.name and jv.docstatus=1 - and jvd.account = %(account)s and jv.posting_date <= %(report_date)s - and ifnull(jv.clearance_date, '4000-01-01') > %(report_date)s - and ifnull(jv.is_opening, 'No') = 'No' - and jv.company = %(company)s """, - filters, - as_dict=1, - ) + je = frappe.qb.DocType("Journal Entry") + jea = frappe.qb.DocType("Journal Entry Account") + return ( + frappe.qb.from_(jea) + .join(je) + .on(jea.parent == je.name) + .select( + ConstantColumn("Journal Entry").as_("payment_document"), + je.name.as_("payment_entry"), + je.posting_date, + jea.debit_in_account_currency.as_("debit"), + jea.credit_in_account_currency.as_("credit"), + jea.against_account, + je.cheque_no.as_("reference_no"), + je.cheque_date.as_("ref_date"), + je.clearance_date, + jea.account_currency, + ) + .where( + (je.docstatus == 1) + & (jea.account == filters.account) + & (je.posting_date <= filters.report_date) + & (je.clearance_date.isnull() | (je.clearance_date > filters.report_date)) + & (je.company == filters.company) + & ((je.is_opening.isnull()) | (je.is_opening == "No")) + ) + .orderby(je.posting_date) + .orderby(je.name, order=Order.desc) + ).run(as_dict=True) def get_payment_entries(filters): - return frappe.db.sql( - """ - select - "Payment Entry" as payment_document, name as payment_entry, - reference_no, reference_date as ref_date, - if(paid_to=%(account)s, received_amount_after_tax, 0) as debit, - if(paid_from=%(account)s, paid_amount_after_tax, 0) as credit, - posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date, - if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency - from `tabPayment Entry` - where - (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1 - and posting_date <= %(report_date)s - and ifnull(clearance_date, '4000-01-01') > %(report_date)s - and company = %(company)s - """, - filters, - as_dict=1, - ) + pe = frappe.qb.DocType("Payment Entry") + return ( + frappe.qb.from_(pe) + .select( + ConstantColumn("Payment Entry").as_("payment_document"), + pe.name.as_("payment_entry"), + pe.reference_no.as_("reference_no"), + pe.reference_date.as_("ref_date"), + Case().when(pe.paid_to == filters.account, pe.received_amount_after_tax).else_(0).as_("debit"), + Case().when(pe.paid_from == filters.account, pe.paid_amount_after_tax).else_(0).as_("credit"), + pe.posting_date, + Coalesce( + pe.party, Case().when(pe.paid_from == filters.account, pe.paid_to).else_(pe.paid_from) + ).as_("against_account"), + pe.clearance_date, + ( + Case() + .when(pe.paid_to == filters.account, pe.paid_to_account_currency) + .else_(pe.paid_from_account_currency) + ).as_("account_currency"), + ) + .where( + (pe.docstatus == 1) + & ((pe.paid_from == filters.account) | (pe.paid_to == filters.account)) + & (pe.posting_date <= filters.report_date) + & (pe.clearance_date.isnull() | (pe.clearance_date > filters.report_date)) + & (pe.company == filters.company) + ) + .orderby(pe.posting_date) + .orderby(pe.name, order=Order.desc) + ).run(as_dict=True) + + +def get_purchase_invoices(filters): + pi = frappe.qb.DocType("Purchase Invoice") + acc = frappe.qb.DocType("Account") + return ( + frappe.qb.from_(pi) + .inner_join(acc) + .on(pi.cash_bank_account == acc.name) + .select( + ConstantColumn("Purchase Invoice").as_("payment_document"), + pi.name.as_("payment_entry"), + pi.bill_no.as_("reference_no"), + pi.posting_date.as_("ref_date"), + Case().when(pi.paid_amount < 0, pi.paid_amount * -1).else_(0).as_("debit"), + Case().when(pi.paid_amount > 0, pi.paid_amount).else_(0).as_("credit"), + pi.posting_date, + pi.supplier.as_("against_account"), + pi.clearance_date, + acc.account_currency, + ) + .where( + (pi.docstatus == 1) + & (pi.is_paid == 1) + & (pi.cash_bank_account == filters.account) + & (pi.posting_date <= filters.report_date) + & (pi.clearance_date.isnull() | (pi.clearance_date > filters.report_date)) + & (pi.company == filters.company) + ) + .orderby(pi.posting_date) + .orderby(pi.name, order=Order.desc) + ).run(as_dict=True) def get_pos_entries(filters): - return frappe.db.sql( - """ - select - "Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit, - si.posting_date, si.debit_to as against_account, sip.clearance_date, - account.account_currency, 0 as credit - from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account - where - sip.account=%(account)s and si.docstatus=1 and sip.parent = si.name - and account.name = sip.account and si.posting_date <= %(report_date)s and - ifnull(sip.clearance_date, '4000-01-01') > %(report_date)s - and si.company = %(company)s - order by - si.posting_date ASC, si.name DESC - """, - filters, - as_dict=1, - ) + si = frappe.qb.DocType("Sales Invoice") + si_payment = frappe.qb.DocType("Sales Invoice Payment") + acc = frappe.qb.DocType("Account") + return ( + frappe.qb.from_(si_payment) + .join(si) + .on(si_payment.parent == si.name) + .join(acc) + .on(si_payment.account == acc.name) + .select( + ConstantColumn("Sales Invoice").as_("payment_document"), + si.name.as_("payment_entry"), + si_payment.amount.as_("debit"), + si.posting_date, + si.debit_to.as_("against_account"), + si_payment.clearance_date, + acc.account_currency, + ConstantColumn(0).as_("credit"), + ) + .where( + (si_payment.account == filters.account) + & (si.docstatus == 1) + & (si.posting_date <= filters.report_date) + & (si_payment.clearance_date.isnull() | (si_payment.clearance_date > filters.report_date)) + & (si.company == filters.company) + ) + .orderby(si.posting_date) + .orderby(si_payment.name, order=Order.desc) + ).run(as_dict=True) def get_amounts_not_reflected_in_system(filters): @@ -205,30 +279,66 @@ def get_amounts_not_reflected_in_system(filters): def get_amounts_not_reflected_in_system_for_bank_reconciliation_statement(filters): - je_amount = frappe.db.sql( - """ - select sum(jvd.debit_in_account_currency - jvd.credit_in_account_currency) - from `tabJournal Entry Account` jvd, `tabJournal Entry` jv - where jvd.parent = jv.name and jv.docstatus=1 and jvd.account=%(account)s - and jv.posting_date > %(report_date)s and jv.clearance_date <= %(report_date)s - and ifnull(jv.is_opening, 'No') = 'No' """, - filters, + je = frappe.qb.DocType("Journal Entry") + jea = frappe.qb.DocType("Journal Entry Account") + + je_amount = ( + frappe.qb.from_(jea) + .inner_join(je) + .on(jea.parent == je.name) + .select( + Sum(jea.debit_in_account_currency - jea.credit_in_account_currency).as_("amount"), + ) + .where( + (je.docstatus == 1) + & (jea.account == filters.account) + & (je.posting_date > filters.report_date) + & (je.clearance_date <= filters.report_date) + & (je.company == filters.company) + & ((je.is_opening.isnull()) | (je.is_opening == "No")) + ) + .run(as_dict=True) ) + je_amount = flt(je_amount[0].amount) if je_amount else 0.0 - je_amount = flt(je_amount[0][0]) if je_amount else 0.0 - - pe_amount = frappe.db.sql( - """ - select sum(if(paid_from=%(account)s, paid_amount, received_amount)) - from `tabPayment Entry` - where (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1 - and posting_date > %(report_date)s and clearance_date <= %(report_date)s""", - filters, + pe = frappe.qb.DocType("Payment Entry") + pe_amount = ( + frappe.qb.from_(pe) + .select( + Sum(Case().when(pe.paid_from == filters.account, pe.paid_amount).else_(pe.received_amount)).as_( + "amount" + ), + ) + .where( + ((pe.paid_from == filters.account) | (pe.paid_to == filters.account)) + & (pe.docstatus == 1) + & (pe.posting_date > filters.report_date) + & (pe.clearance_date <= filters.report_date) + & (pe.company == filters.company) + ) + .run(as_dict=True) ) + pe_amount = flt(pe_amount[0].amount) if pe_amount else 0.0 - pe_amount = flt(pe_amount[0][0]) if pe_amount else 0.0 + pi = frappe.qb.DocType("Purchase Invoice") + pi_amount = ( + frappe.qb.from_(pi) + .select( + Sum(pi.paid_amount).as_("amount"), + ) + .where( + (pi.docstatus == 1) + & (pi.is_paid == 1) + & (pi.cash_bank_account == filters.account) + & (pi.posting_date > filters.report_date) + & (pi.clearance_date <= filters.report_date) + & (pi.company == filters.company) + ) + ).run(as_dict=True) - return je_amount + pe_amount + pi_amount = flt(pi_amount[0].amount) if pi_amount else 0.0 + + return je_amount + pe_amount + pi_amount def get_balance_row(label, amount, account_currency): diff --git a/erpnext/accounts/report/bank_reconciliation_statement/test_bank_reconciliation_statement.py b/erpnext/accounts/report/bank_reconciliation_statement/test_bank_reconciliation_statement.py index 1fed7a43f7d..2e73a001107 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/test_bank_reconciliation_statement.py +++ b/erpnext/accounts/report/bank_reconciliation_statement/test_bank_reconciliation_statement.py @@ -2,15 +2,14 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import ( execute, ) -from erpnext.tests.utils import if_lending_app_installed +from erpnext.tests.utils import ERPNextTestSuite, if_lending_app_installed -class TestBankReconciliationStatement(IntegrationTestCase): +class TestBankReconciliationStatement(ERPNextTestSuite): @if_lending_app_installed def test_loan_entries_in_bank_reco_statement(self): from lending.loan_management.doctype.loan.test_loan import create_loan_accounts diff --git a/erpnext/accounts/report/consolidated_trial_balance/test_consolidated_trial_balance.py b/erpnext/accounts/report/consolidated_trial_balance/test_consolidated_trial_balance.py index d9d74f483b0..3166abf4061 100644 --- a/erpnext/accounts/report/consolidated_trial_balance/test_consolidated_trial_balance.py +++ b/erpnext/accounts/report/consolidated_trial_balance/test_consolidated_trial_balance.py @@ -3,36 +3,21 @@ import frappe from frappe import _ -from frappe.tests import IntegrationTestCase from frappe.utils import flt, today from erpnext.accounts.report.consolidated_trial_balance.consolidated_trial_balance import execute from erpnext.setup.utils import get_exchange_rate +from erpnext.tests.utils import ERPNextTestSuite class ForeignCurrencyTranslationReserveNotFoundError(frappe.ValidationError): pass -class TestConsolidatedTrialBalance(IntegrationTestCase): - @classmethod - def setUpClass(cls): - from erpnext.accounts.report.trial_balance.test_trial_balance import create_company +class TestConsolidatedTrialBalance(ERPNextTestSuite): + def setUp(self): from erpnext.accounts.utils import get_fiscal_year - # Group Company - create_company(company_name="Parent Group Company India", is_group=1) - - create_company(company_name="Child Company India", parent_company="Parent Group Company India") - - # Child Company with different currency - create_company( - company_name="Child Company US", - country="United States", - currency="USD", - parent_company="Parent Group Company India", - ) - create_journal_entry( company="Parent Group Company India", acc1="Marketing Expenses - PGCI", @@ -48,7 +33,7 @@ class TestConsolidatedTrialBalance(IntegrationTestCase): company="Child Company US", acc1="Marketing Expenses - CCU", acc2="Cash - CCU", amount=1000 ) - cls.fiscal_year = get_fiscal_year(today(), company="Parent Group Company India")[0] + self.fiscal_year = get_fiscal_year(today(), company="Parent Group Company India")[0] def test_single_company_report(self): filters = frappe._dict({"company": ["Parent Group Company India"], "fiscal_year": self.fiscal_year}) diff --git a/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py index f2ed334d16d..82425ab13b8 100644 --- a/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py @@ -1,5 +1,4 @@ import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import today from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry @@ -7,18 +6,16 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal from erpnext.accounts.report.customer_ledger_summary.customer_ledger_summary import execute from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.controllers.sales_and_purchase_return import make_return_doc +from erpnext.tests.utils import ERPNextTestSuite -class TestCustomerLedgerSummary(AccountsTestMixin, IntegrationTestCase): +class TestCustomerLedgerSummary(AccountsTestMixin, ERPNextTestSuite): def setUp(self): self.create_company() self.create_customer() self.create_item() self.clear_old_entries() - def tearDown(self): - frappe.db.rollback() - def create_sales_invoice(self, do_not_submit=False, **args): si = create_sales_invoice( item=self.item, diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py index fcd9075a30f..15e216e333a 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py @@ -1,6 +1,5 @@ import frappe from frappe import qb -from frappe.tests import IntegrationTestCase from frappe.utils import nowdate from erpnext.accounts.doctype.account.test_account import create_account @@ -11,9 +10,10 @@ from erpnext.accounts.report.deferred_revenue_and_expense.deferred_revenue_and_e ) from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.accounts.utils import get_fiscal_year +from erpnext.tests.utils import ERPNextTestSuite -class TestDeferredRevenueAndExpense(IntegrationTestCase, AccountsTestMixin): +class TestDeferredRevenueAndExpense(ERPNextTestSuite, AccountsTestMixin): maxDiff = None def clear_old_entries(self): @@ -67,10 +67,7 @@ class TestDeferredRevenueAndExpense(IntegrationTestCase, AccountsTestMixin): self.setup_deferred_accounts_and_items() self.clear_old_entries() - def tearDown(self): - frappe.db.rollback() - - @IntegrationTestCase.change_settings("Accounts Settings", {"book_deferred_entries_based_on": "Months"}) + @ERPNextTestSuite.change_settings("Accounts Settings", {"book_deferred_entries_based_on": "Months"}) def test_deferred_revenue(self): self.create_item("_Test Internet Subscription", 0, self.warehouse, self.company) item = frappe.get_doc("Item", self.item) @@ -137,7 +134,7 @@ class TestDeferredRevenueAndExpense(IntegrationTestCase, AccountsTestMixin): ] self.assertEqual(report.period_total, expected) - @IntegrationTestCase.change_settings("Accounts Settings", {"book_deferred_entries_based_on": "Months"}) + @ERPNextTestSuite.change_settings("Accounts Settings", {"book_deferred_entries_based_on": "Months"}) def test_deferred_expense(self): self.create_item("_Test Office Desk", 0, self.warehouse, self.company) item = frappe.get_doc("Item", self.item) @@ -207,7 +204,7 @@ class TestDeferredRevenueAndExpense(IntegrationTestCase, AccountsTestMixin): ] self.assertEqual(report.period_total, expected) - @IntegrationTestCase.change_settings("Accounts Settings", {"book_deferred_entries_based_on": "Months"}) + @ERPNextTestSuite.change_settings("Accounts Settings", {"book_deferred_entries_based_on": "Months"}) def test_zero_months(self): self.create_item("_Test Internet Subscription", 0, self.warehouse, self.company) item = frappe.get_doc("Item", self.item) @@ -272,7 +269,7 @@ class TestDeferredRevenueAndExpense(IntegrationTestCase, AccountsTestMixin): ] self.assertEqual(report.period_total, expected) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"book_deferred_entries_based_on": "Months", "book_deferred_entries_via_journal_entry": 0}, ) diff --git a/erpnext/accounts/report/general_and_payment_ledger_comparison/test_general_and_payment_ledger_comparison.py b/erpnext/accounts/report/general_and_payment_ledger_comparison/test_general_and_payment_ledger_comparison.py index 8c33bc90965..dd836a6891e 100644 --- a/erpnext/accounts/report/general_and_payment_ledger_comparison/test_general_and_payment_ledger_comparison.py +++ b/erpnext/accounts/report/general_and_payment_ledger_comparison/test_general_and_payment_ledger_comparison.py @@ -1,6 +1,5 @@ import frappe from frappe import qb -from frappe.tests import IntegrationTestCase from frappe.utils import add_days from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice @@ -8,16 +7,14 @@ from erpnext.accounts.report.general_and_payment_ledger_comparison.general_and_p execute, ) from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.tests.utils import ERPNextTestSuite -class TestGeneralAndPaymentLedger(IntegrationTestCase, AccountsTestMixin): +class TestGeneralAndPaymentLedger(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_company() self.cleanup() - def tearDown(self): - frappe.db.rollback() - def cleanup(self): doctypes = [] doctypes.append(qb.DocType("GL Entry")) diff --git a/erpnext/accounts/report/general_ledger/test_general_ledger.py b/erpnext/accounts/report/general_ledger/test_general_ledger.py index 24280d4d620..8f558467192 100644 --- a/erpnext/accounts/report/general_ledger/test_general_ledger.py +++ b/erpnext/accounts/report/general_ledger/test_general_ledger.py @@ -3,15 +3,15 @@ import frappe from frappe import qb -from frappe.tests import IntegrationTestCase from frappe.utils import flt, today from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.general_ledger.general_ledger import execute from erpnext.controllers.sales_and_purchase_return import make_return_doc +from erpnext.tests.utils import ERPNextTestSuite -class TestGeneralLedger(IntegrationTestCase): +class TestGeneralLedger(ERPNextTestSuite): def setUp(self): self.company = "_Test Company" self.clear_old_entries() diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 55ab95ac662..a53c2134e3f 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -649,7 +649,7 @@ class GrossProfitGenerator: new_row = row self.set_average_based_on_payment_term_portion(new_row, row, invoice_portion) else: - new_row.qty += flt(row.qty) + new_row.qty = flt((new_row.qty + row.qty), self.float_precision) self.set_average_based_on_payment_term_portion(new_row, row, invoice_portion, True) new_row = self.set_average_rate(new_row) @@ -659,11 +659,17 @@ class GrossProfitGenerator: if i == 0: new_row = row else: - new_row.qty += flt(row.qty) - new_row.buying_amount += flt(row.buying_amount, self.currency_precision) - new_row.base_amount += flt(row.base_amount, self.currency_precision) + new_row.qty = flt((new_row.qty + row.qty), self.float_precision) + new_row.buying_amount = flt( + (new_row.buying_amount + row.buying_amount), self.currency_precision + ) + new_row.base_amount = flt( + (new_row.base_amount + row.base_amount), self.currency_precision + ) if self.filters.get("group_by") == "Sales Person": - new_row.allocated_amount += flt(row.allocated_amount, self.currency_precision) + new_row.allocated_amount = flt( + (new_row.allocated_amount + row.allocated_amount), self.currency_precision + ) new_row = self.set_average_rate(new_row) self.grouped_data.append(new_row) diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py index bf52e127544..ac1ca89b5e4 100644 --- a/erpnext/accounts/report/gross_profit/test_gross_profit.py +++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py @@ -1,6 +1,5 @@ import frappe from frappe import qb -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, flt, get_first_day, get_last_day, nowdate from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note, make_sales_return @@ -10,9 +9,10 @@ from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.tests.utils import ERPNextTestSuite -class TestGrossProfit(IntegrationTestCase): +class TestGrossProfit(ERPNextTestSuite): def setUp(self): self.create_company() self.create_item() @@ -21,9 +21,6 @@ class TestGrossProfit(IntegrationTestCase): self.create_sales_invoice() self.clear_old_entries() - def tearDown(self): - frappe.db.rollback() - def create_company(self): company_name = "_Test Gross Profit" abbr = "_GP" @@ -391,6 +388,7 @@ class TestGrossProfit(IntegrationTestCase): report_output = {k: v for k, v in gp_entry[0].items() if k in expected_entry} self.assertEqual(report_output, expected_entry) + @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": True}) def test_crnote_against_invoice_with_multiple_instances_of_same_item(self): """ Item Qty for Sales Invoices with multiple instances of same item go in the -ve. Ideally, the credit noteshould cancel out the invoice items. diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 7166e5da691..0f0121f4e6c 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -32,6 +32,7 @@ def _execute(filters=None, additional_table_columns=None): item_list = get_items(filters, additional_table_columns) aii_account_map = get_aii_accounts() + default_taxes = {} if item_list: itemised_tax, tax_columns = get_tax_accounts( item_list, @@ -40,6 +41,9 @@ def _execute(filters=None, additional_table_columns=None): doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges", ) + for tax in tax_columns: + default_taxes[f"{tax}_rate"] = 0 + default_taxes[f"{tax}_amount"] = 0 po_pr_map = get_purchase_receipts_against_purchase_order(item_list) @@ -87,6 +91,7 @@ def _execute(filters=None, additional_table_columns=None): total_tax = 0 total_other_charges = 0 + row.update(default_taxes.copy()) for tax, details in itemised_tax.get(d.name, {}).items(): row.update( { diff --git a/erpnext/accounts/report/item_wise_purchase_register/test_item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/test_item_wise_purchase_register.py index 5a2fe7e23ce..eed45ea60bb 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/test_item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/test_item_wise_purchase_register.py @@ -1,21 +1,18 @@ import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import getdate, today from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.report.item_wise_purchase_register.item_wise_purchase_register import execute from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.tests.utils import ERPNextTestSuite -class TestItemWisePurchaseRegister(AccountsTestMixin, IntegrationTestCase): +class TestItemWisePurchaseRegister(AccountsTestMixin, ERPNextTestSuite): def setUp(self): self.create_company() self.create_supplier() self.create_item() - def tearDown(self): - frappe.db.rollback() - def create_purchase_invoice(self, do_not_submit=False): pi = make_purchase_invoice( item=self.item, 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 d77eb56525f..d849aa7e285 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 @@ -33,6 +33,10 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions= return columns, [], None, None, None, 0 itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) + default_taxes = {} + for tax in tax_columns: + default_taxes[f"{tax}_rate"] = 0 + default_taxes[f"{tax}_amount"] = 0 mode_of_payments = get_mode_of_payments(set(d.parent for d in item_list)) so_dn_map = get_delivery_notes_against_sales_order(item_list) @@ -90,6 +94,9 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions= total_tax = 0 total_other_charges = 0 + + row.update(default_taxes.copy()) + for tax, details in itemised_tax.get(d.name, {}).items(): row.update( { diff --git a/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py index 34ec398e7f3..689edeac1c4 100644 --- a/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py @@ -1,24 +1,23 @@ import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import getdate, today from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import execute from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.tests.utils import ERPNextTestSuite -class TestItemWiseSalesRegister(AccountsTestMixin, IntegrationTestCase): +class TestItemWiseSalesRegister(AccountsTestMixin, ERPNextTestSuite): def setUp(self): self.create_company() self.create_customer() self.create_item() - def tearDown(self): - frappe.db.rollback() - - def create_sales_invoice(self, do_not_submit=False): + def create_sales_invoice(self, item=None, taxes=None, do_not_submit=False): si = create_sales_invoice( - item=self.item, + item=item or self.item, + item_name=item or self.item, + description=item or self.item, company=self.company, customer=self.customer, debit_to=self.debit_to, @@ -29,6 +28,19 @@ class TestItemWiseSalesRegister(AccountsTestMixin, IntegrationTestCase): price_list_rate=100, do_not_save=1, ) + + for tax in taxes or []: + si.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": tax["account_head"], + "cost_center": self.cost_center, + "description": tax["description"], + "rate": tax["rate"], + }, + ) + si = si.save() if not do_not_submit: si = si.submit() @@ -62,3 +74,54 @@ class TestItemWiseSalesRegister(AccountsTestMixin, IntegrationTestCase): report_output = {k: v for k, v in report[1][0].items() if k in expected_result} self.assertDictEqual(report_output, expected_result) + + def test_grouped_report_handles_different_tax_descriptions(self): + self.create_item( + item_name="_Test Item Tax Description A", company="_Test Company", warehouse="Stores - _TC" + ) + first_item = self.item + self.create_item( + item_name="_Test Item Tax Description B", company="_Test Company", warehouse="Stores - _TC" + ) + second_item = self.item + + first_tax_description = "Tax Description A" + second_tax_description = "Tax Description B" + first_tax_amount_field = f"{frappe.scrub(first_tax_description)}_amount" + second_tax_amount_field = f"{frappe.scrub(second_tax_description)}_amount" + + self.create_sales_invoice( + item=first_item, + taxes=[ + { + "account_head": "_Test Account VAT - _TC", + "description": first_tax_description, + "rate": 5, + } + ], + ) + self.create_sales_invoice( + item=second_item, + taxes=[ + { + "account_head": "_Test Account Service Tax - _TC", + "description": second_tax_description, + "rate": 2, + } + ], + ) + + filters = frappe._dict( + { + "from_date": today(), + "to_date": today(), + "company": self.company, + "group_by": "Customer", + } + ) + _, data, _, _, _, _ = execute(filters) + + grand_total_row = next(row for row in data if row.get("bold") and row.get("item_code") == "Total") + + self.assertEqual(grand_total_row[first_tax_amount_field], 5.0) + self.assertEqual(grand_total_row[second_tax_amount_field], 2.0) diff --git a/erpnext/accounts/report/payment_ledger/test_payment_ledger.py b/erpnext/accounts/report/payment_ledger/test_payment_ledger.py index 1225fb70101..00206059672 100644 --- a/erpnext/accounts/report/payment_ledger/test_payment_ledger.py +++ b/erpnext/accounts/report/payment_ledger/test_payment_ledger.py @@ -1,13 +1,13 @@ import frappe from frappe import qb -from frappe.tests import IntegrationTestCase from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.payment_ledger.payment_ledger import execute +from erpnext.tests.utils import ERPNextTestSuite -class TestPaymentLedger(IntegrationTestCase): +class TestPaymentLedger(ERPNextTestSuite): def setUp(self): self.create_company() self.cleanup() diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js index e679f1f5728..5c9aaa62c94 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js @@ -39,7 +39,7 @@ frappe.query_reports[PL_REPORT_NAME]["filters"].push( fieldname: "accumulated_values", label: __("Accumulated Values"), fieldtype: "Check", - default: 1, + default: 0, }, { fieldname: "include_default_book_entries", diff --git a/erpnext/accounts/report/profit_and_loss_statement/test_profit_and_loss_statement.py b/erpnext/accounts/report/profit_and_loss_statement/test_profit_and_loss_statement.py index 4f1a08370a4..90d28033f19 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/test_profit_and_loss_statement.py +++ b/erpnext/accounts/report/profit_and_loss_statement/test_profit_and_loss_statement.py @@ -3,24 +3,21 @@ import frappe from frappe.desk.query_report import export_query -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, getdate, today from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.financial_statements import get_period_list from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import execute from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.tests.utils import ERPNextTestSuite -class TestProfitAndLossStatement(AccountsTestMixin, IntegrationTestCase): +class TestProfitAndLossStatement(AccountsTestMixin, ERPNextTestSuite): def setUp(self): self.create_company() self.create_customer() self.create_item() - def tearDown(self): - frappe.db.rollback() - def create_sales_invoice(self, qty=1, rate=150, no_payment_schedule=False, do_not_submit=False): frappe.set_user("Administrator") si = create_sales_invoice( diff --git a/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.json b/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.json index 2080f51933a..37556b6b4c2 100644 --- a/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.json +++ b/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.json @@ -7,10 +7,10 @@ "docstatus": 0, "doctype": "Report", "filters": [], - "idx": 3, + "idx": 4, "is_standard": "Yes", - "letterhead": null, - "modified": "2025-11-05 11:55:49.950442", + "letter_head": null, + "modified": "2026-03-13 17:35:39.703838", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Trends", diff --git a/erpnext/accounts/report/purchase_register/test_purchase_register.py b/erpnext/accounts/report/purchase_register/test_purchase_register.py index 6f61c44db02..400ee899fa1 100644 --- a/erpnext/accounts/report/purchase_register/test_purchase_register.py +++ b/erpnext/accounts/report/purchase_register/test_purchase_register.py @@ -2,13 +2,13 @@ # MIT License. See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import add_months, today from erpnext.accounts.report.purchase_register.purchase_register import execute +from erpnext.tests.utils import ERPNextTestSuite -class TestPurchaseRegister(IntegrationTestCase): +class TestPurchaseRegister(ERPNextTestSuite): def test_purchase_register(self): frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company 6'") frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 6'") diff --git a/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.json b/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.json index 1ed34ff4c36..93aa6567f0c 100644 --- a/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.json +++ b/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.json @@ -9,8 +9,8 @@ "filters": [], "idx": 3, "is_standard": "Yes", - "letterhead": null, - "modified": "2025-11-05 11:55:50.070651", + "letter_head": null, + "modified": "2026-03-13 17:36:13.725601", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Trends", 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 96e903b93ca..8ec9da89992 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 @@ -2,7 +2,6 @@ # License: GNU General Public License v3. See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import today from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry @@ -10,25 +9,10 @@ from erpnext.accounts.report.sales_payment_summary.sales_payment_summary import get_mode_of_payment_details, get_mode_of_payments, ) - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Sales Invoice"] +from erpnext.tests.utils import ERPNextTestSuite -class TestSalesPaymentSummary(IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - create_records() - pes = frappe.get_all("Payment Entry") - jes = frappe.get_all("Journal Entry") - sis = frappe.get_all("Sales Invoice") - for pe in pes: - frappe.db.set_value("Payment Entry", pe.name, "docstatus", 2) - for je in jes: - frappe.db.set_value("Journal Entry", je.name, "docstatus", 2) - for si in sis: - frappe.db.set_value("Sales Invoice", si.name, "docstatus", 2) - +class TestSalesPaymentSummary(ERPNextTestSuite): def test_get_mode_of_payments(self): filters = get_filters() @@ -94,6 +78,7 @@ class TestSalesPaymentSummary(IntegrationTestCase): mopd = get_mode_of_payment_details(filters) mopd_values = next(iter(mopd.values())) + cc_init_amount = 0 for mopd_value in mopd_values: if mopd_value[0] == "Credit Card": cc_init_amount = mopd_value[1] @@ -110,6 +95,7 @@ class TestSalesPaymentSummary(IntegrationTestCase): mopd = get_mode_of_payment_details(filters) mopd_values = next(iter(mopd.values())) + cc_final_amount = 0 for mopd_value in mopd_values: if mopd_value[0] == "Credit Card": cc_final_amount = mopd_value[1] @@ -147,41 +133,3 @@ def create_sales_invoice_record(qty=1): ], } ) - - -def create_records(): - if frappe.db.exists("Customer", "Prestiga-Biz"): - return - - # customer - frappe.get_doc( - { - "customer_group": "_Test Customer Group", - "customer_name": "Prestiga-Biz", - "customer_type": "Company", - "doctype": "Customer", - "territory": "_Test Territory", - } - ).insert() - - # item - item = frappe.get_doc( - { - "doctype": "Item", - "item_code": "Consulting", - "item_name": "Consulting", - "item_group": "All Item Groups", - "company": "_Test Company", - "is_stock_item": 0, - } - ).insert() - - # item price - frappe.get_doc( - { - "doctype": "Item Price", - "price_list": "Standard Selling", - "item_code": item.item_code, - "price_list_rate": 10000, - } - ).insert() diff --git a/erpnext/accounts/report/sales_register/test_sales_register.py b/erpnext/accounts/report/sales_register/test_sales_register.py index 50f12e52e33..ca284cc636c 100644 --- a/erpnext/accounts/report/sales_register/test_sales_register.py +++ b/erpnext/accounts/report/sales_register/test_sales_register.py @@ -1,22 +1,19 @@ import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import getdate, today from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.sales_register.sales_register import execute from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.tests.utils import ERPNextTestSuite -class TestItemWiseSalesRegister(AccountsTestMixin, IntegrationTestCase): +class TestItemWiseSalesRegister(AccountsTestMixin, ERPNextTestSuite): def setUp(self): self.create_company() self.create_customer() self.create_item() self.create_child_cost_center() - def tearDown(self): - frappe.db.rollback() - def create_child_cost_center(self): cc_name = "South Wing" if frappe.db.exists("Cost Center", cc_name): diff --git a/erpnext/accounts/report/supplier_ledger_summary/test_supplier_ledger_summary.py b/erpnext/accounts/report/supplier_ledger_summary/test_supplier_ledger_summary.py index 376495b5cc9..418d86abb66 100644 --- a/erpnext/accounts/report/supplier_ledger_summary/test_supplier_ledger_summary.py +++ b/erpnext/accounts/report/supplier_ledger_summary/test_supplier_ledger_summary.py @@ -1,22 +1,19 @@ import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import today from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.report.supplier_ledger_summary.supplier_ledger_summary import execute from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.tests.utils import ERPNextTestSuite -class TestSupplierLedgerSummary(AccountsTestMixin, IntegrationTestCase): +class TestSupplierLedgerSummary(AccountsTestMixin, ERPNextTestSuite): def setUp(self): self.create_company() self.create_supplier() self.create_item() self.clear_old_entries() - def tearDown(self): - frappe.db.rollback() - def create_purchase_invoice(self, do_not_submit=False): frappe.set_user("Administrator") pi = make_purchase_invoice( diff --git a/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py index 30a5df3fcb2..a2a732e8de6 100644 --- a/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py @@ -2,7 +2,6 @@ # MIT License. See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import add_to_date, today from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry @@ -14,9 +13,10 @@ from erpnext.accounts.doctype.tax_withholding_category.test_tax_withholding_cate from erpnext.accounts.report.tax_withholding_details.tax_withholding_details import execute from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.accounts.utils import get_fiscal_year +from erpnext.tests.utils import ERPNextTestSuite -class TestTaxWithholdingDetails(AccountsTestMixin, IntegrationTestCase): +class TestTaxWithholdingDetails(AccountsTestMixin, ERPNextTestSuite): def setUp(self): self.create_company() self.clear_old_entries() @@ -126,9 +126,6 @@ class TestTaxWithholdingDetails(AccountsTestMixin, IntegrationTestCase): ) self.assertSequenceEqual(voucher_actual_values, voucher_expected_values) - def tearDown(self): - self.clear_old_entries() - def create_tax_accounts(): account_names = ["TCS", "TDS"] diff --git a/erpnext/accounts/report/trial_balance/test_trial_balance.py b/erpnext/accounts/report/trial_balance/test_trial_balance.py index a7922b716e7..c37f9d5a46a 100644 --- a/erpnext/accounts/report/trial_balance/test_trial_balance.py +++ b/erpnext/accounts/report/trial_balance/test_trial_balance.py @@ -2,19 +2,18 @@ # MIT License. See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import today from erpnext.accounts.report.trial_balance.trial_balance import execute +from erpnext.tests.utils import ERPNextTestSuite -class TestTrialBalance(IntegrationTestCase): +class TestTrialBalance(ERPNextTestSuite): def setUp(self): from erpnext.accounts.doctype.account.test_account import create_account from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.utils import get_fiscal_year - self.company = create_company() create_cost_center( cost_center_name="Test Cost Center", company="Trial Balance Company", @@ -26,7 +25,16 @@ class TestTrialBalance(IntegrationTestCase): parent_account="Temporary Accounts - TBC", ) self.fiscal_year = get_fiscal_year(today(), company="Trial Balance Company")[0] - create_accounting_dimension() + dim = frappe.get_doc("Accounting Dimension", "Branch") + dim.append( + "dimension_defaults", + { + "company": "Trial Balance Company", + "automatically_post_balancing_accounting_entry": 1, + "offsetting_account": "Offsetting - TBC", + }, + ) + dim.save() def test_offsetting_entries_for_accounting_dimensions(self): """ @@ -45,7 +53,7 @@ class TestTrialBalance(IntegrationTestCase): branch2.insert(ignore_if_duplicate=True) si = create_sales_invoice( - company=self.company, + company="Trial Balance Company", debit_to="Debtors - TBC", cost_center="Test Cost Center - TBC", income_account="Sales - TBC", @@ -57,64 +65,7 @@ class TestTrialBalance(IntegrationTestCase): si.submit() filters = frappe._dict( - {"company": self.company, "fiscal_year": self.fiscal_year, "branch": ["Location 1"]} + {"company": "Trial Balance Company", "fiscal_year": self.fiscal_year, "branch": ["Location 1"]} ) total_row = execute(filters)[1][-1] self.assertEqual(total_row["debit"], total_row["credit"]) - - def tearDown(self): - clear_dimension_defaults("Branch") - disable_dimension() - - -def create_company(**args): - args = frappe._dict(args) - company = frappe.get_doc( - { - "doctype": "Company", - "company_name": args.company_name or "Trial Balance Company", - "country": args.country or "India", - "default_currency": args.currency or "INR", - "parent_company": args.get("parent_company"), - "is_group": args.get("is_group"), - } - ) - company.insert(ignore_if_duplicate=True) - return company.name - - -def create_accounting_dimension(**args): - args = frappe._dict(args) - document_type = args.document_type or "Branch" - if frappe.db.exists("Accounting Dimension", document_type): - accounting_dimension = frappe.get_doc("Accounting Dimension", document_type) - accounting_dimension.disabled = 0 - else: - accounting_dimension = frappe.new_doc("Accounting Dimension") - accounting_dimension.document_type = document_type - accounting_dimension.insert() - - accounting_dimension.set("dimension_defaults", []) - accounting_dimension.append( - "dimension_defaults", - { - "company": args.company or "Trial Balance Company", - "automatically_post_balancing_accounting_entry": 1, - "offsetting_account": args.offsetting_account or "Offsetting - TBC", - }, - ) - accounting_dimension.save() - - -def disable_dimension(**args): - args = frappe._dict(args) - document_type = args.document_type or "Branch" - dimension = frappe.get_doc("Accounting Dimension", document_type) - dimension.disabled = 1 - dimension.save() - - -def clear_dimension_defaults(dimension_name): - accounting_dimension = frappe.get_doc("Accounting Dimension", dimension_name) - accounting_dimension.dimension_defaults = [] - accounting_dimension.save() diff --git a/erpnext/accounts/test/test_reports.py b/erpnext/accounts/test/test_reports.py index e702c135b04..8c27452264c 100644 --- a/erpnext/accounts/test/test_reports.py +++ b/erpnext/accounts/test/test_reports.py @@ -1,6 +1,4 @@ -from frappe.tests import IntegrationTestCase - -from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report +from erpnext.tests.utils import ERPNextTestSuite, ReportFilters, ReportName, execute_script_report DEFAULT_FILTERS = { "company": "_Test Company", @@ -34,7 +32,7 @@ REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [ OPTIONAL_FILTERS = {} -class TestReports(IntegrationTestCase): +class TestReports(ERPNextTestSuite): def test_execute_all_accounts_reports(self): """Test that all script report in stock modules are executable with supported filters""" for report, filter in REPORT_FILTER_TEST_CASES: diff --git a/erpnext/accounts/test/test_utils.py b/erpnext/accounts/test/test_utils.py index 6aa1553a656..b4f136142eb 100644 --- a/erpnext/accounts/test/test_utils.py +++ b/erpnext/accounts/test/test_utils.py @@ -1,6 +1,4 @@ import frappe -from frappe.test_runner import make_test_objects -from frappe.tests import IntegrationTestCase from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice @@ -14,18 +12,10 @@ from erpnext.accounts.utils import ( from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.tests.utils import ERPNextTestSuite -class TestUtils(IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - make_test_objects("Address", ADDRESS_RECORDS) - - @classmethod - def tearDownClass(cls): - frappe.db.rollback() - +class TestUtils(ERPNextTestSuite): def test_get_party_shipping_address(self): address = get_party_shipping_address("Customer", "_Test Customer 1") self.assertEqual(address, "_Test Billing Address 2 Title-Billing") @@ -160,45 +150,3 @@ class TestUtils(IntegrationTestCase): self.assertEqual(get_zero_cutoff(None), 0.005) self.assertEqual(get_zero_cutoff("EUR"), 0.005) self.assertEqual(get_zero_cutoff("BHD"), 0.0005) - - -ADDRESS_RECORDS = [ - { - "doctype": "Address", - "address_type": "Billing", - "address_line1": "Address line 1", - "address_title": "_Test Billing Address Title", - "city": "Lagos", - "country": "Nigeria", - "links": [{"link_doctype": "Customer", "link_name": "_Test Customer 2", "doctype": "Dynamic Link"}], - }, - { - "doctype": "Address", - "address_type": "Shipping", - "address_line1": "Address line 2", - "address_title": "_Test Shipping Address 1 Title", - "city": "Lagos", - "country": "Nigeria", - "links": [{"link_doctype": "Customer", "link_name": "_Test Customer 2", "doctype": "Dynamic Link"}], - }, - { - "doctype": "Address", - "address_type": "Shipping", - "address_line1": "Address line 3", - "address_title": "_Test Shipping Address 2 Title", - "city": "Lagos", - "country": "Nigeria", - "is_shipping_address": "1", - "links": [{"link_doctype": "Customer", "link_name": "_Test Customer 2", "doctype": "Dynamic Link"}], - }, - { - "doctype": "Address", - "address_type": "Billing", - "address_line1": "Address line 4", - "address_title": "_Test Billing Address 2 Title", - "city": "Lagos", - "country": "Nigeria", - "is_shipping_address": "1", - "links": [{"link_doctype": "Customer", "link_name": "_Test Customer 1", "doctype": "Dynamic Link"}], - }, -] diff --git a/erpnext/accounts/test_party.py b/erpnext/accounts/test_party.py index 986a5b5d680..0f477103da4 100644 --- a/erpnext/accounts/test_party.py +++ b/erpnext/accounts/test_party.py @@ -1,10 +1,10 @@ import frappe -from frappe.tests import IntegrationTestCase from erpnext.accounts.party import get_default_price_list +from erpnext.tests.utils import ERPNextTestSuite -class PartyTestCase(IntegrationTestCase): +class PartyTestCase(ERPNextTestSuite): def test_get_default_price_list_should_return_none_for_invalid_group(self): customer = frappe.get_doc( { diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 6b71228841f..d0707438745 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1414,6 +1414,78 @@ def get_account_balances( return accounts +@frappe.whitelist() +def get_account_balances_coa(company: str, include_default_fb_balances: bool = False): + company_currency = frappe.get_cached_value("Company", company, "default_currency") + + Account = DocType("Account") + account_list = ( + frappe.qb.from_(Account) + .select(Account.name, Account.parent_account, Account.account_currency) + .where(Account.company == company) + .orderby(Account.lft) + .run(as_dict=True) + ) + + account_balances_cc = {account.get("name"): 0 for account in account_list} + + account_balances_ac = {account.get("name"): 0 for account in account_list} + + GLEntry = DocType("GL Entry") + precision = get_currency_precision() + get_ledger_balances_query = ( + frappe.qb.from_(GLEntry) + .select( + GLEntry.account, + (Sum(Round(GLEntry.debit, precision)) - Sum(Round(GLEntry.credit, precision))).as_("balance"), + ( + Sum(Round(GLEntry.debit_in_account_currency, precision)) + - Sum(Round(GLEntry.credit_in_account_currency, precision)) + ).as_("balance_in_account_currency"), + ) + .groupby(GLEntry.account) + ) + + condition_list = [GLEntry.company == company, GLEntry.is_cancelled == 0] + + default_finance_book = None + + if include_default_fb_balances: + default_finance_book = frappe.get_cached_value("Company", company, "default_finance_book") + + if default_finance_book: + condition_list.append( + (GLEntry.finance_book == default_finance_book) | (GLEntry.finance_book.isnull()) + ) + + for condition in condition_list: + get_ledger_balances_query = get_ledger_balances_query.where(condition) + + ledger_balances = get_ledger_balances_query.run(as_dict=True) + + for ledger_entry in ledger_balances: + account_balances_cc[ledger_entry.get("account")] = ledger_entry.get("balance") + account_balances_ac[ledger_entry.get("account")] = ledger_entry.get("balance_in_account_currency") + + for account in reversed(account_list): + parent = account.get("parent_account") + if parent: + account_balances_cc[parent] += account_balances_cc.get(account.get("name")) + + accounts_data = [ + { + "value": account.get("name"), + "company_currency": company_currency, + "balance": account_balances_cc.get(account.get("name")), + "account_currency": account.get("account_currency"), + "balance_in_account_currency": account_balances_ac.get(account.get("name")), + } + for account in account_list + ] + + return accounts_data + + def create_payment_gateway_account(gateway, payment_channel="Email", company=None): from erpnext.setup.setup_wizard.operations.install_fixtures import create_bank_account @@ -1548,7 +1620,7 @@ def parse_naming_series_variable(doc, variable): getdate(doc.get("posting_date") or doc.get("transaction_date") or doc.get("posting_datetime")) or now_datetime() ) - if frappe.get_single_value("Global Defaults", "use_posting_datetime_for_naming_documents") + if doc and frappe.get_single_value("Global Defaults", "use_posting_datetime_for_naming_documents") else now_datetime() ) return date.strftime(data[variable]) if variable in data else determine_consecutive_week_number(date) @@ -2070,6 +2142,7 @@ def create_payment_ledger_entry( if is_immutable_ledger_enabled(): ple.delinked = 0 ple.posting_date = frappe.form_dict.get("posting_date") or getdate() + ple.flags.ignore_links = True ple.flags.ignore_permissions = 1 ple.flags.adv_adj = adv_adj diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index bbbdf2564eb..1b333d51c17 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -36,6 +36,7 @@ frappe.ui.form.on("Asset", { }, company: function (frm) { + frm.trigger("set_dynamic_labels"); erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); }, @@ -87,6 +88,8 @@ frappe.ui.form.on("Asset", { }, refresh: async function (frm) { + frm.trigger("set_dynamic_labels"); + frappe.ui.form.trigger("Asset", "asset_type"); frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1); @@ -227,6 +230,10 @@ frappe.ui.form.on("Asset", { } }, + set_dynamic_labels: function (frm) { + frm.set_currency_labels(["net_purchase_amount"], erpnext.get_currency(frm.doc.company)); + }, + should_show_accounting_ledger: async function (frm) { if (["Capitalized"].includes(frm.doc.status)) { return false; diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 32f5ce9cf50..c048e972882 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -9,13 +9,13 @@ "engine": "InnoDB", "field_order": [ "naming_series", - "company", "item_code", "item_name", "asset_name", + "location", "image", "column_break_3", - "location", + "company", "asset_category", "asset_type", "maintenance_required", @@ -169,6 +169,8 @@ { "fieldname": "company", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Company", "options": "Company", "remember_last_selected_value": 1, @@ -531,7 +533,7 @@ "fieldtype": "Currency", "label": "Net Purchase Amount", "mandatory_depends_on": "eval:(doc.asset_type != \"Composite Asset\" || doc.docstatus==1)", - "options": "Company:company:default_currency", + "options": "currency", "read_only_depends_on": "eval: doc.asset_type == \"Composite Asset\"" }, { @@ -624,7 +626,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2026-02-05 12:42:45.350216", + "modified": "2026-03-12 16:07:39.543227", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 50b7ddbfd90..036cb8ad669 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -62,7 +62,9 @@ def book_depreciation_entries(date): accounting_dimensions, ) - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() + except Exception as e: frappe.db.rollback() failed_assets.append(asset_name) @@ -72,7 +74,8 @@ def book_depreciation_entries(date): if failed_assets: set_depr_entry_posting_status_for_failed_assets(failed_assets) notify_depr_entry_posting_error(failed_assets, error_logs) - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() def get_depreciable_assets_data(date): diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 4da21e3942b..91ab627b113 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -2,7 +2,6 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import ( add_days, add_months, @@ -36,21 +35,14 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( make_purchase_invoice as make_invoice, ) from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt +from erpnext.tests.utils import ERPNextTestSuite -class AssetSetup(IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() +class AssetSetup(ERPNextTestSuite): + def setUp(self): set_depreciation_settings_in_company() - create_asset_data() enable_cwip_accounting("Computers") make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") - frappe.db.sql("delete from `tabTax Rule`") - - @classmethod - def tearDownClass(cls): - frappe.db.rollback() class TestAsset(AssetSetup): @@ -911,18 +903,9 @@ class TestAsset(AssetSetup): class TestDepreciationMethods(AssetSetup): - @classmethod - def setUpClass(cls): - super().setUpClass() - - cls._old_float_precision = frappe.db.get_single_value("System Settings", "float_precision") + def setUp(self): frappe.db.set_single_value("System Settings", "float_precision", 2) - @classmethod - def tearDownClass(cls): - frappe.db.set_single_value("System Settings", "float_precision", cls._old_float_precision) - super().tearDownClass() - def test_schedule_for_straight_line_method(self): asset = create_asset( calculate_depreciation=1, @@ -1991,31 +1974,9 @@ def get_gl_entries(doctype, docname): ) -def create_asset_data(): - if not frappe.db.exists("Asset Category", "Computers"): - create_asset_category() - - if not frappe.db.exists("Item", "Macbook Pro"): - create_fixed_asset_item() - - if not frappe.db.exists("Location", "Test Location"): - frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert() - - if not frappe.db.exists("Finance Book", "Test Finance Book 1"): - frappe.get_doc({"doctype": "Finance Book", "finance_book_name": "Test Finance Book 1"}).insert() - - if not frappe.db.exists("Finance Book", "Test Finance Book 2"): - frappe.get_doc({"doctype": "Finance Book", "finance_book_name": "Test Finance Book 2"}).insert() - - if not frappe.db.exists("Finance Book", "Test Finance Book 3"): - frappe.get_doc({"doctype": "Finance Book", "finance_book_name": "Test Finance Book 3"}).insert() - - def create_asset(**args): args = frappe._dict(args) - create_asset_data() - asset = frappe.get_doc( { "doctype": "Asset", diff --git a/erpnext/assets/doctype/asset_activity/test_asset_activity.py b/erpnext/assets/doctype/asset_activity/test_asset_activity.py index 1c7d472c88b..0344c7d984b 100644 --- a/erpnext/assets/doctype/asset_activity/test_asset_activity.py +++ b/erpnext/assets/doctype/asset_activity/test_asset_activity.py @@ -2,8 +2,10 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase -class TestAssetActivity(IntegrationTestCase): +from erpnext.tests.utils import ERPNextTestSuite + + +class TestAssetActivity(ERPNextTestSuite): pass diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index e75a5717ca2..e4724dadeee 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -2,7 +2,6 @@ # For license information, please see license.txt import json -from typing import Any import frappe diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index 3509da79b20..aa50083a9d5 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -2,13 +2,11 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import cint, flt, now_datetime from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries from erpnext.assets.doctype.asset.test_asset import ( create_asset, - create_asset_data, create_fixed_asset_item, set_depreciation_settings_in_company, ) @@ -17,12 +15,12 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import ( make_serial_batch_bundle, ) +from erpnext.tests.utils import ERPNextTestSuite -class TestAssetCapitalization(IntegrationTestCase): +class TestAssetCapitalization(ERPNextTestSuite): def setUp(self): set_depreciation_settings_in_company() - create_asset_data() create_asset_capitalization_data() frappe.db.sql("delete from `tabTax Rule`") @@ -378,6 +376,7 @@ class TestAssetCapitalization(IntegrationTestCase): "asset_type": "Composite Component", "purchase_date": pr.posting_date, "available_for_use_date": pr.posting_date, + "location": "Test Location", } ) consumed_asset_doc.save() diff --git a/erpnext/assets/doctype/asset_category/test_asset_category.py b/erpnext/assets/doctype/asset_category/test_asset_category.py index 995185fbf95..b12387bb2c0 100644 --- a/erpnext/assets/doctype/asset_category/test_asset_category.py +++ b/erpnext/assets/doctype/asset_category/test_asset_category.py @@ -2,12 +2,12 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from erpnext.assets.doctype.asset.test_asset import create_asset +from erpnext.tests.utils import ERPNextTestSuite -class TestAssetCategory(IntegrationTestCase): +class TestAssetCategory(ERPNextTestSuite): def test_mandatory_fields(self): asset_category = frappe.new_doc("Asset Category") asset_category.asset_category_name = "Computers" diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py index b7e96f935d1..449f6091d01 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py @@ -2,14 +2,13 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import cstr, flt, getdate from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.assets.doctype.asset.depreciation import ( post_depreciation_entries, ) -from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data +from erpnext.assets.doctype.asset.test_asset import create_asset from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( get_asset_depr_schedule_doc, get_depr_schedule, @@ -18,12 +17,10 @@ from erpnext.assets.doctype.asset_repair.test_asset_repair import create_asset_r from erpnext.assets.doctype.asset_value_adjustment.test_asset_value_adjustment import ( make_asset_value_adjustment, ) +from erpnext.tests.utils import ERPNextTestSuite -class TestAssetDepreciationSchedule(IntegrationTestCase): - def setUp(self): - create_asset_data() - +class TestAssetDepreciationSchedule(ERPNextTestSuite): def test_throw_error_if_another_asset_depr_schedule_exist(self): asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, submit=1) @@ -815,7 +812,6 @@ class TestAssetDepreciationSchedule(IntegrationTestCase): def test_depreciation_on_return_of_sold_asset(self): from erpnext.controllers.sales_and_purchase_return import make_return_doc - create_asset_data() asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, submit=1) post_depreciation_entries(getdate("2021-09-30")) diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py index 8b22991f7b0..068c6cff9c9 100644 --- a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py @@ -2,14 +2,14 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, get_last_day, nowdate from erpnext.assets.doctype.asset_maintenance.asset_maintenance import calculate_next_due_date from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt +from erpnext.tests.utils import ERPNextTestSuite -class TestAssetMaintenance(IntegrationTestCase): +class TestAssetMaintenance(ERPNextTestSuite): def setUp(self): set_depreciation_settings_in_company() self.pr = make_purchase_receipt( diff --git a/erpnext/assets/doctype/asset_maintenance/test_records.json b/erpnext/assets/doctype/asset_maintenance/test_records.json deleted file mode 100644 index 8306fad6cba..00000000000 --- a/erpnext/assets/doctype/asset_maintenance/test_records.json +++ /dev/null @@ -1,68 +0,0 @@ -[ - { - "doctype": "Asset Category", - "asset_category_name": "Equipment", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 3, - "accounts": [ - { - "company_name": "_Test Company", - "fixed_asset_account": "_Test Fixed Asset - _TC", - "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC", - "depreciation_expense_account": "_Test Depreciations - _TC" - } - ] - }, - { - "doctype": "Location", - "location_name": "Test Location" - }, - { - "doctype": "Role", - "role_name": "Technician" - }, - { - "doctype": "User", - "email": "marcus@abc.com", - "first_name": "marcus@abc.com", - "new_password": "password", - "roles": [{"doctype": "Has Role", "role": "Technician"}] - }, - { - "doctype": "User", - "email": "thalia@abc.com", - "first_name": "thalia@abc.com", - "new_password": "password", - "roles": [{"doctype": "Has Role", "role": "Technician"}] - }, - { - "doctype": "User", - "email": "mathias@abc.com", - "first_name": "mathias@abc.com", - "new_password": "password", - "roles": [{"doctype": "Has Role", "role": "Technician"}] - }, - { - "doctype": "Asset Maintenance Team", - "maintenance_manager": "marcus@abc.com", - "maintenance_team_name": "Team Awesome", - "company": "_Test Company", - "maintenance_team_members": [ - {"team_member": "marcus@abc.com", "full_name": "marcus@abc.com", "maintenance_role": "Technician"}, - {"team_member": "thalia@abc.com", "full_name": "thalia@abc.com", "maintenance_role": "Technician"}, - {"team_member": "mathias@abc.com", "full_name": "mathias@abc.com", "maintenance_role": "Technician"} - ] - }, - { - "doctype": "Item", - "item_code": "Photocopier", - "item_name": "Photocopier", - "item_group": "All Item Groups", - "company": "_Test Company", - "is_fixed_asset": 1, - "is_stock_item": 0, - "asset_category": "Equipment", - "auto_create_assets": 1, - "asset_naming_series": "ABC.###" - } -] diff --git a/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.py b/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.py index 7842e41bd2f..3e9f210892a 100644 --- a/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.py +++ b/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.py @@ -1,8 +1,8 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestAssetMaintenanceLog(IntegrationTestCase): +class TestAssetMaintenanceLog(ERPNextTestSuite): pass diff --git a/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.py b/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.py index 1d0c51f3f09..ff75c3c071f 100644 --- a/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.py +++ b/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.py @@ -1,8 +1,8 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestAssetMaintenanceTeam(IntegrationTestCase): +class TestAssetMaintenanceTeam(ERPNextTestSuite): pass diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.json b/erpnext/assets/doctype/asset_movement/asset_movement.json index a656acf1265..aedfc149f64 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.json +++ b/erpnext/assets/doctype/asset_movement/asset_movement.json @@ -32,6 +32,7 @@ { "fieldname": "purpose", "fieldtype": "Select", + "in_list_view": 1, "label": "Purpose", "options": "\nIssue\nReceipt\nTransfer\nTransfer and Issue", "reqd": 1 @@ -97,7 +98,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-05-30 17:01:55.864353", + "modified": "2026-03-09 17:19:02.087333", "modified_by": "Administrator", "module": "Assets", "name": "Asset Movement", diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py index 33eaa7e7e4c..533a1866e76 100644 --- a/erpnext/assets/doctype/asset_movement/test_asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py @@ -2,21 +2,19 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, now -from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data +from erpnext.assets.doctype.asset.test_asset import create_asset from erpnext.setup.doctype.employee.test_employee import make_employee from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt +from erpnext.tests.utils import ERPNextTestSuite -class TestAssetMovement(IntegrationTestCase): +class TestAssetMovement(ERPNextTestSuite): def setUp(self): frappe.db.set_value( "Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC" ) - create_asset_data() - make_location() def test_movement(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") @@ -40,10 +38,6 @@ class TestAssetMovement(IntegrationTestCase): if asset.docstatus == 0: asset.submit() - # check asset movement is created - if not frappe.db.exists("Location", "Test Location 2"): - frappe.get_doc({"doctype": "Location", "location_name": "Test Location 2"}).insert() - create_asset_movement( purpose="Transfer", company=asset.company, @@ -122,9 +116,6 @@ class TestAssetMovement(IntegrationTestCase): 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) @@ -150,9 +141,6 @@ class TestAssetMovement(IntegrationTestCase): asset = create_asset(item_code="Macbook Pro", do_not_save=1) asset.save().submit() - if not frappe.db.exists("Location", "Test Location 2"): - frappe.get_doc({"doctype": "Location", "location_name": "Test Location 2"}).insert() - asset_creation_date = frappe.db.get_value( "Asset Movement", [["Asset Movement Item", "asset", "=", asset.name], ["docstatus", "=", 1]], @@ -197,9 +185,3 @@ def create_asset_movement(**args): movement.submit() return movement - - -def make_location(): - for location in ["Pune", "Mumbai", "Nagpur"]: - if not frappe.db.exists("Location", location): - frappe.get_doc({"doctype": "Location", "location_name": location}).insert(ignore_permissions=True) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js index bdbf60f653e..4d9ef28ceae 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.js +++ b/erpnext/assets/doctype/asset_repair/asset_repair.js @@ -86,6 +86,26 @@ frappe.ui.form.on("Asset Repair", { } }, + show_general_ledger: function (frm) { + if (frm.doc.docstatus > 0) { + frm.add_custom_button( + __("Accounting Ledger"), + function () { + frappe.route_options = { + voucher_no: frm.doc.name, + from_date: frm.doc.completion_date, + to_date: moment(frm.doc.modified).format("YYYY-MM-DD"), + company: frm.doc.company, + categorize_by: "", + show_cancelled_entries: frm.doc.docstatus === 2, + }; + frappe.set_route("query-report", "General Ledger"); + }, + __("View") + ); + } + }, + repair_status: (frm) => { if (frm.doc.completion_date && frm.doc.repair_status == "Completed") { frappe.call({ @@ -164,26 +184,6 @@ frappe.ui.form.on("Asset Repair Purchase Invoice", { }, }); }, - - show_general_ledger: (frm) => { - if (frm.doc.docstatus > 0) { - frm.add_custom_button( - __("Accounting Ledger"), - function () { - frappe.route_options = { - voucher_no: frm.doc.name, - from_date: frm.doc.posting_date, - to_date: moment(frm.doc.modified).format("YYYY-MM-DD"), - company: frm.doc.company, - categorize_by: "", - show_cancelled_entries: frm.doc.docstatus === 2, - }; - frappe.set_route("query-report", "General Ledger"); - }, - __("View") - ); - } - }, }); frappe.ui.form.on("Asset Repair Consumed Item", { diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 012a3895768..717435e4caa 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -4,7 +4,6 @@ import frappe from frappe import qb from frappe.query_builder.functions import Sum -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, add_months, flt, get_first_day, nowdate, nowtime, today from erpnext.assets.doctype.asset.asset import ( @@ -14,7 +13,6 @@ from erpnext.assets.doctype.asset.asset import ( ) from erpnext.assets.doctype.asset.test_asset import ( create_asset, - create_asset_data, set_depreciation_settings_in_company, ) from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( @@ -25,16 +23,14 @@ from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle get_serial_nos_from_bundle, make_serial_batch_bundle, ) +from erpnext.tests.utils import ERPNextTestSuite -class TestAssetRepair(IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() +class TestAssetRepair(ERPNextTestSuite): + def setUp(self): + self.load_test_records("Stock Entry") set_depreciation_settings_in_company() - create_asset_data() create_item("_Test Stock Item") - frappe.db.sql("delete from `tabTax Rule`") def test_asset_status(self): date = nowdate() diff --git a/erpnext/assets/doctype/asset_shift_allocation/test_asset_shift_allocation.py b/erpnext/assets/doctype/asset_shift_allocation/test_asset_shift_allocation.py index 2764a2cc373..5b1cdaf0277 100644 --- a/erpnext/assets/doctype/asset_shift_allocation/test_asset_shift_allocation.py +++ b/erpnext/assets/doctype/asset_shift_allocation/test_asset_shift_allocation.py @@ -2,25 +2,19 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import cstr from erpnext.assets.doctype.asset.test_asset import create_asset from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( get_depr_schedule, ) +from erpnext.tests.utils import ERPNextTestSuite -class TestAssetShiftAllocation(IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() +class TestAssetShiftAllocation(ERPNextTestSuite): + def setUp(self): create_asset_shift_factors() - @classmethod - def tearDownClass(cls): - frappe.db.rollback() - def test_asset_shift_allocation(self): asset = create_asset( calculate_depreciation=1, diff --git a/erpnext/assets/doctype/asset_shift_factor/test_asset_shift_factor.py b/erpnext/assets/doctype/asset_shift_factor/test_asset_shift_factor.py index 12d68b02c5f..7470e55b6fb 100644 --- a/erpnext/assets/doctype/asset_shift_factor/test_asset_shift_factor.py +++ b/erpnext/assets/doctype/asset_shift_factor/test_asset_shift_factor.py @@ -2,8 +2,10 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase -class TestAssetShiftFactor(IntegrationTestCase): +from erpnext.tests.utils import ERPNextTestSuite + + +class TestAssetShiftFactor(ERPNextTestSuite): 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 ba45510191e..ce27f3852f9 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 @@ -2,22 +2,20 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, cstr, get_last_day, getdate, nowdate from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries -from erpnext.assets.doctype.asset.test_asset import create_asset_data from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( get_asset_depr_schedule_doc, ) from erpnext.assets.doctype.asset_repair.test_asset_repair import create_asset_repair from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt +from erpnext.tests.utils import ERPNextTestSuite -class TestAssetValueAdjustment(IntegrationTestCase): +class TestAssetValueAdjustment(ERPNextTestSuite): def setUp(self): - create_asset_data() frappe.db.set_value( "Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC" ) diff --git a/erpnext/assets/doctype/location/location.py b/erpnext/assets/doctype/location/location.py index 7878294caad..c2c3d5a2f1e 100644 --- a/erpnext/assets/doctype/location/location.py +++ b/erpnext/assets/doctype/location/location.py @@ -77,7 +77,7 @@ class Location(NestedSet): location = json.loads(self.location) location["features"] = features - self.db_set("location", json.dumps(location), commit=True) + self.db_set("location", json.dumps(location)) def update_ancestor_location_features(self): self_features = set(self.add_child_property()) @@ -105,7 +105,7 @@ class Location(NestedSet): ancestor_features[index] = json.loads(feature) ancestor_doc.set_location_features(features=ancestor_features) - ancestor_doc.db_set("area", ancestor_doc.area + self.area_difference, commit=True) + ancestor_doc.db_set("area", ancestor_doc.area + self.area_difference) def remove_ancestor_location_features(self): for ancestor in self.get_ancestors(): @@ -116,7 +116,7 @@ class Location(NestedSet): ancestor_features[index] = json.loads(feature) ancestor_doc.set_location_features(features=ancestor_features) - ancestor_doc.db_set("area", ancestor_doc.area - self.area, commit=True) + ancestor_doc.db_set("area", ancestor_doc.area - self.area) def add_child_property(self): features = self.get_location_features() diff --git a/erpnext/assets/doctype/location/test_location.py b/erpnext/assets/doctype/location/test_location.py index 6d777c06181..14fde6a1547 100644 --- a/erpnext/assets/doctype/location/test_location.py +++ b/erpnext/assets/doctype/location/test_location.py @@ -3,11 +3,12 @@ import json import frappe -from frappe.tests import IntegrationTestCase + +from erpnext.tests.utils import ERPNextTestSuite -class TestLocation(IntegrationTestCase): - def runTest(self): +class TestLocation(ERPNextTestSuite): + def test_location_features(self): locations = ["Basil Farm", "Division 1", "Field 1", "Block 1"] area = 0 formatted_locations = [] diff --git a/erpnext/assets/doctype/location/test_records.json b/erpnext/assets/doctype/location/test_records.json deleted file mode 100644 index 8a89cf8bdd3..00000000000 --- a/erpnext/assets/doctype/location/test_records.json +++ /dev/null @@ -1,44 +0,0 @@ -[ - { - "doctype": "Location", - "location_name": "Test Location Area", - "is_group": 1, - "is_container": 1 - }, - { - "doctype": "Location", - "location_name": "Basil Farm", - "location": "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"point_type\":\"circle\",\"radius\":884.5625420736483},\"geometry\":{\"type\":\"Point\",\"coordinates\":[72.875834,19.100566]}}]}", - "parent_location": "Test Location Area", - "parent": "Test Location Area", - "is_group": 1, - "is_container": 1 - }, - { - "doctype": "Location", - "location_name": "Division 1", - "location": "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"point_type\":\"circle\",\"radius\":542.3424997060739},\"geometry\":{\"type\":\"Point\",\"coordinates\":[72.852359,19.11557]}}]}", - "parent_location": "Basil Farm", - "parent": "Basil Farm", - "is_group": 1, - "is_container": 1 - }, - { - "doctype": "Location", - "location_name": "Field 1", - "location": "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[72.846758,19.118287],[72.846758,19.121206],[72.850535,19.121206],[72.850535,19.118287],[72.846758,19.118287]]]}}]}", - "parent_location": "Division 1", - "parent": "Division 1", - "is_group": 1, - "is_container": 1 - }, - { - "doctype": "Location", - "location_name": "Block 1", - "location": "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[72.921495,19.073313],[72.924929,19.068121],[72.934713,19.06585],[72.929392,19.05579],[72.94158,19.056926],[72.951365,19.095213],[72.921495,19.073313]]]}}]}", - "parent_location": "Field 1", - "parent": "Field 1", - "is_group": 0, - "is_container": 1 - } -] \ No newline at end of file diff --git a/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.py b/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.py index 5de115f22a3..71d13dfd608 100644 --- a/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.py +++ b/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.py @@ -1,8 +1,8 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestMaintenanceTeamMember(IntegrationTestCase): +class TestMaintenanceTeamMember(ERPNextTestSuite): pass diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py index 66e6456d103..e3909ea619a 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py @@ -2,8 +2,10 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase -class TestBulkTransactionLog(IntegrationTestCase): +from erpnext.tests.utils import ERPNextTestSuite + + +class TestBulkTransactionLog(ERPNextTestSuite): pass diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py index 6b9b4637ab4..ae35c697c31 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py @@ -2,8 +2,10 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase -class TestBulkTransactionLogDetail(IntegrationTestCase): +from erpnext.tests.utils import ERPNextTestSuite + + +class TestBulkTransactionLogDetail(ERPNextTestSuite): pass diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index fbe17f3dbbc..5a0edbdf5d1 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -282,13 +282,13 @@ } ], "grid_page_length": 50, - "hide_toolbar": 1, + "hide_toolbar": 0, "icon": "fa fa-cog", "idx": 1, "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-01-02 18:16:35.885540", + "modified": "2026-03-16 13:28:19.432589", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", diff --git a/erpnext/buying/doctype/buying_settings/test_buying_settings.py b/erpnext/buying/doctype/buying_settings/test_buying_settings.py index 2946685ee87..c884f51e7eb 100644 --- a/erpnext/buying/doctype/buying_settings/test_buying_settings.py +++ b/erpnext/buying/doctype/buying_settings/test_buying_settings.py @@ -2,8 +2,8 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestBuyingSettings(IntegrationTestCase): +class TestBuyingSettings(ERPNextTestSuite): pass diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 87435f19393..bda926b4040 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -67,7 +67,7 @@ frappe.ui.form.on("Purchase Order", { }, transaction_date(frm) { - prevent_past_schedule_dates(frm); + erpnext.buying.prevent_past_schedule_dates(frm); frm.set_value("schedule_date", ""); }, @@ -87,7 +87,7 @@ frappe.ui.form.on("Purchase Order", { if (frm.doc.docstatus == 0) { erpnext.set_unit_price_items_note(frm); } - prevent_past_schedule_dates(frm); + erpnext.buying.prevent_past_schedule_dates(frm); }, get_materials_from_supplier: function (frm) { @@ -779,11 +779,3 @@ frappe.ui.form.on("Purchase Order", "is_subcontracted", function (frm) { erpnext.buying.get_default_bom(frm); } }); - -function prevent_past_schedule_dates(frm) { - if (frm.doc.transaction_date) { - frm.fields_dict["schedule_date"].datepicker?.update({ - minDate: new Date(frm.doc.transaction_date), - }); - } -} diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 53879d5eff3..d5b2beea6f6 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -252,6 +252,7 @@ "allow_on_submit": 1, "fieldname": "schedule_date", "fieldtype": "Date", + "in_list_view": 1, "label": "Required By" }, { @@ -1327,7 +1328,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2026-03-02 00:40:47.119584", + "modified": "2026-03-09 17:15:29.184682", "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 2b287d51d8d..3b65f690591 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -785,7 +785,8 @@ def make_purchase_invoice_from_portal(purchase_order_name: str): if frappe.session.user not in frappe.get_all("Portal User", {"parent": doc.supplier}, pluck="user"): frappe.throw(_("Not Permitted"), frappe.PermissionError) doc.save() - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() frappe.response["type"] = "redirect" frappe.response.location = "/purchase-invoices/" + doc.name @@ -894,7 +895,7 @@ def get_list_context(context=None): @frappe.whitelist() def update_status(status: str, name: str): - po = frappe.get_lazy_doc("Purchase Order", name) + po = frappe.get_lazy_doc("Purchase Order", name, check_permission="submit") po.update_status(status) po.update_delivered_qty_in_sales_order() diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 3b7c9db5ee9..02b12bc0f63 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -5,7 +5,7 @@ import json import frappe -from frappe.tests import IntegrationTestCase, change_settings +from frappe.tests import change_settings from frappe.utils import add_days, flt, getdate, nowdate from frappe.utils.data import today @@ -26,9 +26,11 @@ from erpnext.stock.doctype.material_request.test_material_request import make_ma from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( make_purchase_invoice as make_pi_from_pr, ) +from erpnext.tests.utils import ERPNextTestSuite -class TestPurchaseOrder(IntegrationTestCase): +class TestPurchaseOrder(ERPNextTestSuite): + @ERPNextTestSuite.change_settings("Buying Settings", {"allow_multiple_items": 1}) def test_purchase_order_qty(self): po = create_purchase_order(qty=1, do_not_save=True) @@ -540,7 +542,7 @@ class TestPurchaseOrder(IntegrationTestCase): self.assertRaises(frappe.ValidationError, pr.submit) self.assertRaises(frappe.ValidationError, pi.submit) - @IntegrationTestCase.change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1}) + @ERPNextTestSuite.change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1}) def test_make_purchase_invoice_with_terms(self): po = create_purchase_order(do_not_save=True) po.update({"payment_terms_template": "_Test Payment Term Template"}) @@ -712,7 +714,7 @@ class TestPurchaseOrder(IntegrationTestCase): ) self.assertEqual(due_date, "2023-03-31") - @IntegrationTestCase.change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 0}) + @ERPNextTestSuite.change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 0}) 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" @@ -737,7 +739,7 @@ class TestPurchaseOrder(IntegrationTestCase): pi.insert() self.assertTrue(pi.get("payment_schedule")) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1} ) def test_advance_payment_entry_unlink_against_purchase_order(self): @@ -785,6 +787,7 @@ class TestPurchaseOrder(IntegrationTestCase): Test "Advance Paid" on Purchase Order, when "Book Advance Payments in Separate Party Account" is enabled and the payment entry linked to the Order is allocated to Purchase Invoice. """ + frappe.flags.is_reverse_depr_entry = False supplier = "_Test Supplier" company = "_Test Company" @@ -808,12 +811,13 @@ class TestPurchaseOrder(IntegrationTestCase): company_doc.book_advance_payments_in_separate_party_account = False company_doc.save() - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1} ) def test_advance_paid_upon_payment_entry_cancellation(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + frappe.flags.is_reverse_depr_entry = False supplier = "_Test Supplier USD" company = "_Test Company" @@ -848,6 +852,7 @@ class TestPurchaseOrder(IntegrationTestCase): self.assertEqual(po_doc.advance_paid, 0) self.assertEqual(po_doc.party_account_currency, "USD") + @ERPNextTestSuite.change_settings("Buying Settings", {"allow_multiple_items": 1}) def test_schedule_date(self): po = create_purchase_order(do_not_submit=True) po.schedule_date = None @@ -904,7 +909,7 @@ class TestPurchaseOrder(IntegrationTestCase): bo.load_from_db() self.assertEqual(bo.items[0].ordered_qty, 5) - @IntegrationTestCase.change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1}) + @ERPNextTestSuite.change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1}) 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, @@ -1143,7 +1148,7 @@ class TestPurchaseOrder(IntegrationTestCase): # Test - 8: Since this PO is now fully subcontracted, creating a new SCO from it should throw error self.assertRaises(frappe.ValidationError, make_subcontracting_order, po.name) - @IntegrationTestCase.change_settings("Buying Settings", {"auto_create_subcontracting_order": 1}) + @ERPNextTestSuite.change_settings("Buying Settings", {"auto_create_subcontracting_order": 1}) def test_auto_create_subcontracting_order(self): from erpnext.controllers.tests.test_subcontracting_controller import ( make_bom_for_subcontracted_items, @@ -1176,6 +1181,7 @@ class TestPurchaseOrder(IntegrationTestCase): self.assertTrue(frappe.db.get_value("Subcontracting Order", {"purchase_order": po.name})) def test_purchase_order_advance_payment_status(self): + frappe.flags.is_reverse_depr_entry = False from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request @@ -1235,7 +1241,7 @@ class TestPurchaseOrder(IntegrationTestCase): po.reload() self.assertEqual(po.per_billed, 100) - @IntegrationTestCase.change_settings("Buying Settings", {"allow_zero_qty_in_purchase_order": 1}) + @ERPNextTestSuite.change_settings("Buying Settings", {"allow_zero_qty_in_purchase_order": 1}) def test_receive_zero_qty_purchase_order(self): """ Test the flow of a Unit Price PO and PR creation against it until completion. @@ -1284,7 +1290,7 @@ class TestPurchaseOrder(IntegrationTestCase): self.assertEqual(po.per_received, 100.0) self.assertEqual(po.status, "To Bill") - @IntegrationTestCase.change_settings("Buying Settings", {"allow_zero_qty_in_purchase_order": 1}) + @ERPNextTestSuite.change_settings("Buying Settings", {"allow_zero_qty_in_purchase_order": 1}) def test_bill_zero_qty_purchase_order(self): po = create_purchase_order(qty=0) @@ -1309,7 +1315,7 @@ class TestPurchaseOrder(IntegrationTestCase): self.assertFalse(po.per_billed) self.assertEqual(po.status, "To Receive and Bill") - @IntegrationTestCase.change_settings("Buying Settings", {"maintain_same_rate": 0}) + @ERPNextTestSuite.change_settings("Buying Settings", {"maintain_same_rate": 0}) def test_purchase_invoice_creation_with_partial_qty(self): po = create_purchase_order(qty=100, rate=10) @@ -1329,6 +1335,7 @@ class TestPurchaseOrder(IntegrationTestCase): self.assertEqual(pi.items[0].qty, 50) def test_multiple_advances_against_purchase_order_are_allocated_across_partial_purchase_invoices(self): + frappe.flags.is_reverse_depr_entry = False # step - 1: create PO po = create_purchase_order(qty=10, rate=10) @@ -1558,6 +1565,3 @@ def get_ordered_qty(item_code="_Test Item", warehouse="_Test Warehouse - _TC"): def get_requested_qty(item_code="_Test Item", warehouse="_Test Warehouse - _TC"): return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "indented_qty")) - - -EXTRA_TEST_RECORD_DEPENDENCIES = ["BOM", "Item Price", "Warehouse"] diff --git a/erpnext/buying/doctype/purchase_order/test_records.json b/erpnext/buying/doctype/purchase_order/test_records.json deleted file mode 100644 index 4df994a68c6..00000000000 --- a/erpnext/buying/doctype/purchase_order/test_records.json +++ /dev/null @@ -1,36 +0,0 @@ -[ - { - "advance_paid": 0.0, - "buying_price_list": "_Test Price List", - "company": "_Test Company", - "conversion_rate": 1.0, - "currency": "INR", - "doctype": "Purchase Order", - "base_grand_total": 5000.0, - "grand_total": 5000.0, - "is_subcontracted": 0, - "naming_series": "_T-Purchase Order-", - "base_net_total": 5000.0, - "items": [ - { - "base_amount": 5000.0, - "conversion_factor": 1.0, - "description": "_Test Item", - "doctype": "Purchase Order Item", - "item_code": "_Test Item", - "item_name": "_Test Item", - "parentfield": "items", - "qty": 10.0, - "rate": 500.0, - "schedule_date": "2013-03-01", - "stock_uom": "_Test UOM", - "uom": "_Test UOM", - "warehouse": "_Test Warehouse - _TC" - } - ], - "supplier": "_Test Supplier", - "supplier_name": "_Test Supplier", - "transaction_date": "2013-02-12", - "schedule_date": "2013-02-13" - } -] diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 3171ad595b1..8baeba950b9 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -100,6 +100,7 @@ frappe.ui.form.on("Request for Quotation", { fieldname: "print_format", options: "Print Format", placeholder: "Standard", + default: frappe.get_meta("Request for Quotation").default_print_format || "", get_query: () => { return { filters: { @@ -165,14 +166,10 @@ frappe.ui.form.on("Request for Quotation", { }, show_supplier_quotation_comparison(frm) { - const today = new Date(); - const oneMonthAgo = new Date(today); - oneMonthAgo.setMonth(today.getMonth() - 1); - frappe.route_options = { company: frm.doc.company, - from_date: moment(oneMonthAgo).format("YYYY-MM-DD"), - to_date: moment(today).format("YYYY-MM-DD"), + from_date: moment(frm.doc.transaction_date).format("YYYY-MM-DD"), + to_date: moment(new Date()).format("YYYY-MM-DD"), request_for_quotation: frm.doc.name, }; frappe.set_route("query-report", "Supplier Quotation Comparison"); diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json index a10497702ce..de8b4d28547 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json @@ -9,8 +9,6 @@ "field_order": [ "naming_series", "company", - "billing_address", - "billing_address_display", "vendor", "column_break1", "transaction_date", @@ -43,13 +41,18 @@ "select_print_heading", "letter_head", "more_info", - "opportunity" + "opportunity", + "address_and_contact_tab", + "billing_address", + "billing_address_display", + "column_break_czul", + "shipping_address", + "shipping_address_display" ], "fields": [ { "fieldname": "naming_series", "fieldtype": "Select", - "in_list_view": 1, "label": "Series", "no_copy": 1, "oldfieldname": "naming_series", @@ -77,6 +80,7 @@ "fieldname": "vendor", "fieldtype": "Link", "hidden": 1, + "in_list_view": 1, "in_standard_filter": 1, "label": "Supplier", "no_copy": 1, @@ -95,6 +99,7 @@ "fieldname": "transaction_date", "fieldtype": "Date", "in_list_view": 1, + "in_standard_filter": 1, "label": "Date", "oldfieldname": "transaction_date", "oldfieldtype": "Date", @@ -147,7 +152,6 @@ "depends_on": "eval:doc.use_html == 0", "fieldname": "message_for_supplier", "fieldtype": "Text Editor", - "in_list_view": 1, "label": "Message for Supplier", "mandatory_depends_on": "eval:doc.use_html == 0", "print_hide": 1 @@ -225,6 +229,8 @@ { "fieldname": "status", "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Status", "no_copy": 1, "oldfieldname": "status", @@ -263,6 +269,7 @@ { "fieldname": "schedule_date", "fieldtype": "Date", + "in_list_view": 1, "label": "Required Date" }, { @@ -343,6 +350,27 @@ "fieldtype": "Check", "hidden": 1, "label": "Use HTML" + }, + { + "fieldname": "address_and_contact_tab", + "fieldtype": "Tab Break", + "label": "Address & Contact" + }, + { + "fieldname": "column_break_czul", + "fieldtype": "Column Break" + }, + { + "fieldname": "shipping_address", + "fieldtype": "Link", + "label": "Company Shipping Address", + "options": "Address" + }, + { + "fieldname": "shipping_address_display", + "fieldtype": "Text Editor", + "label": "Shipping Address Details", + "read_only": 1 } ], "grid_page_length": 50, @@ -350,7 +378,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2026-03-01 23:38:48.079274", + "modified": "2026-03-19 15:27:56.730649", "modified_by": "Administrator", "module": "Buying", "name": "Request for Quotation", 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 8021906c73d..b483f629d17 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -57,6 +57,8 @@ class RequestforQuotation(BuyingController): select_print_heading: DF.Link | None send_attached_files: DF.Check send_document_print: DF.Check + shipping_address: DF.Link | None + shipping_address_display: DF.TextEditor | None status: DF.Literal["", "Draft", "Submitted", "Cancelled"] subject: DF.Data suppliers: DF.Table[RequestforQuotationSupplier] diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py index 425d172629d..482b5899ec3 100644 --- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py @@ -5,11 +5,10 @@ from urllib.parse import urlparse import frappe -from frappe.tests import IntegrationTestCase, change_settings +from frappe.tests import change_settings from frappe.utils import nowdate from erpnext.buying.doctype.request_for_quotation.request_for_quotation import ( - RequestforQuotation, create_supplier_quotation, get_pdf, make_supplier_quotation_from_rfq, @@ -19,9 +18,10 @@ from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotati from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity from erpnext.stock.doctype.item.test_item import make_item from erpnext.templates.pages.rfq import check_supplier_has_docname_access +from erpnext.tests.utils import ERPNextTestSuite -class TestRequestforQuotation(IntegrationTestCase): +class TestRequestforQuotation(ERPNextTestSuite): def test_rfq_qty(self): rfq = make_request_for_quotation(qty=0, do_not_save=True) with self.assertRaises(InvalidQtyError): @@ -222,7 +222,7 @@ class TestRequestforQuotation(IntegrationTestCase): supplier_doc.reload() self.assertTrue(supplier_doc.portal_users[0].user) - @IntegrationTestCase.change_settings("Buying Settings", {"allow_zero_qty_in_request_for_quotation": 1}) + @ERPNextTestSuite.change_settings("Buying Settings", {"allow_zero_qty_in_request_for_quotation": 1}) def test_supplier_quotation_from_zero_qty_rfq(self): rfq = make_request_for_quotation(qty=0) sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get("suppliers")[0].supplier) @@ -231,7 +231,7 @@ class TestRequestforQuotation(IntegrationTestCase): self.assertEqual(sq.items[0].qty, 0) self.assertEqual(sq.items[0].item_code, rfq.items[0].item_code) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Buying Settings", { "allow_zero_qty_in_request_for_quotation": 1, @@ -249,7 +249,7 @@ class TestRequestforQuotation(IntegrationTestCase): self.assertEqual(sq.items[0].item_code, rfq.items[0].item_code) -def make_request_for_quotation(**args) -> "RequestforQuotation": +def make_request_for_quotation(**args): """ :param supplier_data: List containing supplier data """ diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 08178831751..60e90517b17 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -178,6 +178,7 @@ "default": "Company", "fieldname": "supplier_type", "fieldtype": "Select", + "in_list_view": 1, "label": "Supplier Type", "options": "Company\nIndividual\nPartnership", "reqd": 1 @@ -238,6 +239,7 @@ "fieldname": "default_currency", "fieldtype": "Link", "ignore_user_permissions": 1, + "in_list_view": 1, "label": "Billing Currency", "no_copy": 1, "options": "Currency" @@ -515,7 +517,7 @@ "link_fieldname": "party" } ], - "modified": "2026-02-08 20:28:01.101808", + "modified": "2026-03-09 17:15:25.465759", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", diff --git a/erpnext/buying/doctype/supplier/test_records.json b/erpnext/buying/doctype/supplier/test_records.json deleted file mode 100644 index 1bb9899cc85..00000000000 --- a/erpnext/buying/doctype/supplier/test_records.json +++ /dev/null @@ -1,50 +0,0 @@ -[ - { - "doctype": "Supplier", - "supplier_name": "_Test Supplier With Template 1", - "supplier_group": "_Test Supplier Group", - "payment_terms": "_Test Payment Term Template 3" - }, - { - "doctype": "Supplier", - "supplier_name": "_Test Supplier P", - "supplier_group": "_Test Supplier Group" - }, - { - "doctype": "Supplier", - "supplier_name": "_Test Supplier with Country", - "supplier_group": "_Test Supplier Group", - "country": "Greece" - }, - { - "doctype": "Supplier", - "supplier_name": "_Test Supplier", - "supplier_group": "_Test Supplier Group" - }, - { - "doctype": "Supplier", - "supplier_name": "_Test Supplier 1", - "supplier_group": "_Test Supplier Group" - }, - { - "doctype": "Supplier", - "supplier_name": "_Test Supplier 2", - "supplier_group": "_Test Supplier Group" - }, - { - "doctype": "Supplier", - "supplier_name": "_Test Supplier USD", - "supplier_group": "_Test Supplier Group", - "default_currency": "USD", - "accounts": [{ - "company": "_Test Company", - "account": "_Test Payable USD - _TC" - }] - }, - { - "doctype": "Supplier", - "supplier_name": "_Test Supplier With Tax Category", - "supplier_group": "_Test Supplier Group", - "tax_category": "_Test Tax Category 1" - } -] diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py index f3f0ede3e17..e2a7d5627b5 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.py +++ b/erpnext/buying/doctype/supplier/test_supplier.py @@ -7,14 +7,10 @@ import frappe from erpnext.accounts.party import get_due_date from erpnext.controllers.website_list_for_contact import get_customers_suppliers from erpnext.exceptions import PartyDisabled - -EXTRA_TEST_RECORD_DEPENDENCIES = ["Payment Term", "Payment Terms Template"] +from erpnext.tests.utils import ERPNextTestSuite -from frappe.tests import IntegrationTestCase - - -class TestSupplier(IntegrationTestCase): +class TestSupplier(ERPNextTestSuite): def test_get_supplier_group_details(self): doc = frappe.new_doc("Supplier Group") doc.supplier_group_name = "_Testing Supplier Group" @@ -175,7 +171,10 @@ def create_supplier(**args): return doc -class TestSupplierPortal(IntegrationTestCase): +from erpnext.tests.utils import ERPNextTestSuite + + +class TestSupplierPortal(ERPNextTestSuite): def test_portal_user_can_access_supplier_data(self): supplier = create_supplier() diff --git a/erpnext/buying/doctype/supplier_quotation/test_records.json b/erpnext/buying/doctype/supplier_quotation/test_records.json index 8acac3210d5..1b9b922e3c9 100644 --- a/erpnext/buying/doctype/supplier_quotation/test_records.json +++ b/erpnext/buying/doctype/supplier_quotation/test_records.json @@ -1,31 +1,31 @@ [ { - "buying_price_list": "_Test Price List", - "company": "_Test Company", - "conversion_rate": 1.0, - "currency": "INR", - "doctype": "Supplier Quotation", - "base_grand_total": 5000.0, - "grand_total": 5000.0, - "is_subcontracted": 0, - "naming_series": "_T-Supplier Quotation-", - "base_net_total": 5000.0, + "buying_price_list": "_Test Price List", + "company": "_Test Company", + "conversion_rate": 1.0, + "currency": "INR", + "doctype": "Supplier Quotation", + "base_grand_total": 5000.0, + "grand_total": 5000.0, + "is_subcontracted": 0, + "naming_series": "_T-Supplier Quotation-", + "base_net_total": 5000.0, "items": [ { - "base_amount": 5000.0, - "description": "_Test FG Item", - "doctype": "Supplier Quotation Item", - "item_code": "_Test FG Item", - "item_name": "_Test FG Item", - "parentfield": "items", - "qty": 10.0, - "rate": 500.0, - "uom": "_Test UOM", + "base_amount": 5000.0, + "description": "_Test FG Item", + "doctype": "Supplier Quotation Item", + "item_code": "_Test FG Item", + "item_name": "_Test FG Item", + "parentfield": "items", + "qty": 10.0, + "rate": 500.0, + "uom": "_Test UOM", "warehouse": "_Test Warehouse - _TC" } - ], - "supplier": "_Test Supplier", - "supplier_name": "_Test Supplier", + ], + "supplier": "_Test Supplier", + "supplier_name": "_Test Supplier", "transaction_date": "2013-02-12" } -] \ No newline at end of file +] diff --git a/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py index 7c2714ba671..c271d34b35d 100644 --- a/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py +++ b/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py @@ -5,14 +5,18 @@ import json import frappe -from frappe.tests import IntegrationTestCase, change_settings +from frappe.tests import change_settings from frappe.utils import add_days, today from erpnext.buying.doctype.supplier_quotation.supplier_quotation import make_purchase_order from erpnext.controllers.accounts_controller import InvalidQtyError, update_child_qty_rate +from erpnext.tests.utils import ERPNextTestSuite -class TestPurchaseOrder(IntegrationTestCase): +class TestPurchaseOrder(ERPNextTestSuite): + def setUp(self): + self.load_test_records("Supplier Quotation") + def test_update_child_supplier_quotation_add_item(self): sq = frappe.copy_doc(self.globalTestRecords["Supplier Quotation"][0]) sq.submit() @@ -166,7 +170,7 @@ class TestPurchaseOrder(IntegrationTestCase): po.insert() - @IntegrationTestCase.change_settings("Buying Settings", {"allow_zero_qty_in_supplier_quotation": 1}) + @ERPNextTestSuite.change_settings("Buying Settings", {"allow_zero_qty_in_supplier_quotation": 1}) def test_map_purchase_order_from_zero_qty_supplier_quotation(self): sq = frappe.copy_doc(self.globalTestRecords["Supplier Quotation"][0]) sq.items[0].qty = 0 diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py index 7aaaceaed08..c11ee6383dc 100644 --- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py +++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py @@ -266,8 +266,8 @@ def get_scorecard_date(period, start_date): return end_date -def make_default_records(): - install_variable_docs = [ +def get_default_scorecard_variables(): + return [ { "param_name": "total_accepted_items", "variable_label": "Total Accepted Items", @@ -374,7 +374,10 @@ def make_default_records(): "path": "get_invoiced_qty", }, ] - install_standing_docs = [ + + +def get_default_scorecard_standing(): + return [ { "min_grade": 0.0, "prevent_rfqs": 1, @@ -425,12 +428,17 @@ def make_default_records(): }, ] + +def make_default_records(): + install_variable_docs = get_default_scorecard_variables() for d in install_variable_docs: try: d["doctype"] = "Supplier Scorecard Variable" frappe.get_doc(d).insert() except frappe.NameError: pass + + install_standing_docs = get_default_scorecard_standing() for d in install_standing_docs: try: d["doctype"] = "Supplier Scorecard Standing" diff --git a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py index c65ebe2086f..a1d1dfc4709 100644 --- a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py +++ b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py @@ -3,10 +3,11 @@ import frappe -from frappe.tests import IntegrationTestCase + +from erpnext.tests.utils import ERPNextTestSuite -class TestSupplierScorecard(IntegrationTestCase): +class TestSupplierScorecard(ERPNextTestSuite): def test_create_scorecard(self): doc = make_supplier_scorecard().insert() self.assertEqual(doc.name, valid_scorecard[0].get("supplier")) 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 114ff2b56b3..951d33ed901 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 @@ -3,10 +3,11 @@ import frappe -from frappe.tests import IntegrationTestCase + +from erpnext.tests.utils import ERPNextTestSuite -class TestSupplierScorecardCriteria(IntegrationTestCase): +class TestSupplierScorecardCriteria(ERPNextTestSuite): def test_variables_exist(self): delete_test_scorecards() for d in test_good_criteria: diff --git a/erpnext/buying/doctype/supplier_scorecard_period/test_supplier_scorecard_period.py b/erpnext/buying/doctype/supplier_scorecard_period/test_supplier_scorecard_period.py index b4975edfc75..4762c25ae51 100644 --- a/erpnext/buying/doctype/supplier_scorecard_period/test_supplier_scorecard_period.py +++ b/erpnext/buying/doctype/supplier_scorecard_period/test_supplier_scorecard_period.py @@ -1,8 +1,8 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestSupplierScorecardPeriod(IntegrationTestCase): +class TestSupplierScorecardPeriod(ERPNextTestSuite): pass diff --git a/erpnext/buying/doctype/supplier_scorecard_standing/test_supplier_scorecard_standing.py b/erpnext/buying/doctype/supplier_scorecard_standing/test_supplier_scorecard_standing.py index 97cb93f4c69..64f8abacadb 100644 --- a/erpnext/buying/doctype/supplier_scorecard_standing/test_supplier_scorecard_standing.py +++ b/erpnext/buying/doctype/supplier_scorecard_standing/test_supplier_scorecard_standing.py @@ -1,8 +1,8 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestSupplierScorecardStanding(IntegrationTestCase): +class TestSupplierScorecardStanding(ERPNextTestSuite): pass 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 8f4b2f4ecdf..f390eefcc65 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 @@ -3,14 +3,14 @@ import frappe -from frappe.tests import IntegrationTestCase from erpnext.buying.doctype.supplier_scorecard_variable.supplier_scorecard_variable import ( VariablePathNotFound, ) +from erpnext.tests.utils import ERPNextTestSuite -class TestSupplierScorecardVariable(IntegrationTestCase): +class TestSupplierScorecardVariable(ERPNextTestSuite): def test_variable_exist(self): for d in test_existing_variables: my_doc = frappe.get_doc("Supplier Scorecard Variable", d.get("name")) diff --git a/erpnext/accounts/print_format/point_of_sale/__init__.py b/erpnext/buying/print_format/request_for_quotation_with_item_image/__init__.py similarity index 100% rename from erpnext/accounts/print_format/point_of_sale/__init__.py rename to erpnext/buying/print_format/request_for_quotation_with_item_image/__init__.py diff --git a/erpnext/buying/print_format/request_for_quotation_with_item_image/request_for_quotation_with_item_image.json b/erpnext/buying/print_format/request_for_quotation_with_item_image/request_for_quotation_with_item_image.json new file mode 100644 index 00000000000..26f131aec5b --- /dev/null +++ b/erpnext/buying/print_format/request_for_quotation_with_item_image/request_for_quotation_with_item_image.json @@ -0,0 +1,33 @@ +{ + "absolute_value": 0, + "align_labels_right": 0, + "creation": "2026-03-19 15:17:39.094444", + "custom_format": 1, + "default_print_language": "en", + "disabled": 0, + "doc_type": "Request for Quotation", + "docstatus": 0, + "doctype": "Print Format", + "font_size": 14, + "html": "{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead, footer, print_settings=None, print_heading_template=None) -%}\n\n{% if letter_head and not no_letterhead %}\n
{{ letter_head }}
\n{% endif %}\n{% if print_heading_template %}\n{{ frappe.render_template(print_heading_template, {\"doc\":doc}) }}\n{% endif %}\n{%- endmacro -%}\n\n{% for page in layout %}\n
\n\t
\n\t\t{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}\n\t
\n\t{%- if doc.meta.is_submittable and doc.docstatus==2-%}\n\t\t
\n\t\t\t

{{ _(\"CANCELLED\") }}

\n\t\t
\n\t{%- endif -%}\n\t{%- if doc.meta.is_submittable and doc.docstatus==0 and (print_settings==None or print_settings.add_draft_heading) -%}\n\t\t
\n\t\t\t

{{ _(\"DRAFT\") }}

\n\t\t
\n\t{%- endif -%}\n\n\t\n\n\t
\n\t\t\n\t\t\t\n\t\t\t\t\n\n\t\t\t\t\n\t\t\t\n\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ _(\"Supplier Name:\") }}
\n\t\t\t\t\t\t
{{ _(\"Shipping Address:\") }}
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ doc.vendor }}
\n\t\t\t\t\t\t
\n \t\t\t\t\t{% if doc.shipping_address %}\n \t\t\t\t\t\t{% set shipping_address = frappe.db.get_value(\"Address\", doc.shipping_address, [\"address_line1\", \"address_line2\", \"city\", \"state\", \"pincode\", \"country\"], as_dict=True) %}\n {{ doc.shipping_address }}
\n \t\t\t\t\t\t{{ shipping_address.address_line1 or \"\" }}
\n \t\t\t\t\t\t{% if shipping_address.address_line2 %}{{ shipping_address.address_line2 }}
{% endif %}\n \t\t\t\t\t\t{{ shipping_address.city or \"\" }} {{ shipping_address.state or \"\" }} {{ shipping_address.pincode or \"\" }} {{ shipping_address.country or \"\" }}
\n \t\t\t\t\t{% endif %}\n\t\t\t\t\t\t
\n\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ _(\"Order Date:\") }}
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ frappe.utils.format_date(doc.transaction_date) }}
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ _(\"Required By:\") }}
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ frappe.utils.format_date(doc.schedule_date) }}
\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\n\t\t{% set item_naming_by = frappe.db.get_single_value(\"Stock Settings\", \"item_naming_by\") %}\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if item_naming_by != \"Item Code\" %}\n\t\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\t{% for item in doc.items %}\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if item_naming_by != \"Item Code\" %}\n\t\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t{% endfor %}\n\t\t\t\n\t\t
{{ _(\"No\") }}{{ _(\"Item\") }}{{ _(\"Item Code\") }}{{ _(\"Quantity\") }}
{{ loop.index }}\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{% if item.image %}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{% endif %}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{{ item.item_name }}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t
{{ item.item_code }}{{ item.get_formatted(\"qty\", 0) }} {{ item.uom }}
\n\n\n\t\t\n\t\t{% if doc.terms %}\n\t\t
\n\t\t\t
{{ _(\"Terms and Conditions\") }}
\n\t\t\t{{ doc.terms}}\n\t\t
\n\t\t{% endif %}\n\t
\n
\n{% endfor %}\n", + "idx": 0, + "line_breaks": 0, + "margin_bottom": 15.0, + "margin_left": 15.0, + "margin_right": 15.0, + "margin_top": 15.0, + "modified": "2026-03-23 14:29:41.591636", + "modified_by": "Administrator", + "module": "Buying", + "name": "Request for Quotation with Item Image", + "owner": "Administrator", + "page_number": "Hide", + "pdf_generator": "wkhtmltopdf", + "print_format_builder": 0, + "print_format_builder_beta": 0, + "print_format_for": "DocType", + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, + "standard": "Yes" +} diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index f344730a7fa..1577bf2cbf6 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -2,8 +2,8 @@ # For license information, please see license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestProcurementTracker(IntegrationTestCase): +class TestProcurementTracker(ERPNextTestSuite): pass diff --git a/erpnext/buying/report/purchase_analytics/purchase_analytics.js b/erpnext/buying/report/purchase_analytics/purchase_analytics.js index 23a188057d9..b66c1c429d0 100644 --- a/erpnext/buying/report/purchase_analytics/purchase_analytics.js +++ b/erpnext/buying/report/purchase_analytics/purchase_analytics.js @@ -65,6 +65,11 @@ frappe.query_reports["Purchase Analytics"] = { default: "Monthly", reqd: 1, }, + { + fieldname: "show_aggregate_value_from_subsidiary_companies", + label: __("Show Aggregate Value from Subsidiary Companies"), + fieldtype: "Check", + }, ], get_datatable_options(options) { return Object.assign(options, { diff --git a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.json b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.json index 0047d6ecbe5..e53b8e6d669 100644 --- a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.json +++ b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.json @@ -9,8 +9,8 @@ "filters": [], "idx": 3, "is_standard": "Yes", - "letterhead": null, - "modified": "2025-11-05 11:55:50.058154", + "letter_head": null, + "modified": "2026-03-13 17:36:05.561765", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Trends", diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py index ca0b1abad0c..38f2d7426ff 100644 --- a/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py +++ b/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py @@ -2,7 +2,6 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, today from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt @@ -11,11 +10,14 @@ from erpnext.buying.report.requested_items_to_order_and_receive.requested_items_ ) from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.material_request.material_request import make_purchase_order +from erpnext.tests.utils import ERPNextTestSuite -class TestRequestedItemsToOrderAndReceive(IntegrationTestCase): +class TestRequestedItemsToOrderAndReceive(ERPNextTestSuite): def setUp(self) -> None: create_item("Test MR Report Item") + self.load_test_records("Material Request") + frappe.db.set_single_value("Buying Settings", "allow_multiple_items", 1) self.setup_material_request() # to order and receive self.setup_material_request(order=True, days=1) # to receive (ordered) self.setup_material_request(order=True, receive=True, days=2) # complete (ordered & received) @@ -27,9 +29,6 @@ class TestRequestedItemsToOrderAndReceive(IntegrationTestCase): item_code="Test MR Report Item", ) - def tearDown(self) -> None: - frappe.db.rollback() - def test_date_range(self): data = get_data(self.filters) self.assertEqual(len(data), 2) # MRs today should be fetched 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 5a4c8ec6ba0..1b6bf653a77 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 @@ -7,7 +7,6 @@ import copy import frappe -from frappe.tests import IntegrationTestCase from erpnext.buying.report.subcontracted_item_to_be_received.subcontracted_item_to_be_received import ( execute, @@ -22,9 +21,10 @@ from erpnext.controllers.tests.test_subcontracting_controller import ( from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( make_subcontracting_receipt, ) +from erpnext.tests.utils import ERPNextTestSuite -class TestSubcontractedItemToBeReceived(IntegrationTestCase): +class TestSubcontractedItemToBeReceived(ERPNextTestSuite): def test_pending_and_received_qty(self): make_service_item("Subcontracted Service Item 1") service_items = [ 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 ef28eda62a5..4526bbf1703 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 @@ -41,6 +41,7 @@ def get_columns(filters): "fieldname": "transferred_qty", "width": 200, }, + {"label": _("Returned Quantity"), "fieldtype": "Float", "fieldname": "returned_qty", "width": 150}, {"label": _("Pending Quantity"), "fieldtype": "Float", "fieldname": "p_qty", "width": 150}, ] @@ -50,7 +51,7 @@ def get_data(filters): data = [] for row in order_rm_item_details: - transferred_qty = row.get("transferred_qty") or 0 + transferred_qty = (row.get("transferred_qty") or 0) - (row.get("returned_qty") or 0) if transferred_qty < row.get("reqd_qty", 0): pending_qty = frappe.utils.flt(row.get("reqd_qty", 0) - transferred_qty) row.p_qty = pending_qty if pending_qty > 0 else 0 @@ -86,6 +87,7 @@ def get_order_items_to_supply(filters): f"`tab{supplied_items_table}`.rm_item_code as rm_item_code", f"`tab{supplied_items_table}`.required_qty as reqd_qty", f"`tab{supplied_items_table}`.supplied_qty as transferred_qty", + f"`tab{supplied_items_table}`.returned_qty as returned_qty", ], filters=record_filters, ) diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py index 3553906ae63..ee76c50261d 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py @@ -4,7 +4,6 @@ # Decompiled by https://python-decompiler.com import frappe -from frappe.tests import IntegrationTestCase from erpnext.buying.report.subcontracted_raw_materials_to_be_transferred.subcontracted_raw_materials_to_be_transferred import ( execute, @@ -15,9 +14,10 @@ from erpnext.controllers.tests.test_subcontracting_controller import ( make_service_item, ) from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry +from erpnext.tests.utils import ERPNextTestSuite -class TestSubcontractedItemToBeTransferred(IntegrationTestCase): +class TestSubcontractedItemToBeTransferred(ERPNextTestSuite): def test_pending_and_transferred_qty(self): make_service_item("Subcontracted Service Item 1") service_items = [ diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 09a31eea4be..baa80c2154a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2290,6 +2290,16 @@ class AccountsController(TransactionBase): return stock_items + def get_asset_items(self): + asset_items = [] + item_codes = list(set(item.item_code for item in self.get("items"))) + if item_codes: + asset_items = frappe.db.get_values( + "Item", {"name": ["in", item_codes], "is_fixed_asset": 1}, pluck="name", cache=True + ) + + return asset_items + def calculate_total_advance_from_ledger(self): adv = frappe.qb.DocType("Advance Payment Ledger Entry") return ( @@ -4328,6 +4338,8 @@ def get_missing_company_details(doctype: str, docname: str): company = frappe.db.get_value(doctype, docname, "company") if doctype in ["Purchase Order", "Purchase Invoice"]: company_address = frappe.db.get_value(doctype, docname, "billing_address") + elif doctype in ["Request for Quotation"]: + company_address = frappe.db.get_value(doctype, docname, "shipping_address") else: company_address = frappe.db.get_value(doctype, docname, "company_address") @@ -4427,6 +4439,8 @@ def update_doc_company_address(current_doctype, docname, company_address, detail "Sales Invoice": ("company_address", "company_address_display"), "Delivery Note": ("company_address", "company_address_display"), "POS Invoice": ("company_address", "company_address_display"), + "Quotation": ("company_address", "company_address_display"), + "Request for Quotation": ("shipping_address", "shipping_address_display"), } address_field, display_field = address_field_map.get( diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 86cfbc01172..67ccc4c7fe4 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -1092,12 +1092,10 @@ class BuyingController(SubcontractingController): } ) for dimension in accounting_dimensions[0]: - asset.update( - { - dimension["fieldname"]: self.get(dimension["fieldname"]) - or dimension.get("default_dimension") - } - ) + fieldname = dimension["fieldname"] + default_dimension = accounting_dimensions[1].get(self.company, {}).get(fieldname) + if not asset.get(fieldname): + asset.update({fieldname: row.get(fieldname) or self.get(fieldname) or default_dimension}) asset.flags.ignore_validate = True asset.flags.ignore_mandatory = True diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index f539ef15536..689f0c6a3f6 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -366,13 +366,13 @@ def copy_attributes_to_variant(item, variant): else: if item.variant_based_on == "Item Attribute": if variant.attributes: - attributes_description = item.description + " " + attributes_description = item.description or "" for d in variant.attributes: attributes_description += ( "
" + d.attribute + ": " + cstr(d.attribute_value) + "
" ) - if attributes_description not in variant.description: + if attributes_description not in (variant.description or ""): variant.description = attributes_description diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index c3503da61a4..00bb257a90b 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -11,10 +11,11 @@ from frappe.desk.reportview import get_filters_cond, get_match_cond from frappe.permissions import has_permission from frappe.query_builder import Criterion, CustomFunction from frappe.query_builder.functions import Concat, Locate, Sum -from frappe.utils import cint, nowdate, today, unique +from frappe.utils import nowdate, today, unique from pypika import Order import erpnext +from erpnext.accounts.utils import build_qb_match_conditions from erpnext.stock.get_item_details import ItemDetailsCtx, _get_item_tax_template @@ -634,34 +635,37 @@ def get_blanket_orders(doctype: str, txt: str, searchfield: str, start: int, pag @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_income_account(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict): - from erpnext.controllers.queries import get_match_cond - # income account can be any Credit account, # but can also be a Asset account with account_type='Income Account' in special circumstances. # Hence the first condition is an "OR" + if not filters: filters = {} - doctype = "Account" - condition = "" + dt = "Account" + + acc = qb.DocType(dt) + condition = [ + (acc.report_type.eq("Profit and Loss") | acc.account_type.isin(["Income Account", "Temporary"])), + acc.is_group.eq(0), + acc.disabled.eq(0), + ] + if txt: + condition.append(acc.name.like(f"%{txt}%")) + if filters.get("company"): - condition += "and tabAccount.company = %(company)s" + condition.append(acc.company.eq(filters.get("company"))) - condition += " and tabAccount.disabled = %(disabled)s" + user_perms = build_qb_match_conditions(dt) + condition.extend(user_perms) - return frappe.db.sql( - f"""select tabAccount.name from `tabAccount` - where (tabAccount.report_type = "Profit and Loss" - or tabAccount.account_type in ("Income Account", "Temporary")) - and tabAccount.is_group=0 - and tabAccount.`{searchfield}` LIKE %(txt)s - {condition} {get_match_cond(doctype)} - order by idx desc, name""", - { - "txt": "%" + txt + "%", - "company": filters.get("company", ""), - "disabled": cint(filters.get("disabled", 0)), - }, + return ( + qb.from_(acc) + .select(acc.name) + .where(Criterion.all(condition)) + .orderby(acc.idx, order=Order.desc) + .orderby(acc.name) + .run() ) @@ -730,26 +734,38 @@ def get_filtered_dimensions( @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_expense_account(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict): - from erpnext.controllers.queries import get_match_cond - if not filters: filters = {} - doctype = "Account" - condition = "" - if filters.get("company"): - condition += "and tabAccount.company = %(company)s" + dt = "Account" - return frappe.db.sql( - f"""select tabAccount.name from `tabAccount` - where (tabAccount.report_type = "Profit and Loss" - or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed", "Capital Work in Progress")) - and tabAccount.is_group=0 - and tabAccount.disabled = 0 - and tabAccount.{searchfield} LIKE %(txt)s - {condition} {get_match_cond(doctype)}""", - {"company": filters.get("company", ""), "txt": "%" + txt + "%"}, - ) + acc = qb.DocType(dt) + condition = [ + ( + acc.report_type.eq("Profit and Loss") + | acc.account_type.isin( + [ + "Expense Account", + "Fixed Asset", + "Temporary", + "Asset Received But Not Billed", + "Capital Work in Progress", + ] + ) + ), + acc.is_group.eq(0), + acc.disabled.eq(0), + ] + if txt: + condition.append(acc.name.like(f"%{txt}%")) + + if filters.get("company"): + condition.append(acc.company.eq(filters.get("company"))) + + user_perms = build_qb_match_conditions(dt) + condition.extend(user_perms) + + return qb.from_(acc).select(acc.name).where(Criterion.all(condition)).run() @frappe.whitelist() diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 7c47bf1ae41..409af70bb75 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -596,7 +596,6 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai target_doc.against_sales_order = source_doc.against_sales_order target_doc.against_sales_invoice = source_doc.against_sales_invoice target_doc.so_detail = source_doc.so_detail - target_doc.si_detail = source_doc.si_detail target_doc.expense_account = source_doc.expense_account target_doc.dn_detail = source_doc.name if default_warehouse_for_sales_return: diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 742d8627fdf..8138a94693c 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -526,6 +526,9 @@ class SellingController(StockController): if self.doctype not in ("Delivery Note", "Sales Invoice"): return + if self.doctype == "Sales Invoice" and not self.update_stock and not self.is_internal_transfer(): + return + from erpnext.stock.serial_batch_bundle import get_batch_nos, get_serial_nos allow_at_arms_length_price = frappe.get_cached_value( diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 4ec5e739143..3ce079a62a6 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -267,8 +267,8 @@ class StatusUpdater(Document): self.global_amount_allowance = None for args in self.status_updater: - if "target_ref_field" not in args: - # if target_ref_field is not specified, the programmer does not want to validate qty / amount + if "target_ref_field" not in args or args.get("validate_qty") is False: + # if target_ref_field is not specified or validate_qty is explicitly set to False, skip validation continue items_to_validate = [] @@ -444,7 +444,10 @@ class StatusUpdater(Document): ): return - if args["source_dt"] != "Pick List Item" and args["target_dt"] != "Quotation Item": + if args["source_dt"] != "Pick List Item" and args["target_dt"] not in [ + "Quotation Item", + "Packed Item", + ]: if qty_or_amount == "qty": action_msg = _( 'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.' diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index beaacd3674b..01366d7bcc8 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -1435,10 +1435,20 @@ class StockController(AccountsController): elif self.doctype == "Stock Entry" and row.t_warehouse: qi_required = True # inward stock needs inspection - if row.get("is_scrap_item"): + if row.get("type") or row.get("is_legacy_scrap_item"): continue if qi_required: # validate row only if inspection is required on item level + if self.doctype in [ + "Purchase Receipt", + "Purchase Invoice", + "Sales Invoice", + "Delivery Note", + ] and frappe.get_single_value( + "Stock Settings", "allow_to_make_quality_inspection_after_purchase_or_delivery" + ): + return + self.validate_qi_presence(row) if self.docstatus == 1: self.validate_qi_submission(row) @@ -1446,16 +1456,6 @@ class StockController(AccountsController): def validate_qi_presence(self, row): """Check if QI is present on row level. Warn on save and stop on submit if missing.""" - if self.doctype in [ - "Purchase Receipt", - "Purchase Invoice", - "Sales Invoice", - "Delivery Note", - ] and frappe.get_single_value( - "Stock Settings", "allow_to_make_quality_inspection_after_purchase_or_delivery" - ): - return - if not row.quality_inspection: msg = _("Row #{0}: Quality Inspection is required for Item {1}").format( row.idx, frappe.bold(row.item_code) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 47528184a8b..5052e0b0a46 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -160,7 +160,7 @@ class SubcontractingController(StockController): ).format(item.idx, get_link_to_form("Item", item.item_code)) ) - if not item.get("is_scrap_item"): + if not item.get("type") and not item.get("is_legacy_scrap_item"): if not is_sub_contracted_item: frappe.throw( _("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name) @@ -206,7 +206,7 @@ class SubcontractingController(StockController): ).format(item.idx, item.item_name) ) - if self.doctype != "Subcontracting Inward Order": + if self.doctype not in ["Subcontracting Inward Order", "Subcontracting Receipt"]: item.amount = item.qty * item.rate if item.bom: @@ -238,7 +238,7 @@ class SubcontractingController(StockController): and self._doc_before_save ): for row in self._doc_before_save.get("items"): - item_dict[row.name] = (row.item_code, row.qty + (row.get("rejected_qty") or 0)) + item_dict[row.name] = (row.item_code, row.received_qty) return item_dict @@ -264,7 +264,7 @@ class SubcontractingController(StockController): self.__reference_name.append(row.name) if (row.name not in item_dict) or ( row.item_code, - row.qty + (row.get("rejected_qty") or 0), + row.received_qty, ) != item_dict[row.name]: self.__changed_name.append(row.name) @@ -961,7 +961,7 @@ class SubcontractingController(StockController): ): qty = ( flt(bom_item.qty_consumed_per_unit) - * flt(row.qty + (row.get("rejected_qty") or 0)) + * flt(row.get("received_qty") or (row.qty + (row.get("rejected_qty") or 0))) * row.conversion_factor ) bom_item.main_item_code = row.item_code @@ -1086,6 +1086,12 @@ class SubcontractingController(StockController): if self.doctype not in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]: return + if ( + frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on") + == "BOM" + ): + return + for row in self.get(self.raw_material_table): key = (row.rm_item_code, row.main_item_code, row.get(self.subcontract_data.order_field)) if not self.__transferred_items or not self.__transferred_items.get(key): @@ -1278,22 +1284,28 @@ class SubcontractingController(StockController): if self.total_additional_costs: if self.distribute_additional_costs_based_on == "Amount": total_amt = sum( - flt(item.amount) for item in self.get("items") if not item.get("is_scrap_item") + flt(item.amount) + for item in self.get("items") + if not item.get("type") and not item.get("is_legacy_scrap_item") ) for item in self.items: - if not item.get("is_scrap_item"): + if not item.get("type") and not item.get("is_legacy_scrap_item"): item.additional_cost_per_qty = ( (item.amount * self.total_additional_costs) / total_amt ) / item.qty else: - total_qty = sum(flt(item.qty) for item in self.get("items") if not item.get("is_scrap_item")) + total_qty = sum( + flt(item.qty) + for item in self.get("items") + if not item.get("type") and not item.get("is_legacy_scrap_item") + ) additional_cost_per_qty = self.total_additional_costs / total_qty for item in self.items: - if not item.get("is_scrap_item"): + if not item.get("type") and not item.get("is_legacy_scrap_item"): item.additional_cost_per_qty = additional_cost_per_qty else: for item in self.items: - if not item.get("is_scrap_item"): + if not item.get("type") and not item.get("is_legacy_scrap_item"): item.additional_cost_per_qty = 0 @frappe.whitelist() diff --git a/erpnext/controllers/subcontracting_inward_controller.py b/erpnext/controllers/subcontracting_inward_controller.py index 1167cdcb59d..77a6a9ea19d 100644 --- a/erpnext/controllers/subcontracting_inward_controller.py +++ b/erpnext/controllers/subcontracting_inward_controller.py @@ -1,3 +1,5 @@ +from collections import defaultdict + import frappe from frappe import _, bold from frappe.query_builder import Case @@ -18,7 +20,7 @@ class SubcontractingInwardController: def on_submit_subcontracting_inward(self): self.update_inward_order_item() self.update_inward_order_received_items() - self.update_inward_order_scrap_items() + self.update_inward_order_secondary_items() self.create_stock_reservation_entries_for_inward() self.update_inward_order_status() @@ -28,7 +30,7 @@ class SubcontractingInwardController: self.validate_delivery() self.validate_receive_from_customer_cancel() self.update_inward_order_received_items() - self.update_inward_order_scrap_items() + self.update_inward_order_secondary_items() self.remove_reference_for_additional_items() self.update_inward_order_status() @@ -239,7 +241,8 @@ class SubcontractingInwardController: item for item in self.get("items") if not item.is_finished_item - and not item.is_scrap_item + and not item.type + and not item.is_legacy_scrap_item and frappe.get_cached_value("Item", item.item_code, "is_customer_provided_item") ] @@ -368,7 +371,9 @@ class SubcontractingInwardController: if self.subcontracting_inward_order: if self.purpose in ["Subcontracting Delivery", "Subcontracting Return", "Manufacture"]: for item in self.items: - if (item.is_finished_item or item.is_scrap_item) and item.valuation_rate == 0: + if ( + item.is_finished_item or item.type or item.is_legacy_scrap_item + ) and item.valuation_rate == 0: item.allow_zero_valuation_rate = 1 def validate_warehouse_(self): @@ -467,7 +472,7 @@ class SubcontractingInwardController: self.validate_delivery_on_save() else: for item in self.items: - if not item.is_scrap_item: + if not item.type and not item.is_legacy_scrap_item: delivered_qty, returned_qty = frappe.get_value( "Subcontracting Inward Order Item", item.scio_detail, @@ -519,7 +524,7 @@ class SubcontractingInwardController: if max_allowed_qty: max_allowed_qty = max_allowed_qty[0] else: - table = frappe.qb.DocType("Subcontracting Inward Order Scrap Item") + table = frappe.qb.DocType("Subcontracting Inward Order Secondary Item") query = ( frappe.qb.from_(table) .select((table.produced_qty - table.delivered_qty).as_("max_allowed_qty")) @@ -538,8 +543,8 @@ class SubcontractingInwardController: bold( frappe.get_cached_value( "Subcontracting Inward Order Item" - if not item.is_scrap_item - else "Subcontracting Inward Order Scrap Item", + if not item.type and not item.is_legacy_scrap_item + else "Subcontracting Inward Order Secondary Item", item.scio_detail, "stock_uom", ) @@ -590,9 +595,9 @@ class SubcontractingInwardController: ) for item in [item for item in self.items if not item.is_finished_item]: - if item.is_scrap_item: - scio_scrap_item = frappe.get_value( - "Subcontracting Inward Order Scrap Item", + if item.type or item.is_legacy_scrap_item: + scio_secondary_item = frappe.get_value( + "Subcontracting Inward Order Secondary Item", { "docstatus": 1, "item_code": item.item_code, @@ -603,12 +608,13 @@ class SubcontractingInwardController: as_dict=True, ) if ( - scio_scrap_item - and scio_scrap_item.delivered_qty > scio_scrap_item.produced_qty - item.transfer_qty + scio_secondary_item + and scio_secondary_item.delivered_qty + > scio_secondary_item.produced_qty - item.transfer_qty ): frappe.throw( _( - "Row #{0}: Cannot cancel this Manufacturing Stock Entry as quantity of Scrap Item {1} produced cannot be less than quantity delivered." + "Row #{0}: Cannot cancel this Manufacturing Stock Entry as quantity of Secondary Item {1} produced cannot be less than quantity delivered." ).format(item.idx, get_link_to_form("Item", item.item_code)) ) else: @@ -648,8 +654,8 @@ class SubcontractingInwardController: for item in self.items: doctype = ( "Subcontracting Inward Order Item" - if not item.is_scrap_item - else "Subcontracting Inward Order Scrap Item" + if not item.type and not item.is_legacy_scrap_item + else "Subcontracting Inward Order Secondary Item" ) frappe.db.set_value( doctype, @@ -715,6 +721,7 @@ class SubcontractingInwardController: "Subcontracting Inward Order Item", item.against_fg, "item_code" ), ) + scio_rm.flags.skip_docstatus_validation = True scio_rm.insert() scio_rm.submit() item.db_set("scio_detail", scio_rm.name) @@ -763,7 +770,11 @@ class SubcontractingInwardController: customer_warehouse = frappe.get_cached_value( "Subcontracting Inward Order", self.subcontracting_inward_order, "customer_warehouse" ) - items = [item for item in self.items if not item.is_finished_item and not item.is_scrap_item] + items = [ + item + for item in self.items + if not item.is_finished_item and not item.type and not item.is_legacy_scrap_item + ] item_code_wh = frappe._dict( { ( @@ -857,27 +868,28 @@ class SubcontractingInwardController: warehouse=extra_item.s_warehouse, is_additional_item=True, ) + doc.flags.skip_docstatus_validation = True doc.insert() doc.submit() - def update_inward_order_scrap_items(self): + def update_inward_order_secondary_items(self): if (scio := self.subcontracting_inward_order) and self.purpose == "Manufacture": - scrap_items_list = [item for item in self.items if item.is_scrap_item] - scrap_items = frappe._dict( - { - (item.item_code, item.t_warehouse): item.transfer_qty - if self._action == "submit" - else -item.transfer_qty - for item in scrap_items_list - } - ) - if scrap_items: - item_codes, warehouses = zip(*list(scrap_items.keys()), strict=True) + secondary_items_list = [item for item in self.items if item.type or item.is_legacy_scrap_item] + + secondary_items = defaultdict(float) + for item in secondary_items_list: + secondary_items[(item.item_code, item.t_warehouse)] += ( + item.transfer_qty if self._action == "submit" else -item.transfer_qty + ) + secondary_items = frappe._dict(secondary_items) + + if secondary_items: + item_codes, warehouses = zip(*list(secondary_items.keys()), strict=True) item_codes = list(item_codes) warehouses = list(warehouses) result = frappe.get_all( - "Subcontracting Inward Order Scrap Item", + "Subcontracting Inward Order Secondary Item", filters={ "item_code": ["in", item_codes], "warehouse": ["in", warehouses], @@ -890,7 +902,7 @@ class SubcontractingInwardController: ) if result: - scrap_item_dict = frappe._dict( + secondary_items_dict = frappe._dict( { (d.item_code, d.warehouse): frappe._dict( {"name": d.name, "produced_qty": d.produced_qty} @@ -900,45 +912,51 @@ class SubcontractingInwardController: ) deleted_docs = [] case_expr = Case() - table = frappe.qb.DocType("Subcontracting Inward Order Scrap Item") - for key, value in scrap_item_dict.items(): - if self._action == "cancel" and value.produced_qty - abs(scrap_items.get(key)) == 0: + table = frappe.qb.DocType("Subcontracting Inward Order Secondary Item") + for key, value in secondary_items_dict.items(): + if ( + self._action == "cancel" + and value.produced_qty - abs(secondary_items.get(key)) == 0 + ): deleted_docs.append(value.name) - frappe.delete_doc("Subcontracting Inward Order Scrap Item", value.name) + frappe.delete_doc("Subcontracting Inward Order Secondary Item", value.name) else: case_expr = case_expr.when( - table.name == value.name, value.produced_qty + scrap_items.get(key) + table.name == value.name, value.produced_qty + secondary_items.get(key) ) if final_list := list( - set([v.name for v in scrap_item_dict.values()]) - set(deleted_docs) + set([v.name for v in secondary_items_dict.values()]) - set(deleted_docs) ): frappe.qb.update(table).set(table.produced_qty, case_expr).where( (table.name.isin(final_list)) & (table.docstatus == 1) ).run() fg_item_code = next(fg for fg in self.items if fg.is_finished_item).item_code - for scrap_item in [ + for secondary_item in [ item - for item in scrap_items_list + for item in secondary_items_list if (item.item_code, item.t_warehouse) not in [(d.item_code, d.warehouse) for d in result] ]: doc = frappe.new_doc( - "Subcontracting Inward Order Scrap Item", + "Subcontracting Inward Order Secondary Item", parent=scio, parenttype="Subcontracting Inward Order", - parentfield="scrap_items", - idx=frappe.db.count("Subcontracting Inward Order Scrap Item", {"parent": scio}) + 1, - item_code=scrap_item.item_code, + parentfield="secondary_items", + idx=frappe.db.count("Subcontracting Inward Order Secondary Item", {"parent": scio}) + + 1, + item_code=secondary_item.item_code, fg_item_code=fg_item_code, - stock_uom=scrap_item.stock_uom, - warehouse=scrap_item.t_warehouse, - produced_qty=scrap_item.transfer_qty, + stock_uom=secondary_item.stock_uom, + warehouse=secondary_item.t_warehouse, + produced_qty=secondary_item.transfer_qty, + type=secondary_item.type, delivered_qty=0, reference_name=frappe.get_value( "Work Order", self.work_order, "subcontracting_inward_order_item" ), ) + doc.flags.skip_docstatus_validation = True doc.insert() doc.submit() @@ -965,7 +983,7 @@ class SubcontractingInwardController: and ( not frappe.db.exists("Subcontracting Inward Order Received Item", item.scio_detail) and not frappe.db.exists("Subcontracting Inward Order Item", item.scio_detail) - and not frappe.db.exists("Subcontracting Inward Order Scrap Item", item.scio_detail) + and not frappe.db.exists("Subcontracting Inward Order Secondary Item", item.scio_detail) ) ] for item in items: diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index b94426db475..1cd6f203ec2 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -26,11 +26,12 @@ from erpnext.utilities.regional import temporary_flag class calculate_taxes_and_totals: def __init__(self, doc: Document): self.doc = doc - frappe.flags.round_off_applicable_accounts = [] + frappe.flags.round_off_applicable_accounts = ( + get_round_off_applicable_accounts(self.doc.company, []) or [] + ) frappe.flags.round_row_wise_tax = frappe.get_single_value("Accounts Settings", "round_row_wise_tax") self._items = self.filter_rows() if self.doc.doctype == "Quotation" else self.doc.get("items") - get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts) self.calculate() def filter_rows(self): diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 553a7246fea..838c32c4276 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -5,7 +5,6 @@ import frappe from frappe import qb from frappe.query_builder.functions import Sum -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, getdate, nowdate from frappe.utils.data import getdate as convert_to_date @@ -20,6 +19,7 @@ from erpnext.buying.doctype.purchase_order.test_purchase_order import ( ) from erpnext.projects.doctype.project.test_project import make_project from erpnext.stock.doctype.item.test_item import create_item +from erpnext.tests.utils import ERPNextTestSuite def make_customer(customer_name, currency=None): @@ -51,7 +51,7 @@ def make_supplier(supplier_name, currency=None): return supplier_name -class TestAccountsController(IntegrationTestCase): +class TestAccountsController(ERPNextTestSuite): """ Test Exchange Gain/Loss booking on various scenarios. Test Cases are numbered for better organization @@ -72,9 +72,7 @@ class TestAccountsController(IntegrationTestCase): self.create_item() self.create_parties() self.clear_old_entries() - - def tearDown(self): - frappe.db.rollback() + frappe.flags.is_reverse_depr_entry = False def create_company(self): company_name = "_Test Company" @@ -810,9 +808,7 @@ class TestAccountsController(IntegrationTestCase): self.assertEqual(exc_je_for_si, []) self.assertEqual(exc_je_for_pe, []) - @IntegrationTestCase.change_settings( - "Stock Settings", {"allow_internal_transfer_at_arms_length_price": 1} - ) + @ERPNextTestSuite.change_settings("Stock Settings", {"allow_internal_transfer_at_arms_length_price": 1}) def test_16_internal_transfer_at_arms_length_price(self): from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_purchase_invoice from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse @@ -873,7 +869,7 @@ class TestAccountsController(IntegrationTestCase): self.assertEqual(pi.items[0].rate, arms_length_price) self.assertEqual(pi.items[0].valuation_rate, 100) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"exchange_gain_loss_posting_date": "Reconciliation Date"} ) def test_17_gain_loss_posting_date_for_normal_payment(self): @@ -936,9 +932,9 @@ class TestAccountsController(IntegrationTestCase): self.assertEqual(exc_je_for_si, []) self.assertEqual(exc_je_for_pe, []) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", - {"add_taxes_from_item_tax_template": 0, "add_taxes_from_taxes_and_charges_template": 1}, + {"add_taxes_from_taxes_and_charges_template": 1, "add_taxes_from_item_tax_template": 0}, ) def test_18_fetch_taxes_based_on_taxes_and_charges_template(self): # Create a Sales Taxes and Charges Template @@ -968,7 +964,7 @@ class TestAccountsController(IntegrationTestCase): self.assertEqual(sinv.total_taxes_and_charges, 4.5) - @IntegrationTestCase.change_settings( + @ERPNextTestSuite.change_settings( "Accounts Settings", {"add_taxes_from_item_tax_template": 1, "add_taxes_from_taxes_and_charges_template": 0}, ) @@ -1571,25 +1567,10 @@ class TestAccountsController(IntegrationTestCase): frappe.db.set_value("Company", self.company, "cost_center", cc) - def setup_dimensions(self): - # create dimension - from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import ( - create_dimension, - ) - - create_dimension() - # make it non-mandatory - loc = frappe.get_doc("Accounting Dimension", "Location") - for x in loc.dimension_defaults: - x.mandatory_for_bs = False - x.mandatory_for_pl = False - loc.save() - def test_90_dimensions_filter(self): """ Test workings of dimension filters """ - self.setup_dimensions() rate_in_account_currency = 1 # Invoices @@ -1657,7 +1638,6 @@ class TestAccountsController(IntegrationTestCase): self.assertEqual(len(pr.payments), 1) def test_91_cr_note_should_inherit_dimension(self): - self.setup_dimensions() rate_in_account_currency = 1 # Invoice @@ -1702,7 +1682,6 @@ class TestAccountsController(IntegrationTestCase): def test_92_dimension_inhertiance_exc_gain_loss(self): # Sales Invoice in Foreign Currency - self.setup_dimensions() rate_in_account_currency = 1 dpt = "Research & Development - _TC" @@ -1738,7 +1717,6 @@ class TestAccountsController(IntegrationTestCase): ) def test_93_dimension_inheritance_on_advance(self): - self.setup_dimensions() dpt = "Research & Development - _TC" adv = self.create_payment_entry(amount=1, source_exc_rate=85) @@ -2479,11 +2457,11 @@ class TestAccountsController(IntegrationTestCase): po.items[0].delivered_by_supplier = 1 po.save() - @IntegrationTestCase.change_settings("Global Defaults", {"use_posting_datetime_for_naming_documents": 1}) + @ERPNextTestSuite.change_settings("Global Defaults", {"use_posting_datetime_for_naming_documents": 1}) def test_document_naming_rule_based_on_posting_date(self): frappe.new_doc( "Document Naming Rule", document_type="Sales Invoice", prefix="SI-.MM.-.YYYY.-" - ).submit() + ).insert() si = create_sales_invoice(do_not_save=True) si.set_posting_time = 1 diff --git a/erpnext/controllers/tests/test_distributed_discount.py b/erpnext/controllers/tests/test_distributed_discount.py index 05db1496da8..4f4911c8537 100644 --- a/erpnext/controllers/tests/test_distributed_discount.py +++ b/erpnext/controllers/tests/test_distributed_discount.py @@ -1,11 +1,11 @@ -from frappe.tests import IntegrationTestCase - from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order +from erpnext.tests.utils import ERPNextTestSuite -class TestTaxesAndTotals(AccountsTestMixin, IntegrationTestCase): +class TestTaxesAndTotals(AccountsTestMixin, ERPNextTestSuite): + @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": 1}) def test_distributed_discount_amount(self): so = make_sales_order(do_not_save=1) so.apply_discount_on = "Net Total" @@ -27,6 +27,7 @@ class TestTaxesAndTotals(AccountsTestMixin, IntegrationTestCase): self.assertEqual(so.net_total, 1400) self.assertEqual(so.grand_total, 1400) + @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": 1}) def test_distributed_discount_amount_with_taxes(self): so = make_sales_order(do_not_save=1) so.apply_discount_on = "Grand Total" diff --git a/erpnext/controllers/tests/test_item_variant.py b/erpnext/controllers/tests/test_item_variant.py index 6bbe6cd035c..04634cf911a 100644 --- a/erpnext/controllers/tests/test_item_variant.py +++ b/erpnext/controllers/tests/test_item_variant.py @@ -1,16 +1,16 @@ import json import frappe -from frappe.tests import IntegrationTestCase from erpnext.controllers.item_variant import copy_attributes_to_variant, make_variant_item_code from erpnext.stock.doctype.item.test_item import set_item_variant_settings from erpnext.stock.doctype.quality_inspection.test_quality_inspection import ( create_quality_inspection_parameter, ) +from erpnext.tests.utils import ERPNextTestSuite -class TestItemVariant(IntegrationTestCase): +class TestItemVariant(ERPNextTestSuite): def test_tables_in_template_copied_to_variant(self): fields = [{"field_name": "quality_inspection_template"}] set_item_variant_settings(fields) diff --git a/erpnext/controllers/tests/test_item_wise_inventory_account.py b/erpnext/controllers/tests/test_item_wise_inventory_account.py index a783193d60e..2f45ea31466 100644 --- a/erpnext/controllers/tests/test_item_wise_inventory_account.py +++ b/erpnext/controllers/tests/test_item_wise_inventory_account.py @@ -3,7 +3,6 @@ import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, today from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom @@ -12,9 +11,10 @@ from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt +from erpnext.tests.utils import ERPNextTestSuite -class TestItemWiseInventoryAccount(IntegrationTestCase): +class TestItemWiseInventoryAccount(ERPNextTestSuite): def setUp(self): self.company = make_company() self.company_abbr = frappe.db.get_value("Company", self.company, "abbr") @@ -273,7 +273,7 @@ class TestItemWiseInventoryAccount(IntegrationTestCase): } for row in items: - self.make_item_group(items[row]["item_group"]) + self._make_item_group(items[row]["item_group"]) inventory_account_dict = frappe._dict() for item_name, item_data in items.items(): @@ -357,7 +357,7 @@ class TestItemWiseInventoryAccount(IntegrationTestCase): } for row in items: - self.make_item_group(items[row]["item_group"]) + self._make_item_group(items[row]["item_group"]) inventory_account_dict = frappe._dict() for item_name, item_data in items.items(): @@ -451,7 +451,7 @@ class TestItemWiseInventoryAccount(IntegrationTestCase): self.assertEqual(sle_value[0].value, gl_value, f"GL Entry not created for {item_code} correctly") - def make_item_group(self, item_name): + def _make_item_group(self, item_name): if not frappe.db.exists("Item Group", item_name): item_group = frappe.get_doc( { diff --git a/erpnext/controllers/tests/test_item_wise_tax_details.py b/erpnext/controllers/tests/test_item_wise_tax_details.py index 138429dcee9..93de1d8890d 100644 --- a/erpnext/controllers/tests/test_item_wise_tax_details.py +++ b/erpnext/controllers/tests/test_item_wise_tax_details.py @@ -1,8 +1,9 @@ import frappe -from frappe.tests.utils import FrappeTestCase + +from erpnext.tests.utils import ERPNextTestSuite -class TestTaxesAndTotals(FrappeTestCase): +class TestTaxesAndTotals(ERPNextTestSuite): def setUp(self): self.doc = frappe.get_doc( { diff --git a/erpnext/controllers/tests/test_mapper.py b/erpnext/controllers/tests/test_mapper.py index 5d20684ff03..3a804d78b8f 100644 --- a/erpnext/controllers/tests/test_mapper.py +++ b/erpnext/controllers/tests/test_mapper.py @@ -3,13 +3,15 @@ import json import frappe import frappe.utils from frappe.model import mapper -from frappe.tests import IntegrationTestCase from frappe.utils import add_months, nowdate -EXTRA_TEST_RECORD_DEPENDENCIES = ["Item"] +from erpnext.tests.utils import ERPNextTestSuite -class TestMapper(IntegrationTestCase): +class TestMapper(ERPNextTestSuite): + def setUp(self): + self.load_test_records("Sales Order") + def test_map_docs(self): """Test mapping of multiple source docs on a single target doc""" @@ -37,6 +39,7 @@ class TestMapper(IntegrationTestCase): "order_type": "Sales", "transaction_date": nowdate(), "valid_till": add_months(nowdate(), 1), + "company": "_Test Company", } ) for item in item_list: @@ -63,6 +66,6 @@ class TestMapper(IntegrationTestCase): "uom": "_Test UOM", } ) - so = frappe.get_doc(frappe.get_test_records("Sales Order")[0]) + so = frappe.get_doc(self.globalTestRecords["Sales Order"][0]) so.insert(ignore_permissions=True) return so, [item.item_code] diff --git a/erpnext/controllers/tests/test_qty_based_taxes.py b/erpnext/controllers/tests/test_qty_based_taxes.py index 27b4d57aa3c..a22a029436b 100644 --- a/erpnext/controllers/tests/test_qty_based_taxes.py +++ b/erpnext/controllers/tests/test_qty_based_taxes.py @@ -1,14 +1,15 @@ from uuid import uuid4 as _uuid4 import frappe -from frappe.tests import IntegrationTestCase + +from erpnext.tests.utils import ERPNextTestSuite def uuid4(): return str(_uuid4()) -class TestTaxes(IntegrationTestCase): +class TestTaxes(ERPNextTestSuite): def setUp(self): self.company = frappe.get_doc( { @@ -124,12 +125,3 @@ class TestTaxes(IntegrationTestCase): doc.insert() self.assertEqual(doc.taxes[0].tax_amount, 600) self.created_docs.append(doc) - - def tearDown(self): - for doc in self.created_docs: - doc.delete() - self.item.delete() - self.item_group.delete() - self.item_tax_template.delete() - self.account.delete() - self.company.delete() diff --git a/erpnext/controllers/tests/test_queries.py b/erpnext/controllers/tests/test_queries.py index 1f17e91231e..755961331db 100644 --- a/erpnext/controllers/tests/test_queries.py +++ b/erpnext/controllers/tests/test_queries.py @@ -13,19 +13,7 @@ def add_default_params(func, doctype): return partial(func, doctype=doctype, txt="", searchfield="name", start=0, page_len=20, filters=None) -EXTRA_TEST_RECORD_DEPENDENCIES = ["Item", "BOM", "Account"] - - class TestQueries(ERPNextTestSuite): - # All tests are based on self.globalTestRecords[doctype] - - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.make_employees() - cls.make_leads() - cls.make_projects() - def assert_nested_in(self, item, container): self.assertIn(item, [vals for tuples in container for vals in tuples]) @@ -96,6 +84,8 @@ class TestQueries(ERPNextTestSuite): self.assertGreaterEqual(frappe.db.count("UOM", {"enabled": 1}), 10) def test_employee_query_with_user_permissions(self): + employee = frappe.db.get_all("Employee", {"first_name": "_Test Employee"})[0].name + # party field is a dynamic link field in Payment Entry doctype with ignore_user_permissions=0 ps = make_property_setter( doctype="Payment Entry", @@ -110,7 +100,7 @@ class TestQueries(ERPNextTestSuite): { "user": user.name, "doctype": "Employee", - "docname": self.employees[0].name, + "docname": employee, "is_default": 1, "apply_to_all_doctypes": 1, "applicable_doctypes": [], @@ -118,7 +108,7 @@ class TestQueries(ERPNextTestSuite): } ) - with self.set_user(user.name): + with ERPNextTestSuite.set_user(self, user.name): params = { "doctype": "Employee", "txt": "", diff --git a/erpnext/controllers/tests/test_reactivity.py b/erpnext/controllers/tests/test_reactivity.py index 8ba58419581..2f8b3a06784 100644 --- a/erpnext/controllers/tests/test_reactivity.py +++ b/erpnext/controllers/tests/test_reactivity.py @@ -1,12 +1,12 @@ import frappe from frappe import qb -from frappe.tests import IntegrationTestCase from frappe.utils import today from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.tests.utils import ERPNextTestSuite -class TestReactivity(AccountsTestMixin, IntegrationTestCase): +class TestReactivity(AccountsTestMixin, ERPNextTestSuite): def setUp(self): self.create_company() self.create_customer() @@ -15,9 +15,6 @@ class TestReactivity(AccountsTestMixin, IntegrationTestCase): self.create_price_list() self.clear_old_entries() - def tearDown(self): - frappe.db.rollback() - def disable_dimensions(self): res = frappe.db.get_all("Accounting Dimension", filters={"disabled": False}) for x in res: diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py index b6c21fa0b45..0dbacb3c22d 100644 --- a/erpnext/controllers/tests/test_subcontracting_controller.py +++ b/erpnext/controllers/tests/test_subcontracting_controller.py @@ -5,8 +5,7 @@ import copy from collections import defaultdict import frappe -from frappe.tests import IntegrationTestCase -from frappe.utils import cint +from frappe.utils import cint, flt from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.controllers.subcontracting_controller import ( @@ -23,9 +22,10 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( make_subcontracting_receipt, ) +from erpnext.tests.utils import ERPNextTestSuite -class TestSubcontractingController(IntegrationTestCase): +class TestSubcontractingController(ERPNextTestSuite): def setUp(self): make_subcontracted_items() make_raw_materials() @@ -75,6 +75,7 @@ class TestSubcontractingController(IntegrationTestCase): sco.create_raw_materials_supplied_or_received() self.assertIsNotNone(sco.supplied_items) + @ERPNextTestSuite.change_settings("Buying Settings", {"allow_multiple_items": 1}) def test_sco_with_bom(self): """ - Set backflush based on BOM. @@ -500,8 +501,8 @@ class TestSubcontractingController(IntegrationTestCase): scr1.items[0].qty = 2 add_second_row_in_scr(scr1) scr1.flags.ignore_mandatory = True - scr1.save() scr1.set_missing_values() + scr1.save() scr1.submit() for _key, value in get_supplied_items(scr1).items(): @@ -512,8 +513,8 @@ class TestSubcontractingController(IntegrationTestCase): scr2.items[0].qty = 2 add_second_row_in_scr(scr2) scr2.flags.ignore_mandatory = True - scr2.save() scr2.set_missing_values() + scr2.save() scr2.submit() for _key, value in get_supplied_items(scr2).items(): @@ -522,8 +523,8 @@ class TestSubcontractingController(IntegrationTestCase): scr3 = make_subcontracting_receipt(sco.name) scr3.items[0].qty = 2 scr3.flags.ignore_mandatory = True - scr3.save() scr3.set_missing_values() + scr3.save() scr3.submit() for _key, value in get_supplied_items(scr3).items(): @@ -741,6 +742,7 @@ class TestSubcontractingController(IntegrationTestCase): "doctype": "Serial No", "item_code": "Subcontracted SRM Item 2", "serial_no": serial_no, + "company": "_Test Company", } ).insert() @@ -1162,6 +1164,54 @@ class TestSubcontractingController(IntegrationTestCase): self.assertEqual([item.rm_item_code for item in sco.supplied_items], expected) + def test_co_by_product(self): + frappe.set_value("UOM", "Nos", "must_be_whole_number", 0) + + fg_item = make_item("FG Item", properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name + rm_item = make_item("RM Item", properties={"is_stock_item": 1}).name + scrap_item = make_item("Scrap Item", properties={"is_stock_item": 1}).name + make_bom( + item=fg_item, raw_materials=[rm_item], scrap_items=[scrap_item], process_loss_percentage=10 + ).name + + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 11", + "qty": 5, + "rate": 100, + "fg_item": fg_item, + "fg_item_qty": 5, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr1 = make_subcontracting_receipt(sco.name) + scr1.get_secondary_items() + scr1.save() + + self.assertEqual(scr1.items[0].received_qty, 5) + self.assertEqual(scr1.items[0].process_loss_qty, 0.5) + self.assertEqual(scr1.items[0].qty, 4.5) + self.assertEqual(scr1.items[0].rate, 200) + self.assertEqual(scr1.items[0].amount, 900) + + self.assertEqual(scr1.items[1].item_code, scrap_item) + self.assertEqual(scr1.items[1].received_qty, 5) + self.assertEqual(scr1.items[1].process_loss_qty, 0.5) + self.assertEqual(scr1.items[1].qty, 4.5) + self.assertEqual(flt(scr1.items[1].rate, 3), 11.111) + self.assertEqual(scr1.items[1].amount, 50) + + frappe.set_value("UOM", "Nos", "must_be_whole_number", 1) + def add_second_row_in_scr(scr): item_dict = {} @@ -1512,7 +1562,7 @@ def make_subcontracted_item(**args): }, ) - args.raw_materials = ["_Test FG Item", "Test Extra Item 1"] + args.raw_materials = ["_Test Extra Item 1", "Test Extra Item 2"] if not frappe.db.get_value("BOM", {"item": args.item_code}, "name"): make_bom(item=args.item_code, raw_materials=args.get("raw_materials")) diff --git a/erpnext/controllers/tests/test_taxes_and_totals.py b/erpnext/controllers/tests/test_taxes_and_totals.py new file mode 100644 index 00000000000..ac713415970 --- /dev/null +++ b/erpnext/controllers/tests/test_taxes_and_totals.py @@ -0,0 +1,31 @@ +from unittest.mock import patch + +import frappe + +from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order +from erpnext.tests.utils import ERPNextTestSuite + + +class TestTaxesAndTotals(ERPNextTestSuite): + def test_regional_round_off_accounts(self): + """ + Regional overrides cannot extend the list in-place — the return + value must be assigned back to frappe.flags.round_off_applicable_accounts. + """ + test_account = "_Test Round Off Account" + + def mock_regional(company, account_list: list) -> list: + # Simulates a regional override + account_list.extend([test_account]) + return account_list + + so = make_sales_order(do_not_save=True) + + with patch( + "erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts", + mock_regional, + ): + calculate_taxes_and_totals(so) + + self.assertIn(test_account, frappe.flags.round_off_applicable_accounts) diff --git a/erpnext/controllers/tests/test_transaction_base.py b/erpnext/controllers/tests/test_transaction_base.py index fca8c03c5fb..3348892ae21 100644 --- a/erpnext/controllers/tests/test_transaction_base.py +++ b/erpnext/controllers/tests/test_transaction_base.py @@ -1,8 +1,9 @@ import frappe -from frappe.tests import IntegrationTestCase + +from erpnext.tests.utils import ERPNextTestSuite -class TestUtils(IntegrationTestCase): +class TestUtils(ERPNextTestSuite): def test_reset_default_field_value(self): doc = frappe.get_doc( { diff --git a/erpnext/controllers/trends.py b/erpnext/controllers/trends.py index 752ec87d501..bde00074ecb 100644 --- a/erpnext/controllers/trends.py +++ b/erpnext/controllers/trends.py @@ -55,6 +55,14 @@ def validate_filters(filters): if filters.get("based_on") == filters.get("group_by"): frappe.throw(_("'Based On' and 'Group By' can not be same")) + if filters.get("period_based_on") and filters.period_based_on not in ["bill_date", "posting_date"]: + frappe.throw( + msg=_("{0} can be either {1} or {2}.").format( + frappe.bold("Period based On"), frappe.bold("Posting Date"), frappe.bold("Billing Date") + ), + title=_("Invalid Filter"), + ) + def get_data(filters, conditions): data = [] diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index b41064ce9e3..9a213aea5fc 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -120,7 +120,8 @@ class Appointment(Document): self.auto_assign() self.create_calendar_event() self.save(ignore_permissions=True) - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() def create_lead_and_link(self): # Return if already linked diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index ed8ca5c1bff..83eacca83b6 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -3,7 +3,8 @@ import datetime import frappe -from frappe.tests import IntegrationTestCase + +from erpnext.tests.utils import ERPNextTestSuite LEAD_EMAIL = "test_appointment_lead@example.com" @@ -25,13 +26,9 @@ def create_test_appointment(): return test_appointment -class TestAppointment(IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - frappe.db.delete("Lead", {"email_id": LEAD_EMAIL}) - +class TestAppointment(ERPNextTestSuite): def setUp(self): + frappe.db.delete("Lead", {"email_id": LEAD_EMAIL}) self.test_appointment = create_test_appointment() self.test_appointment.set_verified(self.test_appointment.customer_email) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json index d1f70af86f1..b79e974e301 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -102,10 +102,10 @@ } ], "grid_page_length": 50, - "hide_toolbar": 1, + "hide_toolbar": 0, "issingle": 1, "links": [], - "modified": "2026-01-02 18:18:46.617101", + "modified": "2026-03-16 13:28:21.198138", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", diff --git a/erpnext/crm/doctype/appointment_booking_settings/test_appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/test_appointment_booking_settings.py index f06d6f807f3..6a78d53ba49 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/test_appointment_booking_settings.py +++ b/erpnext/crm/doctype/appointment_booking_settings/test_appointment_booking_settings.py @@ -2,8 +2,8 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestAppointmentBookingSettings(IntegrationTestCase): +class TestAppointmentBookingSettings(ERPNextTestSuite): pass diff --git a/erpnext/crm/doctype/campaign/test_campaign.py b/erpnext/crm/doctype/campaign/test_campaign.py index 7442fb7c731..8876e640475 100644 --- a/erpnext/crm/doctype/campaign/test_campaign.py +++ b/erpnext/crm/doctype/campaign/test_campaign.py @@ -2,8 +2,8 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestCampaign(IntegrationTestCase): +class TestCampaign(ERPNextTestSuite): pass diff --git a/erpnext/crm/doctype/competitor/test_competitor.py b/erpnext/crm/doctype/competitor/test_competitor.py index 4b24378337e..f348014224b 100644 --- a/erpnext/crm/doctype/competitor/test_competitor.py +++ b/erpnext/crm/doctype/competitor/test_competitor.py @@ -2,8 +2,8 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestCompetitor(IntegrationTestCase): +class TestCompetitor(ERPNextTestSuite): pass diff --git a/erpnext/crm/doctype/contract/test_contract.py b/erpnext/crm/doctype/contract/test_contract.py index de7aba10e1a..03c3864c700 100644 --- a/erpnext/crm/doctype/contract/test_contract.py +++ b/erpnext/crm/doctype/contract/test_contract.py @@ -2,11 +2,12 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import add_days, nowdate +from erpnext.tests.utils import ERPNextTestSuite -class TestContract(IntegrationTestCase): + +class TestContract(ERPNextTestSuite): def setUp(self): frappe.db.sql("delete from `tabContract`") self.contract_doc = get_contract() diff --git a/erpnext/crm/doctype/contract_fulfilment_checklist/test_contract_fulfilment_checklist.py b/erpnext/crm/doctype/contract_fulfilment_checklist/test_contract_fulfilment_checklist.py index f3cfec6ba0e..d19492ec399 100644 --- a/erpnext/crm/doctype/contract_fulfilment_checklist/test_contract_fulfilment_checklist.py +++ b/erpnext/crm/doctype/contract_fulfilment_checklist/test_contract_fulfilment_checklist.py @@ -1,8 +1,8 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestContractFulfilmentChecklist(IntegrationTestCase): +class TestContractFulfilmentChecklist(ERPNextTestSuite): pass diff --git a/erpnext/crm/doctype/contract_template/contract_template.json b/erpnext/crm/doctype/contract_template/contract_template.json index 223464d3eb8..baa6b289005 100644 --- a/erpnext/crm/doctype/contract_template/contract_template.json +++ b/erpnext/crm/doctype/contract_template/contract_template.json @@ -56,7 +56,7 @@ } ], "links": [], - "modified": "2024-03-27 13:06:46.495091", + "modified": "2026-03-25 19:27:19.162421", "modified_by": "Administrator", "module": "CRM", "name": "Contract Template", @@ -75,44 +75,36 @@ "write": 1 }, { - "create": 1, - "delete": 1, "email": 1, "export": 1, "print": 1, "read": 1, "report": 1, "role": "Sales Manager", - "share": 1, - "write": 1 + "share": 1 }, { - "create": 1, - "delete": 1, "email": 1, "export": 1, "print": 1, "read": 1, "report": 1, "role": "Purchase Manager", - "share": 1, - "write": 1 + "share": 1 }, { - "create": 1, - "delete": 1, "email": 1, "export": 1, "print": 1, "read": 1, "report": 1, "role": "HR Manager", - "share": 1, - "write": 1 + "share": 1 } ], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/crm/doctype/contract_template/test_contract_template.py b/erpnext/crm/doctype/contract_template/test_contract_template.py index b4271fad8a1..6362da2afb7 100644 --- a/erpnext/crm/doctype/contract_template/test_contract_template.py +++ b/erpnext/crm/doctype/contract_template/test_contract_template.py @@ -1,8 +1,8 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestContractTemplate(IntegrationTestCase): +class TestContractTemplate(ERPNextTestSuite): pass diff --git a/erpnext/crm/doctype/crm_settings/crm_settings.json b/erpnext/crm/doctype/crm_settings/crm_settings.json index 3f231d460ab..4760d504a57 100644 --- a/erpnext/crm/doctype/crm_settings/crm_settings.json +++ b/erpnext/crm/doctype/crm_settings/crm_settings.json @@ -101,12 +101,12 @@ } ], "grid_page_length": 50, - "hide_toolbar": 1, + "hide_toolbar": 0, "icon": "fa fa-cog", "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-01-02 18:18:52.204988", + "modified": "2026-03-16 13:28:19.573964", "modified_by": "Administrator", "module": "CRM", "name": "CRM Settings", diff --git a/erpnext/crm/doctype/crm_settings/test_crm_settings.py b/erpnext/crm/doctype/crm_settings/test_crm_settings.py index 722810264f6..64a5addefcb 100644 --- a/erpnext/crm/doctype/crm_settings/test_crm_settings.py +++ b/erpnext/crm/doctype/crm_settings/test_crm_settings.py @@ -2,8 +2,8 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestCRMSettings(IntegrationTestCase): +class TestCRMSettings(ERPNextTestSuite): pass diff --git a/erpnext/crm/doctype/email_campaign/test_email_campaign.py b/erpnext/crm/doctype/email_campaign/test_email_campaign.py index f865ab05273..02524421327 100644 --- a/erpnext/crm/doctype/email_campaign/test_email_campaign.py +++ b/erpnext/crm/doctype/email_campaign/test_email_campaign.py @@ -2,8 +2,8 @@ # See license.txt # import frappe -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestEmailCampaign(IntegrationTestCase): +class TestEmailCampaign(ERPNextTestSuite): pass diff --git a/erpnext/crm/doctype/lead/test_lead.py b/erpnext/crm/doctype/lead/test_lead.py index 1ccaa5f8050..6f9692e10f2 100644 --- a/erpnext/crm/doctype/lead/test_lead.py +++ b/erpnext/crm/doctype/lead/test_lead.py @@ -10,19 +10,16 @@ from erpnext.tests.utils import ERPNextTestSuite class TestLead(ERPNextTestSuite): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.make_leads() - def test_make_customer(self): from erpnext.crm.doctype.lead.lead import make_customer + lead = frappe.db.get_all("Lead", {"lead_name": "_Test Lead"})[0].name + frappe.delete_doc_if_exists("Customer", "_Test Lead") - customer = make_customer(self.leads[0].name) + customer = make_customer(lead) self.assertEqual(customer.doctype, "Customer") - self.assertEqual(customer.lead_name, self.leads[0].name) + self.assertEqual(customer.lead_name, lead) customer.company = "_Test Company" customer.customer_group = "_Test Customer Group" @@ -46,9 +43,10 @@ class TestLead(ERPNextTestSuite): def test_make_customer_from_organization(self): from erpnext.crm.doctype.lead.lead import make_customer - customer = make_customer(self.leads[1].name) + lead = frappe.db.get_all("Lead", {"lead_name": "_Test Lead 1"})[0].name + customer = make_customer(lead) self.assertEqual(customer.doctype, "Customer") - self.assertEqual(customer.lead_name, self.leads[1].name) + self.assertEqual(customer.lead_name, lead) customer.company = "_Test Company" customer.customer_group = "_Test Customer Group" @@ -125,6 +123,7 @@ class TestLead(ERPNextTestSuite): create_todo("followup", "Lead", lead.name) opportunity = make_opportunity(lead.name) + opportunity.company = "_Test Company" opportunity.save() self.assertEqual(opportunity.get("party_name"), lead.name) diff --git a/erpnext/crm/doctype/market_segment/test_market_segment.py b/erpnext/crm/doctype/market_segment/test_market_segment.py index 02aa6a2e7c9..f194660eb77 100644 --- a/erpnext/crm/doctype/market_segment/test_market_segment.py +++ b/erpnext/crm/doctype/market_segment/test_market_segment.py @@ -1,8 +1,8 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestMarketSegment(IntegrationTestCase): +class TestMarketSegment(ERPNextTestSuite): pass diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py index 4add7c4074a..a1b2787b5ed 100644 --- a/erpnext/crm/doctype/opportunity/test_opportunity.py +++ b/erpnext/crm/doctype/opportunity/test_opportunity.py @@ -12,14 +12,6 @@ from erpnext.tests.utils import ERPNextTestSuite class TestOpportunity(ERPNextTestSuite): - @classmethod - def setUpClass(cls): - super().setUpClass() - # Only first lead is required - # TODO: dynamically generate limited test records - cls.make_leads() - cls.make_opportunities() - @classmethod def make_opportunities(cls): records = [ @@ -29,6 +21,7 @@ class TestOpportunity(ERPNextTestSuite): "opportunity_from": "Lead", "enquiry_type": "Sales", "party_name": cls.leads[0].name, + "company": cls.companies[0].name, "transaction_date": "2013-12-12", "items": [ {"item_name": "Test Item", "description": "Some description", "qty": 5, "rate": 100} @@ -55,7 +48,7 @@ class TestOpportunity(ERPNextTestSuite): self.assertEqual(doc.status, "Quotation") def test_make_new_lead_if_required(self): - opp_doc = make_opportunity_from_lead() + opp_doc = make_opportunity_from_lead("_Test Company") self.assertTrue(opp_doc.party_name) self.assertEqual(opp_doc.opportunity_from, "Lead") @@ -99,7 +92,7 @@ class TestOpportunity(ERPNextTestSuite): create_communication(opp_doc.doctype, opp_doc.name, opp_doc.contact_email) -def make_opportunity_from_lead(): +def make_opportunity_from_lead(company): new_lead_email_id = f"new{random_string(5)}@example.com" args = { "doctype": "Opportunity", @@ -107,6 +100,7 @@ def make_opportunity_from_lead(): "opportunity_type": "Sales", "with_items": 0, "transaction_date": today(), + "company": company, } # new lead should be created against the new.opportunity@example.com opp_doc = frappe.get_doc(args).insert(ignore_permissions=True) diff --git a/erpnext/crm/doctype/opportunity_type/test_opportunity_type.py b/erpnext/crm/doctype/opportunity_type/test_opportunity_type.py index 1e35529e69f..f04d536a406 100644 --- a/erpnext/crm/doctype/opportunity_type/test_opportunity_type.py +++ b/erpnext/crm/doctype/opportunity_type/test_opportunity_type.py @@ -1,8 +1,8 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestOpportunityType(IntegrationTestCase): +class TestOpportunityType(ERPNextTestSuite): pass diff --git a/erpnext/crm/doctype/prospect/test_prospect.py b/erpnext/crm/doctype/prospect/test_prospect.py index a349e2d978f..92158a30dbb 100644 --- a/erpnext/crm/doctype/prospect/test_prospect.py +++ b/erpnext/crm/doctype/prospect/test_prospect.py @@ -2,20 +2,21 @@ # See license.txt import frappe -from frappe.tests import IntegrationTestCase from frappe.utils import random_string from erpnext.crm.doctype.lead.lead import add_lead_to_prospect from erpnext.crm.doctype.lead.test_lead import make_lead +from erpnext.tests.utils import ERPNextTestSuite -class TestProspect(IntegrationTestCase): +class TestProspect(ERPNextTestSuite): def test_add_lead_to_prospect_and_address_linking(self): + company = "_Test Company" lead_doc = make_lead() address_doc = make_address(address_title=lead_doc.name) address_doc.append("links", {"link_doctype": lead_doc.doctype, "link_name": lead_doc.name}) address_doc.save() - prospect_doc = make_prospect() + prospect_doc = make_prospect(company=company, company_name=company) add_lead_to_prospect(lead_doc.name, prospect_doc.name) prospect_doc.reload() lead_exists_in_prosoect = False @@ -36,6 +37,7 @@ class TestProspect(IntegrationTestCase): "doctype": "Prospect", "company_name": "_Test Prospect", "customer_group": "_Test Customer Group", + "company": "_Test Company", } ) prospect.insert() @@ -57,6 +59,7 @@ def make_prospect(**args): { "doctype": "Prospect", "company_name": args.company_name or f"_Test Company {random_string(3)}", + "company": args.company, } ).insert() diff --git a/erpnext/crm/doctype/sales_stage/test_sales_stage.py b/erpnext/crm/doctype/sales_stage/test_sales_stage.py index 839e18c23e1..1703382bcb4 100644 --- a/erpnext/crm/doctype/sales_stage/test_sales_stage.py +++ b/erpnext/crm/doctype/sales_stage/test_sales_stage.py @@ -1,8 +1,8 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from frappe.tests import IntegrationTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestSalesStage(IntegrationTestCase): +class TestSalesStage(ERPNextTestSuite): pass diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py b/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py index 10447f39c92..4d5ba1dc9a2 100644 --- a/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py +++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py @@ -1,21 +1,14 @@ -from frappe.tests import IntegrationTestCase - from erpnext.crm.report.opportunity_summary_by_sales_stage.opportunity_summary_by_sales_stage import ( execute, ) from erpnext.crm.report.sales_pipeline_analytics.test_sales_pipeline_analytics import ( - create_company, - create_customer, create_opportunity, ) +from erpnext.tests.utils import ERPNextTestSuite -class TestOpportunitySummaryBySalesStage(IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - create_company() - create_customer() +class TestOpportunitySummaryBySalesStage(ERPNextTestSuite): + def setUp(self): create_opportunity() def test_opportunity_summary_by_sales_stage(self): diff --git a/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py index 0bb90923e57..49247b524f5 100644 --- a/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py +++ b/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py @@ -1,13 +1,11 @@ import frappe -from frappe.tests import IntegrationTestCase from erpnext.crm.report.sales_pipeline_analytics.sales_pipeline_analytics import execute +from erpnext.tests.utils import ERPNextTestSuite -class TestSalesPipelineAnalytics(IntegrationTestCase): +class TestSalesPipelineAnalytics(ERPNextTestSuite): def setUp(self): - create_company() - create_customer() create_opportunity() def test_sales_pipeline_analytics(self): @@ -179,23 +177,6 @@ class TestSalesPipelineAnalytics(IntegrationTestCase): self.assertEqual(expected_data, report[1]) -def create_company(): - doc = frappe.db.exists("Company", "Best Test") - if not doc: - doc = frappe.new_doc("Company") - doc.company_name = "Best Test" - doc.default_currency = "INR" - doc.insert() - - -def create_customer(): - doc = frappe.db.exists("Customer", "_Test NC") - if not doc: - doc = frappe.new_doc("Customer") - doc.customer_name = "_Test NC" - doc.insert() - - def create_opportunity(): doc = frappe.db.exists({"doctype": "Opportunity", "party_name": "_Test NC"}) if not doc: diff --git a/erpnext/desktop_icon/banking.json b/erpnext/desktop_icon/banking.json index 66ee8c7b318..6e55cfa0d02 100644 --- a/erpnext/desktop_icon/banking.json +++ b/erpnext/desktop_icon/banking.json @@ -10,7 +10,7 @@ "label": "Banking", "link_to": "Banking", "link_type": "Workspace Sidebar", - "modified": "2026-01-12 12:29:48.687545", + "modified": "2026-02-12 12:29:48.687545", "modified_by": "Administrator", "name": "Banking", "owner": "Administrator", diff --git a/erpnext/edi/doctype/code_list/code_list.py b/erpnext/edi/doctype/code_list/code_list.py index 8957c6565b9..e723157e7a0 100644 --- a/erpnext/edi/doctype/code_list/code_list.py +++ b/erpnext/edi/doctype/code_list/code_list.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING import frappe from frappe.model.document import Document +from frappe.utils import escape_html if TYPE_CHECKING: from lxml.etree import Element @@ -63,14 +64,16 @@ class CodeList(Document): def from_genericode(self, root: "Element"): """Extract Code List details from genericode XML""" - self.title = root.find(".//Identification/ShortName").text + self.title = escape_html(root.find(".//Identification/ShortName").text) self.version = root.find(".//Identification/Version").text self.canonical_uri = root.find(".//CanonicalUri").text # optionals - self.description = getattr(root.find(".//Identification/LongName"), "text", None) - self.publisher = getattr(root.find(".//Identification/Agency/ShortName"), "text", None) + self.description = escape_html(getattr(root.find(".//Identification/LongName"), "text", None)) + self.publisher = escape_html(getattr(root.find(".//Identification/Agency/ShortName"), "text", None)) if not self.publisher: - self.publisher = getattr(root.find(".//Identification/Agency/LongName"), "text", None) + self.publisher = escape_html( + getattr(root.find(".//Identification/Agency/LongName"), "text", None) + ) self.publisher_id = getattr(root.find(".//Identification/Agency/Identifier"), "text", None) self.url = getattr(root.find(".//Identification/LocationUri"), "text", None) diff --git a/erpnext/edi/doctype/code_list/code_list_import.py b/erpnext/edi/doctype/code_list/code_list_import.py index 3909eb22766..71cb7d0f82d 100644 --- a/erpnext/edi/doctype/code_list/code_list_import.py +++ b/erpnext/edi/doctype/code_list/code_list_import.py @@ -3,6 +3,7 @@ import json import frappe import requests from frappe import _ +from frappe.utils import escape_html from lxml import etree URL_PREFIXES = ("http://", "https://") @@ -32,7 +33,12 @@ def import_genericode(): content = f.read() # Parse the xml content - parser = etree.XMLParser(remove_blank_text=True) + parser = etree.XMLParser( + remove_blank_text=True, + resolve_entities=False, + load_dtd=False, + no_network=True, + ) try: root = etree.fromstring(content, parser=parser) except Exception as e: @@ -104,7 +110,7 @@ def get_genericode_columns_and_examples(root): # Get column names for column in root.findall(".//Column"): - column_id = column.get("Id") + column_id = escape_html(column.get("Id")) columns.append(column_id) example_values[column_id] = [] filterable_columns[column_id] = set() @@ -112,7 +118,7 @@ def get_genericode_columns_and_examples(root): # Get all values and count unique occurrences for row in root.findall(".//SimpleCodeList/Row"): for value in row.findall("Value"): - column_id = value.get("ColumnRef") + column_id = escape_html(value.get("ColumnRef")) if column_id not in columns: # Handle undeclared column columns.append(column_id) @@ -123,7 +129,7 @@ def get_genericode_columns_and_examples(root): if simple_value is None: continue - filterable_columns[column_id].add(simple_value.text) + filterable_columns[column_id].add(escape_html(simple_value.text)) # Get example values (up to 3) and filter columns with cardinality <= 5 for row in root.findall(".//SimpleCodeList/Row")[:3]: @@ -133,7 +139,7 @@ def get_genericode_columns_and_examples(root): if simple_value is None: continue - example_values[column_id].append(simple_value.text) + example_values[column_id].append(escape_html(simple_value.text)) filterable_columns = {k: list(v) for k, v in filterable_columns.items() if len(v) <= 5} diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py index 51b2519c659..12105703772 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py @@ -3,7 +3,6 @@ import json import frappe -from frappe.tests import IntegrationTestCase from frappe.utils.response import json_handler from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import ( @@ -13,22 +12,10 @@ from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import ( get_plaid_configuration, new_bank_transaction, ) +from erpnext.tests.utils import ERPNextTestSuite -class TestPlaidSettings(IntegrationTestCase): - def setUp(self): - pass - - def tearDown(self): - for bt in frappe.get_all("Bank Transaction"): - doc = frappe.get_doc("Bank Transaction", bt.name) - doc.cancel() - doc.delete() - - for doctype in ("Bank Account", "Bank Account Type", "Bank Account Subtype"): - for d in frappe.get_all(doctype): - frappe.delete_doc(doctype, d.name, force=True) - +class TestPlaidSettings(ERPNextTestSuite): def test_plaid_disabled(self): frappe.db.set_single_value("Plaid Settings", "enabled", 0) self.assertTrue(get_plaid_configuration() == "disabled") @@ -68,7 +55,7 @@ class TestPlaidSettings(IntegrationTestCase): } bank = json.dumps(frappe.get_doc("Bank", "Citi").as_dict(), default=json_handler) - company = frappe.db.get_single_value("Global Defaults", "default_company") + company = "_Test Company" add_bank_accounts(bank_accounts, bank, company) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 03391161ed2..12bbf689b37 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -63,7 +63,6 @@ welcome_email = "erpnext.setup.utils.welcome_email" setup_wizard_requires = "assets/erpnext/js/setup_wizard.js" setup_wizard_stages = "erpnext.setup.setup_wizard.setup_wizard.get_setup_stages" setup_wizard_complete = "erpnext.setup.setup_wizard.setup_wizard.setup_demo" -setup_wizard_test = "erpnext.setup.setup_wizard.test_setup_wizard.run_setup_wizard_test" after_install = "erpnext.setup.install.after_install" @@ -221,11 +220,12 @@ website_route_rules = [ standard_navbar_items = [ { - "item_label": "Clear Demo Data", + "item_label": "Delete Demo Data", "item_type": "Action", "action": "erpnext.demo.clear_demo();", "is_standard": 1, - "condition": "eval: frappe.boot.sysdefaults.demo_company", + "condition": "eval: frappe.boot.sysdefaults.demo_company && frappe.boot.sysdefaults.demo_company.length > 0", + "icon": "trash", }, ] @@ -319,8 +319,6 @@ has_website_permission = { "Project": "erpnext.controllers.website_list_for_contact.has_website_permission", } -before_tests = "erpnext.setup.utils.before_tests" - period_closing_doctypes = [ "Sales Invoice", @@ -418,11 +416,19 @@ naming_series_variables = { for variable in naming_series_variables_list } -# On cancel event Payment Entry will be exempted and all linked submittable doctype will get cancelled. -# to maintain data integrity we exempted payment entry. it will un-link when sales invoice get cancelled. -# if payment entry not in auto cancel exempted doctypes it will cancel payment entry. auto_cancel_exempted_doctypes = [ + # On cancel event Payment Entry will be exempted and all linked submittable doctype will get cancelled. + # to maintain data integrity we exempted payment entry. it will un-link when sales invoice get cancelled. + # if payment entry not in auto cancel exempted doctypes it will cancel payment entry. "Payment Entry", + # Reverse ledger entries are created instead to ensure ledger immutability. + "GL Entry", + "Stock Ledger Entry", + "Payment Ledger Entry", + "Advance Payment Ledger Entry", + # May be linked to Period Closing Voucher, but cancelled with custom logic in PCV. + # This is better to avoid stale docs when cancelling PCV from backend. + "Account Closing Balance", ] scheduler_events = { @@ -695,3 +701,4 @@ fields_for_group_similar_items = ["qty", "amount"] # ------------ # List of apps whose translatable strings should be excluded from this app's translations. ignore_translatable_strings_from = ["frappe"] +require_type_annotated_api_methods = True diff --git a/erpnext/locale/ar.po b/erpnext/locale/ar.po index 6b262043d1f..87c7c17b786 100644 --- a/erpnext/locale/ar.po +++ b/erpnext/locale/ar.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: frappe\n" "Report-Msgid-Bugs-To: hello@frappe.io\n" -"POT-Creation-Date: 2026-02-22 09:43+0000\n" -"PO-Revision-Date: 2026-02-22 16:37\n" +"POT-Creation-Date: 2026-03-22 09:45+0000\n" +"PO-Revision-Date: 2026-03-23 06:42\n" "Last-Translator: hello@frappe.io\n" "Language-Team: Arabic\n" "MIME-Version: 1.0\n" @@ -18,7 +18,7 @@ msgstr "" "X-Crowdin-File-ID: 46\n" "Language: ar_SA\n" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1474 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1521 msgid "\n" "\t\t\tThe Batch {0} of an item {1} has negative stock in the warehouse {2}{3}.\n" "\t\t\tPlease add a stock quantity of {4} to proceed with this entry.\n" @@ -36,7 +36,7 @@ msgstr "" msgid " Address" msgstr "" -#: erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py:604 +#: erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py:611 msgid " Amount" msgstr "" @@ -73,7 +73,7 @@ msgstr "" msgid " Phantom Item" msgstr " عنصر شبح" -#: erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py:595 +#: erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py:602 msgid " Rate" msgstr "" @@ -150,6 +150,11 @@ msgstr "" msgid "% Completed" msgstr "" +#. Label of the cost_allocation_per (Percent) field in DocType 'BOM' +#: erpnext/manufacturing/doctype/bom/bom.json +msgid "% Cost Allocation" +msgstr "" + #. Label of the per_delivered (Percent) field in DocType 'Pick List' #. Label of the per_delivered (Percent) field in DocType 'Subcontracting Inward #. Order' @@ -158,7 +163,7 @@ msgstr "" msgid "% Delivered" msgstr "% تسليم" -#: erpnext/manufacturing/doctype/bom/bom.js:992 +#: erpnext/manufacturing/doctype/bom/bom.js:973 #, python-format msgid "% Finished Item Quantity" msgstr "% كمية المنتج النهائي" @@ -173,8 +178,8 @@ msgstr "" msgid "% Occupied" msgstr "" -#: erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py:278 -#: erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py:330 +#: erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py:283 +#: erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py:337 msgid "% Of Grand Total" msgstr "" @@ -263,11 +268,11 @@ msgstr "" msgid "% of materials delivered against this Sales Order" msgstr "" -#: erpnext/controllers/accounts_controller.py:2371 +#: erpnext/controllers/accounts_controller.py:2372 msgid "'Account' in the Accounting section of Customer {0}" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:359 +#: erpnext/selling/doctype/sales_order/sales_order.py:361 msgid "'Allow Multiple Sales Orders Against a Customer's Purchase Order'" msgstr "" @@ -279,17 +284,17 @@ msgstr "'على أساس' و 'المجموعة حسب' لا يمكن أن يكو msgid "'Days Since Last Order' must be greater than or equal to zero" msgstr "يجب أن تكون \"الأيام منذ آخر طلب\" أكبر من أو تساوي الصفر" -#: erpnext/controllers/accounts_controller.py:2376 +#: erpnext/controllers/accounts_controller.py:2377 msgid "'Default {0} Account' in Company {1}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1220 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1227 msgid "'Entries' cannot be empty" msgstr "المدخلات لا يمكن أن تكون فارغة" #: erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py:24 #: erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py:127 -#: erpnext/stock/report/stock_analytics/stock_analytics.py:312 +#: erpnext/stock/report/stock_analytics/stock_analytics.py:322 msgid "'From Date' is required" msgstr "من تاريخ (مطلوب)" @@ -301,22 +306,23 @@ msgstr "\"من تاريخ \" يجب أن يكون بعد \" إلى تاريخ \" msgid "'Has Serial No' can not be 'Yes' for non-stock item" msgstr "\"لهُ رقم تسلسل\" لا يمكن ان يكون \"نعم\" لبند غير قابل للتخزين" -#: erpnext/stock/doctype/quality_inspection/quality_inspection.py:143 +#: erpnext/stock/doctype/quality_inspection/quality_inspection.py:145 msgid "'Inspection Required before Delivery' has disabled for the item {0}, no need to create the QI" msgstr "" -#: erpnext/stock/doctype/quality_inspection/quality_inspection.py:134 +#: erpnext/stock/doctype/quality_inspection/quality_inspection.py:136 msgid "'Inspection Required before Purchase' has disabled for the item {0}, no need to create the QI" msgstr "" -#: erpnext/stock/report/stock_ledger/stock_ledger.py:600 -#: erpnext/stock/report/stock_ledger/stock_ledger.py:633 +#: erpnext/stock/report/stock_ledger/stock_ledger.py:664 +#: erpnext/stock/report/stock_ledger/stock_ledger.py:705 +#: erpnext/stock/report/stock_ledger/stock_ledger.py:810 msgid "'Opening'" msgstr "'افتتاحي'" #: erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py:27 #: erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py:129 -#: erpnext/stock/report/stock_analytics/stock_analytics.py:318 +#: erpnext/stock/report/stock_analytics/stock_analytics.py:328 msgid "'To Date' is required" msgstr "' إلى تاريخ ' مطلوب" @@ -328,7 +334,7 @@ msgstr "" msgid "'Update Stock' can not be checked because items are not delivered via {0}" msgstr ""الأوراق المالية التحديث" لا يمكن التحقق من أنه لم يتم تسليم المواد عن طريق {0}" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:413 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:414 msgid "'Update Stock' cannot be checked for fixed asset sale" msgstr "لا يمكن التحقق من ' تحديث المخزون ' لبيع الأصول الثابتة\\n
\\n'Update Stock' cannot be checked for fixed asset sale" @@ -340,8 +346,8 @@ msgstr "" msgid "'{0}' has been already added." msgstr "لقد تمت إضافة '{0}' بالفعل." -#: erpnext/setup/doctype/company/company.py:302 -#: erpnext/setup/doctype/company/company.py:313 +#: erpnext/setup/doctype/company/company.py:303 +#: erpnext/setup/doctype/company/company.py:314 msgid "'{0}' should be in company currency {1}." msgstr "" @@ -597,12 +603,12 @@ msgstr "" msgid "90 Above" msgstr "" -#: erpnext/accounts/report/accounts_receivable/accounts_receivable.py:1288 -#: erpnext/accounts/report/accounts_receivable/accounts_receivable.py:1289 +#: erpnext/accounts/report/accounts_receivable/accounts_receivable.py:1330 +#: erpnext/accounts/report/accounts_receivable/accounts_receivable.py:1331 msgid "<0" msgstr "<0" -#: erpnext/assets/doctype/asset/asset.py:544 +#: erpnext/assets/doctype/asset/asset.py:545 msgid "Cannot create asset.

You're trying to create {0} asset(s) from {2} {3}.
However, only {1} item(s) were purchased and {4} asset(s) already exist against {5}." msgstr "" @@ -610,7 +616,7 @@ msgstr "" msgid "From Time cannot be later than To Time for {0}" msgstr "من الوقت لا يمكن أن يكون بعد من إلى الوقت لـ {0}" -#: erpnext/accounts/doctype/pos_invoice/pos_invoice.py:432 +#: erpnext/accounts/doctype/pos_invoice/pos_invoice.py:433 msgid "Row #{0}: Bundle {1} in warehouse {2} has insufficient packed items:
" msgstr "" @@ -734,19 +740,19 @@ msgstr "" msgid "" msgstr "" -#: erpnext/accounts/doctype/bank_clearance/bank_clearance.py:123 +#: erpnext/accounts/doctype/bank_clearance/bank_clearance.py:125 msgid "
  • Clearance date must be after cheque date for row(s): {0}
  • " msgstr "" -#: erpnext/controllers/accounts_controller.py:2264 +#: erpnext/controllers/accounts_controller.py:2265 msgid "
  • Item {0} in row(s) {1} billed more than {2}
  • " msgstr "" -#: erpnext/accounts/doctype/pos_invoice/pos_invoice.py:423 +#: erpnext/accounts/doctype/pos_invoice/pos_invoice.py:424 msgid "
  • Packed Item {0}: Required {1}, Available {2}
  • " msgstr "" -#: erpnext/accounts/doctype/bank_clearance/bank_clearance.py:118 +#: erpnext/accounts/doctype/bank_clearance/bank_clearance.py:120 msgid "
  • Payment document required for row(s): {0}
  • " msgstr "" @@ -755,7 +761,7 @@ msgstr "" msgid "
  • {}
  • " msgstr "" -#: erpnext/controllers/accounts_controller.py:2261 +#: erpnext/controllers/accounts_controller.py:2262 msgid "

    Cannot overbill for the following Items:

    " msgstr "" @@ -787,7 +793,7 @@ msgid "

    In your Email Template, you can use the following special varia "

    Apart from these, you can access all values in this RFQ, like {{ message_for_supplier }} or {{ terms }}.

    " msgstr "" -#: erpnext/accounts/doctype/bank_clearance/bank_clearance.py:116 +#: erpnext/accounts/doctype/bank_clearance/bank_clearance.py:118 msgid "

    Please correct the following row(s):