mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-10 08:23:01 +00:00
Compare commits
1 Commits
v16.19.1
...
coderabbit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
741e6a7e52 |
@@ -45,9 +45,3 @@ d827ed21adc7b36047e247cbb0dc6388d048a7f9
|
||||
|
||||
# `frappe.flags.in_test` => `frappe.in_test`
|
||||
7a482a69985c952de0e8193c9d4e086aee65ee6d
|
||||
|
||||
# these commits actually changed something valuable
|
||||
# but they have a lot of whitespace changes that make blame noisy
|
||||
# PR: https://github.com/frappe/erpnext/pull/49816
|
||||
3ffd50c772735877b330d010c1058f623da8721d
|
||||
0e8f8677b8eb31e7834f72d1c6314d3c3f392ca6
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -60,7 +60,7 @@ body:
|
||||
description: Share exact version number of Frappe and ERPNext you are using.
|
||||
placeholder: |
|
||||
Frappe version -
|
||||
ERPNext version -
|
||||
ERPNext Verion -
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout Actions
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: "frappe/backport"
|
||||
path: ./actions
|
||||
|
||||
4
.github/workflows/docs-checker.yml
vendored
4
.github/workflows/docs-checker.yml
vendored
@@ -13,12 +13,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 'Setup Environment'
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: 'Clone repo'
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Validate Docs
|
||||
env:
|
||||
|
||||
6
.github/workflows/generate-pot-file.yml
vendored
6
.github/workflows/generate-pot-file.yml
vendored
@@ -21,14 +21,14 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ matrix.branch }}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.14"
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Run script to update POT file
|
||||
run: |
|
||||
|
||||
2
.github/workflows/initiate_release.yml
vendored
2
.github/workflows/initiate_release.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: ["14", "15", "16"]
|
||||
version: ["14", "15"]
|
||||
|
||||
steps:
|
||||
- uses: octokit/request-action@v2.x
|
||||
|
||||
19
.github/workflows/linters.yml
vendored
19
.github/workflows/linters.yml
vendored
@@ -12,12 +12,12 @@ jobs:
|
||||
name: linters
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python 3.14
|
||||
uses: actions/setup-python@v6
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.14'
|
||||
python-version: '3.10'
|
||||
cache: pip
|
||||
|
||||
- name: Install and Run Pre-commit
|
||||
@@ -27,12 +27,12 @@ jobs:
|
||||
name: semgrep
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python 3.14
|
||||
uses: actions/setup-python@v6
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.14'
|
||||
python-version: '3.10'
|
||||
cache: pip
|
||||
|
||||
- name: Download Semgrep rules
|
||||
@@ -43,6 +43,3 @@ jobs:
|
||||
|
||||
- name: Run Semgrep rules
|
||||
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
|
||||
|
||||
- name: Semgrep for Test Correctness
|
||||
run: semgrep ci --include=**/test_*.py --config ./semgrep/test-correctness.yml
|
||||
|
||||
24
.github/workflows/patch.yml
vendored
24
.github/workflows/patch.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mariadb:11.8
|
||||
image: mariadb:10.6
|
||||
env:
|
||||
MARIADB_ROOT_PASSWORD: 'root'
|
||||
ports:
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check for valid Python & Merge Conflicts
|
||||
run: |
|
||||
@@ -49,17 +49,14 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: |
|
||||
3.11
|
||||
3.13
|
||||
3.14
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
node-version: 18
|
||||
check-latest: true
|
||||
|
||||
- name: Add to Hosts
|
||||
@@ -113,8 +110,8 @@ jobs:
|
||||
jq 'del(.install_apps)' ~/frappe-bench/sites/test_site/site_config.json > tmp.json
|
||||
mv tmp.json ~/frappe-bench/sites/test_site/site_config.json
|
||||
|
||||
wget https://frappe.io/files/erpnext-v14.sql.gz
|
||||
bench --site test_site --force restore ~/frappe-bench/erpnext-v14.sql.gz
|
||||
wget https://erpnext.com/files/v13-erpnext.sql.gz
|
||||
bench --site test_site --force restore ~/frappe-bench/v13-erpnext.sql.gz
|
||||
|
||||
git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git
|
||||
git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git
|
||||
@@ -135,14 +132,15 @@ jobs:
|
||||
# Resetup env and install apps
|
||||
pgrep honcho | xargs kill
|
||||
rm -rf ~/frappe-bench/env
|
||||
bench -v setup env --python python$2
|
||||
bench -v setup env
|
||||
bench pip install -e ./apps/erpnext
|
||||
bench start &>> ~/frappe-bench/bench_start.log &
|
||||
|
||||
bench --site test_site migrate
|
||||
}
|
||||
|
||||
update_to_version 15 3.13
|
||||
update_to_version 14
|
||||
update_to_version 15
|
||||
|
||||
echo "Updating to latest version"
|
||||
git -C "apps/frappe" fetch --depth 1 upstream "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
|
||||
|
||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -2,7 +2,7 @@ name: Generate Semantic Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- version-16
|
||||
- version-13
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -13,12 +13,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Entire Repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Setup dependencies
|
||||
|
||||
@@ -4,8 +4,8 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: server-individual-tests-lightmode-develop
|
||||
cancel-in-progress: true
|
||||
group: server-individual-tests-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -17,11 +17,11 @@ jobs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- id: set-matrix
|
||||
run: |
|
||||
# Use grep and find to get the list of test files
|
||||
matrix=$(find . -path '*/test_*.py' | xargs grep -l 'def test_' | sort | awk '{
|
||||
matrix=$(find . -path '*/doctype/*/test_*.py' | xargs grep -l 'def test_' | awk '{
|
||||
# Remove ./ prefix, file extension, and replace / with .
|
||||
gsub(/^\.\//, "", $0)
|
||||
gsub(/\.py$/, "", $0)
|
||||
@@ -58,7 +58,6 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{fromJson(needs.discover.outputs.matrix)}}
|
||||
max-parallel: 14
|
||||
|
||||
name: Test
|
||||
|
||||
@@ -73,17 +72,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.14'
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
node-version: 18
|
||||
check-latest: true
|
||||
|
||||
- name: Add to Hosts
|
||||
@@ -131,13 +130,4 @@ jobs:
|
||||
FRAPPE_BRANCH: ${{ github.event.inputs.branch }}
|
||||
|
||||
- name: Run Tests
|
||||
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
|
||||
|
||||
run: 'cd ~/frappe-bench/ && bench --site test_site run-tests --app erpnext --module ${{ matrix.test }}'
|
||||
4
.github/workflows/semantic-commits.yml
vendored
4
.github/workflows/semantic-commits.yml
vendored
@@ -15,11 +15,11 @@ jobs:
|
||||
name: Check Commit Titles
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 200
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
check-latest: true
|
||||
|
||||
@@ -7,7 +7,6 @@ on:
|
||||
paths:
|
||||
- "**.js"
|
||||
- "**.css"
|
||||
- "**.svg"
|
||||
- "**.md"
|
||||
- "**.html"
|
||||
- 'crowdin.yml'
|
||||
|
||||
17
.github/workflows/server-tests-mariadb.yml
vendored
17
.github/workflows/server-tests-mariadb.yml
vendored
@@ -7,7 +7,6 @@ on:
|
||||
paths-ignore:
|
||||
- '**.js'
|
||||
- '**.css'
|
||||
- '**.svg'
|
||||
- '**.md'
|
||||
- '**.html'
|
||||
- 'crowdin.yml'
|
||||
@@ -41,7 +40,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
TZ: 'Asia/Kolkata'
|
||||
NODE_ENV: "production"
|
||||
WITH_COVERAGE: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
@@ -57,7 +55,6 @@ jobs:
|
||||
mysql:
|
||||
image: mariadb:10.6
|
||||
env:
|
||||
TZ: 'Asia/Kolkata'
|
||||
MARIADB_ROOT_PASSWORD: 'root'
|
||||
ports:
|
||||
- 3306:3306
|
||||
@@ -65,12 +62,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.14'
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Check for valid Python & Merge Conflicts
|
||||
run: |
|
||||
@@ -81,9 +78,9 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
node-version: 18
|
||||
check-latest: true
|
||||
|
||||
- name: Add to Hosts
|
||||
@@ -131,7 +128,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 --lightmode --app erpnext --total-builds ${{ strategy.job-total }} --build-number ${{ matrix.container }} --with-coverage'
|
||||
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --total-builds ${{ strategy.job-total }} --build-number ${{ matrix.container }} --with-coverage'
|
||||
env:
|
||||
TYPE: server
|
||||
|
||||
@@ -152,7 +149,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
10
.github/workflows/server-tests-postgres.yml
vendored
10
.github/workflows/server-tests-postgres.yml
vendored
@@ -47,12 +47,12 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Clone
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.14'
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Check for valid Python & Merge Conflicts
|
||||
run: |
|
||||
@@ -63,9 +63,9 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
node-version: 18
|
||||
check-latest: true
|
||||
|
||||
- name: Add to Hosts
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"branches": ["version-16"],
|
||||
"branches": ["version-13"],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer", {
|
||||
"preset": "angular",
|
||||
@@ -21,4 +21,4 @@
|
||||
],
|
||||
"@semantic-release/github"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
<div align="center">
|
||||
<a href="https://frappe.io/erpnext">
|
||||
<img src="./erpnext/public/images/v16/erpnext.svg" alt="ERPNext Logo" height="80px" width="80xp"/>
|
||||
|
||||
@@ -6,7 +6,7 @@ import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.user import is_website_user
|
||||
|
||||
__version__ = "16.19.1"
|
||||
__version__ = "16.0.0-dev"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"cards": [
|
||||
{
|
||||
"card": "Total Outgoing Bills"
|
||||
},
|
||||
{
|
||||
"card": "Total Incoming Bills"
|
||||
},
|
||||
{
|
||||
"card": "Total Incoming Payment"
|
||||
},
|
||||
{
|
||||
"card": "Total Outgoing Payment"
|
||||
}
|
||||
],
|
||||
"charts": [
|
||||
{
|
||||
"chart": "Incoming Bills (Purchase Invoice)",
|
||||
"width": "Half"
|
||||
},
|
||||
{
|
||||
"chart": "Outgoing Bills (Sales Invoice)",
|
||||
"width": "Half"
|
||||
},
|
||||
{
|
||||
"chart": "Accounts Receivable Ageing",
|
||||
"width": "Half"
|
||||
},
|
||||
{
|
||||
"chart": "Accounts Payable Ageing",
|
||||
"width": "Half"
|
||||
},
|
||||
{
|
||||
"chart": "Bank Balance",
|
||||
"width": "Full"
|
||||
}
|
||||
],
|
||||
"creation": "2026-01-26 21:25:12.793893",
|
||||
"dashboard_name": "Payments",
|
||||
"docstatus": 0,
|
||||
"doctype": "Dashboard",
|
||||
"idx": 0,
|
||||
"is_default": 0,
|
||||
"is_standard": 1,
|
||||
"modified": "2026-01-26 21:25:12.793893",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payments",
|
||||
"owner": "Administrator"
|
||||
}
|
||||
@@ -9,20 +9,18 @@
|
||||
"idx": 0,
|
||||
"is_public": 1,
|
||||
"is_standard": 1,
|
||||
"last_synced_on": "2026-01-02 13:01:24.037552",
|
||||
"modified": "2026-01-02 13:04:57.850305",
|
||||
"last_synced_on": "2020-07-22 12:19:59.879476",
|
||||
"modified": "2020-07-22 12:21:48.780513",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Balance",
|
||||
"number_of_groups": 0,
|
||||
"owner": "Administrator",
|
||||
"roles": [],
|
||||
"show_values_over_chart": 1,
|
||||
"source": "Account Balance Timeline",
|
||||
"time_interval": "Monthly",
|
||||
"timeseries": 1,
|
||||
"time_interval": "Quarterly",
|
||||
"timeseries": 0,
|
||||
"timespan": "Last Year",
|
||||
"type": "Line",
|
||||
"use_report_chart": 0,
|
||||
"y_axis": []
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"chart_name": "Profit and Loss",
|
||||
"chart_type": "Report",
|
||||
"creation": "2025-04-01 20:38:16.986176",
|
||||
"creation": "2020-07-17 11:25:34.448572",
|
||||
"docstatus": 0,
|
||||
"doctype": "Dashboard Chart",
|
||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
|
||||
@@ -9,7 +9,7 @@
|
||||
"idx": 0,
|
||||
"is_public": 1,
|
||||
"is_standard": 1,
|
||||
"modified": "2025-12-19 12:37:31.673782",
|
||||
"modified": "2023-07-19 13:08:56.470390",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Profit and Loss",
|
||||
@@ -17,9 +17,8 @@
|
||||
"owner": "Administrator",
|
||||
"report_name": "Profit and Loss Statement",
|
||||
"roles": [],
|
||||
"show_values_over_chart": 1,
|
||||
"timeseries": 0,
|
||||
"type": "Line",
|
||||
"type": "Bar",
|
||||
"use_report_chart": 1,
|
||||
"y_axis": []
|
||||
}
|
||||
}
|
||||
@@ -450,12 +450,14 @@ def process_deferred_accounting(posting_date=None):
|
||||
for company in companies:
|
||||
for record_type in ("Income", "Expense"):
|
||||
doc = frappe.get_doc(
|
||||
doctype="Process Deferred Accounting",
|
||||
company=company.name,
|
||||
posting_date=posting_date,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
type=record_type,
|
||||
dict(
|
||||
doctype="Process Deferred Accounting",
|
||||
company=company.name,
|
||||
posting_date=posting_date,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
type=record_type,
|
||||
)
|
||||
)
|
||||
|
||||
doc.insert()
|
||||
@@ -524,8 +526,7 @@ def make_gl_entries(
|
||||
if gl_entries:
|
||||
try:
|
||||
make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True)
|
||||
if not frappe.in_test:
|
||||
frappe.db.commit()
|
||||
frappe.db.commit()
|
||||
except Exception as e:
|
||||
if frappe.in_test:
|
||||
doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}")
|
||||
@@ -607,8 +608,7 @@ def book_revenue_via_journal_entry(
|
||||
if submit:
|
||||
journal_entry.submit()
|
||||
|
||||
if not frappe.in_test:
|
||||
frappe.db.commit()
|
||||
frappe.db.commit()
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}")
|
||||
|
||||
@@ -5,7 +5,8 @@ frappe.ui.form.on("Account", {
|
||||
setup: function (frm) {
|
||||
frm.add_fetch("parent_account", "report_type", "report_type");
|
||||
frm.add_fetch("parent_account", "root_type", "root_type");
|
||||
|
||||
},
|
||||
onload: function (frm) {
|
||||
frm.set_query("parent_account", function (doc) {
|
||||
return {
|
||||
filters: {
|
||||
@@ -14,18 +15,7 @@ frappe.ui.form.on("Account", {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("account_category", function () {
|
||||
if (!frm.doc.root_type) return;
|
||||
|
||||
return {
|
||||
filters: {
|
||||
root_type: ["in", [frm.doc.root_type, ""]],
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
frm.toggle_display("account_name", frm.is_new());
|
||||
|
||||
@@ -68,20 +58,12 @@ frappe.ui.form.on("Account", {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
account_type: function (frm) {
|
||||
if (frm.doc.is_group == 0) {
|
||||
frm.toggle_display(["tax_rate"], frm.doc.account_type == "Tax");
|
||||
frm.toggle_display("warehouse", frm.doc.account_type == "Stock");
|
||||
}
|
||||
},
|
||||
|
||||
root_type: function (frm) {
|
||||
if (frm.doc.account_category) {
|
||||
frm.set_value("account_category", "");
|
||||
}
|
||||
},
|
||||
|
||||
add_toolbar_buttons: function (frm) {
|
||||
frm.add_custom_button(
|
||||
__("Chart of Accounts"),
|
||||
|
||||
@@ -203,7 +203,7 @@
|
||||
"idx": 1,
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-14 18:14:42.202065",
|
||||
"modified": "2025-08-02 06:26:44.657146",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Account",
|
||||
@@ -256,14 +256,6 @@
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"role": "HR User",
|
||||
"select": 1
|
||||
},
|
||||
{
|
||||
"role": "HR Manager",
|
||||
"select": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
|
||||
@@ -52,55 +52,59 @@ frappe.treeview_settings["Account"] = {
|
||||
],
|
||||
root_label: "Accounts",
|
||||
get_tree_nodes: "erpnext.accounts.utils.get_children",
|
||||
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;
|
||||
|
||||
// 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();
|
||||
$(
|
||||
'<span class="balance-area pull-right">' +
|
||||
(account.account_currency != account.company_currency
|
||||
? format(account.balance_in_account_currency, account.account_currency) +
|
||||
" / "
|
||||
: "") +
|
||||
format(account.balance, account.company_currency) +
|
||||
" " +
|
||||
dr_or_cr +
|
||||
"</span>"
|
||||
).insertBefore(node.$ul);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
on_get_node: function (nodes, deep = false) {
|
||||
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();
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let accounts = [];
|
||||
if (deep) {
|
||||
// in case of `get_all_nodes`
|
||||
accounts = nodes.reduce((acc, node) => [...acc, ...node.data], []);
|
||||
} else {
|
||||
render_balances();
|
||||
accounts = nodes;
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
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();
|
||||
$(
|
||||
'<span class="balance-area pull-right">' +
|
||||
(account.balance_in_account_currency
|
||||
? format(
|
||||
account.balance_in_account_currency,
|
||||
account.account_currency
|
||||
) + " / "
|
||||
: "") +
|
||||
format(account.balance, account.company_currency) +
|
||||
" " +
|
||||
dr_or_cr +
|
||||
"</span>"
|
||||
).insertBefore(node.$ul);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
add_tree_node: "erpnext.accounts.utils.add_ac",
|
||||
menu_items: [
|
||||
|
||||
@@ -34,13 +34,6 @@
|
||||
"account_number": "0430",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Anlagen im Bau": {
|
||||
"is_group": 1,
|
||||
"Andere Anlagen, Betriebs- und Geschäftsausstattung im Bau": {
|
||||
"account_number": "0498",
|
||||
"account_type": "Capital Work in Progress"
|
||||
}
|
||||
},
|
||||
"Accumulated Depreciation": {
|
||||
"account_type": "Accumulated Depreciation"
|
||||
}
|
||||
@@ -324,21 +317,13 @@
|
||||
"account_number": "3800",
|
||||
"account_type": "Expenses Included In Asset Valuation"
|
||||
},
|
||||
"Bestandsveränderungen Roh-, Hilfs- und Betriebsstoffe sowie bezogene Waren": {
|
||||
"account_number": "3960",
|
||||
"account_type": "Stock Adjustment"
|
||||
},
|
||||
"Herstellungskosten": {
|
||||
"account_number": "4996",
|
||||
"account_type": "Cost of Goods Sold"
|
||||
},
|
||||
"Anlagenabgänge Sachanlagen (Restbuchwert bei Buchverlust)": {
|
||||
"account_number": "2310",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Verluste aus dem Abgang von Gegenständen des Anlagevermögens": {
|
||||
"account_number": "2320",
|
||||
"account_type": "Expense Account"
|
||||
"account_type": "Stock Adjustment"
|
||||
},
|
||||
"Verwaltungskosten": {
|
||||
"account_number": "4997",
|
||||
@@ -355,7 +340,7 @@
|
||||
"is_group": 1,
|
||||
"Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": {
|
||||
"account_number": "4830",
|
||||
"account_type": "Depreciation"
|
||||
"account_type": "Accumulated Depreciation"
|
||||
},
|
||||
"Abschreibungen auf Gebäude": {
|
||||
"account_number": "4831",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -33,17 +33,6 @@
|
||||
},
|
||||
"account_number": "1151.000"
|
||||
},
|
||||
"Pajak Dibayar di Muka": {
|
||||
"PPN Masukan": {
|
||||
"account_number": "1152.001",
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"PPh 23 Dibayar di Muka": {
|
||||
"account_number": "1152.002",
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"account_number": "1152.000"
|
||||
},
|
||||
"account_number": "1150.000"
|
||||
},
|
||||
"Kas": {
|
||||
@@ -108,6 +97,17 @@
|
||||
},
|
||||
"account_number": "1130.000"
|
||||
},
|
||||
"Pajak Dibayar di Muka": {
|
||||
"PPN Masukan": {
|
||||
"account_number": "1151.001",
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"PPh 23 Dibayar di Muka": {
|
||||
"account_number": "1152.001",
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"account_number": "1150.000"
|
||||
},
|
||||
"account_number": "1100.000"
|
||||
|
||||
},
|
||||
|
||||
@@ -6,83 +6,64 @@
|
||||
"Current Assets": {
|
||||
"Accounts Receivable": {
|
||||
"Debtors": {
|
||||
"account_type": "Receivable",
|
||||
"account_category": "Trade Receivables"
|
||||
"account_type": "Receivable"
|
||||
}
|
||||
},
|
||||
"Bank Accounts": {
|
||||
"account_type": "Bank",
|
||||
"is_group": 1,
|
||||
"account_category": "Cash and Cash Equivalents"
|
||||
"is_group": 1
|
||||
},
|
||||
"Cash In Hand": {
|
||||
"Cash": {
|
||||
"account_type": "Cash",
|
||||
"account_category": "Cash and Cash Equivalents"
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"account_type": "Cash",
|
||||
"account_category": "Cash and Cash Equivalents"
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Loans and Advances (Assets)": {
|
||||
"is_group": 1,
|
||||
"account_category": "Other Receivables"
|
||||
"is_group": 1
|
||||
},
|
||||
"Securities and Deposits": {
|
||||
"Earnest Money": {
|
||||
"account_category": "Other Current Assets"
|
||||
}
|
||||
"Earnest Money": {}
|
||||
},
|
||||
"Stock Assets": {
|
||||
"Stock In Hand": {
|
||||
"account_type": "Stock",
|
||||
"account_category": "Stock Assets"
|
||||
"account_type": "Stock"
|
||||
},
|
||||
"account_type": "Stock",
|
||||
"account_category": "Stock Assets"
|
||||
"account_type": "Stock"
|
||||
},
|
||||
"Tax Assets": {
|
||||
"is_group": 1,
|
||||
"account_category": "Other Current Assets"
|
||||
"is_group": 1
|
||||
}
|
||||
},
|
||||
"Fixed Assets": {
|
||||
"Capital Equipment": {
|
||||
"account_type": "Fixed Asset",
|
||||
"account_category": "Tangible Assets"
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Electronic Equipment": {
|
||||
"account_type": "Fixed Asset",
|
||||
"account_category": "Tangible Assets"
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Furniture and Fixtures": {
|
||||
"account_type": "Fixed Asset",
|
||||
"account_category": "Tangible Assets"
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Office Equipment": {
|
||||
"account_type": "Fixed Asset",
|
||||
"account_category": "Tangible Assets"
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Plants and Machineries": {
|
||||
"account_type": "Fixed Asset",
|
||||
"account_category": "Tangible Assets"
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Buildings": {
|
||||
"account_type": "Fixed Asset",
|
||||
"account_category": "Tangible Assets"
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Accumulated Depreciations": {
|
||||
"account_type": "Accumulated Depreciation",
|
||||
"account_category": "Tangible Assets"
|
||||
"account_type": "Accumulated Depreciation"
|
||||
}
|
||||
},
|
||||
"Investments": {
|
||||
"is_group": 1,
|
||||
"account_category": "Long-term Investments"
|
||||
"is_group": 1
|
||||
},
|
||||
"Temporary Accounts": {
|
||||
"Temporary Opening": {
|
||||
"account_type": "Temporary",
|
||||
"account_category": "Other Non-current Assets"
|
||||
"account_type": "Temporary"
|
||||
}
|
||||
},
|
||||
"root_type": "Asset"
|
||||
@@ -91,103 +72,55 @@
|
||||
"Direct Expenses": {
|
||||
"Stock Expenses": {
|
||||
"Cost of Goods Sold": {
|
||||
"account_type": "Cost of Goods Sold",
|
||||
"account_category": "Cost of Goods Sold"
|
||||
"account_type": "Cost of Goods Sold"
|
||||
},
|
||||
"Expenses Included In Valuation": {
|
||||
"account_type": "Expenses Included In Valuation",
|
||||
"account_category": "Other Direct Costs"
|
||||
"account_type": "Expenses Included In Valuation"
|
||||
},
|
||||
"Stock Adjustment": {
|
||||
"account_type": "Stock Adjustment",
|
||||
"account_category": "Other Direct Costs"
|
||||
"account_type": "Stock Adjustment"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Indirect Expenses": {
|
||||
"Administrative Expenses": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Commission on Sales": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Administrative Expenses": {},
|
||||
"Commission on Sales": {},
|
||||
"Depreciation": {
|
||||
"account_type": "Depreciation",
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Entertainment Expenses": {
|
||||
"account_category": "Operating Expenses"
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Entertainment Expenses": {},
|
||||
"Freight and Forwarding Charges": {
|
||||
"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"
|
||||
"account_type": "Chargeable"
|
||||
},
|
||||
"Legal Expenses": {},
|
||||
"Marketing Expenses": {},
|
||||
"Miscellaneous Expenses": {},
|
||||
"Office Maintenance Expenses": {},
|
||||
"Office Rent": {},
|
||||
"Postal Expenses": {},
|
||||
"Print and Stationery": {},
|
||||
"Rounded Off": {
|
||||
"account_type": "Round Off",
|
||||
"account_category": "Operating Expenses"
|
||||
"account_type": "Round Off"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
"Salary": {},
|
||||
"Sales Expenses": {},
|
||||
"Telephone Expenses": {},
|
||||
"Travel Expenses": {},
|
||||
"Utility Expenses": {},
|
||||
"Write Off": {},
|
||||
"Exchange Gain/Loss": {},
|
||||
"Gain/Loss on Asset Disposal": {},
|
||||
"Impairment": {}
|
||||
},
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Income": {
|
||||
"Direct Income": {
|
||||
"Sales": {
|
||||
"account_type": "Income Account",
|
||||
"account_category": "Revenue from Operations"
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
"Service": {
|
||||
"account_type": "Income Account",
|
||||
"account_category": "Revenue from Operations"
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
@@ -199,51 +132,31 @@
|
||||
},
|
||||
"Source of Funds (Liabilities)": {
|
||||
"Capital Account": {
|
||||
"Reserves and Surplus": {
|
||||
"account_category": "Reserves and Surplus"
|
||||
},
|
||||
"Shareholders Funds": {
|
||||
"account_category": "Share Capital"
|
||||
},
|
||||
"Revaluation Surplus": {
|
||||
"account_category": "Reserves and Surplus"
|
||||
}
|
||||
"Reserves and Surplus": {},
|
||||
"Shareholders Funds": {},
|
||||
"Revaluation Surplus": {}
|
||||
},
|
||||
"Current Liabilities": {
|
||||
"Accounts Payable": {
|
||||
"Creditors": {
|
||||
"account_type": "Payable",
|
||||
"account_category": "Trade Payables"
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Payroll Payable": {
|
||||
"account_category": "Other Payables"
|
||||
}
|
||||
"Payroll Payable": {}
|
||||
},
|
||||
"Stock Liabilities": {
|
||||
"Stock Received But Not Billed": {
|
||||
"account_type": "Stock Received But Not Billed",
|
||||
"account_category": "Trade Payables"
|
||||
"account_type": "Stock Received But Not Billed"
|
||||
}
|
||||
},
|
||||
"Duties and Taxes": {
|
||||
"TDS": {
|
||||
"account_type": "Tax",
|
||||
"account_category": "Current Tax Liabilities"
|
||||
},
|
||||
"account_type": "Tax",
|
||||
"is_group": 1,
|
||||
"account_category": "Current Tax Liabilities"
|
||||
"account_type": "Tax"
|
||||
}
|
||||
},
|
||||
"Loans (Liabilities)": {
|
||||
"Secured Loans": {
|
||||
"account_category": "Long-term Borrowings"
|
||||
},
|
||||
"Unsecured Loans": {
|
||||
"account_category": "Long-term Borrowings"
|
||||
},
|
||||
"Bank Overdraft Account": {
|
||||
"account_category": "Short-term Borrowings"
|
||||
}
|
||||
"Secured Loans": {},
|
||||
"Unsecured Loans": {},
|
||||
"Bank Overdraft Account": {}
|
||||
}
|
||||
},
|
||||
"root_type": "Liability"
|
||||
|
||||
@@ -1,840 +0,0 @@
|
||||
{
|
||||
"name": "Philippines",
|
||||
"country": "Philippines",
|
||||
"tree": {
|
||||
"Asset": {
|
||||
"account_number": "1000",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Current Assets": {
|
||||
"account_number": "1001",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Cash": {
|
||||
"account_number": "1100",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Cash",
|
||||
"Cash on Hand": {
|
||||
"account_number": "1101",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Petty Cash Fund": {
|
||||
"account_number": "1200",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Cash",
|
||||
"Petty Cash Fund": {
|
||||
"account_number": "1201",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Cash"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Bank Accounts": {
|
||||
"account_number": "1102",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Bank"
|
||||
},
|
||||
"Advances to Officers & Employees": {
|
||||
"account_number": "1290",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Advances to Officers & Employees": {
|
||||
"account_number": "1291",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"Accounts Receivable Trade": {
|
||||
"account_number": "1300",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Accounts Receivable - Trade": {
|
||||
"account_number": "1301",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Receivable"
|
||||
}
|
||||
},
|
||||
"Accounts Receivable - Affiliates": {
|
||||
"account_number": "1310",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Due from Company": {
|
||||
"account_number": "1311",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"Accounts Receivable - Others": {
|
||||
"account_number": "1400",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Accounts Receivable - Others": {
|
||||
"account_number": "1401",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"Parts, Materials and Supplies": {
|
||||
"account_number": "1500",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Parts, Materials and Supplies": {
|
||||
"account_number": "1501",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"Raw Materials - Demo": {
|
||||
"account_number": "1502",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"Project in Progress": {
|
||||
"account_number": "1510",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Project in Progress": {
|
||||
"account_number": "1511",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"Factory Overhead Variance": {
|
||||
"account_number": "1512",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"Finished Goods": {
|
||||
"account_number": "1520",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Finished Goods Inventory": {
|
||||
"account_number": "1531",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Stock"
|
||||
},
|
||||
"Inventory in Transit": {
|
||||
"account_number": "1532",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Stock Adjustment"
|
||||
}
|
||||
},
|
||||
"Prepayments": {
|
||||
"account_number": "1600",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Prepaid Insurance & Bonds": {
|
||||
"account_number": "1601",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"Prepaid Rent": {
|
||||
"account_number": "1602",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"VAT Input Tax": {
|
||||
"account_number": "1610",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"VAT Input Tax - Goods": {
|
||||
"account_number": "1611",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Tax"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Non - Current Assets": {
|
||||
"account_number": "1002",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Property, Plants And Equipments": {
|
||||
"account_number": "1700",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Land": {
|
||||
"account_number": "1701",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Buildings & Improvements": {
|
||||
"account_number": "1702",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Delivery & Trans Equipment": {
|
||||
"account_number": "1703",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Furniture & Fixtures": {
|
||||
"account_number": "1704",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Machinery & Equipment": {
|
||||
"account_number": "1705",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Fixed Asset"
|
||||
}
|
||||
},
|
||||
"Accum Depr. - Property, Plants and Equipment": {
|
||||
"account_number": "1800",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Accumulated Dep Bdgs & Improv": {
|
||||
"account_number": "1801",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
},
|
||||
"Accumulated Dep Delivery & Trans": {
|
||||
"account_number": "1802",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
},
|
||||
"Accumulated Dep Furniture & Fixture": {
|
||||
"account_number": "1803",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
},
|
||||
"Accumulated Depreciation - Machinery & Equipment": {
|
||||
"account_number": "1804",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Other Assets": {
|
||||
"account_number": "1003",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Advances To Supplier": {
|
||||
"account_number": "1900",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Advances To Supplier": {
|
||||
"account_number": "1901",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"Miscellaneous Deposits": {
|
||||
"account_number": "1910",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Miscellaneous Deposits": {
|
||||
"account_number": "1911",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"Retirement Fund": {
|
||||
"account_number": "1920",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Retirement Fund": {
|
||||
"account_number": "1921",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"Investment": {
|
||||
"account_number": "1930",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Investment": {
|
||||
"account_number": "1931",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"System Development": {
|
||||
"account_number": "1940",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"System Development": {
|
||||
"account_number": "1941",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Liability": {
|
||||
"account_number": "2000",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Current Liabilities": {
|
||||
"account_number": "2001",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Accounts Payable Trade": {
|
||||
"account_number": "2100",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Accounts Payable - Trade": {
|
||||
"account_number": "2101",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability",
|
||||
"account_type": "Payable"
|
||||
}
|
||||
},
|
||||
"Accounts Payable Others": {
|
||||
"account_number": "2110",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Accounts Payable - Payroll": {
|
||||
"account_number": "2111",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
}
|
||||
},
|
||||
"VAT Output Tax": {
|
||||
"account_number": "2200",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"VAT Output Tax": {
|
||||
"account_number": "2201",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability",
|
||||
"account_type": "Tax"
|
||||
}
|
||||
},
|
||||
"Withholding Taxes Payable Wages": {
|
||||
"account_number": "2210",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Withholding Taxes Payable Wages": {
|
||||
"account_number": "2211",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability",
|
||||
"account_type": "Tax"
|
||||
}
|
||||
},
|
||||
"Withholding Taxes Payable Expanded": {
|
||||
"account_number": "2220",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Withholding Taxes Payable Expanded": {
|
||||
"account_number": "2221",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability",
|
||||
"account_type": "Tax"
|
||||
}
|
||||
},
|
||||
"Accruals And Other Current Payables": {
|
||||
"account_number": "2300",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Stock Received But Not Billed": {
|
||||
"account_number": "2301",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability",
|
||||
"account_type": "Stock Received But Not Billed"
|
||||
}
|
||||
},
|
||||
"Payable to Government and Other Institutions": {
|
||||
"account_number": "2400",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"SSS Premium Payable": {
|
||||
"account_number": "2401",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"SSS Salary Loan Payable": {
|
||||
"account_number": "2402",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"PhilHealth Premium": {
|
||||
"account_number": "2403",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"Pag-ibig Loan Payable": {
|
||||
"account_number": "2404",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"Coop Loans": {
|
||||
"account_number": "2405",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"Coop Contributions": {
|
||||
"account_number": "2406",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"Canteen": {
|
||||
"account_number": "2407",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"AUB Loan Payable": {
|
||||
"account_number": "2408",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"HSBC Loan Payable": {
|
||||
"account_number": "2409",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
}
|
||||
},
|
||||
"Customer Deposits": {
|
||||
"account_number": "2500",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability",
|
||||
"account_type": "Payable"
|
||||
}
|
||||
},
|
||||
"Non Current Liabilities": {
|
||||
"account_number": "2002",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Due To Associated Company": {
|
||||
"account_number": "2600",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Due To Associated Company": {
|
||||
"account_number": "2601",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
}
|
||||
},
|
||||
"Deferred Income": {
|
||||
"account_number": "2700",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Deferred Income": {
|
||||
"account_number": "2701",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
}
|
||||
},
|
||||
"Notes Payable": {
|
||||
"account_number": "2800",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Notes Payable": {
|
||||
"account_number": "2801",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
}
|
||||
},
|
||||
"Dividends Payable": {
|
||||
"account_number": "2900",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Equity": {
|
||||
"account_number": "3000",
|
||||
"is_group": 1,
|
||||
"root_type": "Equity",
|
||||
"STOCKHOLDER'S EQUITY": {
|
||||
"account_number": "3001",
|
||||
"is_group": 1,
|
||||
"root_type": "Equity",
|
||||
"Capital Stocks": {
|
||||
"account_number": "3100",
|
||||
"is_group": 1,
|
||||
"root_type": "Equity",
|
||||
"Capital Stocks": {
|
||||
"account_number": "3101",
|
||||
"is_group": 0,
|
||||
"root_type": "Equity"
|
||||
}
|
||||
},
|
||||
"Subscription Receivable": {
|
||||
"account_number": "3200",
|
||||
"is_group": 1,
|
||||
"root_type": "Equity",
|
||||
"Subscription Receivable": {
|
||||
"account_number": "3201",
|
||||
"is_group": 0,
|
||||
"root_type": "Equity"
|
||||
}
|
||||
},
|
||||
"Retained Earnings": {
|
||||
"account_number": "3300",
|
||||
"is_group": 1,
|
||||
"root_type": "Equity",
|
||||
"Retained Earnings": {
|
||||
"account_number": "3301",
|
||||
"is_group": 0,
|
||||
"root_type": "Equity"
|
||||
}
|
||||
},
|
||||
"Current Year (Profit/Loss)": {
|
||||
"account_number": "3400",
|
||||
"is_group": 0,
|
||||
"root_type": "Equity"
|
||||
},
|
||||
"Drawings": {
|
||||
"account_number": "3500",
|
||||
"is_group": 1,
|
||||
"root_type": "Equity",
|
||||
"Drawings": {
|
||||
"account_number": "3501",
|
||||
"is_group": 0,
|
||||
"root_type": "Equity"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Income": {
|
||||
"account_number": "4000",
|
||||
"is_group": 1,
|
||||
"root_type": "Income",
|
||||
"Gross Sales": {
|
||||
"account_number": "4100",
|
||||
"is_group": 1,
|
||||
"root_type": "Income",
|
||||
"Sales": {
|
||||
"account_number": "4101",
|
||||
"is_group": 0,
|
||||
"root_type": "Income"
|
||||
}
|
||||
},
|
||||
"Sales Adjustment": {
|
||||
"account_number": "4200",
|
||||
"is_group": 1,
|
||||
"root_type": "Income",
|
||||
"Sales Return And Allowance": {
|
||||
"account_number": "4201",
|
||||
"is_group": 0,
|
||||
"root_type": "Income"
|
||||
}
|
||||
},
|
||||
"Sales Discount": {
|
||||
"account_number": "4300",
|
||||
"is_group": 1,
|
||||
"root_type": "Income",
|
||||
"Sales Discount": {
|
||||
"account_number": "4301",
|
||||
"is_group": 0,
|
||||
"root_type": "Income"
|
||||
}
|
||||
},
|
||||
"Other Income": {
|
||||
"account_number": "6000",
|
||||
"is_group": 1,
|
||||
"root_type": "Income",
|
||||
"Interest Income Bank": {
|
||||
"account_number": "6010",
|
||||
"is_group": 1,
|
||||
"root_type": "Income",
|
||||
"Interest Income Bank": {
|
||||
"account_number": "6011",
|
||||
"is_group": 0,
|
||||
"root_type": "Income"
|
||||
}
|
||||
},
|
||||
"Dividend Income": {
|
||||
"account_number": "6020",
|
||||
"is_group": 1,
|
||||
"root_type": "Income",
|
||||
"Dividend Income": {
|
||||
"account_number": "6021",
|
||||
"is_group": 0,
|
||||
"root_type": "Income"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Expense": {
|
||||
"account_number": "5000",
|
||||
"is_group": 1,
|
||||
"root_type": "Expense",
|
||||
"Operating Expenses": {
|
||||
"account_number": "5100",
|
||||
"is_group": 1,
|
||||
"root_type": "Expense",
|
||||
"Salaries, Wages": {
|
||||
"account_number": "5101",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"13th Month Pay & Bonus": {
|
||||
"account_number": "5102",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Overtime & Night Diff": {
|
||||
"account_number": "5103",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Incentive/Performance Bonus": {
|
||||
"account_number": "5104",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Employees Benefits": {
|
||||
"account_number": "5105",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Advertising & Promotions": {
|
||||
"account_number": "5106",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Amortization of Leasehold Improvement": {
|
||||
"account_number": "5107",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Amortization of Pre-Operating": {
|
||||
"account_number": "5108",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Amortization of System Development": {
|
||||
"account_number": "5109",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Audit & Legal Fee": {
|
||||
"account_number": "5110",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Bad Debts Expenses": {
|
||||
"account_number": "5111",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Client Service & Maintenance": {
|
||||
"account_number": "5112",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Commission Expenses": {
|
||||
"account_number": "5113",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Communications": {
|
||||
"account_number": "5114",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Contractual Services": {
|
||||
"account_number": "5115",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Depreciation Expenses": {
|
||||
"account_number": "5116",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense",
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Donation & Contribution": {
|
||||
"account_number": "5117",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Dues & Subscription": {
|
||||
"account_number": "5118",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Employee Med/Dental/Hosp Expenses": {
|
||||
"account_number": "5119",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Employee Uniforms": {
|
||||
"account_number": "5120",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Equipage": {
|
||||
"account_number": "5121",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Expenses for Reclassification": {
|
||||
"account_number": "5122",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Gas & Oil": {
|
||||
"account_number": "5123",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Insurance Expenses": {
|
||||
"account_number": "5124",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Light & Water": {
|
||||
"account_number": "5125",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Local/Overseas Travel": {
|
||||
"account_number": "5126",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Meals & Transportation Expenses": {
|
||||
"account_number": "5127",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Meeting & Conferences": {
|
||||
"account_number": "5128",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Miscellaneous Expenses": {
|
||||
"account_number": "5129",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Mockup Expenses": {
|
||||
"account_number": "5130",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Obsolescence Expenses": {
|
||||
"account_number": "5131",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Other Support Cost": {
|
||||
"account_number": "5132",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Pag-ibig Contribution": {
|
||||
"account_number": "5133",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Performance Bonds": {
|
||||
"account_number": "5134",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Pre Employment Expenses": {
|
||||
"account_number": "5135",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Professional Fees": {
|
||||
"account_number": "5136",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Recruitment & Employment": {
|
||||
"account_number": "5137",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Rent Expenses": {
|
||||
"account_number": "5138",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Rent Expenses Others": {
|
||||
"account_number": "5139",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Repairs & Maintenance": {
|
||||
"account_number": "5140",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Representation Expenses": {
|
||||
"account_number": "5141",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Research & Development": {
|
||||
"account_number": "5142",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Security Expenses": {
|
||||
"account_number": "5143",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Shared Services Fee": {
|
||||
"account_number": "5144",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"SSS/Medicare/EC Contributions": {
|
||||
"account_number": "5145",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Stationery & Supplies": {
|
||||
"account_number": "5146",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Taxes & Licenses": {
|
||||
"account_number": "5147",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense",
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Training & Seminar": {
|
||||
"account_number": "5148",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
}
|
||||
},
|
||||
"Stock Adjustment": {
|
||||
"account_number": "5200",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense",
|
||||
"account_type": "Stock Adjustment"
|
||||
},
|
||||
"Round Off": {
|
||||
"account_number": "5300",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense",
|
||||
"account_type": "Round Off"
|
||||
},
|
||||
"Expenses Included In Valuation": {
|
||||
"account_number": "5400",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense",
|
||||
"account_type": "Expenses Included In Valuation"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.utils import nowdate
|
||||
|
||||
from erpnext.accounts.doctype.account.account import (
|
||||
@@ -11,10 +12,11 @@ from erpnext.accounts.doctype.account.account import (
|
||||
update_account_number,
|
||||
)
|
||||
from erpnext.stock import get_company_default_inventory_account, get_warehouse_account
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = ["Company"]
|
||||
|
||||
|
||||
class TestAccount(ERPNextTestSuite):
|
||||
class TestAccount(IntegrationTestCase):
|
||||
def test_rename_account(self):
|
||||
if not frappe.db.exists("Account", "1210 - Debtors - _TC"):
|
||||
acc = frappe.new_doc("Account")
|
||||
@@ -321,6 +323,72 @@ class TestAccount(ERPNextTestSuite):
|
||||
self.assertEqual(balance, 0)
|
||||
|
||||
|
||||
def _make_test_records(verbose=None):
|
||||
from frappe.tests.utils import make_test_objects
|
||||
|
||||
accounts = [
|
||||
# [account_name, parent_account, is_group]
|
||||
["_Test Bank", "Bank Accounts", 0, "Bank", None],
|
||||
["_Test Bank USD", "Bank Accounts", 0, "Bank", "USD"],
|
||||
["_Test Bank EUR", "Bank Accounts", 0, "Bank", "EUR"],
|
||||
["_Test Cash", "Cash In Hand", 0, "Cash", None],
|
||||
["_Test Account Stock Expenses", "Direct Expenses", 1, None, None],
|
||||
["_Test Account Shipping Charges", "_Test Account Stock Expenses", 0, "Chargeable", None],
|
||||
["_Test Account Customs Duty", "_Test Account Stock Expenses", 0, "Tax", None],
|
||||
["_Test Account Insurance Charges", "_Test Account Stock Expenses", 0, "Chargeable", None],
|
||||
["_Test Account Stock Adjustment", "_Test Account Stock Expenses", 0, "Stock Adjustment", None],
|
||||
["_Test Employee Advance", "Current Liabilities", 0, None, None],
|
||||
["_Test Account Tax Assets", "Current Assets", 1, None, None],
|
||||
["_Test Account VAT", "_Test Account Tax Assets", 0, "Tax", None],
|
||||
["_Test Account Service Tax", "_Test Account Tax Assets", 0, "Tax", None],
|
||||
["_Test Account Reserves and Surplus", "Current Liabilities", 0, None, None],
|
||||
["_Test Account Cost for Goods Sold", "Expenses", 0, None, None],
|
||||
["_Test Account Excise Duty", "_Test Account Tax Assets", 0, "Tax", None],
|
||||
["_Test Account Education Cess", "_Test Account Tax Assets", 0, "Tax", None],
|
||||
["_Test Account S&H Education Cess", "_Test Account Tax Assets", 0, "Tax", None],
|
||||
["_Test Account CST", "Direct Expenses", 0, "Tax", None],
|
||||
["_Test Account Discount", "Direct Expenses", 0, None, None],
|
||||
["_Test Write Off", "Indirect Expenses", 0, None, None],
|
||||
["_Test Exchange Gain/Loss", "Indirect Expenses", 0, None, None],
|
||||
["_Test Account Sales", "Direct Income", 0, None, None],
|
||||
# related to Account Inventory Integration
|
||||
["_Test Account Stock In Hand", "Current Assets", 0, None, None],
|
||||
# fixed asset depreciation
|
||||
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
|
||||
["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None],
|
||||
["_Test Depreciations", "Expenses", 0, "Depreciation", None],
|
||||
["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None],
|
||||
# Receivable / Payable Account
|
||||
["_Test Receivable", "Current Assets", 0, "Receivable", None],
|
||||
["_Test Payable", "Current Liabilities", 0, "Payable", None],
|
||||
["_Test Receivable USD", "Current Assets", 0, "Receivable", "USD"],
|
||||
["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"],
|
||||
]
|
||||
|
||||
for company, abbr in [
|
||||
["_Test Company", "_TC"],
|
||||
["_Test Company 1", "_TC1"],
|
||||
["_Test Company with perpetual inventory", "TCP1"],
|
||||
]:
|
||||
test_objects = make_test_objects(
|
||||
"Account",
|
||||
[
|
||||
{
|
||||
"doctype": "Account",
|
||||
"account_name": account_name,
|
||||
"parent_account": parent_account + " - " + abbr,
|
||||
"company": company,
|
||||
"is_group": is_group,
|
||||
"account_type": account_type,
|
||||
"account_currency": currency,
|
||||
}
|
||||
for account_name, parent_account, is_group, account_type, currency in accounts
|
||||
],
|
||||
)
|
||||
|
||||
return test_objects
|
||||
|
||||
|
||||
def get_inventory_account(company, warehouse=None):
|
||||
account = None
|
||||
if warehouse:
|
||||
@@ -347,13 +415,15 @@ def create_account(**kwargs):
|
||||
return account.name
|
||||
else:
|
||||
account = frappe.get_doc(
|
||||
doctype="Account",
|
||||
is_group=kwargs.get("is_group", 0),
|
||||
account_name=kwargs.get("account_name"),
|
||||
account_type=kwargs.get("account_type"),
|
||||
parent_account=kwargs.get("parent_account"),
|
||||
company=kwargs.get("company"),
|
||||
account_currency=kwargs.get("account_currency"),
|
||||
dict(
|
||||
doctype="Account",
|
||||
is_group=kwargs.get("is_group", 0),
|
||||
account_name=kwargs.get("account_name"),
|
||||
account_type=kwargs.get("account_type"),
|
||||
parent_account=kwargs.get("parent_account"),
|
||||
company=kwargs.get("company"),
|
||||
account_currency=kwargs.get("account_currency"),
|
||||
)
|
||||
)
|
||||
|
||||
account.save()
|
||||
|
||||
6
erpnext/accounts/doctype/account/test_records.json
Normal file
6
erpnext/accounts/doctype/account/test_records.json
Normal file
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"doctype": "Account",
|
||||
"name": "_Test Account 1"
|
||||
}
|
||||
]
|
||||
@@ -7,8 +7,6 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"account_category_name",
|
||||
"root_type",
|
||||
"column_break_qluu",
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
@@ -16,7 +14,6 @@
|
||||
"fieldname": "account_category_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Account Category Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
@@ -25,29 +22,12 @@
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_qluu",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "root_type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Root Type",
|
||||
"options": "\nAsset\nLiability\nIncome\nExpense\nEquity"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [
|
||||
{
|
||||
"link_doctype": "Account",
|
||||
"link_fieldname": "account_category"
|
||||
}
|
||||
],
|
||||
"modified": "2026-03-05 06:49:34.430723",
|
||||
"links": [],
|
||||
"modified": "2025-10-15 03:19:47.171349",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Account Category",
|
||||
@@ -84,7 +64,7 @@
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"search_fields": "account_category_name, root_type",
|
||||
"search_fields": "account_category_name, description",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
|
||||
@@ -21,7 +21,6 @@ class AccountCategory(Document):
|
||||
|
||||
account_category_name: DF.Data
|
||||
description: DF.SmallText | None
|
||||
root_type: DF.Literal["", "Asset", "Liability", "Income", "Expense", "Equity"]
|
||||
# end: auto-generated types
|
||||
|
||||
def after_rename(self, old_name, new_name, merge):
|
||||
|
||||
@@ -2,3 +2,19 @@
|
||||
# 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
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestAccountClosingBalance(ERPNextTestSuite):
|
||||
class TestAccountClosingBalance(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
@@ -43,7 +43,6 @@ class AccountingDimension(Document):
|
||||
def validate(self):
|
||||
self.validate_doctype()
|
||||
validate_column_name(self.fieldname)
|
||||
self.validate_fieldname_conflict()
|
||||
self.validate_dimension_defaults()
|
||||
|
||||
def validate_doctype(self):
|
||||
@@ -75,27 +74,6 @@ class AccountingDimension(Document):
|
||||
message += _("Please create a new Accounting Dimension if required.")
|
||||
frappe.throw(message)
|
||||
|
||||
def validate_fieldname_conflict(self):
|
||||
conflicting_doctypes = []
|
||||
for doctype in get_doctypes_with_dimensions():
|
||||
meta = frappe.get_meta(doctype, cached=False)
|
||||
if any(f.fieldname == self.fieldname for f in meta.get("fields")):
|
||||
conflicting_doctypes.append(doctype)
|
||||
|
||||
if conflicting_doctypes:
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Fieldname {0} already exists in the following doctypes: {1}. "
|
||||
"A separate dimension field will not be added to these doctypes. "
|
||||
"GL Entries will use the value of the existing field as the dimension value."
|
||||
).format(
|
||||
frappe.bold(self.fieldname),
|
||||
", ".join(frappe.bold(d) for d in conflicting_doctypes),
|
||||
),
|
||||
title=_("Fieldname Conflict"),
|
||||
indicator="orange",
|
||||
)
|
||||
|
||||
def validate_dimension_defaults(self):
|
||||
companies = []
|
||||
for default in self.get("dimension_defaults"):
|
||||
@@ -104,7 +82,7 @@ class AccountingDimension(Document):
|
||||
else:
|
||||
frappe.throw(_("Company {0} is added more than once").format(frappe.bold(default.company)))
|
||||
|
||||
def on_update(self):
|
||||
def after_insert(self):
|
||||
if frappe.in_test:
|
||||
make_dimension_in_accounting_doctypes(doc=self)
|
||||
else:
|
||||
@@ -125,6 +103,10 @@ class AccountingDimension(Document):
|
||||
if not self.fieldname:
|
||||
self.fieldname = scrub(self.label)
|
||||
|
||||
def on_update(self):
|
||||
frappe.flags.accounting_dimensions = None
|
||||
frappe.flags.accounting_dimensions_details = None
|
||||
|
||||
|
||||
def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
||||
if not doclist:
|
||||
@@ -259,26 +241,34 @@ def get_doctypes_with_dimensions():
|
||||
return frappe.get_hooks("accounting_dimension_doctypes")
|
||||
|
||||
|
||||
def get_accounting_dimensions(as_list=True):
|
||||
accounting_dimensions = frappe.get_all(
|
||||
"Accounting Dimension",
|
||||
fields=["label", "fieldname", "disabled", "document_type"],
|
||||
filters={"disabled": 0},
|
||||
)
|
||||
def get_accounting_dimensions(as_list=True, filters=None):
|
||||
if not filters:
|
||||
filters = {"disabled": 0}
|
||||
|
||||
if frappe.flags.accounting_dimensions is None:
|
||||
frappe.flags.accounting_dimensions = frappe.get_all(
|
||||
"Accounting Dimension",
|
||||
fields=["label", "fieldname", "disabled", "document_type"],
|
||||
filters=filters,
|
||||
)
|
||||
|
||||
if as_list:
|
||||
return [d.fieldname for d in accounting_dimensions]
|
||||
return [d.fieldname for d in frappe.flags.accounting_dimensions]
|
||||
else:
|
||||
return accounting_dimensions
|
||||
return frappe.flags.accounting_dimensions
|
||||
|
||||
|
||||
def get_checks_for_pl_and_bs_accounts():
|
||||
return frappe.db.sql(
|
||||
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
|
||||
if frappe.flags.accounting_dimensions_details is None:
|
||||
# nosemgrep
|
||||
frappe.flags.accounting_dimensions_details = frappe.db.sql(
|
||||
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
|
||||
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
|
||||
WHERE p.name = c.parent AND p.disabled = 0""",
|
||||
as_dict=1,
|
||||
)
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
return frappe.flags.accounting_dimensions_details
|
||||
|
||||
|
||||
def get_dimension_with_children(doctype, dimensions):
|
||||
|
||||
@@ -3,13 +3,18 @@
|
||||
import unittest
|
||||
|
||||
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
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = ["Cost Center", "Location", "Warehouse", "Department"]
|
||||
|
||||
|
||||
class TestAccountingDimension(ERPNextTestSuite):
|
||||
class TestAccountingDimension(IntegrationTestCase):
|
||||
def setUp(self):
|
||||
create_dimension()
|
||||
|
||||
def test_dimension_against_sales_invoice(self):
|
||||
si = create_sales_invoice(do_not_save=1)
|
||||
|
||||
@@ -74,3 +79,68 @@ class TestAccountingDimension(ERPNextTestSuite):
|
||||
|
||||
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()
|
||||
|
||||
@@ -69,34 +69,37 @@ class AccountingDimensionFilter(Document):
|
||||
|
||||
|
||||
def get_dimension_filter_map():
|
||||
filters = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
||||
p.allow_or_restrict, p.fieldname, a.is_mandatory
|
||||
FROM
|
||||
`tabApplicable On Account` a,
|
||||
`tabAccounting Dimension Filter` p
|
||||
LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
|
||||
WHERE
|
||||
p.name = a.parent
|
||||
AND p.disabled = 0
|
||||
""",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
dimension_filter_map = {}
|
||||
|
||||
for f in filters:
|
||||
build_map(
|
||||
dimension_filter_map,
|
||||
f.fieldname,
|
||||
f.applicable_on_account,
|
||||
f.dimension_value,
|
||||
f.allow_or_restrict,
|
||||
f.is_mandatory,
|
||||
if not frappe.flags.get("dimension_filter_map"):
|
||||
filters = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
||||
p.allow_or_restrict, p.fieldname, a.is_mandatory
|
||||
FROM
|
||||
`tabApplicable On Account` a,
|
||||
`tabAccounting Dimension Filter` p
|
||||
LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
|
||||
WHERE
|
||||
p.name = a.parent
|
||||
AND p.disabled = 0
|
||||
""",
|
||||
as_dict=1,
|
||||
)
|
||||
return dimension_filter_map
|
||||
|
||||
dimension_filter_map = {}
|
||||
|
||||
for f in filters:
|
||||
build_map(
|
||||
dimension_filter_map,
|
||||
f.fieldname,
|
||||
f.applicable_on_account,
|
||||
f.dimension_value,
|
||||
f.allow_or_restrict,
|
||||
f.is_mandatory,
|
||||
)
|
||||
frappe.flags.dimension_filter_map = dimension_filter_map
|
||||
|
||||
return frappe.flags.dimension_filter_map
|
||||
|
||||
|
||||
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
|
||||
|
||||
@@ -5,13 +5,19 @@ 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
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = ["Location", "Cost Center", "Department"]
|
||||
|
||||
|
||||
class TestAccountingDimensionFilter(ERPNextTestSuite):
|
||||
class TestAccountingDimensionFilter(unittest.TestCase):
|
||||
def setUp(self):
|
||||
create_dimension()
|
||||
create_accounting_dimension_filter()
|
||||
self.invoice_list = []
|
||||
|
||||
@@ -38,6 +44,17 @@ class TestAccountingDimensionFilter(ERPNextTestSuite):
|
||||
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"}):
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"column_break_4",
|
||||
"company",
|
||||
"disabled",
|
||||
"exempted_role",
|
||||
"section_break_7",
|
||||
"closed_documents"
|
||||
],
|
||||
@@ -20,6 +19,7 @@
|
||||
{
|
||||
"fieldname": "period_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Period Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
@@ -67,18 +67,10 @@
|
||||
"label": "Closed Documents",
|
||||
"options": "Closed Document",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"description": "Role allowed to bypass period restrictions.",
|
||||
"fieldname": "exempted_role",
|
||||
"fieldtype": "Link",
|
||||
"label": "Exempted Role",
|
||||
"link_filters": "[[\"Role\",\"disabled\",\"=\",0]]",
|
||||
"options": "Role"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2026-03-09 17:15:33.577217",
|
||||
"modified": "2025-10-06 15:00:15.568067",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting Period",
|
||||
|
||||
@@ -30,7 +30,6 @@ class AccountingPeriod(Document):
|
||||
company: DF.Link
|
||||
disabled: DF.Check
|
||||
end_date: DF.Date
|
||||
exempted_role: DF.Link | None
|
||||
period_name: DF.Data
|
||||
start_date: DF.Date
|
||||
# end: auto-generated types
|
||||
@@ -97,7 +96,7 @@ def validate_accounting_period_on_doc_save(doc, method=None):
|
||||
if doc.doctype == "Bank Clearance":
|
||||
return
|
||||
elif doc.doctype == "Asset":
|
||||
if doc.asset_type == "Existing Asset":
|
||||
if doc.is_existing_asset:
|
||||
return
|
||||
else:
|
||||
date = doc.available_for_use_date
|
||||
@@ -114,7 +113,7 @@ def validate_accounting_period_on_doc_save(doc, method=None):
|
||||
accounting_period = (
|
||||
frappe.qb.from_(ap)
|
||||
.from_(cd)
|
||||
.select(ap.name, ap.exempted_role)
|
||||
.select(ap.name)
|
||||
.where(
|
||||
(ap.name == cd.parent)
|
||||
& (ap.company == doc.company)
|
||||
@@ -127,11 +126,6 @@ def validate_accounting_period_on_doc_save(doc, method=None):
|
||||
).run(as_dict=1)
|
||||
|
||||
if accounting_period:
|
||||
if (
|
||||
accounting_period[0].get("exempted_role")
|
||||
and accounting_period[0].get("exempted_role") in frappe.get_roles()
|
||||
):
|
||||
return
|
||||
frappe.throw(
|
||||
_("You cannot create a {0} within the closed Accounting Period {1}").format(
|
||||
doc.doctype, frappe.bold(accounting_period[0]["name"])
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.utils import add_months, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.accounting_period.accounting_period import (
|
||||
@@ -10,10 +11,11 @@ from erpnext.accounts.doctype.accounting_period.accounting_period import (
|
||||
OverlapError,
|
||||
)
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = ["Item"]
|
||||
|
||||
|
||||
class TestAccountingPeriod(ERPNextTestSuite):
|
||||
class TestAccountingPeriod(IntegrationTestCase):
|
||||
def test_overlap(self):
|
||||
ap1 = create_accounting_period(
|
||||
start_date="2018-04-01", end_date="2018-06-30", company="Wind Power LLC"
|
||||
@@ -35,58 +37,9 @@ class TestAccountingPeriod(ERPNextTestSuite):
|
||||
doc = create_sales_invoice(do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC")
|
||||
self.assertRaises(ClosedAccountingPeriod, doc.save)
|
||||
|
||||
def test_accounting_period_exempted_role(self):
|
||||
# Create Accounting Period with exempted role
|
||||
ap = create_accounting_period(
|
||||
period_name="Test Accounting Period Exempted",
|
||||
exempted_role="Accounts Manager",
|
||||
start_date="2025-12-01",
|
||||
end_date="2025-12-31",
|
||||
)
|
||||
ap.save()
|
||||
|
||||
# Create users
|
||||
users = frappe.get_all("User", filters={"email": ["like", "test%"]}, limit=1)
|
||||
user = None
|
||||
|
||||
if users[0].name:
|
||||
user = frappe.get_doc("User", users[0].name)
|
||||
else:
|
||||
user = frappe.get_doc(
|
||||
{
|
||||
"doctype": "User",
|
||||
"email": "test1@example.com",
|
||||
"first_name": "Test1",
|
||||
}
|
||||
)
|
||||
user.insert()
|
||||
|
||||
user.roles = []
|
||||
user.append("roles", {"role": "Accounts User"})
|
||||
|
||||
# ---- Non-exempted user should FAIL ----
|
||||
user.save(ignore_permissions=True)
|
||||
frappe.clear_cache(user=user.name)
|
||||
|
||||
frappe.set_user(user.name)
|
||||
posting_date = "2025-12-11"
|
||||
doc = create_sales_invoice(
|
||||
do_not_save=1,
|
||||
posting_date=posting_date,
|
||||
)
|
||||
|
||||
with self.assertRaises(frappe.ValidationError):
|
||||
doc.submit()
|
||||
|
||||
# ---- Exempted role should PASS ----
|
||||
user.append("roles", {"role": "Accounts Manager"})
|
||||
user.save(ignore_permissions=True)
|
||||
frappe.clear_cache(user=user.name)
|
||||
|
||||
doc = create_sales_invoice(do_not_save=1, posting_date=posting_date)
|
||||
|
||||
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):
|
||||
@@ -98,6 +51,5 @@ def create_accounting_period(**args):
|
||||
accounting_period.company = args.company or "_Test Company"
|
||||
accounting_period.period_name = args.period_name or "_Test_Period_Name_1"
|
||||
accounting_period.append("closed_documents", {"document_type": "Sales Invoice", "closed": 1})
|
||||
accounting_period.exempted_role = args.exempted_role or ""
|
||||
|
||||
return accounting_period
|
||||
|
||||
@@ -2,15 +2,7 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Accounts Settings", {
|
||||
refresh: function (frm) {
|
||||
frm.set_query("document_type", "repost_allowed_types", function (doc, cdt, cdn) {
|
||||
return {
|
||||
filters: {
|
||||
name: ["in", frappe.boot.sysdefaults.repost_allowed_doctypes],
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
refresh: function (frm) {},
|
||||
enable_immutable_ledger: function (frm) {
|
||||
if (!frm.doc.enable_immutable_ledger) {
|
||||
return;
|
||||
@@ -38,6 +30,16 @@ frappe.ui.form.on("Accounts Settings", {
|
||||
add_taxes_from_item_tax_template(frm) {
|
||||
toggle_tax_settings(frm, "add_taxes_from_item_tax_template");
|
||||
},
|
||||
|
||||
drop_ar_procedures: function (frm) {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
method: "drop_ar_sql_procedures",
|
||||
callback: function (r) {
|
||||
frappe.show_alert(__("Procedures dropped"), 5);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function toggle_tax_settings(frm, field_name) {
|
||||
|
||||
@@ -16,15 +16,10 @@
|
||||
"invoicing_features_section",
|
||||
"check_supplier_invoice_uniqueness",
|
||||
"automatically_fetch_payment_terms",
|
||||
"enable_subscription",
|
||||
"column_break_17",
|
||||
"enable_common_party_accounting",
|
||||
"allow_multi_currency_invoices_against_single_party_account",
|
||||
"confirm_before_resetting_posting_date",
|
||||
"analytics_section",
|
||||
"enable_accounting_dimensions",
|
||||
"column_break_vtnr",
|
||||
"enable_discounts_and_margin",
|
||||
"journals_section",
|
||||
"merge_similar_account_heads",
|
||||
"deferred_accounting_settings_section",
|
||||
@@ -56,19 +51,12 @@
|
||||
"allow_pegged_currencies_exchange_rates",
|
||||
"column_break_yuug",
|
||||
"stale_days",
|
||||
"payments_tab",
|
||||
"section_break_jpd0",
|
||||
"auto_reconcile_payments",
|
||||
"auto_reconciliation_job_trigger",
|
||||
"reconciliation_queue_size",
|
||||
"column_break_resa",
|
||||
"exchange_gain_loss_posting_date",
|
||||
"repost_section",
|
||||
"repost_allowed_types",
|
||||
"payment_options_section",
|
||||
"enable_loyalty_point_program",
|
||||
"column_break_ctam",
|
||||
"fetch_payment_schedule_in_payment_request",
|
||||
"invoicing_settings_tab",
|
||||
"accounts_transactions_settings_section",
|
||||
"over_billing_allowance",
|
||||
@@ -76,6 +64,10 @@
|
||||
"role_allowed_to_over_bill",
|
||||
"credit_controller",
|
||||
"make_payment_via_journal_entry",
|
||||
"pos_tab",
|
||||
"pos_setting_section",
|
||||
"post_change_gl_entries",
|
||||
"column_break_xrnd",
|
||||
"assets_tab",
|
||||
"asset_settings_section",
|
||||
"calculate_depr_using_total_days",
|
||||
@@ -87,6 +79,11 @@
|
||||
"ignore_account_closing_balance",
|
||||
"use_legacy_controller_for_pcv",
|
||||
"column_break_25",
|
||||
"tab_break_dpet",
|
||||
"show_balance_in_coa",
|
||||
"banking_tab",
|
||||
"enable_party_matching",
|
||||
"enable_fuzzy_matching",
|
||||
"reports_tab",
|
||||
"remarks_section",
|
||||
"general_ledger_remarks_length",
|
||||
@@ -94,19 +91,13 @@
|
||||
"receivable_payable_remarks_length",
|
||||
"accounts_receivable_payable_tuning_section",
|
||||
"receivable_payable_fetch_method",
|
||||
"default_ageing_range",
|
||||
"column_break_ntmi",
|
||||
"drop_ar_procedures",
|
||||
"legacy_section",
|
||||
"ignore_is_opening_check_for_reporting",
|
||||
"tab_break_dpet",
|
||||
"chart_of_accounts_section",
|
||||
"show_balance_in_coa",
|
||||
"banking_section",
|
||||
"enable_party_matching",
|
||||
"enable_fuzzy_matching",
|
||||
"payment_request_section",
|
||||
"payment_request_settings",
|
||||
"create_pr_in_draft_status",
|
||||
"budget_section",
|
||||
"budget_settings",
|
||||
"use_legacy_budget_controller"
|
||||
],
|
||||
"fields": [
|
||||
@@ -208,7 +199,7 @@
|
||||
"description": "Payment Terms from orders will be fetched into the invoices as is",
|
||||
"fieldname": "automatically_fetch_payment_terms",
|
||||
"fieldtype": "Check",
|
||||
"label": "Automatically Fetch Payment Terms from Order/Quotation"
|
||||
"label": "Automatically Fetch Payment Terms from Order"
|
||||
},
|
||||
{
|
||||
"description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ",
|
||||
@@ -290,9 +281,16 @@
|
||||
"fieldname": "column_break_19",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"description": "If enabled, ledger entries will be posted for change amount in POS transactions",
|
||||
"fieldname": "post_change_gl_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Create Ledger Entries for Change Amount"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Learn about <a href=\"https://docs.frappe.io/erpnext/user/manual/en/common_party_accounting\" rel=\"noopener noreferrer\">Common Party</a>",
|
||||
"description": "Learn about <a href=\"https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier.\">Common Party</a>",
|
||||
"fieldname": "enable_common_party_accounting",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Common Party Accounting"
|
||||
@@ -330,6 +328,11 @@
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Accounts Closing"
|
||||
},
|
||||
{
|
||||
"fieldname": "pos_setting_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "POS Setting"
|
||||
},
|
||||
{
|
||||
"fieldname": "invoice_and_billing_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
@@ -344,6 +347,11 @@
|
||||
"fieldname": "column_break_17",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "pos_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "POS"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Enabling this will allow creation of multi-currency invoices against single party account in company currency",
|
||||
@@ -354,7 +362,7 @@
|
||||
{
|
||||
"fieldname": "tab_break_dpet",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Others"
|
||||
"label": "Chart Of Accounts"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
@@ -398,6 +406,11 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Taxes as Table in Print"
|
||||
},
|
||||
{
|
||||
"fieldname": "banking_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Banking"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Auto match and set the Party in Bank Transactions",
|
||||
@@ -473,9 +486,14 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Calculate daily depreciation using total days in depreciation period"
|
||||
},
|
||||
{
|
||||
"description": "Payment Request created from Sales Order or Purchase Order will be in Draft status. When disabled document will be in unsaved state.",
|
||||
"fieldname": "payment_request_settings",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Payment Request"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"description": "Payment Requests made from Sales / Purchase Invoice will be put in Draft explicitly",
|
||||
"fieldname": "create_pr_in_draft_status",
|
||||
"fieldtype": "Check",
|
||||
"label": "Create in Draft Status"
|
||||
@@ -517,12 +535,16 @@
|
||||
"label": "Posting Date Inheritance for Exchange Gain / Loss",
|
||||
"options": "Invoice\nPayment\nReconciliation Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_xrnd",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Buffered Cursor",
|
||||
"fieldname": "receivable_payable_fetch_method",
|
||||
"fieldtype": "Select",
|
||||
"label": "Data Fetch Method",
|
||||
"options": "Buffered Cursor\nUnBuffered Cursor"
|
||||
"options": "Buffered Cursor\nUnBuffered Cursor\nRaw SQL"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounts_receivable_payable_tuning_section",
|
||||
@@ -556,6 +578,11 @@
|
||||
"label": "Role Allowed to Override Stop Action",
|
||||
"options": "Role"
|
||||
},
|
||||
{
|
||||
"fieldname": "budget_settings",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Budget"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"description": "If enabled, user will be alerted before resetting posting date to current date in relevant transactions",
|
||||
@@ -591,6 +618,13 @@
|
||||
"fieldname": "column_break_ntmi",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.receivable_payable_fetch_method == \"Raw SQL\"",
|
||||
"description": "Drops existing SQL Procedures and Function setup by Accounts Receivable report",
|
||||
"fieldname": "drop_ar_procedures",
|
||||
"fieldtype": "Button",
|
||||
"label": "Drop Procedures"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "fetch_valuation_rate_for_internal_transaction",
|
||||
@@ -615,109 +649,15 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Role to Notify on Depreciation Failure",
|
||||
"options": "Role"
|
||||
},
|
||||
{
|
||||
"default": "30, 60, 90, 120",
|
||||
"fieldname": "default_ageing_range",
|
||||
"fieldtype": "Data",
|
||||
"label": "Default Ageing Range"
|
||||
},
|
||||
{
|
||||
"fieldname": "chart_of_accounts_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Chart Of Accounts"
|
||||
},
|
||||
{
|
||||
"fieldname": "banking_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Banking"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_request_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Payment Request"
|
||||
},
|
||||
{
|
||||
"fieldname": "budget_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Budget"
|
||||
},
|
||||
{
|
||||
"fieldname": "analytics_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Analytical Accounting"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_vtnr",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Apply discounts and margins on products",
|
||||
"fieldname": "enable_discounts_and_margin",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Discounts and Margin"
|
||||
},
|
||||
{
|
||||
"fieldname": "payments_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Payments"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_options_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Payment Options"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_loyalty_point_program",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Loyalty Point Program"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ctam",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Enable cost center, projects and other custom accounting dimensions",
|
||||
"fieldname": "enable_accounting_dimensions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Accounting Dimensions"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"description": "Enable Subscription tracking in invoice",
|
||||
"fieldname": "enable_subscription",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Subscription"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "fetch_payment_schedule_in_payment_request",
|
||||
"fieldtype": "Check",
|
||||
"label": "Fetch Payment Schedule In Payment Request"
|
||||
},
|
||||
{
|
||||
"fieldname": "repost_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Repost"
|
||||
},
|
||||
{
|
||||
"fieldname": "repost_allowed_types",
|
||||
"fieldtype": "Table",
|
||||
"label": "Allowed Doctypes",
|
||||
"options": "Repost Allowed Types"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "icon-cog",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-05-18 12:16:33.679345",
|
||||
"modified": "2025-12-03 20:42:13.238050",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -10,33 +10,8 @@ from frappe.custom.doctype.property_setter.property_setter import make_property_
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
)
|
||||
from erpnext.accounts.utils import sync_auto_reconcile_config
|
||||
|
||||
SELLING_DOCTYPES = [
|
||||
"Sales Invoice",
|
||||
"Sales Order",
|
||||
"Delivery Note",
|
||||
"Quotation",
|
||||
"Sales Invoice Item",
|
||||
"Sales Order Item",
|
||||
"Delivery Note Item",
|
||||
"Quotation Item",
|
||||
"POS Invoice",
|
||||
"POS Invoice Item",
|
||||
]
|
||||
|
||||
BUYING_DOCTYPES = [
|
||||
"Purchase Invoice",
|
||||
"Purchase Order",
|
||||
"Purchase Receipt",
|
||||
"Purchase Invoice Item",
|
||||
"Purchase Order Item",
|
||||
"Purchase Receipt Item",
|
||||
]
|
||||
|
||||
|
||||
class AccountsSettings(Document):
|
||||
# begin: auto-generated types
|
||||
@@ -47,8 +22,6 @@ class AccountsSettings(Document):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.accounts.doctype.repost_allowed_types.repost_allowed_types import RepostAllowedTypes
|
||||
|
||||
add_taxes_from_item_tax_template: DF.Check
|
||||
add_taxes_from_taxes_and_charges_template: DF.Check
|
||||
allow_multi_currency_invoices_against_single_party_account: DF.Check
|
||||
@@ -67,19 +40,13 @@ class AccountsSettings(Document):
|
||||
confirm_before_resetting_posting_date: DF.Check
|
||||
create_pr_in_draft_status: DF.Check
|
||||
credit_controller: DF.Link | None
|
||||
default_ageing_range: DF.Data | None
|
||||
delete_linked_ledger_entries: DF.Check
|
||||
determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"]
|
||||
enable_accounting_dimensions: DF.Check
|
||||
enable_common_party_accounting: DF.Check
|
||||
enable_discounts_and_margin: DF.Check
|
||||
enable_fuzzy_matching: DF.Check
|
||||
enable_immutable_ledger: DF.Check
|
||||
enable_loyalty_point_program: DF.Check
|
||||
enable_party_matching: DF.Check
|
||||
enable_subscription: DF.Check
|
||||
exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"]
|
||||
fetch_payment_schedule_in_payment_request: DF.Check
|
||||
fetch_valuation_rate_for_internal_transaction: DF.Check
|
||||
general_ledger_remarks_length: DF.Int
|
||||
ignore_account_closing_balance: DF.Check
|
||||
@@ -89,10 +56,10 @@ class AccountsSettings(Document):
|
||||
make_payment_via_journal_entry: DF.Check
|
||||
merge_similar_account_heads: DF.Check
|
||||
over_billing_allowance: DF.Currency
|
||||
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor"]
|
||||
post_change_gl_entries: DF.Check
|
||||
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor", "Raw SQL"]
|
||||
receivable_payable_remarks_length: DF.Int
|
||||
reconciliation_queue_size: DF.Int
|
||||
repost_allowed_types: DF.Table[RepostAllowedTypes]
|
||||
role_allowed_to_over_bill: DF.Link | None
|
||||
role_to_notify_on_depreciation_failure: DF.Link | None
|
||||
role_to_override_stop_action: DF.Link | None
|
||||
@@ -131,27 +98,10 @@ class AccountsSettings(Document):
|
||||
if old_doc.show_payment_schedule_in_print != self.show_payment_schedule_in_print:
|
||||
self.enable_payment_schedule_in_print()
|
||||
|
||||
if old_doc.enable_accounting_dimensions != self.enable_accounting_dimensions:
|
||||
toggle_accounting_dimension_sections(not self.enable_accounting_dimensions)
|
||||
clear_cache = True
|
||||
|
||||
if old_doc.enable_discounts_and_margin != self.enable_discounts_and_margin:
|
||||
toggle_sales_discount_section(not self.enable_discounts_and_margin)
|
||||
clear_cache = True
|
||||
|
||||
if old_doc.enable_loyalty_point_program != self.enable_loyalty_point_program:
|
||||
toggle_loyalty_point_program_section(not self.enable_loyalty_point_program)
|
||||
clear_cache = True
|
||||
|
||||
if old_doc.enable_subscription != self.enable_subscription:
|
||||
toggle_subscription_sections(not self.enable_subscription)
|
||||
clear_cache = True
|
||||
|
||||
if clear_cache:
|
||||
frappe.clear_cache()
|
||||
|
||||
self.validate_and_sync_auto_reconcile_config()
|
||||
self.update_property_for_accounting_dimension()
|
||||
|
||||
def validate_stale_days(self):
|
||||
if not self.allow_stale and cint(self.stale_days) <= 0:
|
||||
@@ -198,61 +148,10 @@ class AccountsSettings(Document):
|
||||
title=_("Auto Tax Settings Error"),
|
||||
)
|
||||
|
||||
def update_property_for_accounting_dimension(self):
|
||||
doctypes = [entry.document_type for entry in self.repost_allowed_types]
|
||||
if not doctypes:
|
||||
return
|
||||
@frappe.whitelist()
|
||||
def drop_ar_sql_procedures(self):
|
||||
from erpnext.accounts.report.accounts_receivable.accounts_receivable import InitSQLProceduresForAR
|
||||
|
||||
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import get_child_docs
|
||||
|
||||
doctypes += get_child_docs(doctypes)
|
||||
|
||||
set_allow_on_submit_for_dimension_fields(doctypes)
|
||||
|
||||
|
||||
def toggle_accounting_dimension_sections(hide):
|
||||
accounting_dimension_doctypes = frappe.get_hooks("accounting_dimension_doctypes")
|
||||
for doctype in accounting_dimension_doctypes:
|
||||
create_property_setter_for_hiding_field(doctype, "accounting_dimensions_section", hide)
|
||||
|
||||
|
||||
def toggle_sales_discount_section(hide):
|
||||
for doctype in SELLING_DOCTYPES + BUYING_DOCTYPES:
|
||||
meta = frappe.get_meta(doctype)
|
||||
if meta.has_field("additional_discount_section"):
|
||||
create_property_setter_for_hiding_field(doctype, "additional_discount_section", hide)
|
||||
if meta.has_field("discount_and_margin"):
|
||||
create_property_setter_for_hiding_field(doctype, "discount_and_margin", hide)
|
||||
|
||||
|
||||
def toggle_loyalty_point_program_section(hide):
|
||||
for doctype in SELLING_DOCTYPES:
|
||||
meta = frappe.get_meta(doctype)
|
||||
if meta.has_field("loyalty_points_redemption"):
|
||||
create_property_setter_for_hiding_field(doctype, "loyalty_points_redemption", hide)
|
||||
|
||||
|
||||
def toggle_subscription_sections(hide):
|
||||
subscription_doctypes = frappe.get_hooks("subscription_doctypes")
|
||||
for doctype in subscription_doctypes:
|
||||
create_property_setter_for_hiding_field(doctype, "subscription_section", hide)
|
||||
|
||||
|
||||
def create_property_setter_for_hiding_field(doctype, field_name, hide):
|
||||
make_property_setter(
|
||||
doctype,
|
||||
field_name,
|
||||
"hidden",
|
||||
hide,
|
||||
"Check",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
|
||||
|
||||
def set_allow_on_submit_for_dimension_fields(doctypes):
|
||||
for dt in doctypes:
|
||||
meta = frappe.get_meta(dt)
|
||||
for dimension in get_accounting_dimensions():
|
||||
df = meta.get_field(dimension)
|
||||
if df and not df.allow_on_submit:
|
||||
frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1)
|
||||
frappe.db.sql(f"drop function if exists {InitSQLProceduresForAR.genkey_function_name}")
|
||||
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.init_procedure_name}")
|
||||
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.allocate_procedure_name}")
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestAccountsSettings(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()
|
||||
|
||||
def test_stale_days(self):
|
||||
cur_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
|
||||
cur_settings.allow_stale = 0
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# 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
|
||||
@@ -9,13 +10,14 @@ 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 ERPNextTestSuite, the doctype test records and all
|
||||
# On IntegrationTestCase, 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
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class TestAdvancePaymentLedgerEntry(ERPNextTestSuite, AccountsTestMixin):
|
||||
class TestAdvancePaymentLedgerEntry(AccountsTestMixin, IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for AdvancePaymentLedgerEntry.
|
||||
Use this class for testing interactions between multiple components.
|
||||
@@ -28,6 +30,9 @@ class TestAdvancePaymentLedgerEntry(ERPNextTestSuite, AccountsTestMixin):
|
||||
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
|
||||
|
||||
57
erpnext/accounts/doctype/advance_tax/advance_tax.json
Normal file
57
erpnext/accounts/doctype/advance_tax/advance_tax.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2021-11-25 10:24:39.836195",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"reference_type",
|
||||
"reference_name",
|
||||
"reference_detail",
|
||||
"account_head",
|
||||
"allocated_amount"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "reference_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Reference Type",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Reference Name",
|
||||
"options": "reference_type"
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_detail",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reference Detail"
|
||||
},
|
||||
{
|
||||
"fieldname": "account_head",
|
||||
"fieldtype": "Link",
|
||||
"label": "Account Head",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Allocated Amount",
|
||||
"options": "party_account_currency"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:05:58.308002",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Advance Tax",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class TransactionDeletionRecordToDelete(Document):
|
||||
class AdvanceTax(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
@@ -14,14 +14,14 @@ class TransactionDeletionRecordToDelete(Document):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
child_doctypes: DF.SmallText | None
|
||||
company_field: DF.Data | None
|
||||
deleted: DF.Check
|
||||
doctype_name: DF.Link | None
|
||||
document_count: DF.Int
|
||||
account_head: DF.Link | None
|
||||
allocated_amount: DF.Currency
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
reference_detail: DF.Data | None
|
||||
reference_name: DF.DynamicLink | None
|
||||
reference_type: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -14,7 +14,6 @@
|
||||
"description",
|
||||
"included_in_paid_amount",
|
||||
"set_by_item_tax_template",
|
||||
"is_tax_withholding_account",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
@@ -26,6 +25,7 @@
|
||||
"net_amount",
|
||||
"tax_amount",
|
||||
"total",
|
||||
"allocated_amount",
|
||||
"column_break_13",
|
||||
"base_tax_amount",
|
||||
"base_net_amount",
|
||||
@@ -97,11 +97,11 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8",
|
||||
@@ -172,6 +172,12 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Considered In Paid Amount"
|
||||
},
|
||||
{
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Allocated Amount",
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fetch_from": "account_head.account_currency",
|
||||
"fieldname": "currency",
|
||||
@@ -207,26 +213,18 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_tax_withholding_account",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Tax Withholding Account",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-15 06:42:18.707671",
|
||||
"modified": "2024-11-22 19:16:22.346267",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Advance Taxes and Charges",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "ASC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ class AdvanceTaxesandCharges(Document):
|
||||
|
||||
account_head: DF.Link
|
||||
add_deduct_tax: DF.Literal["Add", "Deduct"]
|
||||
allocated_amount: DF.Currency
|
||||
base_net_amount: DF.Currency
|
||||
base_tax_amount: DF.Currency
|
||||
base_total: DF.Currency
|
||||
@@ -27,12 +28,10 @@ class AdvanceTaxesandCharges(Document):
|
||||
currency: DF.Link | None
|
||||
description: DF.SmallText
|
||||
included_in_paid_amount: DF.Check
|
||||
is_tax_withholding_account: DF.Check
|
||||
net_amount: DF.Currency
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
project: DF.Link | None
|
||||
rate: DF.Float
|
||||
row_id: DF.Data | None
|
||||
set_by_item_tax_template: DF.Check
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
frappe.provide("erpnext.integrations");
|
||||
|
||||
frappe.ui.form.on("Bank", {
|
||||
onload: function (frm) {
|
||||
add_fields_to_mapping_table(frm);
|
||||
},
|
||||
refresh: function (frm) {
|
||||
add_fields_to_mapping_table(frm);
|
||||
frm.toggle_display(["address_html", "contact_html"], !frm.doc.__islocal);
|
||||
@@ -34,11 +37,11 @@ let add_fields_to_mapping_table = function (frm) {
|
||||
});
|
||||
});
|
||||
|
||||
const grid = frm.fields_dict.bank_transaction_mapping?.grid;
|
||||
|
||||
if (grid) {
|
||||
grid.update_docfield_property("bank_transaction_field", "options", options);
|
||||
}
|
||||
frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property(
|
||||
"bank_transaction_field",
|
||||
"options",
|
||||
options
|
||||
);
|
||||
};
|
||||
|
||||
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
||||
@@ -113,7 +116,7 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
||||
"There was an issue connecting to Plaid's authentication server. Check browser console for more information"
|
||||
)
|
||||
);
|
||||
console.error(error);
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
plaid_success(token, response) {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestBank(ERPNextTestSuite):
|
||||
class TestBank(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
@@ -42,4 +42,8 @@ frappe.ui.form.on("Bank Account", {
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
is_company_account: function (frm) {
|
||||
frm.set_df_property("account", "reqd", frm.doc.is_company_account);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company Account",
|
||||
"mandatory_depends_on": "is_company_account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
@@ -99,7 +98,6 @@
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"mandatory_depends_on": "is_company_account",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
@@ -254,7 +252,7 @@
|
||||
"link_fieldname": "default_bank_account"
|
||||
}
|
||||
],
|
||||
"modified": "2026-01-20 00:46:16.633364",
|
||||
"modified": "2025-08-29 12:32:01.081687",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Account",
|
||||
|
||||
@@ -51,29 +51,25 @@ class BankAccount(Document):
|
||||
delete_contact_and_address("Bank Account", self.name)
|
||||
|
||||
def validate(self):
|
||||
self.validate_is_company_account()
|
||||
self.validate_company()
|
||||
self.validate_account()
|
||||
self.update_default_bank_account()
|
||||
|
||||
def validate_is_company_account(self):
|
||||
if self.is_company_account:
|
||||
if not self.company:
|
||||
frappe.throw(_("Company is mandatory for company account"))
|
||||
|
||||
if not self.account:
|
||||
frappe.throw(_("Company Account is mandatory"))
|
||||
|
||||
self.validate_account()
|
||||
|
||||
def validate_account(self):
|
||||
if accounts := frappe.db.get_all(
|
||||
"Bank Account", filters={"account": self.account, "name": ["!=", self.name]}, as_list=1
|
||||
):
|
||||
frappe.throw(
|
||||
_("'{0}' account is already used by {1}. Use another account.").format(
|
||||
frappe.bold(self.account),
|
||||
frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])),
|
||||
if self.account:
|
||||
if accounts := frappe.db.get_all(
|
||||
"Bank Account", filters={"account": self.account, "name": ["!=", self.name]}, as_list=1
|
||||
):
|
||||
frappe.throw(
|
||||
_("'{0}' account is already used by {1}. Use another account.").format(
|
||||
frappe.bold(self.account),
|
||||
frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def validate_company(self):
|
||||
if self.is_company_account and not self.company:
|
||||
frappe.throw(_("Company is mandatory for company account"))
|
||||
|
||||
def update_default_bank_account(self):
|
||||
if self.is_default and not self.disabled:
|
||||
@@ -116,7 +112,6 @@ def get_default_company_bank_account(company, party_type, party):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_bank_account_details(bank_account):
|
||||
frappe.has_permission("Bank Account", doc=bank_account, ptype="read", throw=True)
|
||||
return frappe.get_cached_value(
|
||||
"Bank Account", bank_account, ["account", "bank", "bank_account_no"], as_dict=1
|
||||
)
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
import frappe
|
||||
from frappe import ValidationError
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestBankAccount(ERPNextTestSuite):
|
||||
class TestBankAccount(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestBankAccountSubtype(ERPNextTestSuite):
|
||||
class TestBankAccountSubtype(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestBankAccountType(ERPNextTestSuite):
|
||||
class TestBankAccountType(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
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 frappe.utils import cint, flt, fmt_money, get_link_to_form, getdate
|
||||
from pypika import Order
|
||||
|
||||
import erpnext
|
||||
@@ -127,7 +125,7 @@ class BankClearance(Document):
|
||||
)
|
||||
|
||||
msg += "</ul>"
|
||||
msgprint(_(msg))
|
||||
frappe.throw(_(msg))
|
||||
return
|
||||
|
||||
if not entries_to_update:
|
||||
@@ -136,44 +134,16 @@ class BankClearance(Document):
|
||||
|
||||
for d in entries_to_update:
|
||||
if d.payment_document == "Sales Invoice":
|
||||
old_clearance_date = frappe.db.get_value(
|
||||
frappe.db.set_value(
|
||||
"Sales Invoice Payment",
|
||||
{
|
||||
"parent": d.payment_entry,
|
||||
"account": self.account,
|
||||
"amount": [">", 0],
|
||||
},
|
||||
{"parent": d.payment_entry, "account": self.get("account"), "amount": [">", 0]},
|
||||
"clearance_date",
|
||||
d.clearance_date,
|
||||
)
|
||||
if d.clearance_date or old_clearance_date:
|
||||
frappe.db.set_value(
|
||||
"Sales Invoice Payment",
|
||||
{"parent": d.payment_entry, "account": self.get("account"), "amount": [">", 0]},
|
||||
"clearance_date",
|
||||
d.clearance_date,
|
||||
)
|
||||
sales_invoice = frappe.get_lazy_doc("Sales Invoice", d.payment_entry)
|
||||
sales_invoice.add_comment(
|
||||
"Comment",
|
||||
_("Clearance date changed from {0} to {1} via Bank Clearance Tool").format(
|
||||
old_clearance_date, d.clearance_date
|
||||
),
|
||||
)
|
||||
|
||||
else:
|
||||
# using db_set to trigger notification
|
||||
payment_entry = frappe.get_lazy_doc(d.payment_document, d.payment_entry)
|
||||
old_clearance_date = payment_entry.clearance_date
|
||||
|
||||
if d.clearance_date or old_clearance_date:
|
||||
# using db_set to trigger notification
|
||||
payment_entry.db_set("clearance_date", d.clearance_date)
|
||||
|
||||
payment_entry.add_comment(
|
||||
"Comment",
|
||||
_("Clearance date changed from {0} to {1} via Bank Clearance Tool").format(
|
||||
old_clearance_date, d.clearance_date
|
||||
),
|
||||
)
|
||||
payment_entry.db_set("clearance_date", d.clearance_date)
|
||||
|
||||
self.get_payment_entries()
|
||||
msgprint(_("Clearance Date updated"))
|
||||
@@ -184,162 +154,65 @@ def get_payment_entries_for_bank_clearance(
|
||||
):
|
||||
entries = []
|
||||
|
||||
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")
|
||||
)
|
||||
)
|
||||
|
||||
condition = ""
|
||||
pe_condition = ""
|
||||
if not include_reconciled_entries:
|
||||
journal_entry_query = journal_entry_query.where(
|
||||
(journal_entry.clearance_date.isnull()) | (journal_entry.clearance_date == "0000-00-00")
|
||||
)
|
||||
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_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)
|
||||
)
|
||||
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,
|
||||
)
|
||||
|
||||
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
|
||||
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,
|
||||
)
|
||||
|
||||
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 = []
|
||||
|
||||
pos_sales_invoices, pos_purchase_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_query = (
|
||||
pos_sales_invoices = (
|
||||
frappe.qb.from_(si_payment)
|
||||
.inner_join(si)
|
||||
.on(si_payment.parent == si.name)
|
||||
@@ -362,22 +235,38 @@ 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)
|
||||
|
||||
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")
|
||||
pi = frappe.qb.DocType("Purchase Invoice")
|
||||
|
||||
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"),
|
||||
)
|
||||
|
||||
pos_sales_invoices = (
|
||||
pos_sales_invoices_query.orderby(si.posting_date).orderby(si.name, order=Order.desc)
|
||||
.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)
|
||||
).run(as_dict=True)
|
||||
|
||||
entries = (
|
||||
list(payment_entries)
|
||||
+ list(journal_entries)
|
||||
+ list(pos_sales_invoices)
|
||||
+ list(paid_purchase_invoices)
|
||||
list(payment_entries) + list(journal_entries) + list(pos_sales_invoices) + list(pos_purchase_invoices)
|
||||
)
|
||||
|
||||
return entries
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import unittest
|
||||
|
||||
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,12 +15,13 @@ 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 ERPNextTestSuite, if_lending_app_installed, if_lending_app_not_installed
|
||||
from erpnext.tests.utils import if_lending_app_installed, if_lending_app_not_installed
|
||||
|
||||
|
||||
class TestBankClearance(ERPNextTestSuite):
|
||||
def setUp(self):
|
||||
frappe.clear_cache()
|
||||
class TestBankClearance(IntegrationTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
create_warehouse(
|
||||
warehouse_name="_Test Warehouse",
|
||||
properties={"parent_warehouse": "All Warehouses - _TC"},
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
"label": "Payment Entry",
|
||||
"oldfieldname": "voucher_id",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "payment_document"
|
||||
"options": "payment_document",
|
||||
"width": "50"
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
@@ -68,7 +69,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"columns": 2,
|
||||
"fieldname": "cheque_number",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
@@ -78,10 +79,8 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "cheque_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Cheque Date",
|
||||
"oldfieldname": "cheque_date",
|
||||
"oldfieldtype": "Date",
|
||||
@@ -97,19 +96,17 @@
|
||||
"oldfieldtype": "Date"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-17 14:33:45.913311",
|
||||
"modified": "2024-03-27 13:06:37.609319",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Clearance Detail",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "ASC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestBankGuarantee(ERPNextTestSuite):
|
||||
class TestBankGuarantee(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
@@ -304,7 +304,6 @@ def create_payment_entry_bts(
|
||||
project=None,
|
||||
cost_center=None,
|
||||
allow_edit=None,
|
||||
company_bank_account=None,
|
||||
):
|
||||
# Create a new payment entry based on the bank transaction
|
||||
bank_transaction = frappe.db.get_values(
|
||||
@@ -346,9 +345,6 @@ def create_payment_entry_bts(
|
||||
pe.project = project
|
||||
pe.cost_center = cost_center
|
||||
|
||||
if company_bank_account:
|
||||
pe.bank_account = company_bank_account
|
||||
|
||||
pe.validate()
|
||||
|
||||
if allow_edit:
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
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 (
|
||||
@@ -12,10 +13,9 @@ 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(ERPNextTestSuite, AccountsTestMixin):
|
||||
class TestBankReconciliationTool(AccountsTestMixin, IntegrationTestCase):
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
self.create_customer()
|
||||
@@ -24,6 +24,9 @@ class TestBankReconciliationTool(ERPNextTestSuite, AccountsTestMixin):
|
||||
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(
|
||||
{
|
||||
@@ -40,7 +43,6 @@ class TestBankReconciliationTool(ERPNextTestSuite, AccountsTestMixin):
|
||||
"bank": bank.name,
|
||||
"is_company_account": True,
|
||||
"account": self.bank, # account from Chart of Accounts
|
||||
"company": self.company,
|
||||
}
|
||||
)
|
||||
.insert()
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
# 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(ERPNextTestSuite):
|
||||
class TestBankStatementImport(unittest.TestCase):
|
||||
"""Unit tests for Bank Statement Import functions"""
|
||||
|
||||
def test_preprocess_mt940_content_with_long_statement_number(self):
|
||||
|
||||
@@ -38,10 +38,7 @@
|
||||
"column_break_3czf",
|
||||
"bank_party_name",
|
||||
"bank_party_account_number",
|
||||
"bank_party_iban",
|
||||
"extended_bank_statement_section",
|
||||
"included_fee",
|
||||
"excluded_fee"
|
||||
"bank_party_iban"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -236,32 +233,12 @@
|
||||
{
|
||||
"fieldname": "column_break_oufv",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "extended_bank_statement_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Extended Bank Statement"
|
||||
},
|
||||
{
|
||||
"fieldname": "included_fee",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Included Fee",
|
||||
"non_negative": 1,
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"description": "On save, the Excluded Fee will be converted to an Included Fee.",
|
||||
"fieldname": "excluded_fee",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Excluded Fee",
|
||||
"non_negative": 1,
|
||||
"options": "currency"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-07 20:49:18.600757",
|
||||
"modified": "2025-10-23 17:32:58.514807",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Transaction",
|
||||
|
||||
@@ -32,8 +32,6 @@ class BankTransaction(Document):
|
||||
date: DF.Date | None
|
||||
deposit: DF.Currency
|
||||
description: DF.SmallText | None
|
||||
excluded_fee: DF.Currency
|
||||
included_fee: DF.Currency
|
||||
naming_series: DF.Literal["ACC-BTN-.YYYY.-"]
|
||||
party: DF.DynamicLink | None
|
||||
party_type: DF.Link | None
|
||||
@@ -47,14 +45,9 @@ class BankTransaction(Document):
|
||||
# end: auto-generated types
|
||||
|
||||
def before_validate(self):
|
||||
self.handle_excluded_fee()
|
||||
self.update_allocated_amount()
|
||||
|
||||
def on_discard(self):
|
||||
self.db_set("status", "Cancelled")
|
||||
|
||||
def validate(self):
|
||||
self.validate_included_fee()
|
||||
self.validate_duplicate_references()
|
||||
self.validate_currency()
|
||||
|
||||
@@ -139,8 +132,6 @@ class BankTransaction(Document):
|
||||
self.set_status()
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = ["GL Entry"]
|
||||
|
||||
for payment_entry in self.payment_entries:
|
||||
self.delink_payment_entry(payment_entry)
|
||||
|
||||
@@ -316,40 +307,6 @@ class BankTransaction(Document):
|
||||
|
||||
self.party_type, self.party = result
|
||||
|
||||
def validate_included_fee(self):
|
||||
"""
|
||||
The included_fee is only handled for withdrawals. An included_fee for a deposit, is not credited to the account and is
|
||||
therefore outside of the deposit value and can be larger than the deposit itself.
|
||||
"""
|
||||
|
||||
if self.included_fee and self.withdrawal:
|
||||
if self.included_fee > self.withdrawal:
|
||||
frappe.throw(_("Included fee is bigger than the withdrawal itself."))
|
||||
|
||||
def handle_excluded_fee(self):
|
||||
# Include the excluded fee on validate to handle all further processing the same
|
||||
excluded_fee = flt(self.excluded_fee)
|
||||
if excluded_fee <= 0:
|
||||
return
|
||||
|
||||
# Suppress a negative deposit (aka withdrawal), likely not intendend
|
||||
if flt(self.deposit) > 0 and (flt(self.deposit) - excluded_fee) < 0:
|
||||
frappe.throw(_("The Excluded Fee is bigger than the Deposit it is deducted from."))
|
||||
|
||||
# Enforce directionality
|
||||
if flt(self.deposit) > 0 and flt(self.withdrawal) > 0:
|
||||
frappe.throw(
|
||||
_("Only one of Deposit or Withdrawal should be non-zero when applying an Excluded Fee.")
|
||||
)
|
||||
|
||||
if flt(self.deposit) > 0:
|
||||
self.deposit = flt(self.deposit) - excluded_fee
|
||||
# A fee applied to deposit and withdrawal equal 0 become a withdrawal
|
||||
elif flt(self.withdrawal) >= 0:
|
||||
self.withdrawal = flt(self.withdrawal) + excluded_fee
|
||||
self.included_fee = flt(self.included_fee) + excluded_fee
|
||||
self.excluded_fee = 0
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_doctypes_for_bank_reconciliation():
|
||||
@@ -375,12 +332,11 @@ def get_clearance_details(transaction, payment_entry, bt_allocations, gl_entries
|
||||
("unallocated_amount", "bank_account"),
|
||||
as_dict=True,
|
||||
)
|
||||
bt_bank_account = frappe.db.get_value("Bank Account", bt.bank_account, "account")
|
||||
|
||||
if bt_bank_account != gl_bank_account:
|
||||
if bt.bank_account != gl_bank_account:
|
||||
frappe.throw(
|
||||
_("Bank Account {} in Bank Transaction {} is not matching with Bank Account {}").format(
|
||||
bt_bank_account, payment_entry.payment_entry, gl_bank_account
|
||||
bt.bank_account, payment_entry.payment_entry, gl_bank_account
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -2,20 +2,27 @@
|
||||
# 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(ERPNextTestSuite):
|
||||
def setUp(self):
|
||||
class TestAutoMatchParty(IntegrationTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
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:])
|
||||
|
||||
@@ -6,6 +6,7 @@ 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,
|
||||
@@ -18,10 +19,12 @@ 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 ERPNextTestSuite, if_lending_app_installed
|
||||
from erpnext.tests.utils import if_lending_app_installed
|
||||
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = ["Item", "Cost Center"]
|
||||
|
||||
|
||||
class TestBankTransaction(ERPNextTestSuite):
|
||||
class TestBankTransaction(IntegrationTestCase):
|
||||
def setUp(self):
|
||||
make_pos_profile()
|
||||
|
||||
@@ -382,7 +385,7 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Customer",
|
||||
"customer_group": "Individual",
|
||||
"customer_group": "All Customer Groups",
|
||||
"customer_type": "Company",
|
||||
"customer_name": "Poore Simon's",
|
||||
}
|
||||
@@ -413,7 +416,7 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Customer",
|
||||
"customer_group": "Individual",
|
||||
"customer_group": "All Customer Groups",
|
||||
"customer_type": "Company",
|
||||
"customer_name": "Fayva",
|
||||
}
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestBankTransactionFees(ERPNextTestSuite):
|
||||
def test_included_fee_throws(self):
|
||||
"""A fee that's part of a withdrawal cannot be bigger than the
|
||||
withdrawal itself."""
|
||||
bt = frappe.new_doc("Bank Transaction")
|
||||
bt.withdrawal = 100
|
||||
bt.included_fee = 101
|
||||
|
||||
self.assertRaises(frappe.ValidationError, bt.validate_included_fee)
|
||||
|
||||
def test_included_fee_allows_equal(self):
|
||||
"""A fee that's part of a withdrawal may be equal to the withdrawal
|
||||
amount (only the fee was deducted from the account)."""
|
||||
bt = frappe.new_doc("Bank Transaction")
|
||||
bt.withdrawal = 100
|
||||
bt.included_fee = 100
|
||||
|
||||
bt.validate_included_fee()
|
||||
|
||||
def test_included_fee_allows_for_deposit(self):
|
||||
"""For deposits, a fee may be recorded separately without limiting the
|
||||
received amount."""
|
||||
bt = frappe.new_doc("Bank Transaction")
|
||||
bt.deposit = 10
|
||||
bt.included_fee = 999
|
||||
|
||||
bt.validate_included_fee()
|
||||
|
||||
def test_excluded_fee_noop_when_zero(self):
|
||||
"""When there is no excluded fee to apply, the amounts should remain
|
||||
unchanged."""
|
||||
bt = frappe.new_doc("Bank Transaction")
|
||||
bt.deposit = 100
|
||||
bt.withdrawal = 0
|
||||
bt.included_fee = 5
|
||||
bt.excluded_fee = 0
|
||||
|
||||
bt.handle_excluded_fee()
|
||||
|
||||
self.assertEqual(bt.deposit, 100)
|
||||
self.assertEqual(bt.withdrawal, 0)
|
||||
self.assertEqual(bt.included_fee, 5)
|
||||
self.assertEqual(bt.excluded_fee, 0)
|
||||
|
||||
def test_excluded_fee_throws_when_exceeds_deposit(self):
|
||||
"""A fee deducted from an incoming payment must not exceed the incoming
|
||||
amount (else it would be a withdrawal, a conversion we don't support)."""
|
||||
bt = frappe.new_doc("Bank Transaction")
|
||||
bt.deposit = 10
|
||||
bt.excluded_fee = 11
|
||||
|
||||
self.assertRaises(frappe.ValidationError, bt.handle_excluded_fee)
|
||||
|
||||
def test_excluded_fee_throws_when_both_deposit_and_withdrawal_are_set(self):
|
||||
"""A transaction must be either incoming or outgoing when applying a
|
||||
fee, not both."""
|
||||
bt = frappe.new_doc("Bank Transaction")
|
||||
bt.deposit = 10
|
||||
bt.withdrawal = 10
|
||||
bt.excluded_fee = 1
|
||||
|
||||
self.assertRaises(frappe.ValidationError, bt.handle_excluded_fee)
|
||||
|
||||
def test_excluded_fee_deducts_from_deposit(self):
|
||||
"""When a fee is deducted from an incoming payment, the net received
|
||||
amount decreases and the fee is tracked as included."""
|
||||
bt = frappe.new_doc("Bank Transaction")
|
||||
bt.deposit = 100
|
||||
bt.withdrawal = 0
|
||||
bt.included_fee = 2
|
||||
bt.excluded_fee = 5
|
||||
|
||||
bt.handle_excluded_fee()
|
||||
|
||||
self.assertEqual(bt.deposit, 95)
|
||||
self.assertEqual(bt.withdrawal, 0)
|
||||
self.assertEqual(bt.included_fee, 7)
|
||||
self.assertEqual(bt.excluded_fee, 0)
|
||||
|
||||
def test_excluded_fee_can_reduce_an_incoming_payment_to_zero(self):
|
||||
"""A separately-deducted fee may reduce an incoming payment to zero,
|
||||
while still tracking the fee."""
|
||||
bt = frappe.new_doc("Bank Transaction")
|
||||
bt.deposit = 5
|
||||
bt.withdrawal = 0
|
||||
bt.included_fee = 0
|
||||
bt.excluded_fee = 5
|
||||
|
||||
bt.handle_excluded_fee()
|
||||
|
||||
self.assertEqual(bt.deposit, 0)
|
||||
self.assertEqual(bt.withdrawal, 0)
|
||||
self.assertEqual(bt.included_fee, 5)
|
||||
self.assertEqual(bt.excluded_fee, 0)
|
||||
|
||||
def test_excluded_fee_increases_outgoing_payment(self):
|
||||
"""When a separately-deducted fee is provided for an outgoing payment,
|
||||
the total money leaving increases and the fee is tracked."""
|
||||
bt = frappe.new_doc("Bank Transaction")
|
||||
bt.deposit = 0
|
||||
bt.withdrawal = 100
|
||||
bt.included_fee = 2
|
||||
bt.excluded_fee = 5
|
||||
|
||||
bt.handle_excluded_fee()
|
||||
|
||||
self.assertEqual(bt.deposit, 0)
|
||||
self.assertEqual(bt.withdrawal, 105)
|
||||
self.assertEqual(bt.included_fee, 7)
|
||||
self.assertEqual(bt.excluded_fee, 0)
|
||||
|
||||
def test_excluded_fee_turns_zero_amount_into_withdrawal(self):
|
||||
"""If only an excluded fee is provided, it should be treated as an
|
||||
outgoing payment and the fee is then tracked as included."""
|
||||
bt = frappe.new_doc("Bank Transaction")
|
||||
bt.deposit = 0
|
||||
bt.withdrawal = 0
|
||||
bt.included_fee = 0
|
||||
bt.excluded_fee = 5
|
||||
|
||||
bt.handle_excluded_fee()
|
||||
|
||||
self.assertEqual(bt.deposit, 0)
|
||||
self.assertEqual(bt.withdrawal, 5)
|
||||
self.assertEqual(bt.included_fee, 5)
|
||||
self.assertEqual(bt.excluded_fee, 0)
|
||||
@@ -2,10 +2,8 @@
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestBisectAccountingStatements(ERPNextTestSuite):
|
||||
class TestBisectAccountingStatements(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestBisectNodes(ERPNextTestSuite):
|
||||
class TestBisectNodes(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
@@ -12,15 +12,6 @@ frappe.ui.form.on("Budget", {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("account", function () {
|
||||
return {
|
||||
filters: {
|
||||
is_group: 0,
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
frappe.db.get_single_value("Accounts Settings", "use_legacy_budget_controller").then((value) => {
|
||||
if (value) {
|
||||
@@ -33,16 +24,24 @@ frappe.ui.form.on("Budget", {
|
||||
frm.trigger("toggle_reqd_fields");
|
||||
|
||||
if (!frm.doc.__islocal && frm.doc.docstatus == 1) {
|
||||
frm.add_custom_button(
|
||||
__("Revise Budget"),
|
||||
function () {
|
||||
frm.events.revise_budget_action(frm);
|
||||
},
|
||||
__("Actions")
|
||||
let exception_role = await frappe.db.get_value(
|
||||
"Company",
|
||||
frm.doc.company,
|
||||
"exception_budget_approver_role"
|
||||
);
|
||||
}
|
||||
|
||||
toggle_distribution_fields(frm);
|
||||
const role = exception_role.message.exception_budget_approver_role;
|
||||
|
||||
if (role && frappe.user.has_role(role)) {
|
||||
frm.add_custom_button(
|
||||
__("Revise Budget"),
|
||||
function () {
|
||||
frm.events.revise_budget_action(frm);
|
||||
},
|
||||
__("Actions")
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
budget_against: function (frm) {
|
||||
@@ -55,15 +54,10 @@ frappe.ui.form.on("Budget", {
|
||||
frm.doc.budget_distribution.forEach((row) => {
|
||||
row.amount = flt((row.percent / 100) * frm.doc.budget_amount, 2);
|
||||
});
|
||||
set_total_budget_amount(frm);
|
||||
frm.refresh_field("budget_distribution");
|
||||
}
|
||||
},
|
||||
|
||||
distribute_equally: function (frm) {
|
||||
toggle_distribution_fields(frm);
|
||||
},
|
||||
|
||||
set_null_value: function (frm) {
|
||||
if (frm.doc.budget_against == "Cost Center") {
|
||||
frm.set_value("project", null);
|
||||
@@ -106,8 +100,6 @@ frappe.ui.form.on("Budget Distribution", {
|
||||
let row = frappe.get_doc(cdt, cdn);
|
||||
if (frm.doc.budget_amount) {
|
||||
row.percent = flt((row.amount / frm.doc.budget_amount) * 100, 2);
|
||||
|
||||
set_total_budget_amount(frm);
|
||||
frm.refresh_field("budget_distribution");
|
||||
}
|
||||
},
|
||||
@@ -115,29 +107,7 @@ frappe.ui.form.on("Budget Distribution", {
|
||||
let row = frappe.get_doc(cdt, cdn);
|
||||
if (frm.doc.budget_amount) {
|
||||
row.amount = flt((row.percent / 100) * frm.doc.budget_amount, 2);
|
||||
|
||||
set_total_budget_amount(frm);
|
||||
frm.refresh_field("budget_distribution");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function set_total_budget_amount(frm) {
|
||||
let total = 0;
|
||||
|
||||
(frm.doc.budget_distribution || []).forEach((row) => {
|
||||
total += flt(row.amount);
|
||||
});
|
||||
|
||||
frm.set_value("budget_distribution_total", total);
|
||||
}
|
||||
|
||||
function toggle_distribution_fields(frm) {
|
||||
const grid = frm.fields_dict.budget_distribution.grid;
|
||||
|
||||
["amount", "percent"].forEach((field) => {
|
||||
grid.update_docfield_property(field, "read_only", frm.doc.distribute_equally);
|
||||
});
|
||||
|
||||
grid.refresh();
|
||||
}
|
||||
|
||||
@@ -25,10 +25,6 @@
|
||||
"distribute_equally",
|
||||
"section_break_fpdt",
|
||||
"budget_distribution",
|
||||
"section_break_wkqb",
|
||||
"column_break_paum",
|
||||
"column_break_nwor",
|
||||
"budget_distribution_total",
|
||||
"section_break_6",
|
||||
"applicable_on_material_request",
|
||||
"action_if_annual_budget_exceeded_on_mr",
|
||||
@@ -226,8 +222,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_fpdt",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "budget_distribution",
|
||||
@@ -308,32 +303,13 @@
|
||||
"options": "Monthly\nQuarterly\nHalf-Yearly\nYearly",
|
||||
"read_only_depends_on": "eval: doc.revision_of",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_wkqb",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_paum",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_nwor",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "budget_distribution_total",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Budget Distribution Total",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-10 02:35:01.197613",
|
||||
"modified": "2025-11-19 17:00:00.648224",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Budget",
|
||||
|
||||
@@ -53,7 +53,6 @@ class Budget(Document):
|
||||
budget_against: DF.Literal["", "Cost Center", "Project"]
|
||||
budget_amount: DF.Currency
|
||||
budget_distribution: DF.Table[BudgetDistribution]
|
||||
budget_distribution_total: DF.Currency
|
||||
budget_end_date: DF.Date | None
|
||||
budget_start_date: DF.Date | None
|
||||
company: DF.Link
|
||||
@@ -231,49 +230,28 @@ class Budget(Document):
|
||||
|
||||
def before_save(self):
|
||||
self.allocate_budget()
|
||||
self.budget_distribution_total = sum(flt(row.amount) for row in self.budget_distribution)
|
||||
|
||||
def on_update(self):
|
||||
self.validate_distribution_totals()
|
||||
|
||||
def allocate_budget(self):
|
||||
if self._should_skip_allocation():
|
||||
return
|
||||
|
||||
if self._should_recalculate_manual_distribution():
|
||||
self._recalculate_manual_distribution()
|
||||
if self.revision_of:
|
||||
return
|
||||
|
||||
if not self.should_regenerate_budget_distribution():
|
||||
return
|
||||
|
||||
self._regenerate_distribution()
|
||||
self.set("budget_distribution", [])
|
||||
|
||||
def _should_skip_allocation(self):
|
||||
return self.revision_of and not self.distribute_equally
|
||||
periods = self.get_budget_periods()
|
||||
total_periods = len(periods)
|
||||
row_percent = 100 / total_periods if total_periods else 0
|
||||
|
||||
def _should_recalculate_manual_distribution(self):
|
||||
return (
|
||||
not self.distribute_equally
|
||||
and bool(self.budget_distribution)
|
||||
and self._is_only_budget_amount_changed()
|
||||
)
|
||||
|
||||
def _is_only_budget_amount_changed(self):
|
||||
old = self.get_doc_before_save()
|
||||
if not old:
|
||||
return False
|
||||
|
||||
return (
|
||||
old.budget_amount != self.budget_amount
|
||||
and old.distribution_frequency == self.distribution_frequency
|
||||
and old.budget_start_date == self.budget_start_date
|
||||
and old.budget_end_date == self.budget_end_date
|
||||
)
|
||||
|
||||
def _recalculate_manual_distribution(self):
|
||||
for row in self.budget_distribution:
|
||||
row.amount = flt((row.percent / 100) * self.budget_amount, 3)
|
||||
for start_date, end_date in periods:
|
||||
row = self.append("budget_distribution", {})
|
||||
row.start_date = start_date
|
||||
row.end_date = end_date
|
||||
self.add_allocated_amount(row, row_percent)
|
||||
|
||||
def should_regenerate_budget_distribution(self):
|
||||
"""Check whether budget distribution should be recalculated."""
|
||||
@@ -287,6 +265,7 @@ class Budget(Document):
|
||||
"to_fiscal_year",
|
||||
"budget_amount",
|
||||
"distribution_frequency",
|
||||
"distribute_equally",
|
||||
]
|
||||
for field in changed_fields:
|
||||
if old_doc.get(field) != self.get(field):
|
||||
@@ -294,21 +273,6 @@ class Budget(Document):
|
||||
|
||||
return bool(self.distribute_equally)
|
||||
|
||||
def _regenerate_distribution(self):
|
||||
self.set("budget_distribution", [])
|
||||
|
||||
periods = self.get_budget_periods()
|
||||
total_periods = len(periods)
|
||||
row_percent = 100 / total_periods if total_periods else 0
|
||||
|
||||
for start_date, end_date in periods:
|
||||
row = self.append("budget_distribution", {})
|
||||
row.start_date = start_date
|
||||
row.end_date = end_date
|
||||
self.add_allocated_amount(row, row_percent)
|
||||
|
||||
self.budget_distribution_total = self.budget_amount
|
||||
|
||||
def get_budget_periods(self):
|
||||
"""Return list of (start_date, end_date) tuples based on frequency."""
|
||||
frequency = self.distribution_frequency
|
||||
@@ -348,8 +312,12 @@ class Budget(Document):
|
||||
}.get(frequency, 1)
|
||||
|
||||
def add_allocated_amount(self, row, row_percent):
|
||||
row.amount = flt(self.budget_amount * row_percent / 100, 3)
|
||||
row.percent = flt(row_percent, 3)
|
||||
if not self.distribute_equally:
|
||||
row.amount = 0
|
||||
row.percent = 0
|
||||
else:
|
||||
row.amount = flt(self.budget_amount * row_percent / 100, 3)
|
||||
row.percent = flt(row_percent, 3)
|
||||
|
||||
def validate_distribution_totals(self):
|
||||
if self.should_regenerate_budget_distribution():
|
||||
|
||||
@@ -19,6 +19,12 @@ 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"
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestCashierClosing(ERPNextTestSuite):
|
||||
class TestCashierClosing(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestChartofAccountsImporter(ERPNextTestSuite):
|
||||
class TestChartofAccountsImporter(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestChequePrintTemplate(ERPNextTestSuite):
|
||||
class TestChequePrintTemplate(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
"idx": 1,
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-14 18:15:27.367298",
|
||||
"modified": "2025-01-22 10:46:42.904001",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Cost Center",
|
||||
@@ -173,20 +173,11 @@
|
||||
"role": "Employee",
|
||||
"select": 1,
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"role": "HR User",
|
||||
"select": 1
|
||||
},
|
||||
{
|
||||
"role": "HR Manager",
|
||||
"select": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"search_fields": "parent_cost_center, is_group",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "ASC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,10 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestCostCenter(ERPNextTestSuite):
|
||||
class TestCostCenter(IntegrationTestCase):
|
||||
def test_cost_center_creation_against_child_node(self):
|
||||
cost_center = frappe.get_doc(
|
||||
{
|
||||
|
||||
23
erpnext/accounts/doctype/cost_center/test_records.json
Normal file
23
erpnext/accounts/doctype/cost_center/test_records.json
Normal file
@@ -0,0 +1,23 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
@@ -4,6 +4,7 @@ import unittest
|
||||
|
||||
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,10 +16,9 @@ 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(ERPNextTestSuite):
|
||||
class TestCostCenterAllocation(IntegrationTestCase):
|
||||
def setUp(self):
|
||||
cost_centers = [
|
||||
"Main Cost Center 1",
|
||||
@@ -191,7 +191,7 @@ class TestCostCenterAllocation(ERPNextTestSuite):
|
||||
coa2.cancel()
|
||||
jv.cancel()
|
||||
|
||||
@ERPNextTestSuite.change_settings("System Settings", {"rounding_method": "Commercial Rounding"})
|
||||
@IntegrationTestCase.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
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = ["Item"]
|
||||
|
||||
|
||||
def test_create_test_data():
|
||||
@@ -86,7 +88,6 @@ def test_create_test_data():
|
||||
"partner_name": "_Test Coupon Partner",
|
||||
"commission_rate": 2,
|
||||
"referral_code": "COPART",
|
||||
"territory": "All Territories",
|
||||
}
|
||||
)
|
||||
sales_partner.insert()
|
||||
@@ -109,10 +110,13 @@ def test_create_test_data():
|
||||
coupon_code.insert()
|
||||
|
||||
|
||||
class TestCouponCode(ERPNextTestSuite):
|
||||
class TestCouponCode(IntegrationTestCase):
|
||||
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)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ frappe.ui.form.on("Currency Exchange Settings", {
|
||||
to: "{to_currency}",
|
||||
};
|
||||
add_param(frm, r.message, params, result);
|
||||
} else if (["frankfurter.app", "frankfurter.dev"].includes(frm.doc.service_provider)) {
|
||||
} else if (frm.doc.service_provider == "frankfurter.dev") {
|
||||
let result = ["rates", "{to_currency}"];
|
||||
let params = {
|
||||
base: "{from_currency}",
|
||||
|
||||
@@ -101,11 +101,10 @@
|
||||
"label": "Use HTTP Protocol"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 0,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-16 13:28:21.075743",
|
||||
"modified": "2025-11-25 13:03:41.896424",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Currency Exchange Settings",
|
||||
|
||||
@@ -60,7 +60,7 @@ class CurrencyExchangeSettings(Document):
|
||||
self.append("req_params", {"key": "date", "value": "{transaction_date}"})
|
||||
self.append("req_params", {"key": "from", "value": "{from_currency}"})
|
||||
self.append("req_params", {"key": "to", "value": "{to_currency}"})
|
||||
elif self.service_provider in ("frankfurter.dev", "frankfurter.app"):
|
||||
elif self.service_provider == "frankfurter.dev":
|
||||
self.set("result_key", [])
|
||||
self.set("req_params", [])
|
||||
|
||||
@@ -105,11 +105,9 @@ class CurrencyExchangeSettings(Document):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_api_endpoint(service_provider: str | None = None, use_http: bool = False):
|
||||
if service_provider and service_provider in ["exchangerate.host", "frankfurter.dev", "frankfurter.app"]:
|
||||
if service_provider and service_provider in ["exchangerate.host", "frankfurter.dev"]:
|
||||
if service_provider == "exchangerate.host":
|
||||
api = "api.exchangerate.host/convert"
|
||||
elif service_provider == "frankfurter.app":
|
||||
api = "api.frankfurter.app/{transaction_date}"
|
||||
elif service_provider == "frankfurter.dev":
|
||||
api = "api.frankfurter.dev/v1/{transaction_date}"
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestCurrencyExchangeSettings(ERPNextTestSuite):
|
||||
class TestCurrencyExchangeSettings(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
@@ -4,20 +4,37 @@ 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,
|
||||
)
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = ["Company", "Cost Center"]
|
||||
|
||||
|
||||
class TestDunning(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()
|
||||
|
||||
def test_dunning_without_fees(self):
|
||||
dunning = create_dunning(overdue_days=20)
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestDunningType(ERPNextTestSuite):
|
||||
class TestDunningType(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
36
erpnext/accounts/doctype/dunning_type/test_records.json
Normal file
36
erpnext/accounts/doctype/dunning_type/test_records.json
Normal file
@@ -0,0 +1,36 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
@@ -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(ERPNextTestSuite, AccountsTestMixin):
|
||||
class TestExchangeRateRevaluation(AccountsTestMixin, IntegrationTestCase):
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
self.create_usd_receivable_account()
|
||||
@@ -22,13 +22,14 @@ class TestExchangeRateRevaluation(ERPNextTestSuite, AccountsTestMixin):
|
||||
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.
|
||||
@@ -36,7 +37,7 @@ class TestExchangeRateRevaluation(ERPNextTestSuite, AccountsTestMixin):
|
||||
company_doc.unrealized_exchange_gain_loss_account = company_doc.exchange_gain_loss_account
|
||||
company_doc.save()
|
||||
|
||||
@ERPNextTestSuite.change_settings(
|
||||
@IntegrationTestCase.change_settings(
|
||||
"Accounts Settings",
|
||||
{"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0},
|
||||
)
|
||||
@@ -90,7 +91,7 @@ class TestExchangeRateRevaluation(ERPNextTestSuite, AccountsTestMixin):
|
||||
)[0]
|
||||
self.assertEqual(acc_balance.balance, 8500.0)
|
||||
|
||||
@ERPNextTestSuite.change_settings(
|
||||
@IntegrationTestCase.change_settings(
|
||||
"Accounts Settings",
|
||||
{"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0},
|
||||
)
|
||||
@@ -163,7 +164,7 @@ class TestExchangeRateRevaluation(ERPNextTestSuite, AccountsTestMixin):
|
||||
self.assertEqual(acc_balance.balance, 0.0)
|
||||
self.assertEqual(acc_balance.balance_in_account_currency, 0.0)
|
||||
|
||||
@ERPNextTestSuite.change_settings(
|
||||
@IntegrationTestCase.change_settings(
|
||||
"Accounts Settings",
|
||||
{"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0},
|
||||
)
|
||||
@@ -258,7 +259,7 @@ class TestExchangeRateRevaluation(ERPNextTestSuite, AccountsTestMixin):
|
||||
self.assertEqual(flt(acc_balance.balance, precision), 0.0)
|
||||
self.assertEqual(flt(acc_balance.balance_in_account_currency, precision), 0.0)
|
||||
|
||||
@ERPNextTestSuite.change_settings(
|
||||
@IntegrationTestCase.change_settings(
|
||||
"Accounts Settings",
|
||||
{"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0},
|
||||
)
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
import unittest
|
||||
|
||||
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(ERPNextTestSuite):
|
||||
class TestFinanceBook(IntegrationTestCase):
|
||||
def test_finance_book(self):
|
||||
finance_book = create_finance_book()
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import json
|
||||
import math
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from functools import cache, reduce
|
||||
from functools import reduce
|
||||
from typing import Any, Union
|
||||
|
||||
import frappe
|
||||
@@ -15,8 +15,7 @@ from frappe.database.operator_map import OPERATOR_MAP
|
||||
from frappe.query_builder import Case
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import cstr, date_diff, flt, getdate
|
||||
from frappe.utils.xlsxutils import XLSXMetadata, XLSXStyleBuilder
|
||||
from pypika.terms import Bracket, LiteralValue
|
||||
from pypika.terms import LiteralValue
|
||||
|
||||
from erpnext import get_company_currency
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
@@ -39,9 +38,6 @@ from erpnext.accounts.report.financial_statements import (
|
||||
)
|
||||
from erpnext.accounts.utils import get_children, get_currency_precision
|
||||
|
||||
DEFAULT_BULLET_PREFIX = "• "
|
||||
SEGMENT_PREFIX = "seg_"
|
||||
|
||||
# ============================================================================
|
||||
# DATA MODELS
|
||||
# ============================================================================
|
||||
@@ -75,9 +71,7 @@ class PeriodValue:
|
||||
class AccountData:
|
||||
"""Account data across all periods"""
|
||||
|
||||
account: str # docname
|
||||
account_name: str = "" # account name
|
||||
account_number: str = ""
|
||||
account_name: str
|
||||
period_values: dict[str, PeriodValue] = field(default_factory=dict)
|
||||
|
||||
def add_period(self, period_value: PeriodValue) -> None:
|
||||
@@ -109,11 +103,7 @@ class AccountData:
|
||||
# movement is unaccumulated by default
|
||||
|
||||
def copy(self):
|
||||
copied = AccountData(
|
||||
account=self.account,
|
||||
account_name=self.account_name,
|
||||
account_number=self.account_number,
|
||||
)
|
||||
copied = AccountData(account_name=self.account_name)
|
||||
copied.period_values = {k: v.copy() for k, v in self.period_values.items()}
|
||||
return copied
|
||||
|
||||
@@ -145,7 +135,7 @@ class SegmentData:
|
||||
|
||||
@property
|
||||
def id(self) -> str:
|
||||
return f"{SEGMENT_PREFIX}{self.index}"
|
||||
return f"seg_{self.index}"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -226,38 +216,14 @@ class FinancialReportEngine:
|
||||
return context.get_result()
|
||||
|
||||
def _validate_filters(self, filters: dict[str, Any]) -> None:
|
||||
filter_labels = {
|
||||
"report_template": _("Report Template"),
|
||||
"filter_based_on": _("Filter Based On"),
|
||||
"period_start_date": _("Start Date"),
|
||||
"period_end_date": _("End Date"),
|
||||
"from_fiscal_year": _("Start Year"),
|
||||
"to_fiscal_year": _("End Year"),
|
||||
}
|
||||
|
||||
required_filters_by_basis = {
|
||||
"Date Range": ("period_start_date", "period_end_date"),
|
||||
"Fiscal Year": ("from_fiscal_year", "to_fiscal_year"),
|
||||
}
|
||||
|
||||
required_filters = ["report_template", "filter_based_on"]
|
||||
required_filters.extend(required_filters_by_basis.get(filters.get("filter_based_on"), ()))
|
||||
required_filters = ["report_template", "period_start_date", "period_end_date"]
|
||||
|
||||
for filter_key in required_filters:
|
||||
if not filters.get(filter_key):
|
||||
frappe.throw(
|
||||
title=_("Missing Required Filter"),
|
||||
msg=_("Missing required filter: {0}").format(
|
||||
frappe.bold(filter_labels.get(filter_key, filter_key))
|
||||
),
|
||||
)
|
||||
frappe.throw(_("Missing required filter: {0}").format(filter_key))
|
||||
|
||||
if filters.get("presentation_currency"):
|
||||
frappe.msgprint(
|
||||
title=_("Unsupported Feature"),
|
||||
msg=_("Currency filters are currently unsupported in Custom Financial Report."),
|
||||
indicator="orange",
|
||||
)
|
||||
frappe.msgprint(_("Currency filters are currently unsupported in Custom Financial Report."))
|
||||
|
||||
# Margin view is dependent on first row being an income account. Hence not supported.
|
||||
# Way to implement this would be using calculated rows with formulas.
|
||||
@@ -363,10 +329,12 @@ class DataCollector:
|
||||
self.account_fields = {field.fieldname for field in frappe.get_meta("Account").fields}
|
||||
|
||||
def add_account_request(self, row):
|
||||
accounts = self._parse_account_filter(self.company, row)
|
||||
|
||||
self.account_requests.append(
|
||||
{
|
||||
"row": row,
|
||||
"accounts": self._parse_account_filter(self.company, row),
|
||||
"accounts": accounts,
|
||||
"balance_type": row.balance_type,
|
||||
"reference_code": row.reference_code,
|
||||
"reverse_sign": row.reverse_sign,
|
||||
@@ -377,12 +345,12 @@ class DataCollector:
|
||||
if not self.account_requests:
|
||||
return {"account_data": {}, "summary": {}, "account_details": {}}
|
||||
|
||||
# Get all accounts
|
||||
all_accounts = []
|
||||
|
||||
# Get all unique accounts
|
||||
all_accounts = set()
|
||||
for request in self.account_requests:
|
||||
all_accounts.extend(request["accounts"])
|
||||
all_accounts.update(request["accounts"])
|
||||
|
||||
all_accounts = list(all_accounts)
|
||||
if not all_accounts:
|
||||
return {"account_data": {}, "summary": {}, "account_details": {}}
|
||||
|
||||
@@ -405,9 +373,7 @@ class DataCollector:
|
||||
total_values = [0.0] * len(self.periods)
|
||||
request_account_details = {}
|
||||
|
||||
for account in accounts:
|
||||
account_name = account.name
|
||||
|
||||
for account_name in accounts:
|
||||
if account_name not in account_data:
|
||||
continue
|
||||
|
||||
@@ -430,21 +396,20 @@ class DataCollector:
|
||||
return {"account_data": account_data, "summary": summary, "account_details": account_details}
|
||||
|
||||
@staticmethod
|
||||
def _parse_account_filter(company, report_row) -> list[dict]:
|
||||
def _parse_account_filter(company, report_row) -> list[str]:
|
||||
"""
|
||||
Find accounts matching filter criteria.
|
||||
|
||||
Example:
|
||||
|
||||
- Input: '["account_type", "=", "Cash"]'
|
||||
- Output: [{"name": "Cash - COMP", "account_name": "Cash", "account_number": "1001"}]
|
||||
Input: '["account_type", "=", "Cash"]'
|
||||
Output: ["Cash - COMP", "Petty Cash - COMP", "Bank - COMP"]
|
||||
"""
|
||||
filter_parser = FilterExpressionParser()
|
||||
|
||||
account = frappe.qb.DocType("Account")
|
||||
query = (
|
||||
frappe.qb.from_(account)
|
||||
.select(account.name, account.account_name, account.account_number)
|
||||
.select(account.name)
|
||||
.where(account.disabled == 0)
|
||||
.where(account.is_group == 0)
|
||||
)
|
||||
@@ -458,8 +423,8 @@ class DataCollector:
|
||||
|
||||
query = query.where(where_condition)
|
||||
query = query.orderby(account.name)
|
||||
|
||||
return query.run(as_dict=True)
|
||||
result = query.run(as_dict=True)
|
||||
return [row.name for row in result]
|
||||
|
||||
@staticmethod
|
||||
def get_filtered_accounts(company: str, account_rows: list) -> list[str]:
|
||||
@@ -491,36 +456,17 @@ class FinancialQueryBuilder:
|
||||
self.filters = filters
|
||||
self.periods = periods
|
||||
self.company = filters.get("company")
|
||||
self.account_meta = {} # {name: {account_name, account_number}}
|
||||
self.ignore_opening_entries = False
|
||||
|
||||
def fetch_account_balances(self, accounts: list[dict]) -> dict[str, AccountData]:
|
||||
def fetch_account_balances(self, accounts: list[str]) -> dict[str, AccountData]:
|
||||
"""
|
||||
Fetch account balances for all periods with optimization.
|
||||
Steps: get opening balances → fetch GL entries → calculate running totals
|
||||
|
||||
- accounts: list of accounts with details
|
||||
|
||||
```
|
||||
{
|
||||
"name": "Cash - COMP",
|
||||
"account_name": "Cash",
|
||||
"account_number": "1001",
|
||||
}
|
||||
```
|
||||
|
||||
Returns:
|
||||
dict: {account: AccountData}
|
||||
"""
|
||||
account_names = list({acc.name for acc in accounts})
|
||||
# NOTE: do not change accounts list as it is used in caller function
|
||||
self.account_meta = {
|
||||
acc.name: {"account_name": acc.account_name, "account_number": acc.account_number}
|
||||
for acc in accounts
|
||||
}
|
||||
|
||||
balances_data = self._get_opening_balances(account_names)
|
||||
gl_data = self._get_gl_movements(account_names)
|
||||
balances_data = self._get_opening_balances(accounts)
|
||||
gl_data = self._get_gl_movements(accounts)
|
||||
self._calculate_running_balances(balances_data, gl_data)
|
||||
self._handle_balance_accumulation(balances_data)
|
||||
|
||||
@@ -530,8 +476,6 @@ class FinancialQueryBuilder:
|
||||
"""
|
||||
Return opening balances for *all accounts* defaulting to zero.
|
||||
"""
|
||||
self.ignore_opening_entries = False
|
||||
|
||||
if frappe.get_single_value("Accounts Settings", "ignore_account_closing_balance"):
|
||||
return self._get_opening_balances_from_gl(accounts)
|
||||
|
||||
@@ -551,9 +495,9 @@ class FinancialQueryBuilder:
|
||||
if last_closing_voucher:
|
||||
closing_voucher = last_closing_voucher[0]
|
||||
closing_data = self._get_closing_balances(accounts, closing_voucher.name)
|
||||
self.ignore_opening_entries = True # Else it will double count
|
||||
|
||||
return self._rebase_closing_balances(closing_data, closing_voucher.period_end_date)
|
||||
if sum(closing_data.values()) != 0.0:
|
||||
return self._rebase_closing_balances(closing_data, closing_voucher.period_end_date)
|
||||
|
||||
return self._get_opening_balances_from_gl(accounts)
|
||||
|
||||
@@ -572,7 +516,7 @@ class FinancialQueryBuilder:
|
||||
.where(acb_table.period_closing_voucher == closing_voucher)
|
||||
)
|
||||
|
||||
query = self._apply_standard_filters(query, acb_table, "Account Closing Balance")
|
||||
query = self._apply_standard_filters(query, acb_table)
|
||||
results = self._execute_with_permissions(query, "Account Closing Balance")
|
||||
|
||||
for row in results:
|
||||
@@ -599,8 +543,7 @@ class FinancialQueryBuilder:
|
||||
gap_movement = gap_movements.get(account, 0.0)
|
||||
opening_balance = closing_balance + gap_movement
|
||||
|
||||
account_data = AccountData(account=account, **self._get_account_meta(account))
|
||||
|
||||
account_data = AccountData(account)
|
||||
account_data.add_period(PeriodValue(first_period_key, opening_balance, 0, 0))
|
||||
balances_data[account] = account_data
|
||||
|
||||
@@ -647,12 +590,7 @@ class FinancialQueryBuilder:
|
||||
.groupby(gl_table.account)
|
||||
)
|
||||
|
||||
ignore_is_opening = frappe.get_single_value(
|
||||
"Accounts Settings", "ignore_is_opening_check_for_reporting"
|
||||
)
|
||||
if self.ignore_opening_entries and not ignore_is_opening:
|
||||
# This filter here applies to all accounts (BS & PL)
|
||||
# However, in legacy query, this filter only applies to BS accounts
|
||||
if not frappe.get_single_value("Accounts Settings", "ignore_is_opening_check_for_reporting"):
|
||||
query = query.where(gl_table.is_opening == "No")
|
||||
|
||||
# Add period-specific columns
|
||||
@@ -672,15 +610,12 @@ class FinancialQueryBuilder:
|
||||
return self._execute_with_permissions(query, "GL Entry")
|
||||
|
||||
def _calculate_running_balances(self, balances_data: dict, gl_data: list[dict]) -> dict:
|
||||
gl_dict = {row["account"]: row for row in gl_data}
|
||||
accounts = set(balances_data.keys()) | set(gl_dict.keys())
|
||||
|
||||
for account in accounts:
|
||||
for row in gl_data:
|
||||
account = row["account"]
|
||||
if account not in balances_data:
|
||||
balances_data[account] = AccountData(account=account, **self._get_account_meta(account))
|
||||
balances_data[account] = AccountData(account)
|
||||
|
||||
account_data: AccountData = balances_data[account]
|
||||
gl_movement = gl_dict.get(account, {})
|
||||
|
||||
if account_data.has_periods():
|
||||
first_period = account_data.get_period(self.periods[0]["key"])
|
||||
@@ -690,13 +625,20 @@ class FinancialQueryBuilder:
|
||||
|
||||
for period in self.periods:
|
||||
period_key = period["key"]
|
||||
movement = gl_movement.get(period_key, 0.0)
|
||||
movement = row.get(period_key, 0.0)
|
||||
closing_balance = current_balance + movement
|
||||
|
||||
account_data.add_period(PeriodValue(period_key, current_balance, closing_balance, movement))
|
||||
|
||||
current_balance = closing_balance
|
||||
|
||||
# Accounts with no movements
|
||||
for account_data in balances_data.values():
|
||||
for period in self.periods:
|
||||
period_key = period["key"]
|
||||
if period_key not in account_data.period_values:
|
||||
account_data.add_period(PeriodValue(period_key, 0.0, 0.0, 0.0))
|
||||
|
||||
def _handle_balance_accumulation(self, balances_data):
|
||||
for account_data in balances_data.values():
|
||||
account_data: AccountData
|
||||
@@ -715,19 +657,12 @@ class FinancialQueryBuilder:
|
||||
else:
|
||||
account_data.unaccumulate_values()
|
||||
|
||||
def _apply_standard_filters(self, query, table, doctype: str = "GL Entry"):
|
||||
# Exclude PCV-generated entries except those posted to a closing-account-head
|
||||
# so BS retained earnings survive while P&L reversal entries are filtered out
|
||||
pcv = frappe.qb.DocType("Period Closing Voucher")
|
||||
closing_heads = frappe.qb.from_(pcv).select(pcv.closing_account_head).where(pcv.docstatus == 1)
|
||||
|
||||
if doctype == "GL Entry":
|
||||
is_pcv = table.voucher_type == "Period Closing Voucher"
|
||||
else:
|
||||
# Account Closing Balance
|
||||
is_pcv = table.is_period_closing_voucher_entry == 1
|
||||
|
||||
query = query.where(~is_pcv | table.account.isin(closing_heads))
|
||||
def _apply_standard_filters(self, query, table):
|
||||
if self.filters.get("ignore_closing_entries"):
|
||||
if hasattr(table, "is_period_closing_voucher_entry"):
|
||||
query = query.where(table.is_period_closing_voucher_entry == 0)
|
||||
else:
|
||||
query = query.where(table.voucher_type != "Period Closing Voucher")
|
||||
|
||||
if self.filters.get("project"):
|
||||
projects = self.filters.get("project")
|
||||
@@ -775,13 +710,10 @@ class FinancialQueryBuilder:
|
||||
user_conditions = build_match_conditions(doctype)
|
||||
|
||||
if user_conditions:
|
||||
query = query.where(Bracket(LiteralValue(user_conditions)))
|
||||
query = query.where(LiteralValue(user_conditions))
|
||||
|
||||
return query.run(as_dict=True)
|
||||
|
||||
def _get_account_meta(self, account: str) -> dict[str, Any]:
|
||||
return self.account_meta.get(account, {})
|
||||
|
||||
|
||||
class FilterExpressionParser:
|
||||
"""Direct filter expression to SQL condition builder"""
|
||||
@@ -1435,8 +1367,7 @@ class FormattingEngine:
|
||||
condition=lambda rd: getattr(rd.row, "italic_text", False), format_properties={"italic": True}
|
||||
),
|
||||
FormattingRule(
|
||||
condition=lambda rd: rd.is_detail_row,
|
||||
format_properties={"is_detail": True, "prefix": DEFAULT_BULLET_PREFIX},
|
||||
condition=lambda rd: rd.is_detail_row, format_properties={"is_detail": True, "prefix": "• "}
|
||||
),
|
||||
FormattingRule(
|
||||
condition=lambda rd: getattr(rd.row, "warn_if_negative", False),
|
||||
@@ -1613,29 +1544,20 @@ class RowFormatterBase(ABC):
|
||||
pass
|
||||
|
||||
def _get_values(self, row_data: RowData) -> dict[str, Any]:
|
||||
def _get_row_data(key: str, default: Any = "") -> Any:
|
||||
return getattr(row_data.row, key, default) or default
|
||||
|
||||
def _get_filter_value(key: str, default: Any = "") -> Any:
|
||||
return getattr(self.context.filters, key, default) or default
|
||||
|
||||
# TODO: can be commonify COA? @abdeali
|
||||
child_accounts = []
|
||||
|
||||
if row_data.account_details:
|
||||
child_accounts = list(row_data.account_details.keys())
|
||||
|
||||
display_name = _get_row_data("display_name", "")
|
||||
|
||||
values = {
|
||||
"account": _get_row_data("account", "") or display_name,
|
||||
"account_name": display_name,
|
||||
"acc_name": _get_row_data("account_name", ""),
|
||||
"acc_number": _get_row_data("account_number", ""),
|
||||
"child_accounts": child_accounts,
|
||||
"account": getattr(row_data.row, "display_name", "") or "",
|
||||
"indent": getattr(row_data.row, "indentation_level", 0),
|
||||
"account_name": getattr(row_data.row, "account", "") or "",
|
||||
"currency": self.context.currency or "",
|
||||
"indent": _get_row_data("indentation_level", 0),
|
||||
"period_start_date": _get_filter_value("period_start_date", ""),
|
||||
"period_end_date": _get_filter_value("period_end_date", ""),
|
||||
"period_start_date": getattr(self.context.filters, "period_start_date", "") or "",
|
||||
"period_end_date": getattr(self.context.filters, "period_end_date", "") or "",
|
||||
"total": 0,
|
||||
}
|
||||
|
||||
@@ -1748,8 +1670,8 @@ class DetailRowBuilder:
|
||||
detail_rows = []
|
||||
parent_row = self.parent_row_data.row
|
||||
|
||||
for account_data in self.parent_row_data.account_details.values():
|
||||
detail_row = self._create_detail_row_object(account_data, parent_row)
|
||||
for account_name, account_data in self.parent_row_data.account_details.items():
|
||||
detail_row = self._create_detail_row_object(account_name, parent_row)
|
||||
|
||||
balance_type = getattr(parent_row, "balance_type", "Closing Balance")
|
||||
values = account_data.get_values_by_type(balance_type)
|
||||
@@ -1765,20 +1687,16 @@ class DetailRowBuilder:
|
||||
|
||||
return detail_rows
|
||||
|
||||
def _create_detail_row_object(self, account_data: AccountData, parent_row):
|
||||
acc_name = account_data.account_name or ""
|
||||
acc_number = account_data.account_number or ""
|
||||
|
||||
display_name = f"{_(acc_number)} - {_(acc_name)}" if acc_number else _(acc_name)
|
||||
def _create_detail_row_object(self, account_name: str, parent_row):
|
||||
short_name = account_name.rsplit(" - ", 1)[0].strip()
|
||||
|
||||
return type(
|
||||
"DetailRow",
|
||||
(),
|
||||
{
|
||||
"account": account_data.account,
|
||||
"display_name": display_name,
|
||||
"account_name": acc_name,
|
||||
"account_number": acc_number,
|
||||
"display_name": short_name,
|
||||
"account": account_name,
|
||||
"account_name": short_name,
|
||||
"data_source": "Account Detail",
|
||||
"indentation_level": getattr(parent_row, "indentation_level", 0) + 1,
|
||||
"fieldtype": getattr(parent_row, "fieldtype", None),
|
||||
@@ -1882,124 +1800,3 @@ class GrowthViewTransformer:
|
||||
return 0.0
|
||||
else:
|
||||
return flt(((current_value - previous_value) / abs(previous_value)) * 100, 2)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# XLSX EXPORT STYLING
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def get_xlsx_styles(metadata: XLSXMetadata) -> dict | None:
|
||||
"""
|
||||
Generate XLSX styles for financial report templates.
|
||||
|
||||
NOTE: Currently only custom report generated with "Report Template" filter will have styles applied.
|
||||
"""
|
||||
# skip styling
|
||||
if not metadata.filters.get("report_template"):
|
||||
return
|
||||
|
||||
builder = XLSXStyleBuilder(metadata, default_styling=False)
|
||||
builder.apply_default_styles(currency_formatting=False)
|
||||
|
||||
# currency is fixed for all columns (only if report template filter is applied)
|
||||
currency = get_company_currency(metadata.filters.get("company"))
|
||||
|
||||
styles = {
|
||||
"bold": builder.register_style({"bold": True}),
|
||||
"italic": builder.register_style({"italic": True}),
|
||||
"warning": builder.register_style({"font_color": "#dc3545"}), # text-danger
|
||||
}
|
||||
|
||||
fieldtype_formats = {
|
||||
"Int": builder.register_style({"num_format": "General"}),
|
||||
"Float": builder.register_style({"num_format": builder.get_number_format("Float")}),
|
||||
"Percent": builder.register_style({"num_format": builder.get_number_format("Percent")}),
|
||||
"Currency": builder.register_style({"num_format": builder.get_number_format("Currency", currency)}),
|
||||
}
|
||||
|
||||
# quick access for hot loop
|
||||
style_cell = builder.style_cell
|
||||
|
||||
@cache
|
||||
def get_color_style(color: str) -> int:
|
||||
return builder.register_style({"font_color": color})
|
||||
|
||||
@cache
|
||||
def get_prefix_style(prefix: str) -> int:
|
||||
prefix = f"{prefix or DEFAULT_BULLET_PREFIX}@"
|
||||
|
||||
return builder.register_style({"num_format": prefix})
|
||||
|
||||
@cache
|
||||
def get_indent_style(indent: int) -> int:
|
||||
return builder.register_style({"align": "left", "indent": indent})
|
||||
|
||||
# column level styling of currency columns
|
||||
for col_idx, col in metadata.column_map.items():
|
||||
if col.get("fieldtype") != "Currency":
|
||||
continue
|
||||
|
||||
builder.style_column(col_idx, fieldtype_formats["Currency"])
|
||||
|
||||
# cell level styling
|
||||
for row_idx, row in metadata.row_map.items():
|
||||
# skip total row
|
||||
if metadata.has_total_row and row_idx == builder.last_row_index:
|
||||
continue
|
||||
|
||||
is_segmented = (row.get("_segment_info", {}).get("total_segments", 1) or 1) > 1
|
||||
segment_values = row.get("segment_values", {}) or {}
|
||||
|
||||
for col_idx, col in metadata.column_map.items():
|
||||
fieldname = col.get("fieldname")
|
||||
is_account = fieldname == "account"
|
||||
|
||||
# determine formatting bucket
|
||||
if is_segmented and fieldname.startswith(SEGMENT_PREFIX):
|
||||
formatting = row.copy()
|
||||
|
||||
_, seg_idx, seg_fieldname = fieldname.split("_", 2)
|
||||
is_account = seg_fieldname == "account"
|
||||
formatting.update(segment_values.get(f"{SEGMENT_PREFIX}{seg_idx}", {}) or {})
|
||||
else:
|
||||
formatting = row # default formatting bucket.
|
||||
|
||||
if not is_account and formatting.get("is_blank_line"):
|
||||
continue
|
||||
|
||||
col_fieldtype = col.get("fieldtype")
|
||||
cell_fieldtype = formatting.get("fieldtype") or col_fieldtype
|
||||
cell_value = row.get(fieldname)
|
||||
|
||||
if cell_value in (None, ""):
|
||||
continue
|
||||
|
||||
# account column and other fieldtype styling
|
||||
if is_account:
|
||||
if formatting.get("is_detail") or (prefix := formatting.get("prefix")):
|
||||
style_cell(row_idx, col_idx, get_prefix_style(prefix))
|
||||
|
||||
# custom indentation (different segment might have different indentation levels)
|
||||
if is_segmented and (indent := formatting.get("indent")) and indent > 0:
|
||||
style_cell(row_idx, col_idx, get_indent_style(indent))
|
||||
else:
|
||||
if col_fieldtype != cell_fieldtype and cell_fieldtype in fieldtype_formats:
|
||||
style_cell(row_idx, col_idx, fieldtype_formats[cell_fieldtype])
|
||||
|
||||
# text styles
|
||||
for style_key in ("bold", "italic"):
|
||||
if formatting.get(style_key):
|
||||
style_cell(row_idx, col_idx, styles[style_key])
|
||||
|
||||
# color styles
|
||||
if (
|
||||
formatting.get("warn_if_negative")
|
||||
and cell_fieldtype in frappe.model.numeric_fieldtypes
|
||||
and flt(cell_value) < 0
|
||||
):
|
||||
style_cell(row_idx, col_idx, styles["warning"])
|
||||
elif color := formatting.get("color"):
|
||||
style_cell(row_idx, col_idx, get_color_style(color))
|
||||
|
||||
return builder.result
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
frappe.ui.form.on("Financial Report Template", {
|
||||
refresh(frm) {
|
||||
if (frm.is_new() || frm.doc.rows.length === 0) return;
|
||||
|
||||
// add custom button to view missed accounts
|
||||
frm.add_custom_button(__("View Account Coverage"), function () {
|
||||
let selected_rows = frm.get_field("rows").grid.get_selected_children();
|
||||
@@ -22,7 +20,7 @@ frappe.ui.form.on("Financial Report Template", {
|
||||
});
|
||||
},
|
||||
|
||||
after_save(frm) {
|
||||
validate(frm) {
|
||||
if (!frm.doc.rows || frm.doc.rows.length === 0) {
|
||||
frappe.msgprint(__("At least one row is required for a financial report template"));
|
||||
}
|
||||
@@ -36,6 +34,14 @@ frappe.ui.form.on("Financial Report Row", {
|
||||
update_formula_label(frm, row.data_source);
|
||||
update_formula_description(frm, row.data_source);
|
||||
|
||||
if (row.data_source !== "Account Data") {
|
||||
frappe.model.set_value(cdt, cdn, "balance_type", "");
|
||||
}
|
||||
|
||||
if (["Blank Line", "Column Break", "Section Break"].includes(row.data_source)) {
|
||||
frappe.model.set_value(cdt, cdn, "calculation_formula", "");
|
||||
}
|
||||
|
||||
set_up_filters_editor(frm, cdt, cdn);
|
||||
},
|
||||
|
||||
@@ -316,8 +322,6 @@ function update_formula_description(frm, data_source) {
|
||||
const list_style = `style="margin-bottom: var(--margin-sm); color: var(--text-muted); font-size: 0.9em;"`;
|
||||
const note_style = `style="margin-bottom: 0; color: var(--text-muted); font-size: 0.9em;"`;
|
||||
const tip_style = `style="margin-bottom: 0; color: var(--text-color); font-size: 0.85em;"`;
|
||||
const code_style = `style="background: var(--bg-light-gray); padding: var(--padding-xs); border-radius: var(--border-radius); font-size: 0.85em; width: max-content; margin-bottom: var(--margin-sm);"`;
|
||||
const pre_style = `style="margin: 0; border-radius: var(--border-radius)"`;
|
||||
|
||||
let description_html = "";
|
||||
|
||||
@@ -378,13 +382,8 @@ function update_formula_description(frm, data_source) {
|
||||
<li><code>my_app.financial_reports.get_kpi_data</code></li>
|
||||
</ul>
|
||||
|
||||
<h6 ${subtitle_style}>Method Signature:</h6>
|
||||
<div ${code_style}>
|
||||
<pre ${pre_style}>def get_custom_data(filters, periods, row): <br> # filters: dict — report filters (company, period, etc.) <br> # periods: list[dict] — period definitions <br> # row: dict — the current report row <br><br> return [1000.0, 1200.0, 1150.0] # one value per period</pre>
|
||||
</div>
|
||||
|
||||
<h6 ${subtitle_style}>Return Format:</h6>
|
||||
<p ${text_style}>A list of numbers, one for each period: <code>[1000.0, 1200.0, 1150.0]</code></p>
|
||||
<p ${text_style}>Numbers for each period: <code>[1000.0, 1200.0, 1150.0]</code></p>
|
||||
</div>`;
|
||||
} else if (data_source === "Blank Line") {
|
||||
description_html = `
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:template_name",
|
||||
"creation": "2025-08-02 04:44:15.184541",
|
||||
"doctype": "DocType",
|
||||
@@ -30,8 +31,7 @@
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Report Type",
|
||||
"options": "\nProfit and Loss Statement\nBalance Sheet\nCash Flow\nCustom Financial Statement",
|
||||
"reqd": 1
|
||||
"options": "\nProfit and Loss Statement\nBalance Sheet\nCash Flow\nCustom Financial Statement"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:frappe.boot.developer_mode",
|
||||
@@ -66,7 +66,7 @@
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-23 01:04:05.797161",
|
||||
"modified": "2025-11-14 00:11:03.508139",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Financial Report Template",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user