mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-01 06:48:36 +00:00
Compare commits
2 Commits
copilot/de
...
copilot/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66dac9b7a5 | ||
|
|
7eadf19116 |
26
.github/CONTRIBUTING.md
vendored
26
.github/CONTRIBUTING.md
vendored
@@ -1,4 +1,4 @@
|
||||
### Introduction (for first timers)
|
||||
### Introduction (first timers)
|
||||
|
||||
Thank you for your interest in raising an Issue with ERPNext. An Issue could mean a bug report or a request for a missing feature. By raising a bug report, you are contributing to the development of ERPNext and this is the first step of participating in the community. Bug reports are very helpful for developers as they quickly fix the issue before other users start facing it.
|
||||
|
||||
@@ -6,31 +6,31 @@ Feature requests are also a great way to take the product forward. New ideas can
|
||||
|
||||
When you are raising an Issue, you should keep a few things in mind. Remember that the developer does not have access to your machine so you must give all the information you can while raising an Issue. If you are suggesting a feature, you should be very clear about what you want.
|
||||
|
||||
The Issue list is not the right place to ask a question or start a general discussion. If you want to do that, then the right place is the forum [https://discuss.frappe.io](https://discuss.frappe.io/c/erpnext/6).
|
||||
The Issue list is not the right place to ask a question or start a general discussion. If you want to do that , then the right place is the forum [https://discuss.frappe.io](https://discuss.frappe.io/c/erpnext/6).
|
||||
|
||||
### Reply and Closing Policy
|
||||
|
||||
If your issue is not clear or does not meet the guidelines, then it will be closed. If it is closed, please supply the requested information and re-open it.
|
||||
If your issue is not clear or does not meet the guidelines, then it will be closed. If it is closed, please supply the information asked and re-open it.
|
||||
|
||||
### General Issue Guidelines
|
||||
|
||||
1. **Search existing Issues:** Before raising an Issue, search if it has been raised before. Maybe add a 👍 or give additional help by creating a mockup if it is not already created.
|
||||
2. **Report each issue separately:** Don't club multiple, unrelated issues in one note.
|
||||
3. **Brief:** Please don't include long explanations. Use screenshots and bullet points instead of descriptive paragraphs.
|
||||
1. **Search existing Issues:** Before raising a Issue, search if it has been raised before. Maybe add a 👍 or give additional help by creating a mockup if it is not already created.
|
||||
1. **Report each issue separately:** Don't club multiple, unreleated issues in one note.
|
||||
1. **Brief:** Please don't include long explanations. Use screenshots and bullet points instead of descriptive paragraphs.
|
||||
|
||||
### Bug Report Guidelines
|
||||
|
||||
1. **Steps to Reproduce:** The bug report must have a list of steps needed to reproduce a bug. If we cannot reproduce it, then we cannot solve it.
|
||||
2. **Version Number:** Please add the version number in your report. Often a bug is fixed in the latest version.
|
||||
3. **Clear Title:** Add a clear subject to your bug report like "Unable to submit Purchase Order without Basic Rate" instead of just "Cannot Submit".
|
||||
4. **Screenshots:** Screenshots are a great way of communicating issues. Try adding annotations or using LICEcap to take a screencast in `.gif` format.
|
||||
1. **Version Number:** Please add the version number in your report. Often a bug is fixed in the latest version
|
||||
1. **Clear Title:** Add a clear subject to your bug report like "Unable to submit Purchase Order without Basic Rate" instead of just "Cannot Submit"
|
||||
1. **Screenshots:** Screenshots are a great way of communicating issues. Try adding annotations or using LiceCAP to take a screencast in `gif`.
|
||||
|
||||
### Feature Request Guidelines
|
||||
|
||||
1. **Clarity:** Clearly specify how you want the feature to behave. Don't just say "I would like multiple PDF formats", instead say "Ability to add multiple print formats for customers with different languages".
|
||||
2. **Solution:** Try to identify what the feature should look like.
|
||||
3. **Mockups:** Mockups are a great way to explain your requirement.
|
||||
1. **Clarity:** Clearly specify how do you want the feature to behave. Don't just say "I would like multiple PDF formats", say that "Ability to add multiple print formats for customers with different languages".
|
||||
1. **Solution:** Try and identify how the feature should look like.
|
||||
1. **Mockups:** Mockups are a great way to explain your requirement.
|
||||
|
||||
### What if my issue is closed
|
||||
### What if my Issue is closed
|
||||
|
||||
Don't worry, take the feedback, supply the correct information and re-open it!
|
||||
|
||||
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
|
||||
|
||||
|
||||
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea or enhancement for ERPNext
|
||||
about: Suggest an idea to improve ERPNext
|
||||
title: ''
|
||||
labels: feature-request
|
||||
assignees: ''
|
||||
@@ -17,21 +17,17 @@ Welcome to ERPNext issue tracker! Before creating an issue, please heed the foll
|
||||
3. When making a feature request, make sure to be as verbose as possible. The better you convey your message, the greater the drive to make it happen.
|
||||
|
||||
|
||||
Please keep in mind that we get many requests and we can't possibly work on all of them, we prioritize development based on the goals of the product and organization. Feature requests are still welcome as it helps us in research when we do decide to work on the requested feature.
|
||||
Please keep in mind that we get many many requests and we can't possibly work on all of them, we prioritize development based on the goals of the product and organization. Feature requests are still welcome as it helps us in research when we do decide to work on the requested feature.
|
||||
|
||||
If you're in urgent need of a feature, please try the following channels to get paid developments done quickly:
|
||||
If you're in urgent need to a feature, please try the following channels to get paid developments done quickly:
|
||||
1. Certified ERPNext partners: https://erpnext.com/partners
|
||||
2. Developer community on ERPNext forums: https://discuss.frappe.io/c/framework/5
|
||||
3. Telegram group for ERPNext/Frappe development work: https://t.me/erpnext_opps
|
||||
|
||||
-->
|
||||
## Before Submitting
|
||||
- [ ] I searched existing issues and confirmed this is not a duplicate
|
||||
- [ ] This is a feature request, not a bug or support question
|
||||
- [ ] For support: https://discuss.frappe.io/c/erpnext/6
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. As a [role], I have to [painful task] because [missing feature].
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
@@ -39,17 +35,5 @@ A clear and concise description of what you want to happen.
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Impact**
|
||||
<!-- Check one: -->
|
||||
- [ ] Blocks critical workflow — no viable workaround
|
||||
- [ ] Significant friction — workaround exists but is painful
|
||||
- [ ] Nice to have — minor improvement
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
**Environment**
|
||||
- ERPNext Version: <!-- Find this in Help > About, e.g. v15.12.0 -->
|
||||
- Frappe Version: <!-- Find this in Help > About, e.g. v15.10.0 -->
|
||||
- Deployment: <!-- Frappe Cloud / Self-hosted / ERPNext Cloud -->
|
||||
|
||||
|
||||
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:
|
||||
|
||||
13
.github/workflows/generate-pot-file.yml
vendored
13
.github/workflows/generate-pot-file.yml
vendored
@@ -15,25 +15,20 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
branch: ["develop", "version-16-hotfix"]
|
||||
branch: ["develop"]
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
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"
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
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: ["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
|
||||
|
||||
25
.github/workflows/patch.yml
vendored
25
.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,15 +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 16 3.14
|
||||
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##*/}}"
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
@@ -50,13 +50,13 @@ pull_request_rules:
|
||||
- version-15-hotfix
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
- name: backport to version-16-hotfix
|
||||
- name: backport to version-16-beta
|
||||
conditions:
|
||||
- label="backport version-16-hotfix"
|
||||
- label="backport version-16-beta"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- version-16-hotfix
|
||||
- version-16-beta
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
- name: Automatic merge on CI success and review
|
||||
|
||||
12
CODEOWNERS
12
CODEOWNERS
@@ -7,17 +7,17 @@ erpnext/accounts/ @ruthra-kumar
|
||||
erpnext/assets/ @khushi8112
|
||||
erpnext/regional @ruthra-kumar
|
||||
erpnext/selling @ruthra-kumar
|
||||
erpnext/support/ @ruthra-kumar
|
||||
|
||||
erpnext/buying/ @rohitwaghchaure @mihir-kandoi
|
||||
erpnext/maintenance/ @rohitwaghchaure @mihir-kandoi
|
||||
erpnext/maintenance/ @rohitwaghchaure
|
||||
erpnext/manufacturing/ @rohitwaghchaure @mihir-kandoi
|
||||
erpnext/quality_management/ @rohitwaghchaure @mihir-kandoi
|
||||
erpnext/quality_management/ @rohitwaghchaure
|
||||
erpnext/stock/ @rohitwaghchaure @mihir-kandoi
|
||||
erpnext/subcontracting/ @mihir-kandoi
|
||||
erpnext/projects/ @nishkagosalia
|
||||
erpnext/subcontracting @mihir-kandoi
|
||||
|
||||
erpnext/controllers/ @ruthra-kumar @rohitwaghchaure @mihir-kandoi
|
||||
erpnext/patches/ @ruthra-kumar @rohitwaghchaure @mihir-kandoi
|
||||
erpnext/patches/ @ruthra-kumar
|
||||
|
||||
.github/ @ruthra-kumar @mihir-kandoi
|
||||
.github/ @ruthra-kumar
|
||||
pyproject.toml @ruthra-kumar
|
||||
|
||||
78
README.md
78
README.md
@@ -1,12 +1,11 @@
|
||||
|
||||
<div align="center">
|
||||
<a href="https://frappe.io/erpnext">
|
||||
<img src="./erpnext/public/images/v16/erpnext.svg" alt="ERPNext Logo" height="80px" width="80px"/>
|
||||
<img src="./erpnext/public/images/v16/erpnext.svg" alt="ERPNext Logo" height="80px" width="80xp"/>
|
||||
</a>
|
||||
<h2>ERPNext</h2>
|
||||
<div align="center">
|
||||
<p align="center">
|
||||
<p>Powerful, Intuitive and Open-Source ERP</p>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
[](https://frappe.school)<br><br>
|
||||
[](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml)
|
||||
@@ -15,7 +14,7 @@
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<img src="./erpnext/public/images/v16/hero_image.png" alt="ERPNext Hero Image"/>
|
||||
<img src="./erpnext/public/images/v16/hero_image.png"/>
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
@@ -28,19 +27,19 @@
|
||||
|
||||
## ERPNext
|
||||
|
||||
100% Open-Source ERP System to help you run your business.
|
||||
100% Open-Source ERP system to help you run your business.
|
||||
|
||||
### Motivation
|
||||
|
||||
Running a business is a complex task - handling invoices, tracking stock, managing personnel, and other daily operations. In a market where software is sold separately to manage each of these tasks, ERPNext does all of the above and more, for free.
|
||||
Running a business is a complex task - handling invoices, tracking stock, managing personnel and even more ad-hoc activities. In a market where software is sold separately to manage each of these tasks, ERPNext does all of the above and more, for free.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Accounting**: All the tools you need to manage cash flow in one place, right from recording transactions to summarizing and analyzing financial reports.
|
||||
- **Order Management**: Track inventory levels, replenish stock, and manage sales orders, customers, suppliers, shipments, deliverables, and order fulfillment.
|
||||
- **Manufacturing**: Simplifies the production cycle, helps track material consumption, exhibits capacity planning, handles subcontracting, and more!
|
||||
- **Asset Management**: From purchase to disposal, IT infrastructure to equipment. Covers every branch of your organization, all in one centralized system.
|
||||
- **Projects**: Deliver both internal and external projects on time, budget and profitability. Track tasks, timesheets, and issues by project.
|
||||
- **Asset Management**: From purchase to perishment, IT infrastructure to equipment. Cover every branch of your organization, all in one centralized system.
|
||||
- **Projects**: Delivery both internal and external Projects on time, budget and Profitability. Track tasks, timesheets, and issues by project.
|
||||
|
||||
<details open>
|
||||
|
||||
@@ -53,7 +52,7 @@ Running a business is a complex task - handling invoices, tracking stock, managi
|
||||
|
||||
### Under the Hood
|
||||
|
||||
- [**Frappe Framework**](https://github.com/frappe/frappe): A full-stack web application framework written in Python and JavaScript. The framework provides a robust foundation for building web applications, including a database abstraction layer, user authentication, and a REST API.
|
||||
- [**Frappe Framework**](https://github.com/frappe/frappe): A full-stack web application framework written in Python and Javascript. The framework provides a robust foundation for building web applications, including a database abstraction layer, user authentication, and a REST API.
|
||||
|
||||
- [**Frappe UI**](https://github.com/frappe/frappe-ui): A Vue-based UI library, to provide a modern user interface. The Frappe UI library provides a variety of components that can be used to build single-page applications on top of the Frappe Framework.
|
||||
|
||||
@@ -61,12 +60,12 @@ Running a business is a complex task - handling invoices, tracking stock, managi
|
||||
|
||||
### Managed Hosting
|
||||
|
||||
You can try [Frappe Cloud](https://frappecloud.com), a simple, user-friendly, and sophisticated [open-source](https://github.com/frappe/press) platform to host Frappe applications reliably and securely.
|
||||
You can try [Frappe Cloud](https://frappecloud.com), a simple, user-friendly and sophisticated [open-source](https://github.com/frappe/press) platform to host Frappe applications with peace of mind.
|
||||
|
||||
It handles installation, setup, upgrades, monitoring, maintenance, and support of your Frappe deployments. It is a fully featured developer platform with an ability to manage and control multiple Frappe deployments.
|
||||
It takes care of installation, setup, upgrades, monitoring, maintenance and support of your Frappe deployments. It is a fully featured developer platform with an ability to manage and control multiple Frappe deployments.
|
||||
|
||||
<div>
|
||||
<a href="https://erpnext-demo.frappe.cloud/app/home" target="_blank" rel="noopener noreferrer">
|
||||
<a href="https://erpnext-demo.frappe.cloud/app/home" target="_blank">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://frappe.io/files/try-on-fc-white.png">
|
||||
<img src="https://frappe.io/files/try-on-fc-black.png" alt="Try on Frappe Cloud" height="28" />
|
||||
@@ -75,48 +74,25 @@ It handles installation, setup, upgrades, monitoring, maintenance, and support o
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
### Self-Hosted
|
||||
#### Docker
|
||||
|
||||
See [Frappe Docker Documentation](https://github.com/frappe/frappe_docker) for full documentation & FAQ on Docker setup
|
||||
Prerequisites: docker, docker-compose, git. Refer [Docker Documentation](https://docs.docker.com) for more details on Docker setup.
|
||||
|
||||
#### Prerequisites
|
||||
Run following commands:
|
||||
|
||||
- [Docker](https://docs.docker.com/get-docker/)
|
||||
- [Docker Compose v2](https://docs.docker.com/compose/)
|
||||
- [git](https://docs.github.com/en/get-started/getting-started-with-git/set-up-git)
|
||||
|
||||
> For Docker basics and best practices refer to Docker's [documentation](https://docs.docker.com)
|
||||
|
||||
#### Demo setup
|
||||
|
||||
The fastest way to try ERPNext is to play in a pre-configured sandbox, in your browser, click the button below:
|
||||
|
||||
<a href="https://labs.play-with-docker.com/?stack=https://raw.githubusercontent.com/frappe/frappe_docker/main/pwd.yml">
|
||||
<img src="https://raw.githubusercontent.com/play-with-docker/stacks/master/assets/images/button.png" alt="Try in PWD"/>
|
||||
</a>
|
||||
|
||||
### Try on your environment
|
||||
|
||||
> **⚠️ Disposable demo only**
|
||||
>
|
||||
> **This setup is intended for quick evaluation. Expect to throw the environment away.** You will not be able to install custom apps to this setup. For production deployments, custom configurations, and detailed explanations, see the full documentation.
|
||||
|
||||
First clone the repo:
|
||||
|
||||
```sh
|
||||
```
|
||||
git clone https://github.com/frappe/frappe_docker
|
||||
cd frappe_docker
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```sh
|
||||
docker compose -f pwd.yml up -d
|
||||
```
|
||||
Wait for a couple of minutes for ERPNext site to be created or check the `create-site` container logs before opening browser on port `8080`. (username: `Administrator`, password: `admin`)
|
||||
|
||||
See [Frappe Docker](https://github.com/frappe/frappe_docker/blob/main/docs/01-getting-started/03-arm64.md) for ARM based docker setup
|
||||
After a couple of minutes, site should be accessible on your localhost port: 8080. Use below default login credentials to access the site.
|
||||
- Username: Administrator
|
||||
- Password: admin
|
||||
|
||||
See [Frappe Docker](https://github.com/frappe/frappe_docker?tab=readme-ov-file#to-run-on-arm64-architecture-follow-this-instructions) for ARM based docker setup.
|
||||
|
||||
|
||||
## Development Setup
|
||||
@@ -124,7 +100,7 @@ See [Frappe Docker](https://github.com/frappe/frappe_docker/blob/main/docs/01-ge
|
||||
|
||||
The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details.
|
||||
|
||||
New passwords will be created for the ERPNext "Administrator" user, the MariaDB root user, and the Frappe user (the script displays the passwords and saves them to ~/frappe_passwords.txt).
|
||||
New passwords will be created for the ERPNext "Administrator" user, the MariaDB root user, and the frappe user (the script displays the passwords and saves them to ~/frappe_passwords.txt).
|
||||
|
||||
|
||||
### Local
|
||||
@@ -153,20 +129,20 @@ To setup the repository locally follow the steps mentioned below:
|
||||
|
||||
4. Open the URL `http://erpnext.localhost:8000/app` in your browser, you should see the app running
|
||||
|
||||
## Learning and Community
|
||||
## Learning and community
|
||||
|
||||
1. [Frappe School](https://school.frappe.io) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community.
|
||||
2. [Official documentation](https://docs.erpnext.com/) - Extensive documentation for ERPNext.
|
||||
3. [Discussion Forum](https://discuss.frappe.io/c/erpnext/6) - Engage with the community of ERPNext users and service providers.
|
||||
3. [Discussion Forum](https://discuss.frappe.io/c/erpnext/6) - Engage with community of ERPNext users and service providers.
|
||||
4. [Telegram Group](https://erpnext_public.t.me) - Get instant help from huge community of users.
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
1. [Issue Guidelines](https://github.com/frappe/erpnext/wiki/Issue-Guidelines)
|
||||
2. [Report Security Vulnerabilities](https://erpnext.com/security)
|
||||
3. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines)
|
||||
4. [Translations](https://crowdin.com/project/frappe)
|
||||
1. [Report Security Vulnerabilities](https://erpnext.com/security)
|
||||
1. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines)
|
||||
2. [Translations](https://crowdin.com/project/frappe)
|
||||
|
||||
|
||||
## Logo and Trademark Policy
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Security Policy
|
||||
|
||||
The ERPNext team and community take security issues seriously. To report a security issue, please go through the information mentioned [here](https://frappe.io/security).
|
||||
The ERPNext team and community take security issues seriously. To report a security issue, fill out the form at [https://erpnext.com/security/report](https://erpnext.com/security/report).
|
||||
|
||||
You can help us make ERPNext and all its users more secure by following the [Reporting guidelines](https://frappe.io/security).
|
||||
You can help us make ERPNext and all it's users more secure by following the [Reporting guidelines](https://erpnext.com/security).
|
||||
|
||||
We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly, and will keep you updated throughout the process.
|
||||
We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly, and will keep you updated throughout the process.
|
||||
@@ -18,9 +18,8 @@ We will grant permission to use the ERPNext name and logo for projects that meet
|
||||
|
||||
- The primary purpose of your project is to promote the spread and improvement of the ERPNext software.
|
||||
- Your project is non-commercial in nature (it can make money to cover its costs or contribute to non-profit entities, but it cannot be run as a for-profit project or business).
|
||||
- Your project neither promotes nor is associated with entities that currently fail to comply with the GPL license under which ERPNext is distributed.
|
||||
|
||||
If your project meets these criteria, you will be permitted to use the ERPNext name and logo to promote your project in any way you see fit with one exception: Please do not use ERPNext as part of a domain name.
|
||||
Your project neither promotes nor is associated with entities that currently fail to comply with the GPL license under which ERPNext is distributed.
|
||||
- If your project meets these criteria, you will be permitted to use the ERPNext name and logo to promote your project in any way you see fit with one exception: Please do not use ERPNext as part of a domain name.
|
||||
|
||||
Use of the ERPNext name and logo is additionally allowed in the following situations:
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.user import is_website_user
|
||||
|
||||
__version__ = "17.0.0-dev"
|
||||
__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"
|
||||
}
|
||||
@@ -52,7 +52,7 @@ class ERPNextAddress(Address):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_shipping_address(company: str, address: str | None = None):
|
||||
def get_shipping_address(company, address=None):
|
||||
filters = [
|
||||
["Dynamic Link", "link_doctype", "=", "Company"],
|
||||
["Dynamic Link", "link_name", "=", company],
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
@@ -17,15 +17,6 @@ frappe.dashboards.chart_sources["Account Balance Timeline"] = {
|
||||
fieldtype: "Link",
|
||||
options: "Account",
|
||||
reqd: 1,
|
||||
default: locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"],
|
||||
get_query: () => {
|
||||
return {
|
||||
filters: {
|
||||
account_type: "Bank",
|
||||
is_group: 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import add_to_date, formatdate, getdate, nowdate
|
||||
from frappe.utils import add_to_date, formatdate, get_link_to_form, getdate, nowdate
|
||||
from frappe.utils.dashboard import cache_source
|
||||
from frappe.utils.dateutils import get_from_date_from_timespan, get_period_ending
|
||||
from frappe.utils.nestedset import get_descendants_of
|
||||
@@ -13,15 +13,15 @@ from frappe.utils.nestedset import get_descendants_of
|
||||
@frappe.whitelist()
|
||||
@cache_source
|
||||
def get(
|
||||
chart_name: str | None = None,
|
||||
chart: str | dict | None = None,
|
||||
no_cache: bool | None = None,
|
||||
filters: str | dict | None = None,
|
||||
from_date: str | None = None,
|
||||
to_date: str | None = None,
|
||||
timespan: str | None = None,
|
||||
time_interval: str | None = None,
|
||||
heatmap_year: str | None = None,
|
||||
chart_name=None,
|
||||
chart=None,
|
||||
no_cache=None,
|
||||
filters=None,
|
||||
from_date=None,
|
||||
to_date=None,
|
||||
timespan=None,
|
||||
time_interval=None,
|
||||
heatmap_year=None,
|
||||
):
|
||||
if chart_name:
|
||||
chart = frappe.get_doc("Dashboard Chart", chart_name)
|
||||
@@ -37,14 +37,21 @@ def get(
|
||||
filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json)
|
||||
|
||||
account = filters.get("account")
|
||||
company = filters.get("company")
|
||||
filters.get("company")
|
||||
|
||||
if not company and not account:
|
||||
frappe.throw(_("Company and account filters not set!"))
|
||||
if not company:
|
||||
frappe.throw(_("Company filter not set!"))
|
||||
if not account:
|
||||
frappe.throw(_("Account filter not set!"))
|
||||
if not account and chart_name:
|
||||
frappe.throw(
|
||||
_("Account is not set for the dashboard chart {0}").format(
|
||||
get_link_to_form("Dashboard Chart", chart_name)
|
||||
)
|
||||
)
|
||||
|
||||
if not frappe.db.exists("Account", account) and chart_name:
|
||||
frappe.throw(
|
||||
_("Account {0} does not exists in the dashboard chart {1}").format(
|
||||
account, get_link_to_form("Dashboard Chart", chart_name)
|
||||
)
|
||||
)
|
||||
|
||||
if not to_date:
|
||||
to_date = nowdate()
|
||||
|
||||
@@ -7,6 +7,7 @@ from frappe.utils import (
|
||||
cint,
|
||||
date_diff,
|
||||
flt,
|
||||
formatdate,
|
||||
get_first_day,
|
||||
get_last_day,
|
||||
get_link_to_form,
|
||||
@@ -449,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()
|
||||
@@ -523,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}")
|
||||
@@ -606,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",
|
||||
|
||||
@@ -471,7 +471,7 @@ class Account(NestedSet):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_parent_account(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
|
||||
def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(
|
||||
"""select name from tabAccount
|
||||
where is_group = 1 and docstatus != 2 and company = {}
|
||||
@@ -515,9 +515,7 @@ def get_account_autoname(account_number, account_name, company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_account_number(
|
||||
name: str, account_name: str, account_number: str | None = None, from_descendant: bool = False
|
||||
):
|
||||
def update_account_number(name, account_name, account_number=None, from_descendant=False):
|
||||
_ensure_idle_system()
|
||||
account = frappe.get_cached_doc("Account", name)
|
||||
if not account:
|
||||
@@ -579,7 +577,7 @@ def update_account_number(
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def merge_account(old: str, new: str):
|
||||
def merge_account(old, new):
|
||||
_ensure_idle_system()
|
||||
# Validate properties before merging
|
||||
new_account = frappe.get_cached_doc("Account", new)
|
||||
@@ -616,7 +614,7 @@ def merge_account(old: str, new: str):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_root_company(company: str):
|
||||
def get_root_company(company):
|
||||
# return the topmost company in the hierarchy
|
||||
ancestors = get_ancestors_of("Company", company, "lft asc")
|
||||
return [ancestors[0]] if ancestors else []
|
||||
|
||||
@@ -52,55 +52,60 @@ 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,
|
||||
include_default_fb_balances: true,
|
||||
},
|
||||
});
|
||||
|
||||
get_balances.then((r) => {
|
||||
if (!r.message || r.message.length == 0) return;
|
||||
|
||||
for (let account of r.message) {
|
||||
const node = cur_tree.nodes && cur_tree.nodes[account.value];
|
||||
if (!node || node.is_root) continue;
|
||||
|
||||
// show Dr if positive since balance is calculated as debit - credit else show Cr
|
||||
const balance = account.balance_in_account_currency || account.balance;
|
||||
const dr_or_cr = balance > 0 ? __("Dr") : __("Cr");
|
||||
const format = (value, currency) => format_currency(Math.abs(value), currency);
|
||||
|
||||
if (account.balance !== undefined) {
|
||||
node.parent && node.parent.find(".balance-area").remove();
|
||||
$(
|
||||
'<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: [
|
||||
|
||||
@@ -99,7 +99,7 @@ def identify_is_group(child):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_chart(chart_template: str | None, existing_company: str | None = None):
|
||||
def get_chart(chart_template, existing_company=None):
|
||||
chart = {}
|
||||
if existing_company:
|
||||
return get_account_tree_from_existing_company(existing_company)
|
||||
@@ -132,7 +132,7 @@ def get_chart(chart_template: str | None, existing_company: str | None = None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_charts_for_country(country: str, with_standard: bool = False):
|
||||
def get_charts_for_country(country, with_standard=False):
|
||||
charts = []
|
||||
|
||||
def _get_chart_name(content):
|
||||
@@ -225,7 +225,7 @@ def build_account_tree(tree, parent, all_accounts):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_bank_account(coa: str, bank_account: str):
|
||||
def validate_bank_account(coa, bank_account):
|
||||
accounts = []
|
||||
chart = get_chart(coa)
|
||||
|
||||
@@ -244,9 +244,7 @@ def validate_bank_account(coa: str, bank_account: str):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def build_tree_from_json(
|
||||
chart_template: str, chart_data: dict | None = None, from_coa_importer: bool = False
|
||||
):
|
||||
def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=False):
|
||||
"""get chart template from its folder and parse the json to be rendered as tree"""
|
||||
chart = chart_data or get_chart(chart_template)
|
||||
|
||||
|
||||
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"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,9 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.utils import nowdate
|
||||
|
||||
from erpnext.accounts.doctype.account.account import (
|
||||
@@ -10,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")
|
||||
@@ -320,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:
|
||||
@@ -346,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
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Account Closing Balance", {
|
||||
refresh(frm) {
|
||||
frm.page.btn_secondary.hide();
|
||||
},
|
||||
});
|
||||
// frappe.ui.form.on("Account Closing Balance", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"engine": "InnoDB",
|
||||
"is_submittable": 1,
|
||||
"field_order": [
|
||||
"closing_date",
|
||||
"account",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -82,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:
|
||||
@@ -103,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:
|
||||
@@ -206,7 +210,7 @@ def delete_accounting_dimension(doc):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def disable_dimension(doc: str):
|
||||
def disable_dimension(doc):
|
||||
if frappe.in_test:
|
||||
toggle_disabling(doc=doc)
|
||||
else:
|
||||
@@ -237,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):
|
||||
@@ -274,7 +286,7 @@ def get_dimension_with_children(doctype, dimensions):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_dimensions(with_cost_center_and_project: str | bool = False):
|
||||
def get_dimensions(with_cost_center_and_project=False):
|
||||
c = frappe.qb.DocType("Accounting Dimension Detail")
|
||||
p = frappe.qb.DocType("Accounting Dimension")
|
||||
dimension_filters = (
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
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)
|
||||
|
||||
@@ -73,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()
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe import _, scrub
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
|
||||
create_dimension,
|
||||
disable_dimension,
|
||||
)
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
|
||||
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 = []
|
||||
|
||||
@@ -36,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"}):
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
{
|
||||
"fieldname": "period_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Period Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
@@ -78,7 +79,7 @@
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2026-03-09 17:15:33.577217",
|
||||
"modified": "2025-12-01 16:53:44.631299",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting Period",
|
||||
|
||||
@@ -97,7 +97,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
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
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 (
|
||||
@@ -9,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"
|
||||
@@ -34,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):
|
||||
@@ -97,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;
|
||||
|
||||
@@ -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,20 +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": [
|
||||
@@ -209,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 ",
|
||||
@@ -291,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"
|
||||
@@ -331,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",
|
||||
@@ -345,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",
|
||||
@@ -355,7 +362,7 @@
|
||||
{
|
||||
"fieldname": "tab_break_dpet",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Others"
|
||||
"label": "Chart Of Accounts"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
@@ -399,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",
|
||||
@@ -474,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"
|
||||
@@ -518,6 +535,10 @@
|
||||
"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",
|
||||
@@ -557,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",
|
||||
@@ -623,99 +649,6 @@
|
||||
"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,
|
||||
@@ -724,7 +657,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-13 15:30:28.729627",
|
||||
"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
|
||||
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,68 +148,9 @@ 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
|
||||
|
||||
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)
|
||||
|
||||
@frappe.whitelist()
|
||||
def drop_ar_sql_procedures(self):
|
||||
from erpnext.accounts.report.accounts_receivable.accounts_receivable import InitSQLProceduresForAR
|
||||
|
||||
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.init_procedure_name}")
|
||||
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.allocate_procedure_name}")
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@@ -1,9 +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
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
frappe.ui.form.on("Advance Payment Ledger Entry", {
|
||||
refresh(frm) {
|
||||
frm.page.btn_secondary.hide();
|
||||
frm.set_currency_labels(["amount"], frm.doc.currency);
|
||||
frm.set_currency_labels(["base_amount"], erpnext.get_currency(frm.doc.company));
|
||||
},
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"creation": "2024-10-16 16:57:12.085072",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"is_submittable": 1,
|
||||
"field_order": [
|
||||
"company",
|
||||
"voucher_type",
|
||||
@@ -51,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) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# 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:
|
||||
@@ -115,8 +111,7 @@ def get_default_company_bank_account(company, party_type, party):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_bank_account_details(bank_account: str):
|
||||
frappe.has_permission("Bank Account", doc=bank_account, ptype="read", throw=True)
|
||||
def get_bank_account_details(bank_account):
|
||||
return frappe.get_cached_value(
|
||||
"Bank Account", bank_account, ["account", "bank", "bank_account_no"], as_dict=1
|
||||
)
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# 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
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestBankAccountSubtype(ERPNextTestSuite):
|
||||
class TestBankAccountSubtype(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
# 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
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
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
|
||||
@@ -13,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": []
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestBankGuarantee(ERPNextTestSuite):
|
||||
class TestBankGuarantee(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import json
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
@@ -47,9 +47,7 @@ class BankReconciliationTool(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_bank_transactions(
|
||||
bank_account: str, from_date: str | date | None = None, to_date: str | date | None = None
|
||||
):
|
||||
def get_bank_transactions(bank_account, from_date=None, to_date=None):
|
||||
# returns bank transactions for a bank account
|
||||
filters = []
|
||||
filters.append(["bank_account", "=", bank_account])
|
||||
@@ -59,7 +57,7 @@ def get_bank_transactions(
|
||||
filters.append(["date", "<=", to_date])
|
||||
if from_date:
|
||||
filters.append(["date", ">=", from_date])
|
||||
transactions = frappe.get_list(
|
||||
transactions = frappe.get_all(
|
||||
"Bank Transaction",
|
||||
fields=[
|
||||
"date",
|
||||
@@ -82,9 +80,8 @@ def get_bank_transactions(
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_account_balance(bank_account: str, till_date: str | date, company: str):
|
||||
def get_account_balance(bank_account, till_date, company):
|
||||
# returns account balance till the specified date
|
||||
frappe.has_permission("Bank Account", "read", bank_account, throw=True)
|
||||
account = frappe.db.get_value("Bank Account", bank_account, "account")
|
||||
filters = frappe._dict(
|
||||
{
|
||||
@@ -109,9 +106,7 @@ def get_account_balance(bank_account: str, till_date: str | date, company: str):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_bank_transaction(
|
||||
bank_transaction_name: str, reference_number: str, party_type: str | None = None, party: str | None = None
|
||||
):
|
||||
def update_bank_transaction(bank_transaction_name, reference_number, party_type=None, party=None):
|
||||
# updates bank transaction based on the new parameters provided by the user from Vouchers
|
||||
bank_transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
|
||||
bank_transaction.reference_number = reference_number
|
||||
@@ -140,16 +135,16 @@ def update_bank_transaction(
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_journal_entry_bts(
|
||||
bank_transaction_name: str,
|
||||
reference_number: str | None = None,
|
||||
reference_date: str | None = None,
|
||||
posting_date: str | date | None = None,
|
||||
entry_type: str | None = None,
|
||||
second_account: str | None = None,
|
||||
mode_of_payment: str | None = None,
|
||||
party_type: str | None = None,
|
||||
party: str | None = None,
|
||||
allow_edit: bool | None = None,
|
||||
bank_transaction_name,
|
||||
reference_number=None,
|
||||
reference_date=None,
|
||||
posting_date=None,
|
||||
entry_type=None,
|
||||
second_account=None,
|
||||
mode_of_payment=None,
|
||||
party_type=None,
|
||||
party=None,
|
||||
allow_edit=None,
|
||||
):
|
||||
# Create a new journal entry based on the bank transaction
|
||||
bank_transaction = frappe.db.get_values(
|
||||
@@ -299,17 +294,16 @@ def create_journal_entry_bts(
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_payment_entry_bts(
|
||||
bank_transaction_name: str,
|
||||
reference_number: str | None = None,
|
||||
reference_date: str | None = None,
|
||||
party_type: str | None = None,
|
||||
party: str | None = None,
|
||||
posting_date: str | None = None,
|
||||
mode_of_payment: str | None = None,
|
||||
project: str | None = None,
|
||||
cost_center: str | None = None,
|
||||
allow_edit: bool | None = None,
|
||||
company_bank_account: str | None = None,
|
||||
bank_transaction_name,
|
||||
reference_number=None,
|
||||
reference_date=None,
|
||||
party_type=None,
|
||||
party=None,
|
||||
posting_date=None,
|
||||
mode_of_payment=None,
|
||||
project=None,
|
||||
cost_center=None,
|
||||
allow_edit=None,
|
||||
):
|
||||
# Create a new payment entry based on the bank transaction
|
||||
bank_transaction = frappe.db.get_values(
|
||||
@@ -351,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:
|
||||
@@ -376,12 +367,12 @@ def create_payment_entry_bts(
|
||||
|
||||
@frappe.whitelist()
|
||||
def auto_reconcile_vouchers(
|
||||
bank_account: str,
|
||||
from_date: str | date | None = None,
|
||||
to_date: str | date | None = None,
|
||||
filter_by_reference_date: bool | None = None,
|
||||
from_reference_date: bool | None = None,
|
||||
to_reference_date: str | None = None,
|
||||
bank_account,
|
||||
from_date=None,
|
||||
to_date=None,
|
||||
filter_by_reference_date=None,
|
||||
from_reference_date=None,
|
||||
to_reference_date=None,
|
||||
):
|
||||
bank_transactions = get_bank_transactions(bank_account)
|
||||
|
||||
@@ -476,7 +467,7 @@ def get_auto_reconcile_message(partially_reconciled, reconciled):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def reconcile_vouchers(bank_transaction_name: str, vouchers: str):
|
||||
def reconcile_vouchers(bank_transaction_name, vouchers):
|
||||
# updated clear date of all the vouchers based on the bank transaction
|
||||
vouchers = json.loads(vouchers)
|
||||
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
|
||||
@@ -492,13 +483,13 @@ def reconcile_vouchers(bank_transaction_name: str, vouchers: str):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_linked_payments(
|
||||
bank_transaction_name: str,
|
||||
document_types: str | list[str] | None = None,
|
||||
from_date: str | date | None = None,
|
||||
to_date: str | date | None = None,
|
||||
filter_by_reference_date: bool | None = None,
|
||||
from_reference_date: bool | None = None,
|
||||
to_reference_date: str | None = None,
|
||||
bank_transaction_name,
|
||||
document_types=None,
|
||||
from_date=None,
|
||||
to_date=None,
|
||||
filter_by_reference_date=None,
|
||||
from_reference_date=None,
|
||||
to_reference_date=None,
|
||||
):
|
||||
# get all matching payments for a bank transaction
|
||||
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -14,6 +14,7 @@ import openpyxl
|
||||
from frappe import _
|
||||
from frappe.core.doctype.data_import.data_import import DataImport
|
||||
from frappe.core.doctype.data_import.importer import Importer, ImportFile
|
||||
from frappe.query_builder.functions import Count
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from frappe.utils.file_manager import get_file, save_file
|
||||
from frappe.utils.xlsxutils import ILLEGAL_CHARACTERS_RE, handle_html
|
||||
@@ -143,7 +144,7 @@ def preprocess_mt940_content(content: str) -> str:
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def convert_mt940_to_csv(data_import: str, mt940_file_path: str):
|
||||
def convert_mt940_to_csv(data_import, mt940_file_path):
|
||||
doc = frappe.get_doc("Bank Statement Import", data_import)
|
||||
|
||||
_file_doc, content = get_file(mt940_file_path)
|
||||
@@ -208,28 +209,26 @@ def convert_mt940_to_csv(data_import: str, mt940_file_path: str):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_preview_from_template(
|
||||
data_import: str, import_file: str | None = None, google_sheets_url: str | None = None
|
||||
):
|
||||
def get_preview_from_template(data_import, import_file=None, google_sheets_url=None):
|
||||
return frappe.get_doc("Bank Statement Import", data_import).get_preview_from_template(
|
||||
import_file, google_sheets_url
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def form_start_import(data_import: str):
|
||||
def form_start_import(data_import):
|
||||
job_id = frappe.get_doc("Bank Statement Import", data_import).start_import()
|
||||
return job_id is not None
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_errored_template(data_import_name: str):
|
||||
def download_errored_template(data_import_name):
|
||||
data_import = frappe.get_doc("Bank Statement Import", data_import_name)
|
||||
data_import.export_errored_rows()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_import_log(data_import_name: str):
|
||||
def download_import_log(data_import_name):
|
||||
return frappe.get_doc("Bank Statement Import", data_import_name).download_import_log()
|
||||
|
||||
|
||||
@@ -365,7 +364,7 @@ def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_import_status(docname: str):
|
||||
def get_import_status(docname):
|
||||
import_status = {}
|
||||
|
||||
data_import = frappe.get_doc("Bank Statement Import", docname)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -50,9 +50,6 @@ class BankTransaction(Document):
|
||||
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()
|
||||
@@ -139,8 +136,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)
|
||||
|
||||
@@ -375,12 +370,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
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ def upload_bank_statement():
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_bank_entries(columns: str, data: str, bank_account: str):
|
||||
def create_bank_entries(columns, data, bank_account):
|
||||
header_map = get_header_mapping(columns, bank_account)
|
||||
|
||||
success = 0
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestBankTransactionFees(ERPNextTestSuite):
|
||||
class TestBankTransactionFees(UnitTestCase):
|
||||
def test_included_fee_throws(self):
|
||||
"""A fee that's part of a withdrawal cannot be bigger than the
|
||||
withdrawal itself."""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import add_months, flt, fmt_money, get_last_day, getdate
|
||||
from frappe.utils.data import get_first_day
|
||||
from frappe.utils import add_months, flt, fmt_money, get_last_day, getdate, month_diff
|
||||
from frappe.utils.data import get_first_day, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
@@ -845,7 +847,7 @@ def get_fiscal_year_date_range(from_fiscal_year, to_fiscal_year):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def revise_budget(budget_name: str):
|
||||
def revise_budget(budget_name):
|
||||
old_budget = frappe.get_doc("Budget", budget_name)
|
||||
|
||||
if old_budget.docstatus == 1:
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.utils import flt, now_datetime, nowdate
|
||||
from frappe.client import submit
|
||||
from frappe.utils import add_days, flt, get_first_day, get_last_day, getdate, now_datetime, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.budget.budget import (
|
||||
BudgetError,
|
||||
@@ -17,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"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestCashierClosing(ERPNextTestSuite):
|
||||
class TestCashierClosing(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
@@ -57,7 +57,7 @@ def validate_columns(data):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_company(company: str):
|
||||
def validate_company(company):
|
||||
parent_company, allow_account_creation_against_child_company = frappe.get_cached_value(
|
||||
"Company", company, ["parent_company", "allow_account_creation_against_child_company"]
|
||||
)
|
||||
@@ -74,7 +74,7 @@ def validate_company(company: str):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def import_coa(file_name: str, company: str):
|
||||
def import_coa(file_name, company):
|
||||
# delete existing data for accounts
|
||||
unset_existing_data(company)
|
||||
|
||||
@@ -159,9 +159,7 @@ def generate_data_from_excel(file_doc, extension, as_dict=False):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_coa(
|
||||
doctype: str, parent: str, is_root: bool = False, file_name: str | None = None, for_validate: int = 0
|
||||
):
|
||||
def get_coa(doctype, parent, is_root=False, file_name=None, for_validate=0):
|
||||
"""called by tree view (to fetch node's children)"""
|
||||
|
||||
file_doc, extension = get_file(file_name)
|
||||
@@ -309,7 +307,7 @@ def build_response_as_excel(writer):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_template(file_type: str, template_type: str, company: str):
|
||||
def download_template(file_type, template_type, company):
|
||||
writer = get_template(template_type, company)
|
||||
|
||||
if file_type == "CSV":
|
||||
@@ -363,7 +361,7 @@ def get_sample_template(writer, company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_accounts(file_doc: Document, extension: str):
|
||||
def validate_accounts(file_doc, extension):
|
||||
if extension == "csv":
|
||||
accounts = generate_data_from_csv(file_doc, as_dict=True)
|
||||
else:
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestChartofAccountsImporter(ERPNextTestSuite):
|
||||
class TestChartofAccountsImporter(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
@@ -47,7 +47,7 @@ class ChequePrintTemplate(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_or_update_cheque_print_format(template_name: str):
|
||||
def create_or_update_cheque_print_format(template_name):
|
||||
if not frappe.db.exists("Print Format", template_name):
|
||||
cheque_print = frappe.new_doc("Print Format")
|
||||
cheque_print.update(
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# 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": []
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
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"
|
||||
}
|
||||
]
|
||||
@@ -1,8 +1,10 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
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
|
||||
@@ -14,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",
|
||||
@@ -190,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
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
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():
|
||||
@@ -85,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()
|
||||
@@ -108,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)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user